Skip to content

Commit 13202e6

Browse files
committed
python: tune SPI for pandad
1 parent 7ffc916 commit 13202e6

2 files changed

Lines changed: 42 additions & 20 deletions

File tree

python/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,10 @@ class Panda:
150150
HARNESS_STATUS_NORMAL = 1
151151
HARNESS_STATUS_FLIPPED = 2
152152

153-
def __init__(self, serial: str | None = None, claim: bool = True, disable_checks: bool = True, can_speed_kbps: int = 500, cli: bool = True):
153+
def __init__(self, serial: str | None = None, claim: bool = True, disable_checks: bool = True,
154+
can_speed_kbps: int = 500, cli: bool = True, spi_only: bool = False):
154155
self._disable_checks = disable_checks
156+
self._spi_only = spi_only
155157

156158
self._handle: BaseHandle
157159
self._handle_open = False
@@ -207,7 +209,10 @@ def connect(self, claim=True, wait=False):
207209
self._handle = None
208210
while self._handle is None:
209211
# try USB first, then SPI
210-
self._context, self._handle, serial, self.bootstub = self.usb_connect(self._connect_serial, claim=claim, no_error=wait)
212+
if self._spi_only:
213+
self._context, self._handle, serial, self.bootstub = None, None, None, False
214+
else:
215+
self._context, self._handle, serial, self.bootstub = self.usb_connect(self._connect_serial, claim=claim, no_error=wait)
211216
if self._handle is None:
212217
self._context, self._handle, serial, self.bootstub = self.spi_connect(self._connect_serial)
213218
if not wait:

python/spi.py

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
NACK = 0x1F
3131
CHECKSUM_START = 0xAB
3232

33-
MIN_ACK_TIMEOUT_MS = 100
33+
MIN_ACK_TIMEOUT_MS = 20
34+
SPI_ACK_TIMEOUT_MS = 500
35+
SPI_CLEANUP_TIMEOUT_MS = 1
3436
MAX_XFER_RETRY_COUNT = 5
3537

3638
SPI_BUF_SIZE = 4096 # from panda/board/drivers/spi.h
@@ -124,6 +126,8 @@ class PandaSpiHandle(BaseHandle):
124126
def __init__(self) -> None:
125127
self.dev = SpiDevice()
126128
self.no_retry = "NO_RETRY" in os.environ
129+
self.connected = True
130+
self.comms_healthy = True
127131

128132
# helpers
129133
def _calc_checksum(self, data: bytes) -> int:
@@ -133,10 +137,12 @@ def _calc_checksum(self, data: bytes) -> int:
133137
return cksum
134138

135139
def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int, length: int = 1) -> bytes:
136-
timeout_s = max(MIN_ACK_TIMEOUT_MS, timeout) * 1e-3
140+
if timeout == 0:
141+
timeout = SPI_ACK_TIMEOUT_MS
142+
timeout_s = timeout * 1e-3
137143

138144
start = time.monotonic()
139-
while (timeout == 0) or ((time.monotonic() - start) < timeout_s):
145+
while (time.monotonic() - start) < timeout_s:
140146
dat = spi.xfer2([tx, ] * length)
141147
if dat[0] == ack_val:
142148
return bytes(dat)
@@ -154,7 +160,7 @@ def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: i
154160
spi.xfer2(packet)
155161

156162
logger.debug("- waiting for header ACK")
157-
self._wait_for_ack(spi, HACK, MIN_ACK_TIMEOUT_MS, 0x11)
163+
self._wait_for_ack(spi, HACK, timeout, 0x11)
158164

159165
logger.debug("- sending data")
160166
packet = bytes([*data, self._calc_checksum(data)])
@@ -165,18 +171,14 @@ def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: i
165171
return b""
166172
else:
167173
logger.debug("- waiting for data ACK")
168-
preread_len = USBPACKET_MAX_SIZE + 1 # read enough for a controlRead
169-
dat = self._wait_for_ack(spi, DACK, timeout, 0x13, length=3 + preread_len)
174+
dat = self._wait_for_ack(spi, DACK, timeout, 0x13, length=3)
170175

171176
# get response length, then response
172177
response_len = struct.unpack("<H", dat[1:3])[0]
173178
if response_len > max_rx_len:
174179
raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})")
175180

176-
# read rest
177-
remaining = (response_len + 1) - preread_len
178-
if remaining > 0:
179-
dat += bytes(spi.readbytes(remaining))
181+
dat += bytes(spi.readbytes(response_len + 1))
180182

181183
dat = dat[:3 + response_len + 1]
182184
if self._calc_checksum(dat) != 0:
@@ -189,32 +191,46 @@ def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, e
189191
logger.debug("==============================================")
190192

191193
n = 0
192-
start_time = time.monotonic()
194+
nack_count = 0
195+
timeout_count = 0
193196
exc = PandaSpiException()
194-
while (timeout == 0) or (time.monotonic() - start_time) < timeout*1e-3:
197+
while self.connected:
195198
n += 1
196199
logger.debug("\ntry #%d", n)
197200
with self.dev.acquire() as spi:
198201
try:
199-
return self._transfer_spidev(spi, endpoint, data, timeout, max_rx_len, expect_disconnect)
202+
ret = self._transfer_spidev(spi, endpoint, data, timeout, max_rx_len, expect_disconnect)
203+
self.comms_healthy = True
204+
return ret
200205
except PandaSpiException as e:
201206
exc = e
202207
logger.debug("SPI transfer failed, retrying", exc_info=True)
203208
if self.no_retry:
204209
break
205210

211+
if isinstance(e, PandaSpiMissingAck):
212+
timeout_count += 1
213+
if timeout != 0 and timeout_count > MAX_XFER_RETRY_COUNT:
214+
break
215+
216+
if isinstance(e, PandaSpiNackResponse):
217+
nack_count += 1
218+
if nack_count > 3:
219+
time.sleep(min(max(nack_count * 10, 200), 2000) * 1e-6)
220+
206221
# ensure slave is in a consistent state and ready for the next transfer
207222
# (e.g. slave TX buffer isn't stuck full)
208-
nack_cnt = 0
223+
cleanup_nack_count = 0
209224
attempts = 5
210-
while (nack_cnt <= 3) and (attempts > 0):
225+
while (cleanup_nack_count <= 3) and (attempts > 0):
211226
attempts -= 1
212227
try:
213-
self._wait_for_ack(spi, NACK, MIN_ACK_TIMEOUT_MS, 0x11, length=XFER_SIZE//2)
214-
nack_cnt += 1
228+
self._wait_for_ack(spi, NACK, SPI_CLEANUP_TIMEOUT_MS, 0x14, length=XFER_SIZE//2)
229+
cleanup_nack_count += 1
215230
except PandaSpiException:
216-
nack_cnt = 0
231+
cleanup_nack_count = 0
217232

233+
self.comms_healthy = False
218234
raise exc
219235

220236
def get_protocol_version(self) -> bytes:
@@ -255,6 +271,7 @@ def _get_version(spi) -> bytes:
255271

256272
# libusb1 functions
257273
def close(self):
274+
self.connected = False
258275
self.dev.close()
259276

260277
def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT, expect_disconnect: bool = False):

0 commit comments

Comments
 (0)