Public/Set-PsGadgetGpio.ps1
|
#Requires -Version 5.1 # Set-PsGadgetGpio.ps1 # Public GPIO control function for FTDI devices function Set-PsGadgetGpio { <# .SYNOPSIS Controls GPIO pins on connected FTDI devices. .DESCRIPTION Sets GPIO pins on FTDI devices to HIGH or LOW states. Supports MPSSE-capable devices (FT232H, FT2232H, FT4232H) using ACBUS0-7, and CBUS bit-bang devices (FT232R) using CBUS0-3. For FT232R CBUS GPIO, the CBUS pins must first be programmed in the device EEPROM as FT_CBUS_IOMODE. Run Set-PsGadgetFt232rCbusMode once per device, replug the USB device, then use this function normally. .PARAMETER Index Zero-based index of the FTDI device (from Get-FtdiDevice). .PARAMETER SerialNumber FTDI device serial number. Preferred over Index -- stable across USB replug. .PARAMETER Connection An already-open raw connection object from Connect-PsGadgetFtdi or a PsGadgetFtdi wrapper from New-PsGadgetFtdi. The caller is responsible for closing the connection. .PARAMETER PsGadget A PsGadgetFtdi instance from New-PsGadgetFtdi. The caller is responsible for closing it. .PARAMETER Pins Pin numbers to drive. Range depends on device type: CBUS (FT232R): 0-3 (CBUS0-CBUS3) MPSSE (FT232H, etc.): 0-7 (ACBUS0-ACBUS7) AsyncBitBang: 0-7 (ADBUS0-ADBUS7) .PARAMETER State Pin state: HIGH, H, 1 or LOW, L, 0. .PARAMETER LowPins CBUS only. Pins to drive LOW in the same SetBitMode call as -Pins/-State HIGH. Allows atomic mixed-state writes without two round-trips to the device. Example: -Pins @(0,2) -State HIGH -LowPins @(1,3) sets 0,2=HIGH and 1,3=LOW. .PARAMETER DurationMs Hold the pin state for this many milliseconds, then invert (pulse mode). .PARAMETER PassThru Return a PSCustomObject describing the operation. By default no output. .EXAMPLE # FT232R CBUS -- set all four pins HIGH (after EEPROM programming) Set-PsGadgetGpio -Index 0 -Pins @(0..3) -State HIGH .EXAMPLE # FT232R CBUS -- mixed states in one call Set-PsGadgetGpio -Index 0 -Pins @(0,2) -State HIGH -LowPins @(1,3) .EXAMPLE # FT232H MPSSE -- control ACBUS pins Set-PsGadgetGpio -Index 0 -Pins @(2,4) -State HIGH .EXAMPLE # Pulse ACBUS0 LOW for 500ms Set-PsGadgetGpio -SerialNumber "ABC123" -Pins @(0) -State LOW -DurationMs 500 .EXAMPLE # Persistent connection -- open once, drive multiple times, close when done $conn = Connect-PsGadgetFtdi -SerialNumber "BG01X3AK" Set-PsGadgetGpio -Connection $conn -Pins @(0) -State HIGH Set-PsGadgetGpio -Connection $conn -Pins @(0) -State LOW $conn.Close() .EXAMPLE # OOP style via New-PsGadgetFtdi $dev = New-PsGadgetFtdi -Index 0 $dev.SetPin(0, "HIGH") $dev.SetPin(0, "LOW") $dev.Close() .NOTES Requires FTDI D2XX drivers and FTD2XX_NET.dll. FT232R CBUS pins require prior EEPROM programming via Set-PsGadgetFt232rCbusMode. FT232H MPSSE: ACBUS0-7 map to physical pins 21, 25-31. Use Get-FtdiDevice to list available devices and their serial numbers. #> [CmdletBinding(DefaultParameterSetName = 'ByIndex', SupportsShouldProcess = $true)] [OutputType([void])] param( [Parameter(Mandatory = $true, ParameterSetName = 'ByIndex', Position = 0)] [int]$Index, [Parameter(Mandatory = $true, ParameterSetName = 'BySerial')] [string]$SerialNumber, [Parameter(Mandatory = $true, ParameterSetName = 'ByConnection', Position = 0)] [ValidateNotNull()] [object]$Connection, [Parameter(Mandatory = $true, ParameterSetName = 'PsGadget', Position = 0)] [ValidateNotNull()] [PsGadgetFtdi]$PsGadget, [Parameter(Mandatory = $true, Position = 1)] [ValidateRange(0, 7)] [int[]]$Pins, [Parameter(Mandatory = $true, Position = 2)] [ValidateSet('HIGH', 'LOW', 'H', 'L', '1', '0')] [string]$State, [Parameter()] [ValidateRange(0, 3)] [int[]]$LowPins, [Parameter()] [ValidateRange(0, 7)] [int[]]$InputPins, [Parameter()] [ValidateRange(1, 60000)] [int]$DurationMs, [Parameter()] [switch]$PassThru ) try { # Track whether this function opened the connection (must close it in finally) $ownsConnection = $true if ($PSCmdlet.ParameterSetName -eq 'ByConnection') { $ownsConnection = $false # Unwrap PsGadgetFtdi wrapper if the caller passed one via -Connection if ($Connection.GetType().Name -eq 'PsGadgetFtdi') { if (-not $Connection._connection) { throw "PsGadgetFtdi internal connection is null. Device may not be connected." } $Connection = $Connection._connection } if (-not $Connection.IsOpen) { throw "The supplied connection is not open. Call Connect-PsGadgetFtdi or New-PsGadgetFtdi first." } Write-Debug "Using caller-supplied connection: $($Connection.Description) ($($Connection.SerialNumber))" } elseif ($PSCmdlet.ParameterSetName -eq 'PsGadget') { $ownsConnection = $false if (-not $PsGadget.IsOpen -or -not $PsGadget._connection) { throw "PsGadgetFtdi is not open. Use New-PsGadgetFtdi, which connects automatically." } $Connection = $PsGadget._connection Write-Debug "Using PsGadgetFtdi connection: $($Connection.Description) ($($Connection.SerialNumber))" } else { $devices = Get-FtdiDeviceList if (-not $devices -or $devices.Count -eq 0) { throw "No FTDI devices found. Run Get-FtdiDevice to check available devices." } $targetDevice = $null if ($PSCmdlet.ParameterSetName -eq 'ByIndex') { if ($Index -lt 0 -or $Index -ge $devices.Count) { throw "Device index $Index is out of range. Available devices: 0-$($devices.Count - 1)" } $targetDevice = $devices[$Index] } else { $targetDevice = $devices | Where-Object { $_.SerialNumber -eq $SerialNumber } if (-not $targetDevice) { throw "No device found with serial number '$SerialNumber'" } } Write-Debug "Targeting device: $($targetDevice.Description) ($($targetDevice.SerialNumber))" if ($targetDevice.IsOpen) { $deviceRef = if ($targetDevice.SerialNumber) { "'$($targetDevice.SerialNumber)'" } else { "at index $($targetDevice.Index)" } throw "Device $deviceRef is already open. Run Get-ConnectedPsGadget to find the open handle and call .Close() on it." } $Connection = Connect-PsGadgetFtdi -Index $targetDevice.Index if (-not $Connection) { throw "Failed to connect to FTDI device" } } try { $gpioMethod = if ($Connection.PSObject.Properties['GpioMethod']) { $Connection.GpioMethod } else { 'Unknown' } Write-Verbose "Device $($Connection.Type): GpioMethod=$gpioMethod, pins=[$($Pins -join ',')], state=$State" if (-not $PSCmdlet.ShouldProcess("$($Connection.Type) pins [$($Pins -join ',')]", "Set $State")) { return } $success = $false switch ($gpioMethod) { 'MPSSE' { $params = @{ DeviceHandle = $Connection Pins = $Pins Direction = $State } if ($DurationMs) { $params.DurationMs = $DurationMs } if ($InputPins) { $params.InputPins = $InputPins } $success = Set-FtdiGpioPins @params } 'IoT' { if (-not $Connection.GpioController) { throw "IoT connection is missing GpioController. Re-open the device with Connect-PsGadgetFtdi." } $iotParams = @{ GpioController = $Connection.GpioController Pins = $Pins State = $State } if ($DurationMs) { $iotParams.DurationMs = $DurationMs } $success = Set-FtdiIotGpioPins @iotParams } 'CBUS' { $badPins = $Pins | Where-Object { $_ -gt 3 } if ($badPins) { throw "Pin(s) [$($badPins -join ', ')] are out of range for CBUS bit-bang. $($Connection.Type) supports CBUS0-3 only." } $cbusParams = @{ Connection = $Connection Pins = $Pins State = $State } if ($LowPins) { $cbusParams.LowPins = $LowPins } if ($DurationMs){ $cbusParams.DurationMs = $DurationMs } $success = Set-FtdiCbusBits @cbusParams } 'AsyncBitBang' { $badPins = $Pins | Where-Object { $_ -lt 0 -or $_ -gt 7 } if ($badPins) { throw "Pin(s) [$($badPins -join ', ')] are out of range for async bit-bang. ADBUS supports pins 0-7." } [int]$outByte = 0 foreach ($p in $Pins) { if ($State -in @('HIGH','H','1')) { $outByte = $outByte -bor (1 -shl $p) } } $written = 0 $Connection.Write([byte[]]@($outByte), 1, [ref]$written) | Out-Null if ($DurationMs) { Start-Sleep -Milliseconds $DurationMs $Connection.Write([byte[]]@(0), 1, [ref]$written) | Out-Null } $success = $true } default { Write-Warning "Unknown GpioMethod '$gpioMethod' for device '$($Connection.Type)'. Attempting MPSSE fallback." $params = @{ DeviceHandle = $Connection Pins = $Pins Direction = $State } if ($DurationMs) { $params.DurationMs = $DurationMs } $success = Set-FtdiGpioPins @params } } if (-not $success) { throw "GPIO operation failed" } foreach ($p in $Pins) { Write-Verbose " pin $p -> $State" } } finally { if ($ownsConnection -and $Connection -and $Connection.Close) { try { $Connection.Close() } catch { Write-Warning "Failed to close device connection: $_" } } } if ($PassThru) { $resolvedSerial = '' if ($PSBoundParameters.ContainsKey('Connection')) { $resolvedSerial = 'via-connection' } elseif ($PSBoundParameters.ContainsKey('SerialNumber')) { $resolvedSerial = $SerialNumber } [PSCustomObject]@{ Index = if ($PSBoundParameters.ContainsKey('Index')) { $Index } else { -1 } SerialNumber = $resolvedSerial Pins = $Pins State = $State DurationMs = if ($PSBoundParameters.ContainsKey('DurationMs')) { $DurationMs } else { 0 } Timestamp = [datetime]::UtcNow } } } catch { throw } } |