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: Fix Classic CAN issue where initialising CAN1 corrupts CAN2.
Summary
- Closes #18922 - remaining part of this issue where initialising CAN1 after CAN2 on Classic CAN would clear CAN1's filters.
- Extends test coverage to this case for both pyb.CAN and machine.CAN
- Fixed another potential issue where de-initialising CAN2 would reset CAN1 in RCC block (not necessary to fix the test case from #18922, but may have shown up in other sequences.)
- Extends test coverage to initialising 3x CAN peripherals at one time in any order.
This PR is marked draft until I get some hardware to address the last item.
Testing
- Ran the updated unit tests on a PYBV11 (Classic CAN) and a STM32G4 (FDCAN). PYBV11 fails without this fix, passes with this fix.
- Re-run
ports/stm32/pyb_can*.py extmod_hardware/machine_can*.pyunit tests,multi_extmod/machine_can_*.py multi_pyb_can/*.pymulti-tests.
Trade-offs and Alternatives
- The way pyb.CAN handles the CAN1/CAN2 filter split is a bit awkward, but as we want to focus on machine.CAN it's probably not worth spending more time thinking about it.
Generative AI
I did not use generative AI tools when creating this PR.