Coverage for tropicsquare / transports / spidev.py: 0%
44 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-27 21:24 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-27 21:24 +0000
1"""SPIDev Transport Implementation for Raspberry Pi
3This module provides SPI transport implementation for Linux spidev interface
4with manual GPIO chip select control.
6Recommended Setup:
7 Use general-purpose GPIO (e.g., GPIO 25, physical pin 22)::
9 transport = SpiDevTransport(bus=0, device=0, cs_pin=25)
11Alternative - Hardware CS Pins:
12 GPIO 8 (CE0, pin 24) or GPIO 7 (CE1, pin 26)::
14 transport = SpiDevTransport(bus=0, device=0, cs_pin=8)
16.. note::
17 Device tree overlay requirement depends on your hardware configuration:
19 **No overlay needed** if:
21 - You have NO other SPI devices connected to hardware CS pins (GPIO 8/7)
22 - The kernel will toggle GPIO 8 (for /dev/spidev0.0) or GPIO 7 (for /dev/spidev0.1)
23 during transfers, but this is harmless if nothing is connected there
25 **Overlay REQUIRED** if:
27 - You have other SPI devices connected to CE0 (GPIO 8) or CE1 (GPIO 7)
28 - Without overlay, kernel will activate both your manual CS and hardware CS
29 simultaneously, causing bus conflicts
31 To disable hardware CS, add to /boot/firmware/config.txt and reboot:
33 - ``dtoverlay=spi0-0cs`` - Don't claim any CS pins (recommended for manual CS)
34 - ``dtoverlay=spi0-1cs,cs0_pin=<gpio>`` - Use only one CS pin (specify which)
35 - ``dtoverlay=spi0-2cs,cs0_pin=<gpio>,cs1_pin=<gpio>`` - Remap CS pins
36"""
38import spidev
39from gpiod import Chip, LineSettings
40from gpiod.line import Direction, Value
41from tropicsquare.transports import L1Transport
44class SpiDevTransport(L1Transport):
45 """L1 transport for Linux spidev interface with manual GPIO CS control.
47 This transport uses the spidev library for SPI communication and gpiod
48 for manual chip select control, providing precise timing control over
49 the CS line.
51 :param bus: SPI bus number (default: 0 for /dev/spidev0.x)
52 :param device: SPI device number (default: 1 for CE1, use 0 for CE0)
53 :param cs_pin: GPIO pin number for chip select (default: 25 for CE2, use 8 for CE0 or 7 for CE1)
54 :param max_speed_hz: SPI clock speed in Hz (default: 1000000 = 1 MHz)
55 :param gpio_chip: GPIO chip device path (default: /dev/gpiochip0)
57 Example::
59 from tropicsquare.transports.spidev import SpiDevTransport
60 from tropicsquare import TropicSquareCPython
62 # Create transport for Raspberry Pi (using CE2 - no overlay needed)
63 transport = SpiDevTransport(
64 bus=0,
65 device=1,
66 cs_pin=25 # GPIO 25 (physical pin 22, CE2)
67 )
69 # Create TropicSquare instance
70 ts = TropicSquareCPython(transport)
72 try:
73 # Use the chip
74 chip_id = ts.chip_id
75 print(f"Chip ID: {chip_id}")
76 finally:
77 # Always cleanup
78 transport.close()
79 """
81 def __init__(self,
82 bus: int = 0,
83 device: int = 1,
84 cs_pin: int = 25,
85 max_speed_hz: int = 1000000,
86 gpio_chip: str = "/dev/gpiochip0"):
87 """Initialize SPIDev transport with manual GPIO CS control.
89 :param bus: SPI bus number
90 :param device: SPI device number
91 :param cs_pin: GPIO pin number for chip select
92 :param max_speed_hz: SPI clock speed in Hz
93 :param gpio_chip: Path to GPIO chip device
95 :raises OSError: If SPI or GPIO device cannot be opened
96 """
97 # Initialize SPI
98 self._spi = spidev.SpiDev()
99 self._spi.open(bus, device)
101 # Configure SPI parameters
102 self._spi.mode = 0 # SPI_MODE_0: CPOL=0, CPHA=0
103 self._spi.max_speed_hz = max_speed_hz
104 self._spi.bits_per_word = 8
105 self._spi.lsbfirst = False
107 # Initialize GPIO for chip select
108 self._cs_pin = cs_pin
110 try:
111 self._gpio_chip = Chip(gpio_chip)
113 # Configure CS line as output with initial high state (inactive)
114 line_settings = LineSettings(
115 direction=Direction.OUTPUT,
116 output_value=Value.ACTIVE # Start with CS high (1 = inactive)
117 )
119 # Request CS line
120 self._cs_request = self._gpio_chip.request_lines(
121 consumer="tropicsquare-cs",
122 config={cs_pin: line_settings}
123 )
124 except OSError as e:
125 # None of the GPIO chips worked
126 error_msg = f"Failed to open GPIO {cs_pin} for CS: {e}\n"
127 error_msg += "Hint: Add 'dtoverlay=spi0-0cs' to /boot/firmware/config.txt and reboot"
128 raise OSError(error_msg)
130 def _transfer(self, tx_data: bytes) -> bytes:
131 """SPI bidirectional transfer.
133 Performs full-duplex SPI transfer using xfer2 method which
134 simultaneously sends and receives data.
136 :param tx_data: Data to transmit
138 :returns: Received data (same length as tx_data)
139 :rtype: bytes
140 """
141 # spidev xfer2() expects list of integers and returns list of integers
142 rx_list = self._spi.xfer2(list(tx_data))
143 return bytes(rx_list)
146 def _read(self, length: int) -> bytes:
147 """SPI read operation.
149 Reads specified number of bytes from SPI bus by sending dummy bytes.
151 :param length: Number of bytes to read
153 :returns: Read data
154 :rtype: bytes
155 """
156 # readbytes() sends 0x00 as dummy data and returns list of integers
157 rx_list = self._spi.readbytes(length)
158 return bytes(rx_list)
161 def _cs_low(self) -> None:
162 """Activate chip select (set CS to logic 0)."""
163 self._cs_request.set_value(self._cs_pin, Value.INACTIVE)
166 def _cs_high(self) -> None:
167 """Deactivate chip select (set CS to logic 1)."""
168 self._cs_request.set_value(self._cs_pin, Value.ACTIVE)
171 def close(self) -> None:
172 """Release SPI and GPIO resources.
174 This method should be called when done using the transport to
175 properly cleanup hardware resources. It's recommended to use
176 the transport in a try/finally block or context manager.
178 Example::
180 transport = SpiDevTransport()
181 try:
182 # Use transport
183 pass
184 finally:
185 transport.close()
186 """
187 # Release GPIO
188 try:
189 self._cs_request.release()
190 except Exception:
191 pass # Ignore errors during cleanup
193 try:
194 self._gpio_chip.close()
195 except Exception:
196 pass # Ignore errors during cleanup
198 # Close SPI
199 try:
200 self._spi.close()
201 except Exception:
202 pass # Ignore errors during cleanup