Source code for tropicsquare.transports.spidev

"""SPIDev Transport Implementation for Raspberry Pi

This module provides SPI transport implementation for Linux spidev interface
with manual GPIO chip select control.

Recommended Setup:
    Use general-purpose GPIO (e.g., GPIO 25, physical pin 22)::

        transport = SpiDevTransport(bus=0, device=0, cs_pin=25)

Alternative - Hardware CS Pins:
    GPIO 8 (CE0, pin 24) or GPIO 7 (CE1, pin 26)::

        transport = SpiDevTransport(bus=0, device=0, cs_pin=8)

.. note::
   Device tree overlay requirement depends on your hardware configuration:

   **No overlay needed** if:

   - You have NO other SPI devices connected to hardware CS pins (GPIO 8/7)
   - The kernel will toggle GPIO 8 (for /dev/spidev0.0) or GPIO 7 (for /dev/spidev0.1)
     during transfers, but this is harmless if nothing is connected there

   **Overlay REQUIRED** if:

   - You have other SPI devices connected to CE0 (GPIO 8) or CE1 (GPIO 7)
   - Without overlay, kernel will activate both your manual CS and hardware CS
     simultaneously, causing bus conflicts

   To disable hardware CS, add to /boot/firmware/config.txt and reboot:

   - ``dtoverlay=spi0-0cs`` - Don't claim any CS pins (recommended for manual CS)
   - ``dtoverlay=spi0-1cs,cs0_pin=<gpio>`` - Use only one CS pin (specify which)
   - ``dtoverlay=spi0-2cs,cs0_pin=<gpio>,cs1_pin=<gpio>`` - Remap CS pins
"""

import spidev
from gpiod import Chip, LineSettings
from gpiod.line import Direction, Value
from tropicsquare.transports import L1Transport


[docs] class SpiDevTransport(L1Transport): """L1 transport for Linux spidev interface with manual GPIO CS control. This transport uses the spidev library for SPI communication and gpiod for manual chip select control, providing precise timing control over the CS line. :param bus: SPI bus number (default: 0 for /dev/spidev0.x) :param device: SPI device number (default: 1 for CE1, use 0 for CE0) :param cs_pin: GPIO pin number for chip select (default: 25 for CE2, use 8 for CE0 or 7 for CE1) :param max_speed_hz: SPI clock speed in Hz (default: 1000000 = 1 MHz) :param gpio_chip: GPIO chip device path (default: /dev/gpiochip0) Example:: from tropicsquare.transports.spidev import SpiDevTransport from tropicsquare import TropicSquareCPython # Create transport for Raspberry Pi (using CE2 - no overlay needed) transport = SpiDevTransport( bus=0, device=1, cs_pin=25 # GPIO 25 (physical pin 22, CE2) ) # Create TropicSquare instance ts = TropicSquareCPython(transport) try: # Use the chip chip_id = ts.chip_id print(f"Chip ID: {chip_id}") finally: # Always cleanup transport.close() """
[docs] def __init__(self, bus: int = 0, device: int = 1, cs_pin: int = 25, max_speed_hz: int = 1000000, gpio_chip: str = "/dev/gpiochip0"): """Initialize SPIDev transport with manual GPIO CS control. :param bus: SPI bus number :param device: SPI device number :param cs_pin: GPIO pin number for chip select :param max_speed_hz: SPI clock speed in Hz :param gpio_chip: Path to GPIO chip device :raises OSError: If SPI or GPIO device cannot be opened """ # Initialize SPI self._spi = spidev.SpiDev() self._spi.open(bus, device) # Configure SPI parameters self._spi.mode = 0 # SPI_MODE_0: CPOL=0, CPHA=0 self._spi.max_speed_hz = max_speed_hz self._spi.bits_per_word = 8 self._spi.lsbfirst = False # Initialize GPIO for chip select self._cs_pin = cs_pin try: self._gpio_chip = Chip(gpio_chip) # Configure CS line as output with initial high state (inactive) line_settings = LineSettings( direction=Direction.OUTPUT, output_value=Value.ACTIVE # Start with CS high (1 = inactive) ) # Request CS line self._cs_request = self._gpio_chip.request_lines( consumer="tropicsquare-cs", config={cs_pin: line_settings} ) except OSError as e: # None of the GPIO chips worked error_msg = f"Failed to open GPIO {cs_pin} for CS: {e}\n" error_msg += "Hint: Add 'dtoverlay=spi0-0cs' to /boot/firmware/config.txt and reboot" raise OSError(error_msg)
def _transfer(self, tx_data: bytes) -> bytes: """SPI bidirectional transfer. Performs full-duplex SPI transfer using xfer2 method which simultaneously sends and receives data. :param tx_data: Data to transmit :returns: Received data (same length as tx_data) :rtype: bytes """ # spidev xfer2() expects list of integers and returns list of integers rx_list = self._spi.xfer2(list(tx_data)) return bytes(rx_list) def _read(self, length: int) -> bytes: """SPI read operation. Reads specified number of bytes from SPI bus by sending dummy bytes. :param length: Number of bytes to read :returns: Read data :rtype: bytes """ # readbytes() sends 0x00 as dummy data and returns list of integers rx_list = self._spi.readbytes(length) return bytes(rx_list) def _cs_low(self) -> None: """Activate chip select (set CS to logic 0).""" self._cs_request.set_value(self._cs_pin, Value.INACTIVE) def _cs_high(self) -> None: """Deactivate chip select (set CS to logic 1).""" self._cs_request.set_value(self._cs_pin, Value.ACTIVE)
[docs] def close(self) -> None: """Release SPI and GPIO resources. This method should be called when done using the transport to properly cleanup hardware resources. It's recommended to use the transport in a try/finally block or context manager. Example:: transport = SpiDevTransport() try: # Use transport pass finally: transport.close() """ # Release GPIO try: self._cs_request.release() except Exception: pass # Ignore errors during cleanup try: self._gpio_chip.close() except Exception: pass # Ignore errors during cleanup # Close SPI try: self._spi.close() except Exception: pass # Ignore errors during cleanup