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

1"""SPIDev Transport Implementation for Raspberry Pi 

2 

3This module provides SPI transport implementation for Linux spidev interface 

4with manual GPIO chip select control. 

5 

6Recommended Setup: 

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

8 

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

10 

11Alternative - Hardware CS Pins: 

12 GPIO 8 (CE0, pin 24) or GPIO 7 (CE1, pin 26):: 

13 

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

15 

16.. note:: 

17 Device tree overlay requirement depends on your hardware configuration: 

18 

19 **No overlay needed** if: 

20 

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 

24 

25 **Overlay REQUIRED** if: 

26 

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 

30 

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

32 

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""" 

37 

38import spidev 

39from gpiod import Chip, LineSettings 

40from gpiod.line import Direction, Value 

41from tropicsquare.transports import L1Transport 

42 

43 

44class SpiDevTransport(L1Transport): 

45 """L1 transport for Linux spidev interface with manual GPIO CS control. 

46 

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. 

50 

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) 

56 

57 Example:: 

58 

59 from tropicsquare.transports.spidev import SpiDevTransport 

60 from tropicsquare import TropicSquareCPython 

61 

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 ) 

68 

69 # Create TropicSquare instance 

70 ts = TropicSquareCPython(transport) 

71 

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 """ 

80 

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. 

88 

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 

94 

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) 

100 

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 

106 

107 # Initialize GPIO for chip select 

108 self._cs_pin = cs_pin 

109 

110 try: 

111 self._gpio_chip = Chip(gpio_chip) 

112 

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 ) 

118 

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) 

129 

130 def _transfer(self, tx_data: bytes) -> bytes: 

131 """SPI bidirectional transfer. 

132 

133 Performs full-duplex SPI transfer using xfer2 method which 

134 simultaneously sends and receives data. 

135 

136 :param tx_data: Data to transmit 

137 

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) 

144 

145 

146 def _read(self, length: int) -> bytes: 

147 """SPI read operation. 

148 

149 Reads specified number of bytes from SPI bus by sending dummy bytes. 

150 

151 :param length: Number of bytes to read 

152 

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) 

159 

160 

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) 

164 

165 

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) 

169 

170 

171 def close(self) -> None: 

172 """Release SPI and GPIO resources. 

173 

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. 

177 

178 Example:: 

179 

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 

192 

193 try: 

194 self._gpio_chip.close() 

195 except Exception: 

196 pass # Ignore errors during cleanup 

197 

198 # Close SPI 

199 try: 

200 self._spi.close() 

201 except Exception: 

202 pass # Ignore errors during cleanup