Skip to content

Commit bac45e5

Browse files
committed
tests/ports/stm32/can: Update pyb.CAN tests for FDCAN.
Also rename the prefix from can to pyb_can, in anticipation of machine.CAN tests. Signed-off-by: Angus Gratton <angus@redyak.com.au>
1 parent f625d2e commit bac45e5

9 files changed

Lines changed: 315 additions & 173 deletions

tests/ports/stm32/can2.py

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 101 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@
77
from array import array
88
import micropython
99
import pyb
10+
import sys
11+
12+
# Classic CAN (aka bxCAN) hardware has a different filter API
13+
# and some different behaviours to newer FDCAN hardware
14+
IS_CLASSIC = hasattr(CAN, "MASK16")
15+
16+
# STM32H7 series has a gold-plated FDCAN peripheral with much deeper TX Queue
17+
# than all other parts
18+
IS_H7 = (not IS_CLASSIC) and "STM32H7" in str(sys.implementation)
1019

1120
# test we can correctly create by id (2 handled in can2.py test)
1221
for bus in (-1, 0, 1, 3):
@@ -25,22 +34,26 @@
2534

2635
can.init(CAN.LOOPBACK, num_filter_banks=14)
2736
print(can)
28-
print(can.any(0))
37+
print("any", can.any(0))
2938

3039
# Test state when freshly created
31-
print(can.state() == can.ERROR_ACTIVE)
40+
print("error_active", can.state() == can.ERROR_ACTIVE)
3241

3342
# Test that restart can be called
3443
can.restart()
3544

3645
# Test info returns a sensible value
37-
print(can.info())
46+
print("info", can.info())
3847

39-
# Catch all filter
40-
can.setfilter(0, CAN.MASK16, 0, (0, 0, 0, 0))
48+
# Catch all filter (standard IDs)
49+
if IS_CLASSIC:
50+
can.setfilter(0, CAN.MASK16, 0, (0, 0, 0, 0))
51+
else:
52+
can.setfilter(0, CAN.MASK, 0, (0, 0), extframe=False)
4153

4254
can.send("abcd", 123, timeout=5000)
43-
print(can.any(0), can.info())
55+
pyb.delay(10) # For FDCAN, needs some time to send
56+
print("any+info", can.any(0), can.info())
4457
print(can.recv(0))
4558

4659
can.send("abcd", -1, timeout=5000)
@@ -51,11 +64,16 @@
5164

5265
# Test too long message
5366
try:
54-
can.send("abcdefghi", 0x7FF, timeout=5000)
67+
if IS_CLASSIC:
68+
payload = "abcdefghi" # 9 bytes long
69+
else:
70+
# the pyb.CAN API for FDCAN always accepts messages up to 64 bytes and sends as FDCAN
71+
payload = b"x" * 65
72+
can.send(payload, 0x7FF, timeout=5000)
5573
except ValueError:
56-
print("passed")
74+
print("overlong passed")
5775
else:
58-
print("failed")
76+
print("overlong failed")
5977

6078
# Test that recv can work without allocating memory on the heap
6179

@@ -135,7 +153,13 @@
135153

136154
can = CAN(1, CAN.LOOPBACK)
137155
# Catch all filter, but only for extframe's
138-
can.setfilter(0, CAN.MASK32, 0, (0, 0), extframe=True)
156+
if IS_CLASSIC:
157+
can.setfilter(0, CAN.MASK32, 0, (0, 0), extframe=True)
158+
else:
159+
# FDCAN manages standard and extframe IDs independently, so need to
160+
# clear the standard ID filter and then set the extended ID filter
161+
can.clearfilter(0, extframe=False)
162+
can.setfilter(0, CAN.MASK, 0, (0, 0), extframe=True)
139163

140164
print(can)
141165

@@ -144,11 +168,12 @@
144168
except ValueError:
145169
print("failed")
146170
else:
171+
pyb.delay(10)
147172
r = can.recv(0)
148173
if r[0] == 0x7FF + 1 and r[4] == b"abcde":
149-
print("passed")
174+
print("extframe passed")
150175
else:
151-
print("failed, wrong data received")
176+
print("failed, wrong data received", r)
152177

153178
# Test filters
154179
for n in [0, 8, 16, 24]:
@@ -158,109 +183,40 @@
158183
id_fail = 0b00011010 << n
159184

160185
can.clearfilter(0, extframe=True)
161-
can.setfilter(0, pyb.CAN.MASK32, 0, (filter_id, filter_mask), extframe=True)
186+
if IS_CLASSIC:
187+
can.setfilter(0, CAN.MASK32, 0, (filter_id, filter_mask), extframe=True)
188+
else:
189+
can.setfilter(0, CAN.MASK, 0, (filter_id, filter_mask), extframe=True)
162190

163191
can.send("ok", id_ok, timeout=3, extframe=True)
192+
pyb.delay(10)
164193
if can.any(0):
165194
msg = can.recv(0)
166195
print((hex(filter_id), hex(filter_mask), hex(msg[0]), msg[1], msg[4]))
167196

168197
can.send("fail", id_fail, timeout=3, extframe=True)
198+
pyb.delay(10)
169199
if can.any(0):
170200
msg = can.recv(0)
171201
print((hex(filter_id), hex(filter_mask), hex(msg[0]), msg[1], msg[4]))
172202

173203
del can
174204

175-
# Test RxCallbacks
176-
print("==== TEST rx callbacks ====")
177-
178-
can = CAN(1, CAN.LOOPBACK)
179-
can.setfilter(0, CAN.LIST16, 0, (1, 2, 3, 4))
180-
can.setfilter(1, CAN.LIST16, 1, (5, 6, 7, 8))
181-
182-
183-
def cb0(bus, reason):
184-
print("cb0")
185-
if reason == 0:
186-
print("pending")
187-
if reason == 1:
188-
print("full")
189-
if reason == 2:
190-
print("overflow")
191-
192-
193-
def cb1(bus, reason):
194-
print("cb1")
195-
if reason == 0:
196-
print("pending")
197-
if reason == 1:
198-
print("full")
199-
if reason == 2:
200-
print("overflow")
201-
202-
203-
def cb0a(bus, reason):
204-
print("cb0a")
205-
if reason == 0:
206-
print("pending")
207-
if reason == 1:
208-
print("full")
209-
if reason == 2:
210-
print("overflow")
211-
212-
213-
def cb1a(bus, reason):
214-
print("cb1a")
215-
if reason == 0:
216-
print("pending")
217-
if reason == 1:
218-
print("full")
219-
if reason == 2:
220-
print("overflow")
221-
222-
223-
can.rxcallback(0, cb0)
224-
can.rxcallback(1, cb1)
225-
226-
can.send("11111111", 1, timeout=5000)
227-
can.send("22222222", 2, timeout=5000)
228-
can.send("33333333", 3, timeout=5000)
229-
can.rxcallback(0, cb0a)
230-
can.send("44444444", 4, timeout=5000)
231-
232-
can.send("55555555", 5, timeout=5000)
233-
can.send("66666666", 6, timeout=5000)
234-
can.send("77777777", 7, timeout=5000)
235-
can.rxcallback(1, cb1a)
236-
can.send("88888888", 8, timeout=5000)
237-
238-
print(can.recv(0))
239-
print(can.recv(0))
240-
print(can.recv(0))
241-
print(can.recv(1))
242-
print(can.recv(1))
243-
print(can.recv(1))
244-
245-
can.send("11111111", 1, timeout=5000)
246-
can.send("55555555", 5, timeout=5000)
247-
248-
print(can.recv(0))
249-
print(can.recv(1))
250-
251-
del can
252-
253205
# Testing asynchronous send
254206
print("==== TEST async send ====")
255207

256208
can = CAN(1, CAN.LOOPBACK)
257-
can.setfilter(0, CAN.MASK16, 0, (0, 0, 0, 0))
209+
# Catch all filter (standard IDs)
210+
if IS_CLASSIC:
211+
can.setfilter(0, CAN.MASK16, 0, (0, 0, 0, 0))
212+
else:
213+
can.setfilter(0, CAN.MASK, 0, (0, 0), extframe=False)
258214

259215
while can.any(0):
260216
can.recv(0)
261217

262218
can.send("abcde", 1, timeout=0)
263-
print(can.any(0))
219+
print("any", can.any(0))
264220
while not can.any(0):
265221
pass
266222

@@ -270,45 +226,82 @@ def cb1a(bus, reason):
270226
can.send("abcde", 2, timeout=0)
271227
can.send("abcde", 3, timeout=0)
272228
can.send("abcde", 4, timeout=0)
273-
can.send("abcde", 5, timeout=0)
229+
if not IS_H7:
230+
can.send("abcde", 5, timeout=0)
231+
else:
232+
# Hack around the STM32H7's deeper transmit queue by pretending this call failed
233+
# (STM32G4 will fail here, using otherwise the same code, so there is still some test coverage.)
234+
print("send fail ok")
274235
except OSError as e:
275-
if str(e) == "16":
276-
print("passed")
236+
# When send() fails Classic CAN raises OSError(MP_EBUSY) (16), FDCAN raises OSError(MP_ETIMEDOUT) (110)
237+
if e.errno == (16 if IS_CLASSIC else 110):
238+
print("send fail ok")
277239
else:
278-
print("failed")
240+
print("send fail not ok", e)
279241

280242
pyb.delay(500)
281243
while can.any(0):
282244
print(can.recv(0))
283245

246+
del can
247+
284248
# Testing rtr messages
285249
print("==== TEST rtr messages ====")
286250

287251
bus1 = CAN(1, CAN.LOOPBACK)
288252
while bus1.any(0):
289253
bus1.recv(0)
290-
bus1.setfilter(0, CAN.LIST16, 0, (1, 2, 3, 4))
291-
bus1.setfilter(1, CAN.LIST16, 0, (5, 6, 7, 8), rtr=(True, True, True, True))
292-
bus1.setfilter(2, CAN.MASK16, 0, (64, 64, 32, 32), rtr=(False, True))
254+
255+
if IS_CLASSIC:
256+
# pyb.CAN Classic API allows distinguishing between RTR in the filter
257+
bus1.setfilter(0, CAN.LIST16, 0, (1, 2, 3, 4))
258+
bus1.setfilter(1, CAN.LIST16, 0, (5, 6, 7, 8), rtr=(True, True, True, True))
259+
bus1.setfilter(2, CAN.MASK16, 0, (64, 64, 32, 32), rtr=(False, True))
260+
else:
261+
# pyb.CAN FDCAN API does not allow distinguishing RTR in filter args, so
262+
# instead we'll only filter the message IDs where Classic CAN equivalent is
263+
# setting the RTR flag (meaning we're verifying RTR is received correctly, but
264+
# not verifying the missing filter behaviour.)
265+
bus1.setfilter(0, CAN.RANGE, 0, (5, 8))
266+
bus1.setfilter(1, CAN.MASK, 0, (32, 32))
267+
268+
269+
def print_rtr(msg):
270+
if msg:
271+
# Skip printing msg[3] as this is the filter match index, and the value
272+
# is different between Classic and FDCAN implementations
273+
print(msg[0], msg[1], msg[2], msg[4])
274+
else:
275+
print(msg)
276+
293277

294278
bus1.send("", 1, rtr=True)
295-
print(bus1.any(0))
279+
print("any", bus1.any(0))
296280
bus1.send("", 5, rtr=True)
297-
print(bus1.recv(0))
281+
print_rtr(bus1.recv(0))
298282
bus1.send("", 6, rtr=True)
299-
print(bus1.recv(0))
283+
print_rtr(bus1.recv(0))
300284
bus1.send("", 7, rtr=True)
301-
print(bus1.recv(0))
285+
print_rtr(bus1.recv(0))
302286
bus1.send("", 16, rtr=True)
303-
print(bus1.any(0))
287+
print("any", bus1.any(0))
304288
bus1.send("", 32, rtr=True)
305-
print(bus1.recv(0))
289+
print_rtr(bus1.recv(0))
290+
291+
del bus1
306292

307293
# test HAL error, timeout
308294
print("==== TEST errors ====")
309295

296+
# Note: this test requires no other CAN node is attached to the CAN peripheral
310297
can = pyb.CAN(1, pyb.CAN.NORMAL)
311298
try:
312-
can.send("1", 1, timeout=50)
299+
if IS_CLASSIC:
300+
can.send("1", 1, timeout=50)
301+
else:
302+
# Difference between pyb.CAN on Classic vs FDCAN - Classic waits until the message is sent to the bus,
303+
# FDCAN only times out if the TX queue is full
304+
while True:
305+
can.send("1", 1, timeout=50)
313306
except OSError as e:
314-
print(repr(e))
307+
print("timeout", repr(e))

0 commit comments

Comments
 (0)