Public/Invoke-PsGadgetSpi.ps1
|
#Requires -Version 5.1 # Invoke-PsGadgetSpi.ps1 # High-level SPI dispatch cmdlet for FT232H via MPSSE. function Invoke-PsGadgetSpi { <# .SYNOPSIS Performs a SPI write, read, or full-duplex transfer on an FTDI device. .DESCRIPTION Opens an FTDI device (or reuses an existing one), configures it for MPSSE SPI, then performs the requested operation: -Data only Write-only: sends bytes to the SPI device. -ReadCount only Read-only: clocks in N bytes (MOSI stays LOW). -Data + -ReadCount Full-duplex: writes Data while clocking in ReadCount bytes. Data and ReadCount must be equal for true full-duplex. If ReadCount < Data.Length only the first ReadCount bytes are returned; if ReadCount > Data.Length, Data is zero-padded. The FTDI device is opened and closed automatically unless -PsGadget is supplied, in which case the caller retains ownership and the device stays open after the call. .PARAMETER PsGadget An already-open PsGadgetFtdi object. Device will NOT be closed after the call. .PARAMETER Index FTDI device index (0-based). Default 0. .PARAMETER SerialNumber FTDI device serial number (preferred over Index for stable identification). .PARAMETER Data Bytes to write to MOSI. Required for write and full-duplex operations. .PARAMETER ReadCount Number of bytes to read from MISO. Required for read and full-duplex operations. .PARAMETER ClockHz SPI clock frequency in Hz. Default 1 MHz (1000000). Max 30 MHz. .PARAMETER SpiMode SPI mode 0-3 (CPOL/CPHA). Default 0 (most common: clock idle LOW, sample rising edge). Mode 0: CPOL=0 CPHA=0 -- idle LOW, sample rising, shift falling Mode 1: CPOL=0 CPHA=1 -- idle LOW, sample falling, shift rising Mode 2: CPOL=1 CPHA=0 -- idle HIGH, sample falling, shift rising Mode 3: CPOL=1 CPHA=1 -- idle HIGH, sample rising, shift falling .PARAMETER CsPin ADBUS pin number used for chip select (active low). Default 3 (ADBUS3 / D3). Valid range 3-7. Pins 0-2 are reserved for SCK, MOSI, MISO. .EXAMPLE # Write 3 bytes to a SPI register Invoke-PsGadgetSpi -Index 0 -Data @(0x02, 0x00, 0xFF) .EXAMPLE # Read 4 bytes (MOSI=0x00 during read) $bytes = Invoke-PsGadgetSpi -Index 0 -ReadCount 4 .EXAMPLE # Full-duplex transfer: write command byte, read 3-byte response $response = Invoke-PsGadgetSpi -Index 0 -Data @(0x01, 0x00, 0x00, 0x00) -ReadCount 4 .EXAMPLE # 10 MHz SPI Mode 3, custom CS pin Invoke-PsGadgetSpi -Index 0 -Data @(0xAB) -ClockHz 10000000 -SpiMode 3 -CsPin 4 .EXAMPLE # Reuse an open device (device stays open) $dev = New-PsGadgetFtdi -Index 0 $rx = Invoke-PsGadgetSpi -PsGadget $dev -Data @(0x9F) -ReadCount 3 .EXAMPLE # Polling loop -- keep device open to avoid per-call open/close overhead $dev = New-PsGadgetFtdi -SerialNumber 'FT4ABCDE' try { while ($true) { # MCP3208 8-ch ADC: start=1, single-ended ch0, pad=0x00 $raw = Invoke-PsGadgetSpi -PsGadget $dev -Data @(0x01, 0x80, 0x00) -ReadCount 3 $value = (($raw[1] -band 0x0F) -shl 8) -bor $raw[2] Write-Host "ADC ch0: $value" Start-Sleep -Seconds 5 } } finally { $dev.Close() } .OUTPUTS Write-only: [bool] $true on success, $false on failure. To suppress the bool from the pipeline use [void]: [void](Invoke-PsGadgetSpi ...) Read-only: [byte[]] received bytes. Full-duplex: [byte[]] received bytes (length = ReadCount). .NOTES Requires FT232H in MPSSE mode. FT232R does not support MPSSE SPI. Wire guide: D0=SCK D1=MOSI D2=MISO D3=CS (10k pull-up to VCC) #> [CmdletBinding(DefaultParameterSetName = 'ByIndex')] param( [Parameter(Mandatory = $true, ParameterSetName = 'ByDevice', Position = 0)] [ValidateNotNull()] [object]$PsGadget, [Parameter(Mandatory = $false, ParameterSetName = 'ByIndex')] [ValidateRange(0, 127)] [int]$Index = 0, [Parameter(Mandatory = $true, ParameterSetName = 'BySerial')] [ValidateNotNullOrEmpty()] [string]$SerialNumber, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [byte[]]$Data, [Parameter(Mandatory = $false)] [ValidateRange(1, 65536)] [int]$ReadCount, [Parameter(Mandatory = $false)] [ValidateRange(1000, 30000000)] [int]$ClockHz = 1000000, [Parameter(Mandatory = $false)] [ValidateRange(0, 3)] [int]$SpiMode = 0, [Parameter(Mandatory = $false)] [ValidateRange(3, 7)] [int]$CsPin = 3 ) if (-not $PSBoundParameters.ContainsKey('Data') -and -not $PSBoundParameters.ContainsKey('ReadCount')) { throw "At least one of -Data or -ReadCount must be specified." } $ownsDevice = $PSCmdlet.ParameterSetName -ne 'ByDevice' $ftdi = $null try { # --- open device --- if ($PSCmdlet.ParameterSetName -eq 'ByDevice') { $ftdi = $PsGadget if (-not $ftdi -or -not $ftdi.IsOpen) { throw "PsGadgetFtdi object is not open." } } elseif ($PSCmdlet.ParameterSetName -eq 'BySerial') { $ftdi = New-PsGadgetFtdi -SerialNumber $SerialNumber } else { $ftdi = New-PsGadgetFtdi -Index $Index } if (-not $ftdi -or -not $ftdi.IsOpen) { throw "Failed to open FTDI device" } # --- get or create SPI instance --- $spi = $ftdi.GetSpi($ClockHz, $SpiMode, $CsPin) if (-not $spi.IsInitialized) { throw "SPI initialization failed" } # --- dispatch --- $hasData = $PSBoundParameters.ContainsKey('Data') $hasReadCount = $PSBoundParameters.ContainsKey('ReadCount') if ($hasData -and $hasReadCount) { # Full-duplex: pad or trim Data to match ReadCount $txData = $Data if ($txData.Length -ne $ReadCount) { $padded = [byte[]]::new($ReadCount) $copy = [Math]::Min($txData.Length, $ReadCount) [Array]::Copy($txData, $padded, $copy) $txData = $padded } return $spi.Transfer($txData) } elseif ($hasData) { return $spi.Write($Data) } else { return $spi.Read($ReadCount) } } finally { if ($ownsDevice -and $ftdi -and $ftdi.IsOpen) { $ftdi.Close() } } } |