Pure Python FTDI device driver for USB-to-serial/GPIO/SPI/I2C/JTAG bridge devices
—
JTAG controller supporting TAP state machine management, instruction and data register access, chain discovery, and basic debugging operations for embedded system development and device programming.
Configure and manage JTAG controller with TAP state machine control.
class JtagController:
def configure(self, url: str, **kwargs):
"""
Configure JTAG controller with FTDI device.
Parameters:
- url: FTDI device URL (e.g., 'ftdi:///1')
- frequency: JTAG clock frequency in Hz (default: 3MHz)
- debug: Enable debug output
Raises:
- JtagError: Configuration failed
- FtdiError: Device access error
"""
def close(self):
"""Close JTAG controller and release device."""
def get_frequency(self) -> float:
"""Get actual configured JTAG frequency."""Manage JTAG Test Access Port (TAP) state machine transitions.
def reset(self):
"""Reset JTAG TAP to Test-Logic-Reset state."""
def go_idle(self):
"""Transition TAP to Run-Test-Idle state."""
def capture_ir(self):
"""Transition TAP to Capture-IR state."""
def capture_dr(self):
"""Transition TAP to Capture-DR state."""
def shift_ir(self):
"""Transition TAP to Shift-IR state."""
def shift_dr(self):
"""Transition TAP to Shift-DR state."""
def update_ir(self):
"""Transition TAP to Update-IR state."""
def update_dr(self):
"""Transition TAP to Update-DR state."""
def change_state(self, state: str):
"""
Change TAP to specific state.
Parameters:
- state: Target state name ('reset', 'idle', 'capture_ir', etc.)
"""Access and manipulate JTAG instruction register.
def write_ir(self, instruction: int, length: int = None) -> int:
"""
Write instruction to instruction register.
Parameters:
- instruction: Instruction value to write
- length: Instruction length in bits (auto-detected if None)
Returns:
int: Previous instruction register value
Raises:
- JtagError: Invalid instruction or communication error
"""
def read_ir(self, length: int) -> int:
"""
Read current instruction register value.
Parameters:
- length: Instruction length in bits
Returns:
int: Current instruction register value
"""Access and manipulate JTAG data register.
def write_dr(self, data: int, length: int) -> int:
"""
Write data to data register.
Parameters:
- data: Data value to write
- length: Data length in bits
Returns:
int: Previous data register value
"""
def read_dr(self, length: int) -> int:
"""
Read data register value.
Parameters:
- length: Data length in bits
Returns:
int: Current data register value
"""
def shift_register(self, out: int, length: int) -> int:
"""
Shift data through current register (IR or DR).
Parameters:
- out: Data to shift out
- length: Number of bits to shift
Returns:
int: Data shifted in
"""Discover and manage JTAG device chains.
def detect_register_size(self, register: str = 'ir') -> int:
"""
Detect instruction or data register size.
Parameters:
- register: Register type ('ir' or 'dr')
Returns:
int: Register size in bits
"""
def detect_chain_length(self) -> int:
"""
Detect JTAG chain length (number of devices).
Returns:
int: Number of devices in chain
"""
def identify_devices(self) -> list:
"""
Identify devices in JTAG chain.
Returns:
list: List of device identification codes
"""Higher-level JTAG operations engine for advanced functionality.
class JtagEngine:
def __init__(self, controller: JtagController):
"""
Initialize JTAG engine with controller.
Parameters:
- controller: Configured JtagController instance
"""
def scan_chain(self) -> dict:
"""
Scan and analyze JTAG chain.
Returns:
dict: Chain information with device details
"""
def read_idcodes(self) -> list:
"""
Read IDCODE from all devices in chain.
Returns:
list: List of 32-bit IDCODE values
"""
def bypass_test(self) -> bool:
"""
Perform bypass register test.
Returns:
bool: True if bypass test passes
"""Command-line style JTAG operations for interactive use.
class JtagTool:
def __init__(self, device_url: str):
"""
Initialize JTAG tool with device.
Parameters:
- device_url: FTDI device URL
"""
def execute_command(self, command: str, args: list = None):
"""
Execute JTAG command.
Parameters:
- command: Command name ('reset', 'idcode', 'scan', etc.)
- args: Command arguments
"""
def interactive_session(self):
"""Start interactive JTAG session."""Standard FTDI JTAG pin assignments:
FT232H:
FT2232H/FT4232H:
Supported JTAG frequencies:
Common JTAG instructions:
from pyftdi.jtag import JtagController
# Configure JTAG controller
jtag = JtagController()
jtag.configure('ftdi:///1', frequency=1E6)
# Reset TAP and go to idle
jtag.reset()
jtag.go_idle()
# Read device IDCODE
jtag.write_ir(0x01) # IDCODE instruction
idcode = jtag.read_dr(32) # Read 32-bit IDCODE
print(f"Device IDCODE: 0x{idcode:08x}")
# Clean up
jtag.close()from pyftdi.jtag import JtagController, JtagEngine
jtag = JtagController()
jtag.configure('ftdi:///1')
engine = JtagEngine(jtag)
# Scan JTAG chain
chain_info = engine.scan_chain()
print(f"Chain length: {chain_info['length']}")
# Read all device IDCODEs
idcodes = engine.read_idcodes()
for i, idcode in enumerate(idcodes):
print(f"Device {i}: IDCODE 0x{idcode:08x}")
# Perform bypass test
if engine.bypass_test():
print("Bypass test passed")
else:
print("Bypass test failed")
jtag.close()from pyftdi.jtag import JtagController
jtag = JtagController()
jtag.configure('ftdi:///1')
# Enter boundary scan mode
jtag.reset()
jtag.write_ir(0x02) # SAMPLE/PRELOAD instruction
# Read boundary scan register
boundary_length = 256 # Device-specific
boundary_data = jtag.read_dr(boundary_length)
print(f"Boundary scan data: 0x{boundary_data:x}")
# Write test pattern to boundary scan
test_pattern = 0xAAAA5555 # Alternating pattern
jtag.write_dr(test_pattern, boundary_length)
jtag.close()from pyftdi.jtag import JtagController
import time
jtag = JtagController()
jtag.configure('ftdi:///1')
def program_flash_word(address, data):
"""Program single word to flash via JTAG."""
# Device-specific programming sequence
# Set address
jtag.write_ir(0x10) # Address instruction
jtag.write_dr(address, 32)
# Set data
jtag.write_ir(0x11) # Data instruction
jtag.write_dr(data, 32)
# Program command
jtag.write_ir(0x12) # Program instruction
jtag.write_dr(0x01, 8) # Program enable
# Wait for completion
time.sleep(0.001)
# Check status
jtag.write_ir(0x13) # Status instruction
status = jtag.read_dr(8)
return (status & 0x01) == 0 # Success if bit 0 is clear
# Program flash memory
flash_data = [0x12345678, 0x9ABCDEF0, 0x55AA55AA]
base_address = 0x10000000
for i, data in enumerate(flash_data):
address = base_address + (i * 4)
if program_flash_word(address, data):
print(f"Programmed 0x{data:08x} to 0x{address:08x}")
else:
print(f"Failed to program address 0x{address:08x}")
jtag.close()from pyftdi.jtag import JtagController
class ArmDapJtag:
"""ARM Debug Access Port via JTAG."""
def __init__(self, jtag_controller):
self.jtag = jtag_controller
def read_dap_register(self, reg_addr):
"""Read DAP register."""
# Select DAP
self.jtag.write_ir(0x0A) # DPACC instruction
# Read register
request = (1 << 1) | (reg_addr << 2) # Read + address
self.jtag.write_dr(request, 35)
response = self.jtag.read_dr(35)
return (response >> 3) & 0xFFFFFFFF
def write_dap_register(self, reg_addr, value):
"""Write DAP register."""
self.jtag.write_ir(0x0A) # DPACC instruction
# Write register
request = (0 << 1) | (reg_addr << 2) | (value << 3)
self.jtag.write_dr(request, 35)
# Usage
jtag = JtagController()
jtag.configure('ftdi:///1')
dap = ArmDapJtag(jtag)
# Read IDCODE via DAP
idcode = dap.read_dap_register(0x00)
print(f"DAP IDCODE: 0x{idcode:08x}")
jtag.close()from pyftdi.jtag import JtagTool
# Create interactive JTAG tool
tool = JtagTool('ftdi:///1')
# Execute commands
tool.execute_command('reset')
tool.execute_command('idcode')
tool.execute_command('scan')
# Start interactive session
tool.interactive_session()from pyftdi.jtag import JtagController, JtagError
from pyftdi.ftdi import FtdiError
try:
jtag = JtagController()
jtag.configure('ftdi:///1')
jtag.reset()
idcode = jtag.read_dr(32)
except JtagError as e:
print(f"JTAG operation error: {e}")
except FtdiError as e:
print(f"FTDI device error: {e}")
finally:
if 'jtag' in locals():
jtag.close()# Exception types
class JtagError(Exception):
"""JTAG operation error"""
# TAP states
class JtagState:
"""JTAG TAP state definitions."""
TEST_LOGIC_RESET = 'test_logic_reset'
RUN_TEST_IDLE = 'run_test_idle'
SELECT_DR_SCAN = 'select_dr_scan'
CAPTURE_DR = 'capture_dr'
SHIFT_DR = 'shift_dr'
EXIT1_DR = 'exit1_dr'
PAUSE_DR = 'pause_dr'
EXIT2_DR = 'exit2_dr'
UPDATE_DR = 'update_dr'
SELECT_IR_SCAN = 'select_ir_scan'
CAPTURE_IR = 'capture_ir'
SHIFT_IR = 'shift_ir'
EXIT1_IR = 'exit1_ir'
PAUSE_IR = 'pause_ir'
EXIT2_IR = 'exit2_ir'
UPDATE_IR = 'update_ir'
# Standard JTAG instructions
JTAG_BYPASS = 0xFF
JTAG_IDCODE = 0x01
JTAG_SAMPLE = 0x02
JTAG_PRELOAD = 0x02Install with Tessl CLI
npx tessl i tessl/pypi-pyftdi