Pure Python FTDI device driver for USB-to-serial/GPIO/SPI/I2C/JTAG bridge devices
—
SPI master controller implementation enabling communication with SPI devices. Supports multiple chip selects, simultaneous GPIO access, variable clock speeds up to 30MHz, and advanced features like non-byte-aligned transfers.
Configure and manage SPI controller with multiple device support.
class SpiController:
def configure(self, url, **kwargs):
"""
Configure SPI controller with FTDI device.
Parameters:
- url: FTDI device URL (e.g., 'ftdi:///1')
- frequency: Default SPI clock frequency in Hz (default: 6MHz)
- cs_count: Number of chip select lines (1-8)
- turbo: Enable turbo mode for higher performance
- debug: Enable debug output
Raises:
- SpiIOError: Configuration failed
- FtdiError: Device access error
"""
def terminate(self):
"""Terminate SPI controller and release device."""
def get_frequency(self) -> float:
"""Get actual configured frequency."""
def get_gpio(self):
"""
Get GPIO controller for unused pins.
Returns:
GpioMpsseController: GPIO controller for remaining pins
"""Get SPI port instances for communication with specific devices.
def get_port(self, cs: int, freq: float = 6000000, mode: int = 0) -> 'SpiPort':
"""
Get SPI port for specific chip select.
Parameters:
- cs: Chip select number (0-7)
- freq: SPI clock frequency in Hz
- mode: SPI mode (0-3)
- Mode 0: CPOL=0, CPHA=0 (clock idle low, sample on rising edge)
- Mode 1: CPOL=0, CPHA=1 (clock idle low, sample on falling edge)
- Mode 2: CPOL=1, CPHA=0 (clock idle high, sample on falling edge)
- Mode 3: CPOL=1, CPHA=1 (clock idle high, sample on rising edge)
Returns:
SpiPort: Port instance for device communication
"""Perform SPI transactions with various transfer patterns.
class SpiPort:
def exchange(self, out: bytes, readlen: int = 0, start: bool = True,
stop: bool = True, duplex: bool = False) -> bytes:
"""
Perform full-duplex SPI transaction.
Parameters:
- out: Data bytes to transmit
- readlen: Number of additional bytes to read after transmission
- start: Assert chip select at start
- stop: Deassert chip select at end
- duplex: True for simultaneous read/write, False for write-then-read
Returns:
bytes: Data received during transaction
"""
def read(self, readlen: int, start: bool = True, stop: bool = True) -> bytes:
"""
Read data from SPI device.
Parameters:
- readlen: Number of bytes to read
- start: Assert chip select at start
- stop: Deassert chip select at end
Returns:
bytes: Data read from device
"""
def write(self, out: bytes, start: bool = True, stop: bool = True):
"""
Write data to SPI device.
Parameters:
- out: Data bytes to write
- start: Assert chip select at start
- stop: Deassert chip select at end
"""Non-standard SPI operations and advanced transfer modes.
def write_readinto(self, out: bytes, read_buf: bytearray, start: bool = True,
stop: bool = True):
"""Write data and read response into existing buffer."""
def flush(self):
"""Flush any pending SPI operations."""
def set_frequency(self, frequency: float) -> float:
"""
Set SPI clock frequency for this port.
Parameters:
- frequency: Desired frequency in Hz
Returns:
float: Actual configured frequency
"""
def get_frequency(self) -> float:
"""Get current SPI clock frequency."""SPI port with additional GPIO control for device management.
class SpiGpioPort(SpiPort):
"""SPI port with GPIO capabilities for control signals."""
def set_gpio_direction(self, pins: int, direction: int):
"""Set GPIO pin directions (0=input, 1=output)."""
def read_gpio(self) -> int:
"""Read current GPIO pin states."""
def write_gpio(self, value: int):
"""Write GPIO pin states."""
def exchange_gpio(self, out: bytes, readlen: int, gpio_dir: int,
gpio_out: int) -> tuple:
"""
Perform SPI exchange with simultaneous GPIO control.
Returns:
tuple: (spi_data, gpio_state)
"""Standard FTDI SPI pin assignments:
FT232H (8 pins available):
FT2232H/FT4232H:
Supported SPI clock frequencies:
from pyftdi.spi import SpiController
# Configure SPI controller
spi = SpiController()
spi.configure('ftdi:///1')
# Get SPI port for device on CS0
device = spi.get_port(cs=0, freq=1E6, mode=0)
# Read device ID (common SPI flash operation)
device_id = device.exchange([0x9F], 3) # JEDEC ID command
print(f"Device ID: {device_id.hex()}")
# Write enable command
device.write([0x06])
# Read status register
status = device.exchange([0x05], 1)[0]
print(f"Status: 0x{status:02x}")
# Clean up
spi.terminate()from pyftdi.spi import SpiController
spi = SpiController()
spi.configure('ftdi:///1', cs_count=3)
# Different devices on different chip selects
flash = spi.get_port(cs=0, freq=10E6, mode=0) # SPI Flash
adc = spi.get_port(cs=1, freq=1E6, mode=1) # ADC
dac = spi.get_port(cs=2, freq=5E6, mode=2) # DAC
# Independent communication with each device
flash_id = flash.exchange([0x9F], 3)
adc_value = adc.exchange([0x00, 0x00], 2)
dac.write([0x30, 0xFF, 0x00]) # Set DAC output
spi.terminate()from pyftdi.spi import SpiController
spi = SpiController()
spi.configure('ftdi:///1')
# Get SPI port and GPIO controller
device = spi.get_port(cs=0, freq=6E6, mode=0)
gpio = spi.get_gpio()
# Configure additional pins as GPIO
gpio.set_direction(0xF0, 0xF0) # Upper 4 bits as outputs
# Control reset line while doing SPI
gpio.write(0x10) # Assert reset
device.write([0x01, 0x02, 0x03]) # Send data
gpio.write(0x00) # Release reset
spi.terminate()from pyftdi.spi import SpiController
spi = SpiController()
spi.configure('ftdi:///1', turbo=True) # Enable turbo mode
device = spi.get_port(cs=0, freq=30E6, mode=0) # Max speed
# Large data transfer
data_out = bytes(range(256)) # 256 bytes of test data
response = device.exchange(data_out, len(data_out), duplex=True)
# Continuous operations without CS toggling
device.write([0x02, 0x00, 0x00], start=True, stop=False) # Write command
device.write(data_out, start=False, stop=False) # Data
device.write([0x00], start=False, stop=True) # End transfer
spi.terminate()from pyftdi.spi import SpiController, SpiIOError
from pyftdi.ftdi import FtdiError
try:
spi = SpiController()
spi.configure('ftdi:///1')
device = spi.get_port(cs=0)
data = device.exchange([0x9F], 3)
except SpiIOError as e:
print(f"SPI communication error: {e}")
except FtdiError as e:
print(f"FTDI device error: {e}")
finally:
if 'spi' in locals():
spi.terminate()# Exception types
class SpiIOError(FtdiError):
"""SPI communication error"""
# SPI mode constants
SPI_MODE_0 = 0 # CPOL=0, CPHA=0
SPI_MODE_1 = 1 # CPOL=0, CPHA=1
SPI_MODE_2 = 2 # CPOL=1, CPHA=0
SPI_MODE_3 = 3 # CPOL=1, CPHA=1Install with Tessl CLI
npx tessl i tessl/pypi-pyftdi