Public/Set-PsGadgetFtdiEeprom.ps1
|
# Set-PsGadgetFtdiEeprom.ps1 # Unified EEPROM write function for FT232H and FT232R devices. #Requires -Version 5.1 function Set-PsGadgetFtdiEeprom { <# .SYNOPSIS Writes EEPROM settings for an FT232H or FT232R device. .DESCRIPTION Single command for all EEPROM-level configuration changes across supported FTDI chips. Dispatches to the correct EEPROM writer based on device type. FT232H capabilities: -DisableVcp Clears the IsVCP flag so the chip enumerates as D2XX-only. Eliminates the duplicate COM port that prevents MPSSE from getting exclusive control of the device. [Most common usage] -EnableVcp Re-enables the VCP COM port if you need serial access again. -CbusPins Hashtable of ACBUS pin number (0-9) -> mode name. Useful for configuring ACBUS pins as GPIO (FT_CBUS_IOMODE), clock outputs, LED indicators, etc. -ACDriveCurrent / -ADDriveCurrent Override ACBUS/ADBUS output drive strength (4, 8, 12, or 16 mA). FT232R capabilities: -CbusPins Hashtable of CBUS pin number (0-3) -> mode name. The most common use is setting pins to FT_CBUS_IOMODE to enable GPIO bit-bang. Note: -CbusPins on FT232R maps to Set-FtdiFt232rCbusPinMode internally; same end result as Set-PsGadgetFt232rCbusMode. -DisableVcp Sets the RIsD2XX flag to $true (D2XX-only enumeration). -EnableVcp Sets the RIsD2XX flag to $false (VCP COM port visible). After writing, the function prompts to cycle the USB port automatically. Accepting is equivalent to physically unplugging and replugging the cable. Quick recovery: if MPSSE does not work due to a VCP driver conflict, the fix is: Set-PsGadgetFtdiEeprom -Index 0 -DisableVcp .PARAMETER Index Zero-based device index (as shown by Get-PsGadgetFtdi). .PARAMETER SerialNumber Alternative to Index: identify the device by serial number string. .PARAMETER PsGadget A PsGadgetFtdi object. The device must NOT be open when running EEPROM writes. Call $dev.Close() before running this command. .PARAMETER DisableVcp FT232H: clear IsVCP (device enumerates as D2XX-only, no COM port). FT232R: set RIsD2XX = $true (same effect). After replug, the duplicate COM port will be gone. .PARAMETER EnableVcp FT232H: set IsVCP = $true (re-enables the COM port). FT232R: set RIsD2XX = $false. .PARAMETER CbusPins Hashtable mapping pin numbers to mode names to write into EEPROM. FT232H pin numbers: 0-9 (ACBUS0-ACBUS9) Valid mode names for FT232H: FT_CBUS_TRISTATE High-Z / unused [factory default] FT_CBUS_IOMODE GPIO bit-bang FT_CBUS_TXLED Tx LED indicator FT_CBUS_RXLED Rx LED indicator FT_CBUS_TXRXLED Tx/Rx LED indicator FT_CBUS_PWREN Power-on signal (active low) FT_CBUS_SLEEP Sleep indicator FT_CBUS_DRIVE_0 Drive output LOW FT_CBUS_DRIVE_1 Drive output HIGH FT_CBUS_TXDEN Tx Data Enable FT_CBUS_CLK30 30 MHz clock output FT_CBUS_CLK15 15 MHz clock output FT_CBUS_CLK7_5 7.5 MHz clock output FT232R pin numbers: 0-3 (CBUS0-CBUS3) Valid mode names for FT232R: FT_CBUS_IOMODE GPIO bit-bang [use this to enable Set-PsGadgetGpio] FT_CBUS_TXLED Tx LED FT_CBUS_RXLED Rx LED FT_CBUS_TXRXLED Tx/Rx LED FT_CBUS_PWREN Power-on signal FT_CBUS_SLEEP Sleep indicator FT_CBUS_CLK48 48 MHz clock FT_CBUS_CLK24 24 MHz clock FT_CBUS_CLK12 12 MHz clock FT_CBUS_CLK6 6 MHz clock FT_CBUS_TXDEN Tx Data Enable .PARAMETER ACDriveCurrent FT232H only. Set ACBUS output drive current (4, 8, 12, or 16 mA). Default 4 mA. .PARAMETER ADDriveCurrent FT232H only. Set ADBUS output drive current (4, 8, 12, or 16 mA). Default 4 mA. .EXAMPLE # FT232H: disable the VCP COM port so MPSSE can get exclusive control Set-PsGadgetFtdiEeprom -Index 0 -DisableVcp .EXAMPLE # FT232H: identify by serial number Set-PsGadgetFtdiEeprom -SerialNumber FTAXBFCQ -DisableVcp .EXAMPLE # FT232H: disable VCP and configure ACBUS5 as GPIO in one EEPROM write Set-PsGadgetFtdiEeprom -Index 0 -DisableVcp -CbusPins @{ 5 = 'FT_CBUS_IOMODE' } .EXAMPLE # FT232R: configure CBUS0-CBUS3 for GPIO bit-bang (one-time setup) Set-PsGadgetFtdiEeprom -Index 1 -CbusPins @{ 0='FT_CBUS_IOMODE'; 1='FT_CBUS_IOMODE'; 2='FT_CBUS_IOMODE'; 3='FT_CBUS_IOMODE' } .EXAMPLE # FT232R: disable VCP enumeration (use D2XX only) Set-PsGadgetFtdiEeprom -Index 1 -DisableVcp .NOTES - Device must NOT have an open handle when running this command. If $dev is open, call $dev.Close() first. Otherwise D2XX returns FT_DEVICE_NOT_OPENED. - EEPROM changes require a USB replug (or CyclePort) to take effect. - Use Get-PsGadgetFtdiEeprom before and after to verify the change. - This function requires Windows with the D2XX driver loaded. #> [CmdletBinding( DefaultParameterSetName = 'ByIndex', SupportsShouldProcess, ConfirmImpact = 'High' )] [OutputType([System.Object])] param( [Parameter(Mandatory = $true, ParameterSetName = 'ByIndex', Position = 0)] [int]$Index, [Parameter(Mandatory = $true, ParameterSetName = 'BySerial')] [string]$SerialNumber, [Parameter(Mandatory = $true, ParameterSetName = 'PsGadget', Position = 0)] [ValidateNotNull()] [PsGadgetFtdi]$PsGadget, [Parameter(Mandatory = $false)] [switch]$DisableVcp, [Parameter(Mandatory = $false)] [switch]$EnableVcp, [Parameter(Mandatory = $false)] [hashtable]$CbusPins, [Parameter(Mandatory = $false)] [ValidateSet(4, 8, 12, 16)] [System.Nullable[int]]$ACDriveCurrent, [Parameter(Mandatory = $false)] [ValidateSet(4, 8, 12, 16)] [System.Nullable[int]]$ADDriveCurrent ) try { if ($DisableVcp -and $EnableVcp) { throw "-DisableVcp and -EnableVcp cannot both be specified." } # Resolve device index and type $targetIndex = $Index $targetDev = $null if ($PSCmdlet.ParameterSetName -eq 'BySerial') { $devices = Get-FtdiDeviceList $targetDev = $devices | Where-Object { $_.SerialNumber -eq $SerialNumber } | Select-Object -First 1 if (-not $targetDev) { throw "No FTDI device found with serial number '$SerialNumber'" } $targetIndex = $targetDev.Index } elseif ($PSCmdlet.ParameterSetName -eq 'PsGadget') { $targetIndex = $PsGadget.Index } if (-not $targetDev) { $deviceList = Get-FtdiDeviceList foreach ($d in @($deviceList)) { if ($d.Index -eq $targetIndex) { $targetDev = $d; break } } } if (-not $targetDev) { throw "Device at index $targetIndex not found. Run Get-PsGadgetFtdi to check available devices." } Write-Verbose "Target: $($targetDev.Type) - $($targetDev.Description) ($($targetDev.SerialNumber))" # --- FT232H path --- if ($targetDev.Type -match '^FT232H$') { # Map -DisableVcp/-EnableVcp to IsVCP nullable $vcpFlag = $null if ($DisableVcp) { $vcpFlag = $false } if ($EnableVcp) { $vcpFlag = $true } if (-not $PSCmdlet.ShouldProcess( "$($targetDev.Description) ($($targetDev.SerialNumber))", "Write FT232H EEPROM$(if ($null -ne $vcpFlag) { ': IsVCP=' + $vcpFlag })$(if ($CbusPins) { ' + CbusPins' })")) { return $null } $result = Set-FtdiFt232hEepromMode ` -Index $targetIndex ` -SerialNumber $targetDev.SerialNumber ` -IsVCP $vcpFlag ` -CbusPins $CbusPins ` -ACDriveCurrent $ACDriveCurrent ` -ADDriveCurrent $ADDriveCurrent ` -Confirm:$false if ($result -and $result.Success) { Invoke-FtdiEepromReplugPrompt -TargetDev $targetDev -TargetIndex $targetIndex } return $result } # --- FT232R path --- if ($targetDev.Type -match '^FT232R(L|NL)?$') { # Map -DisableVcp/-EnableVcp to RIsD2XX $rIsD2XX = $null if ($DisableVcp) { $rIsD2XX = $true } if ($EnableVcp) { $rIsD2XX = $false } # Translate CbusPins hashtable to Pins array + Mode string for FT232R backend # FT232R only supports a single mode applied to all requested pins $pins = @(0, 1, 2, 3) $mode = 'FT_CBUS_IOMODE' if ($CbusPins -and $CbusPins.Count -gt 0) { $pins = @($CbusPins.Keys | ForEach-Object { [int]$_ }) # Use the mode from the first entry (all must be the same for FT232R backend) $firstMode = ($CbusPins.GetEnumerator() | Select-Object -First 1).Value if ($firstMode) { $mode = $firstMode } # Check all values are the same mode; warn if mixed $uniqueModes = ($CbusPins.Values | Sort-Object -Unique) if ($uniqueModes.Count -gt 1) { Write-Warning ( "FT232R EEPROM write: multiple different modes in -CbusPins. " + "FT232R supports one mode call per write; each mode will be applied in a separate write." ) # Write each mode group separately $modeGroups = @{} foreach ($entry in $CbusPins.GetEnumerator()) { $m = $entry.Value if (-not $modeGroups.ContainsKey($m)) { $modeGroups[$m] = @() } $modeGroups[$m] += [int]$entry.Key } foreach ($mg in $modeGroups.GetEnumerator()) { if (-not $PSCmdlet.ShouldProcess( "$($targetDev.Description) ($($targetDev.SerialNumber))", "Write FT232R EEPROM: CBUS$($mg.Value -join ',CBUS') -> $($mg.Key)")) { continue } Set-FtdiFt232rCbusPinMode ` -Index $targetIndex ` -Pins $mg.Value ` -Mode $mg.Key ` -SerialNumber $targetDev.SerialNumber ` -RIsD2XX $rIsD2XX ` -Confirm:$false | Out-Null $rIsD2XX = $null # only write once } $result = [PSCustomObject]@{ Success = $true; DeviceIndex = $targetIndex; Message = "FT232R EEPROM written (multi-mode). Replug device to activate." } Invoke-FtdiEepromReplugPrompt -TargetDev $targetDev -TargetIndex $targetIndex return $result } } if (-not $PSCmdlet.ShouldProcess( "$($targetDev.Description) ($($targetDev.SerialNumber))", "Write FT232R EEPROM: CBUS$($pins -join ',CBUS') -> $mode$(if ($null -ne $rIsD2XX) { ' RIsD2XX=' + $rIsD2XX })")) { return $null } $result = Set-FtdiFt232rCbusPinMode ` -Index $targetIndex ` -Pins $pins ` -Mode $mode ` -SerialNumber $targetDev.SerialNumber ` -RIsD2XX $rIsD2XX ` -Confirm:$false if ($result -and $result.Success) { Invoke-FtdiEepromReplugPrompt -TargetDev $targetDev -TargetIndex $targetIndex } return $result } # Unsupported type Write-Warning "Set-PsGadgetFtdiEeprom: EEPROM write for '$($targetDev.Type)' is not yet supported. Supported: FT232H, FT232R / FT232RL / FT232RNL." return $null } catch { Write-Error "Set-PsGadgetFtdiEeprom failed: $_" return $null } } function Invoke-FtdiEepromReplugPrompt { <# .SYNOPSIS Internal helper: prompt to cycle USB port after EEPROM write. #> param( [Parameter(Mandatory)]$TargetDev, [Parameter(Mandatory)][int]$TargetIndex ) Write-Host "" Write-Host "EEPROM written successfully." Write-Host "Changes will not take effect until the device re-enumerates on the USB bus." Write-Host "" Write-Host " [Y] Cycle USB port automatically (no cable unplug needed)" Write-Host " [N] Unplug and replug the USB cable manually" Write-Host "" $choices = [System.Management.Automation.Host.ChoiceDescription[]]@( [System.Management.Automation.Host.ChoiceDescription]::new('&Yes', 'Cycle port now.') [System.Management.Automation.Host.ChoiceDescription]::new('&No', 'I will replug manually.') ) $choice = $Host.UI.PromptForChoice('Apply EEPROM Changes', 'Cycle USB port now?', $choices, 0) if ($choice -eq 0) { Write-Host "Cycling USB port on $($TargetDev.Description) ($($TargetDev.SerialNumber))..." try { $cycleDevice = [PsGadgetFtdi]::new([int]$TargetIndex) $cycleDevice.Connect() $cycleDevice.CyclePort() Write-Host "Port cycled. Device has re-enumerated with the new EEPROM settings." Write-Host "Verify with: Get-PsGadgetFtdiEeprom -Index $TargetIndex" } catch { Write-Warning "CyclePort failed: $_" Write-Warning "Please unplug and replug the USB cable manually." } } else { Write-Host "" Write-Host "ACTION REQUIRED: Unplug and replug the USB cable to apply the new settings." Write-Host "Verify with: Get-PsGadgetFtdiEeprom -Index $TargetIndex" Write-Host "" } } |