Skip to content

STM32 pyb.CAN: Instantiating one CAN instance can corrupts settings of another instance. #18922

Description

@chrismas9

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

Activity

  1. chrismas9 commented on Mar 14, 2026

    @chrismas9
    ContributorAuthor

    The issue also occurs with CAN3 on STM32F413, even though it is somewhat independant of CAN1 and CAN2. Creating an instance of CAN3 corrupts CAN1 and CAN2 settings.

  2. projectgus commented on Mar 18, 2026

    @projectgus
    Contributor

    Thanks for the very clear report, @chrismas9! I was able to fix the issue for CAN2, although I'm not sure what will be the correct fix for CAN3 (similar, I expect.)

  3. projectgus commented on Mar 18, 2026

    @projectgus
    Contributor

    I'm not sure what will be the correct fix for CAN3 (similar, I expect.)

    Looking at the HAL, it looks like the same problem shouldn't reoccur for CAN3 (as it has fully independent filter indexes). So the root cause of CAN3 corruption may be something else. I unfortunately don't have a CAN3-enabled board to test with.

  4. projectgus commented on Mar 18, 2026

    @projectgus
    Contributor

    I unfortunately don't have a CAN3-enabled board to test with.

    Have ordered a NUCLEO-F413ZH board to add to my collection of ST Nucleo boards!

  5. projectgus commented on Mar 18, 2026

    @projectgus
    Contributor

    @chrismas9 Are you able to easily check if the CAN3 corruption still occurs on master? If it's still there then I can reopen this issue (maybe with an edited title.)

  6. added a commit that references this issue on Mar 18, 2026
    4b339ee
  7. chrismas9 commented on Mar 19, 2026

    @chrismas9
    ContributorAuthor

    @projectgus Your fix works for CAN2 corrupting CAN1, but CAN1 still corrupts CAN2 and CAN3 corrupts CAN1 and CAN2.

    In pyb_can2.py You create CAN2 after CAN1. For a complete regression test you may want to test with each CAN last. e.g.
    CAN1, CAN2, CAN3
    CAN1, CAN3, CAN2
    CAN2, CAN3, CAN1

    I'm happy to do further tesing before your F413 board arrives.

    Test results for F413:
    MicroPython v1.28.0-preview.270.gd1c936db73 on 2026-03-19; NUCLEO-F413ZH with STM32F413

    CAN1 last:
    CAN2, CAN1 fails. CAN2 fails, CAN1 passes. CAN1 corrupts CAN2.
    CAN3, CAN1 passes. CAN1 doesn't corrupt CAN3.
    CAN2, CAN3, CAN1 fails. CAN2 fails, CAN1 & CAN3 pass. CAN1 corrupts CAN2, CAN1 doesn't corrupt CAN3.
    CAN3, CAN2, CAN1 fails. CAN2 fails, CAN1 & CAN3 pass. CAN1 corrupts CAN2, CAN1 doesn't corrupt CAN3.
    Result. CAN1 corrupts CAN2, but not CAN3.

    CAN2 last:
    CAN1, CAN2 passes. CAN2 doesn't corrupt CAN1.
    CAN3, CAN2 passes. CAN2 doesn't corrupt CAN3.
    CAN1, CAN3, CAN2 fails. CAN1 fails, CAN2 & CAN3 pass. CAN3 corrupts CAN1, CAN2 doesn't corrupt CAN3.
    CAN3, CAN1, CAN2 passes. CAN2 doesn't corrupt CAN1 or CAN3.
    Result. CAN2 doesn't corrupt CAN1 or CAN3.

    CAN3 last:
    CAN1, CAN3 fails. CAN1 fails. CAN3 corrupts CAN1.
    CAN2, CAN3 fails. CAN2 fails. CAN3 corrupts CAN2.
    CAN1, CAN2, CAN3 fails. CAN1 & CAN2 fail, CAN3 passes. CAN3 corrupts CAN1 & CAN2.
    CAN2, CAN1, CAN3 fails. CAN1 & CAN2 fail, CAN3 passes. CAN3 corrupts CAN1 & CAN2.
    Result. CAN3 corrupts CAN1 & CAN2.

    CAN1, CAN2 tests on PYBv1.0
    MicroPython v1.28.0-preview.270.gd1c936db73 on 2026-03-19; PYBv1.0 with STM32F405RG

    CAN2, CAN1 fails. CAN2 fails, CAN1 passes. CAN1 corrupts CAN2.
    CAN1, CAN2 passes. CAN2 doesn't corrupt CAN1.

    Summary.
    CAN1 corrupts CAN2, but not CAN3.
    CAN2 doesn't corrupt anything. Fixed in latest.
    CAN3 corrupts CAN1 & CAN2.

    from pyb import CAN
    
    # List up to 3 CAN channels in any order. Unavailable channels will be ignored.
    # They will be instantiated and initialised in the order listed.
    test_cans = [1, 2, 3]
    
    cans = []
    print('------------------------------------------------------------------------------------------------------------------------')
    j = 0
    for i in test_cans:
        cans.append([i,None])
        try:
            cans[j][1] = CAN(i, CAN.LOOPBACK)
            cans[j][1].setfilter(0, CAN.LIST16, 0, (123, 124, 125, 126))
        except:
            print('can' + str(i), 'not available')
        j +=1
    print('Test order:\n', cans)
    
    print('------------------------------------------------------------------------------------------------------------------------')
    
    for i in range(0, j):
        can_msg = 'can' + str(cans[i][0])
        if cans[i][1] is not None:
            try:
                cans[i][1].send(can_msg, 123)
                try:
                    print(can_msg, cans[i][1].recv(0, timeout=50))
                except:
                    print(can_msg, 'receive failed')
            except:
                print(can_msg, 'send failed')
    
  8. changed the title [-]STM32 pyb.CAN: Intantiating second instance corrupts settings of first instance.[/-] [+]STM32 pyb.CAN: Intantiating one CAN instance can corrupts settings of another instance.[/+] on Mar 20, 2026
  9. projectgus commented on Mar 20, 2026

    @projectgus
    Contributor

    @projectgus Your fix works for CAN2 corrupting CAN1, but CAN1 still corrupts CAN2 and CAN3 corrupts CAN1 and CAN2.

    Sorry, my mistake - I clearly didn't read the whole issue as submitted, only the first part. Have reopened, and I'll try and take another look soon.

    While testing machine.CAN I did find and fix a problem with FDCAN losing state as the deinit functions were resetting the entire peripheral block in RCC even when another instance was active. It might be something similar is happening with bxCAN, somehow.

  10. chrismas9 commented on Mar 20, 2026

    @chrismas9
    ContributorAuthor

    I wasn't very clear about the reverse order. I also found that my original test script reordered the creation numerically, so I probably didn't even test CAN2 before CAN1. The ordering is fixed in my latest test.

  11. changed the title [-]STM32 pyb.CAN: Intantiating one CAN instance can corrupts settings of another instance.[/-] [+]STM32 pyb.CAN: Instantiating one CAN instance can corrupts settings of another instance.[/+] on Mar 20, 2026
  12. added a commit that references this issue on Sep 21, 2026
    90625c2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions