CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pyftdi

Pure Python FTDI device driver for USB-to-serial/GPIO/SPI/I2C/JTAG bridge devices

Pending
Overview
Eval results
Files

i2c.mddocs/

I2C Master Communication

I2C master controller implementation for communication with I2C devices. Supports standard and fast mode speeds, device addressing, register access, bus scanning, and simultaneous GPIO control for additional device management.

Capabilities

I2C Controller Setup

Configure and manage I2C controller with device detection and bus management.

class I2cController:
    def configure(self, url, **kwargs):
        """
        Configure I2C controller with FTDI device.
        
        Parameters:
        - url: FTDI device URL (e.g., 'ftdi:///1')
        - frequency: I2C clock frequency in Hz (default: 100kHz)
        - clockstretching: Enable clock stretching support
        - debug: Enable debug output
        
        Raises:
        - I2cIOError: Configuration failed
        - FtdiError: Device access error
        """
    
    def terminate(self):
        """Terminate I2C controller and release device."""
    
    def get_frequency(self) -> float:
        """Get actual configured I2C frequency."""
    
    def get_gpio(self):
        """
        Get GPIO controller for unused pins.
        
        Returns:
        GpioMpsseController: GPIO controller for remaining pins
        """

Device Port Access

Get I2C port instances for communication with specific device addresses.

def get_port(self, address: int) -> 'I2cPort':
    """
    Get I2C port for specific device address.
    
    Parameters:
    - address: 7-bit I2C device address (0x08-0x77)
    
    Returns:
    I2cPort: Port instance for device communication
    
    Raises:
    - I2cIOError: Invalid address or device not responding
    """

Bus Management

Scan and manage I2C bus with device detection.

def scan(self, start: int = 0x08, end: int = 0x77) -> list:
    """
    Scan I2C bus for responding devices.
    
    Parameters:
    - start: Starting address to scan
    - end: Ending address to scan
    
    Returns:
    list: List of responding device addresses
    """

def detect_devices(self) -> dict:
    """
    Detect and identify I2C devices on bus.
    
    Returns:
    dict: Device addresses with identification info
    """

I2C Data Transfer

Perform I2C transactions with proper start/stop conditions and ACK handling.

class I2cPort:
    def read(self, readlen: int, relax: bool = True) -> bytes:
        """
        Read data from I2C device.
        
        Parameters:
        - readlen: Number of bytes to read
        - relax: Use relaxed timing for slow devices
        
        Returns:
        bytes: Data read from device
        
        Raises:
        - I2cNackError: Device did not acknowledge
        - I2cTimeoutError: Operation timed out
        """
    
    def write(self, out: bytes, relax: bool = True):
        """
        Write data to I2C device.
        
        Parameters:
        - out: Data bytes to write
        - relax: Use relaxed timing for slow devices
        
        Raises:
        - I2cNackError: Device did not acknowledge
        - I2cTimeoutError: Operation timed out
        """
    
    def exchange(self, out: bytes, readlen: int, relax: bool = True) -> bytes:
        """
        Write data then read response in single transaction.
        
        Parameters:
        - out: Data bytes to write
        - readlen: Number of bytes to read
        - relax: Use relaxed timing
        
        Returns:
        bytes: Data read from device
        """

Register Access

Convenient methods for I2C register-based device communication.

def read_from(self, regaddr: int, readlen: int, relax: bool = True) -> bytes:
    """
    Read from device register.
    
    Parameters:
    - regaddr: Register address (8-bit or 16-bit)
    - readlen: Number of bytes to read
    - relax: Use relaxed timing
    
    Returns:
    bytes: Register data
    """

def write_to(self, regaddr: int, out: bytes, relax: bool = True):
    """
    Write to device register.
    
    Parameters:
    - regaddr: Register address (8-bit or 16-bit)
    - out: Data to write to register
    - relax: Use relaxed timing
    """

def read_from_word(self, regaddr: int, wordsize: int = 2, 
                  bigendian: bool = True, relax: bool = True) -> int:
    """
    Read word (16/32-bit) from register with endianness handling.
    
    Parameters:
    - regaddr: Register address
    - wordsize: Word size in bytes (2 or 4)
    - bigendian: True for big-endian, False for little-endian
    - relax: Use relaxed timing
    
    Returns:
    int: Word value
    """

def write_to_word(self, regaddr: int, value: int, wordsize: int = 2,
                 bigendian: bool = True, relax: bool = True):
    """
    Write word (16/32-bit) to register with endianness handling.
    
    Parameters:
    - regaddr: Register address
    - value: Word value to write
    - wordsize: Word size in bytes (2 or 4)
    - bigendian: True for big-endian, False for little-endian
    - relax: Use relaxed timing
    """

I2C with GPIO Support

I2C port with additional GPIO control for device management.

class I2cGpioPort(I2cPort):
    """I2C 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 I2C exchange with simultaneous GPIO control.
        
        Returns:
        tuple: (i2c_data, gpio_state)
        """

Device Support

Pin Assignments

Standard FTDI I2C pin assignments:

FT232H:

  • SCL: AD0 (I2C Clock)
  • SDA: AD1 (I2C Data - bidirectional)
  • AD2: AD2 (SDA read back)
  • GPIO: AD3-AD7, AC0-AC7 (14 additional GPIO pins)

FT2232H/FT4232H:

  • Similar assignments per interface
  • Multiple interfaces allow independent I2C controllers

Clock Speeds

Supported I2C frequencies:

  • Standard mode: 100 kHz (default)
  • Fast mode: 400 kHz
  • Fast mode plus: 1 MHz
  • High speed: Up to 3.4 MHz (device dependent)

Addressing

  • 7-bit addressing: Standard I2C addressing (0x08-0x77)
  • 10-bit addressing: Not currently supported
  • Reserved addresses: 0x00-0x07, 0x78-0x7F avoided

Usage Examples

Basic I2C Communication

from pyftdi.i2c import I2cController

# Configure I2C controller
i2c = I2cController()
i2c.configure('ftdi:///1')

# Connect to device at address 0x48
device = i2c.get_port(0x48)

# Simple read/write
device.write([0x01, 0xFF])  # Write 0xFF to register 0x01
data = device.read_from(0x01, 1)  # Read back from register 0x01
print(f"Register value: 0x{data[0]:02x}")

# Clean up
i2c.terminate()

Bus Scanning

from pyftdi.i2c import I2cController

i2c = I2cController() 
i2c.configure('ftdi:///1')

# Scan for devices
devices = i2c.scan()
print(f"Found devices at addresses: {[hex(addr) for addr in devices]}")

# Connect to first found device
if devices:
    device = i2c.get_port(devices[0])
    # ... communicate with device

i2c.terminate()

Register Operations

from pyftdi.i2c import I2cController

i2c = I2cController()
i2c.configure('ftdi:///1', frequency=400000)  # Fast mode

# Temperature sensor example (TMP102)
temp_sensor = i2c.get_port(0x48)

# Read temperature register (16-bit, big-endian)
temp_raw = temp_sensor.read_from_word(0x00, wordsize=2, bigendian=True)
temperature = (temp_raw >> 4) * 0.0625  # Convert to Celsius
print(f"Temperature: {temperature:.2f}°C")

# Configure sensor
config = (1 << 15) | (1 << 10)  # One-shot mode, 12-bit resolution
temp_sensor.write_to_word(0x01, config, wordsize=2, bigendian=True)

i2c.terminate()

EEPROM Access

from pyftdi.i2c import I2cController
import time

i2c = I2cController()
i2c.configure('ftdi:///1')

# 24C32 EEPROM at address 0x50
eeprom = i2c.get_port(0x50)

# Write data to address 0x0100 (16-bit addressing)
write_addr = 0x0100
data_to_write = b"Hello, I2C!"

# Write with 16-bit address
eeprom.write([(write_addr >> 8) & 0xFF, write_addr & 0xFF] + list(data_to_write))

# Wait for write cycle to complete
time.sleep(0.01)

# Read data back
eeprom.write([(write_addr >> 8) & 0xFF, write_addr & 0xFF])  # Set address
read_data = eeprom.read(len(data_to_write))
print(f"Read: {read_data.decode()}")

i2c.terminate()

Multiple Devices

from pyftdi.i2c import I2cController

i2c = I2cController()
i2c.configure('ftdi:///1')

# Multiple devices on same bus
rtc = i2c.get_port(0x68)      # Real-time clock
temp = i2c.get_port(0x48)     # Temperature sensor
eeprom = i2c.get_port(0x50)   # EEPROM

# Read from each device
current_time = rtc.read_from(0x00, 7)     # RTC time registers
temperature = temp.read_from_word(0x00)   # Temperature
eeprom_data = eeprom.read_from(0x00, 16)  # First 16 bytes

print(f"RTC: {current_time.hex()}")
print(f"Temp: {temperature}")
print(f"EEPROM: {eeprom_data.hex()}")

i2c.terminate()

I2C with GPIO Control

from pyftdi.i2c import I2cController

i2c = I2cController()
i2c.configure('ftdi:///1')

device = i2c.get_port(0x48)
gpio = i2c.get_gpio()

# Use GPIO for device control
gpio.set_direction(0xF0, 0xF0)  # Upper 4 bits as outputs

# Power on device
gpio.write(0x10)  # Enable power
time.sleep(0.1)

# Configure device via I2C
device.write_to(0x01, [0x80])  # Configuration register

# Read data with status LED
gpio.write(0x30)  # Turn on LED
data = device.read_from(0x00, 4)
gpio.write(0x10)  # Turn off LED

i2c.terminate()

Exception Handling

from pyftdi.i2c import I2cController, I2cNackError, I2cTimeoutError, I2cIOError
from pyftdi.ftdi import FtdiError

try:
    i2c = I2cController()
    i2c.configure('ftdi:///1')
    
    device = i2c.get_port(0x48)
    data = device.read_from(0x00, 2)
    
except I2cNackError as e:
    print(f"Device not responding: {e}")
except I2cTimeoutError as e:
    print(f"I2C timeout: {e}")
except I2cIOError as e:
    print(f"I2C communication error: {e}")
except FtdiError as e:
    print(f"FTDI device error: {e}")
finally:
    if 'i2c' in locals():
        i2c.terminate()

Types

# Exception types
class I2cIOError(IOError):
    """I2C communication error"""

class I2cNackError(I2cIOError):
    """I2C device not acknowledged (NACK)"""

class I2cTimeoutError(TimeoutError):
    """I2C operation timeout"""

# I2C speed constants
I2C_STANDARD_MODE = 100000   # 100 kHz
I2C_FAST_MODE = 400000       # 400 kHz
I2C_FAST_MODE_PLUS = 1000000 # 1 MHz

Install with Tessl CLI

npx tessl i tessl/pypi-pyftdi

docs

core-ftdi.md

eeprom.md

gpio.md

i2c.md

index.md

jtag.md

serial.md

spi.md

usb-tools.md

tile.json