STM32 pyb.CAN: Instantiating one CAN instance can corrupts settings of another instance.
Port, board and/or hardware
STM32 PYBv1.0
MicroPython version
MicroPython v1.28.0-preview.259.g4625f97d09.dirty on 2026-03-13; PYBv1.0 with STM32F405RG
Reproduction
I found a problem with my pyb.CAN test script. Creating an instance of CAN2 corrupts the settings of CAN1, probably because they share a hardware block. I wrote the test way back when I did the F413 port. F413 has three CAN ports. I create and initialise up to three instances in a loop. It works on 1.17, but not on 1.28, possibly since the introduction of FDCAN.
Minimum code demonstrating the problem. This runs on PYBv1.0
from pyb import CAN
can1 = CAN(1, CAN.LOOPBACK)
can1.setfilter(0, CAN.LIST16, 0, (123, 124, 125, 126)) # set a filter to receive messages with id=123, 124, 125 and 126
can2 = CAN(2, CAN.LOOPBACK)
can2.setfilter(0, CAN.LIST16, 0, (123, 124, 125, 126)) # set a filter to receive messages with id=123, 124, 125 and 126
can1.send('message1', 123) # send a message with id 123
print(can1.recv(0, timeout=50)) # receive message on FIFO 0
can2.send('message2', 123) # send a message with id 123
print(can2.recv(0, timeout=50)) # receive message on FIFO 0
Expected behaviour
Works on 1.17
(123, False, False, 0, b'message1')
(123, False, False, 0, b'message2')
Observed behaviour
Fails on 1.28
Traceback (most recent call last):
File "<stdin>", line 8, in <module>
OSError: [Errno 110] ETIMEDOUT
Additional Information
Moving can1.setfilter fixes it.
can1 = CAN(1, CAN.LOOPBACK)
can2 = CAN(2, CAN.LOOPBACK)
can1.setfilter(0, CAN.LIST16, 0, (123, 124, 125, 126)) # set a filter to receive messages with id=123, 124, 125 and 126
can2.setfilter(0, CAN.LIST16, 0, (123, 124, 125, 126)) # set a filter to receive messages with id=123, 124, 125 and 126
I don't think I can help debug this, but I am happy to help with testing on different STM32 MCUs.
This is my original loopback test script for F413. It is a cutdown version of an external CAN test that requires CAN tranceivers.
available_CANs = [1, 2, 3] is for F413.
available_CANs = [1, 2] works on PYBv3 1.14, fails on PYBv1.0 1.28
available_CANs = [2,1] passes for CAN2, then fails for CAN1
available_CANs = [1,] passes.
Note also that the receive message position changed in1.19 and I have updated th script to handle that.
# MicroPython CAN loopback test.
# This test works on all boards with one or more CAN port.
# No external hardware is required.
# Set which CAN controllers to test in "available_CANs".
import pyb
from pyb import CAN
import sys
if sys.implementation[1][1] >18:
msg_idx = 4
else:
msg_idx = 3
# -----------------------------
available_CANs = [1, 2, 3]
# -----------------------------
cans = []
for i in range(0, max(available_CANs)):
cans.append('none')
if (i+1) in available_CANs:
cans[i] = CAN(i+1, CAN.LOOPBACK)
cans[i].setfilter(0, CAN.LIST16, 0, (123, 124, 125, 126)) # set a filter to receive messages with id=123, 124, 125 and 126
# To fix this for 1.27 comment out the line above and enable the next two lines.
#for i in available_CANs:
# cans[i-1].setfilter(0, CAN.LIST16, 0, (123, 124, 125, 126)) # set a filter to receive messages with id=123, 124, 125 and 126
def CAN_send(s):
message = 'CAN ' + str(s) + '->'
cans[s-1].send(message, 123) # send a message with id 123
def CAN_recv(r):
res = cans[r-1].recv(0, timeout=50) # receive message on FIFO 0
return res
try:
while True:
for i in available_CANs:
CAN_send(i)
pyb.delay(100)
res = CAN_recv(i)
print(res[msg_idx].decode("utf-8") + str(i), " passed internal loopback") # change index to 3 for older versions.
print('----------------------------------')
finally:
print('FAIL, CAN dev', i, '\n')
Code of Conduct
Yes, I agree
stm32: Update pyb.CAN loopback unit tests for FDCAN, fix CAN2 filter bug
Summary
- Updates the loopback unit tests in
tests/ports/stm32/can*.pyto pass on CANFD hardware, renames them topyb_can*.pyto account for incoming machine.CAN functionality. - As part of updating
pyb_can2.pyto work with FDCAN, was also able to fix #18922 and make that unit test a regression test for it. - Cleaned up some parameter names and types for
can_clearfilter()as they weren't very clear. - Enabled CAN2 peripheral on the
NUCLEO_G474REboard, as used for testing.
This work is to support #18572 - now tests pass for pyb.CAN we can verify there's no regression when adding machine.CAN. Have made into a separate PR as the other is already too big!
Testing
- Ran
ports/stm32/pyb_can*.pyunit tests on PYBDV11 (Classic CAN), NUCLEO_G474RE and NUCLEO_H723ZG boards (both flavours of FDCAN). All now passing. - Connected a CAN transceiver to CAN2 on NUCLEO_G474RE and confirmed the CAN bus also works on the configured pins in non-loopback mode.
NUCLEO_H723ZGdoesn't have externally usable FDCAN2 pins without changing solder jumper settings (and possibly other things), so I didn't commit the change to this PR as it's only immediately usable for loopback tests. For refernece, the patch for loopback testing is:
--- a/ports/stm32/boards/NUCLEO_H723ZG/mpconfigboard.h
+++ b/ports/stm32/boards/NUCLEO_H723ZG/mpconfigboard.h
@@ -97,6 +97,10 @@
#define MICROPY_HW_CAN1_TX (pin_D1)
#define MICROPY_HW_CAN1_RX (pin_D0)
+#define MICROPY_HW_CAN2_NAME "FDCAN2"
+#define MICROPY_HW_CAN2_TX (pin_B13) // B13, B6
+#define MICROPY_HW_CAN2_RX (pin_B12) // B12, B5
+
// SD card detect switch
#define MICROPY_HW_SDCARD_DETECT_PIN (pin_G2)
#define MICROPY_HW_SDCARD_DETECT_PULL (GPIO_PULLUP)
Generative AI
I did not use generative AI tools when creating this PR.