A Python library for interfacing with DLN2 USB-to-SPI adapters with a spidev-compatible API. This package allows existing Python code written for py-spidev to work with DLN2 devices with minimal modifications.
Originally developed for the Pico USB IO Board - a Raspberry Pi Pico-based USB-to-SPI/I2C/GPIO adapter that implements the DLN2 protocol.
- spidev-compatible API: Drop-in replacement for most
py-spidevusage - Cross-platform support: Works on Linux, Windows, and macOS
- Multiple transfer modes: Single transfers, per-byte transfers with CS control
- Configurable parameters: SPI mode, frequency, bits per word
- Built-in examples: Ready-to-use test scripts and utilities
- Pure Python: No compiled extensions, easy to install and distribute
- Pico USB IO Board support: Native support for Raspberry Pi Pico-based DLN2 adapters
- Pico USB IO Board - Raspberry Pi Pico-based USB-to-SPI/I2C/GPIO adapter with DLN2 protocol implementation
- DLN2 devices - USB-to-SPI adapters implementing the DLN2 protocol (VID: 0x1d50, PID: 0x6170)
- Compatible devices - Any USB device that implements the DLN2 SPI interface
pip install dln2-spi-wrappergit clone https://github.com/ipmgroup/dln2_spi_wrapper.git
cd dln2_spi_wrapper
pip install .git clone https://github.com/ipmgroup/dln2_spi_wrapper.git
cd dln2_spi_wrapper
pip install -e ".[dev]"- Python 3.7+
- pyusb: Automatically installed as a dependency
- DLN2-compatible device:
- Pico USB IO Board (recommended)
- Any DLN2 USB-to-SPI adapter (VID: 0x1d50, PID: 0x6170)
On Linux, you may need to set up udev rules to access the DLN2 device without root privileges:
# Create udev rule for DLN2 device
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1d50", ATTR{idProduct}=="6170", MODE="0666", GROUP="plugdev"' | sudo tee /etc/udev/rules.d/99-dln2.rules
sudo udevadm control --reload-rules
sudo udevadm triggerMake sure your user is in the plugdev group:
sudo usermod -a -G plugdev $USER
# Log out and log back in for the group change to take effectfrom dln2_spi_wrapper import SpiDev
# Create SPI device instance
spi = SpiDev()
# Open the device (bus and device parameters are ignored for DLN2)
spi.open(0, 0)
# Configure SPI parameters
spi.max_speed_hz = 1000000 # 1 MHz
spi.mode = 0 # SPI mode 0 (CPOL=0, CPHA=0)
spi.bits_per_word = 8 # 8 bits per word
# Perform SPI transaction
tx_data = [0x9F, 0x00, 0x00] # Example: JEDEC ID command
rx_data = spi.xfer2(tx_data)
print(f"Received: {[hex(b) for b in rx_data]}")
# Close the device
spi.close()from dln2_spi_wrapper import SpiDev
with SpiDev() as spi:
spi.open(0, 0)
spi.max_speed_hz = 1000000
spi.mode = 0
# Send command and receive response
response = spi.xfer2([0x9F, 0x00, 0x00, 0x00])
print(f"Device ID: {' '.join(f'{b:02x}' for b in response)}")from dln2_spi_wrapper import SpiDev
spi = SpiDev()
spi.open(0, 0)
# Configure advanced options
spi.max_speed_hz = 10000000 # 10 MHz
spi.mode = 1 # SPI mode 1
spi.bits_per_word = 16 # 16-bit words
spi.host_hold_cs = True # Keep CS asserted between transfers
# Per-byte transfers with CS toggling
data = [0x01, 0x02, 0x03]
response = spi.xfer_per_byte(data, inter_byte_delay_ms=1.0)
# Write-only operation
spi.writebytes([0xAA, 0xBB, 0xCC])
# Read-only operation
received = spi.readbytes(4)
spi.close()The main class providing spidev-compatible interface.
open(bus, device): Open SPI device (bus/device ignored for DLN2)close(): Close SPI device and release resourcesxfer2(data): Perform full-duplex SPI transferxfer(data): Alias forxfer2()(spidev compatibility)xfer_per_byte(data, delay_ms=5.0): Transfer bytes individually with CS togglingwritebytes(data): Write-only SPI transferreadbytes(length): Read-only SPI transfer (sends zeros)
max_speed_hz(int): SPI clock frequency in Hzmode(int): SPI mode (0-3, sets CPOL and CPHA)bits_per_word(int): Word size in bits (1-16)host_hold_cs(bool): Keep CS asserted between transfersdebug(bool): Enable debug output
For advanced use cases, you can access the low-level DLN2 client:
from dln2_spi_wrapper import Dln2Usb, find_device
# Find and connect to DLN2 device
device = find_device()
client = Dln2Usb(device, debug=True)
# Enable SPI
client.spi_enable()
# Configure SPI settings
client.spi_set_frequency(1000000)
client.spi_set_mode(0)
# Perform raw SPI transfer
tx_bytes = b'\x9f\x00\x00'
rx_bytes = client.spi_read_write(tx_bytes, leave_ss_low=False)
# Clean up
client.spi_disable()
client.close()The package includes command-line utilities:
Test SPI communication with automatic backend detection:
dln2-spi-test --backend dln --verboseOptions:
--backend {auto,dln,native}: Choose SPI backend--host-cs: Use host-controlled chip select--verbose: Enable debug output--per-byte: Send bytes individually--inter-byte-delay DELAY: Delay between bytes in milliseconds
Test different word sizes (4-16 bits):
dln2-spi-bpw-testThis utility cycles through different bits-per-word settings and sends test patterns.
See the examples/ directory for more usage examples:
spidev_test.py: Basic SPI communication testbpw_tester.py: Bits-per-word functionality test
This library implements the most commonly used parts of the py-spidev API:
| Feature | Status | Notes |
|---|---|---|
open() / close() |
✅ | Bus/device params ignored |
xfer() / xfer2() |
✅ | Full duplex transfers |
writebytes() / readbytes() |
✅ | Half duplex transfers |
max_speed_hz |
✅ | Frequency control |
mode |
✅ | SPI modes 0-3 |
bits_per_word |
✅ | 1-16 bit words |
| Context manager | ✅ | with SpiDev() as spi: |
cshigh / lsbfirst |
📝 | Properties exist but limited DLN2 support |
- Linux: Full support with udev rules for device access
- Windows: Full support with libusb-win32/WinUSB drivers
- macOS: Full support with libusb
- Python 3.7+
- Tested on CPython and PyPy
ValueError: DLN2 device not found (VID:PID 1d50:6170)
Solutions:
- Check device is connected and powered
- Verify USB cable and connection
- On Linux, check udev rules and user permissions
- Try running with
sudo(not recommended for production)
usb.core.USBError: [Errno 13] Access denied (insufficient permissions)
Solutions:
- Set up udev rules (see installation instructions)
- Add user to
plugdevgroup - Check that the device isn't claimed by kernel driver
ModuleNotFoundError: No module named 'usb'
Solution:
pip install pyusbRuntimeError: SPI transfer failed: result=X
Check:
- SPI device is properly connected
- Correct SPI mode and frequency settings
- Adequate power supply for target device
- Proper wiring (MOSI, MISO, SCK, CS)
git clone https://github.com/ipmgroup/dln2_spi_wrapper.git
cd dln2_spi_wrapper
# Create and activate virtual environment
python3 -m venv venv
source venv/bin/activate
# Install in development mode with all dependencies
pip install -e ".[dev]"# Build package
make build
# Check package integrity
make check
# Upload to TestPyPI (requires API token)
make upload-test
# Upload to PyPI (requires API token)
make upload
# Or use the interactive script
./publish.shFor detailed publishing instructions, see PUBLISHING.md and QUICKSTART.md.
pytest tests/black dln2_spi_wrapper/mypy dln2_spi_wrapper/Contributions are welcome! Please:
- Fork the repository
- Create a feature branch:
git checkout -b feature-name - Make your changes and add tests
- Ensure code passes linting and tests
- Submit a pull request
This project is licensed under the Apache License 2.0. See LICENSE for details.
- Originally developed for the Pico USB IO Board project
- Based on DLN2 protocol implementation
- Inspired by the
py-spidevlibrary API - Thanks to the USB and embedded development communities
- Pico USB IO Board: Raspberry Pi Pico-based USB-to-SPI/I2C/GPIO adapter
- py-spidev: Original spidev Python bindings
- libusb: USB library for cross-platform USB device access
- DLN2 Linux Driver: Official Linux kernel driver
For more examples and detailed documentation, visit the project repository.