Files
micropython/tests/multi_extmod/machine_can_07_error_states.py
T
Angus Gratton 6cac2d275d
JavaScript code lint and formatting with Biome / eslint (push) Has been cancelled
Check code formatting / code-formatting (push) Has been cancelled
Check spelling with codespell / codespell (push) Has been cancelled
Build docs / build (push) Has been cancelled
Check examples / embedding (push) Has been cancelled
Package mpremote / build (push) Has been cancelled
.mpy file format and tools / test (push) Has been cancelled
Build ports metadata / build (push) Has been cancelled
alif port / build_alif (alif_ae3_build) (push) Has been cancelled
cc3200 port / build (push) Has been cancelled
esp32 port / build_idf (esp32_build_c2_c5_c6) (push) Has been cancelled
esp32 port / build_idf (esp32_build_cmod_spiram_s2) (push) Has been cancelled
esp32 port / build_idf (esp32_build_p4) (push) Has been cancelled
esp32 port / build_idf (esp32_build_s3_c3) (push) Has been cancelled
esp8266 port / build (push) Has been cancelled
mimxrt port / build (push) Has been cancelled
nrf port / build (push) Has been cancelled
powerpc port / build (push) Has been cancelled
qemu port / build_and_test_arm (bigendian) (push) Has been cancelled
qemu port / build_and_test_arm (sabrelite) (push) Has been cancelled
qemu port / build_and_test_arm (thumb_hardfp) (push) Has been cancelled
qemu port / build_and_test_arm (thumb_softfp) (push) Has been cancelled
qemu port / build_and_test_rv32 (push) Has been cancelled
qemu port / build_and_test_rv64 (push) Has been cancelled
renesas-ra port / build_renesas_ra_board (push) Has been cancelled
rp2 port / build (push) Has been cancelled
samd port / build (push) Has been cancelled
stm32 port / build_stm32 (stm32_misc_build) (push) Has been cancelled
stm32 port / build_stm32 (stm32_nucleo_build) (push) Has been cancelled
stm32 port / build_stm32 (stm32_pyb_build) (push) Has been cancelled
unix port / minimal (push) Has been cancelled
unix port / reproducible (push) Has been cancelled
unix port / standard (push) Has been cancelled
unix port / standard_v2 (push) Has been cancelled
unix port / coverage (push) Has been cancelled
unix port / coverage_32bit (push) Has been cancelled
unix port / nanbox (push) Has been cancelled
unix port / longlong (push) Has been cancelled
unix port / float (push) Has been cancelled
unix port / gil_enabled (push) Has been cancelled
unix port / stackless_clang (push) Has been cancelled
unix port / float_clang (push) Has been cancelled
unix port / settrace_stackless (push) Has been cancelled
unix port / repr_b (push) Has been cancelled
unix port / macos (push) Has been cancelled
unix port / qemu_mips (push) Has been cancelled
unix port / qemu_arm (push) Has been cancelled
unix port / qemu_riscv64 (push) Has been cancelled
unix port / sanitize_address (push) Has been cancelled
unix port / sanitize_undefined (push) Has been cancelled
webassembly port / build (push) Has been cancelled
windows port / build-vs (Debug, true, x64, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Debug, true, x86, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Debug, x64, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Debug, x86, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, dev, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, standard, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, standard, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, dev, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, standard, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, standard, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, x64, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x64, standard, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x86, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x86, standard, 2022, [17, 18)) (push) Has been cancelled
windows port / build-mingw (i686, mingw32, dev) (push) Has been cancelled
windows port / build-mingw (i686, mingw32, standard) (push) Has been cancelled
windows port / build-mingw (x86_64, mingw64, dev) (push) Has been cancelled
windows port / build-mingw (x86_64, mingw64, standard) (push) Has been cancelled
windows port / cross-build-on-linux (push) Has been cancelled
zephyr port / build (push) Has been cancelled
Python code lint and formatting with ruff / ruff (push) Has been cancelled
stm32: Add machine.CAN implementation.
Implemented according to API docs in a parent comment.

Adds new multi_extmod/machine_can_* tests which pass when testing between
NUCLEO_G474RE, NUCLEO_H723ZG and PYBDV11.

This work was mostly funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <angus@redyak.com.au>
2026-03-19 17:36:50 +11:00

206 lines
6.6 KiB
Python

from machine import CAN
from micropython import const
import time
# Test that without a second CAN node on the network the controller will
# correctly go into the correct error states, and can then recover.
#
# Note this test depends on no other CAN node being active on the network apart
# from the two test instances. (Although it's OK for extra nodes to be in a
# "listen only" mode where they won't ACK messages.)
rx_overflow = False
rx_full = False
received = []
# CAN IDs
_ID = const(0x100)
# can.state() result list indexes, as constants
_IDX_TEC = const(0)
_IDX_REC = const(1)
_IDX_NUM_WARNING = const(2)
_IDX_NUM_PASSIVE = const(3)
_IDX_NUM_BUS_OFF = const(4)
_IDX_PEND_TX = const(5)
can = CAN(1, 500_000)
def state_name(state):
for name in dir(CAN):
if name.startswith("STATE_") and state == getattr(CAN, name):
return name
return f"UNKNOWN-{state}"
def irq_recv(can):
while can.irq().flags() & can.IRQ_RX:
can_id, data, _flags, errors = can.recv()
print("recv", hex(can_id), data.hex())
# Receiver
def instance0():
can.irq(irq_recv, trigger=can.IRQ_RX, hard=False)
can.set_filters(None) # receive all
multitest.next()
# Receive at least one CAN message before sender asks us to disable the controller
multitest.wait("disable receiver")
can.deinit()
print("can deinit()")
multitest.broadcast("receiver disabled")
# Wait for the sender to tell us to re-enable
multitest.wait("enable receiver")
# note the irq is no longer active after deinit()
can.init(500_000)
print("can init()")
multitest.broadcast("receiver enabled")
# Receive CAN messages until the sender asks us to switch to an invalid baud rate
multitest.wait("switch baud")
can.init(125_000)
print("can switch baud")
multitest.broadcast("switched baud")
time.sleep_ms(1)
print("sending bad msg")
# trying to send this frame should introduce more bus errors
idx_bad = can.send(_ID, b"BADBAUD")
multitest.wait("fix baud")
print("sending cancelling")
print("cancelled", can.cancel_send(idx_bad))
print("re-init")
can.init(500_000)
multitest.broadcast("fixed baud")
# Should be receiving CAN messages OK again
print("done")
def irq_sender(can):
while flags := can.irq().flags():
if flags & can.IRQ_STATE:
print("irq state", can.state())
if flags & can.IRQ_TX:
print("irq sent", not (flags & can.IRQ_TX_FAILED))
# Sender
def instance1():
can.irq(irq_sender, CAN.IRQ_STATE | CAN.IRQ_TX, hard=False)
can.set_filters(None)
multitest.next()
print("started", state_name(can.state())) # should be ERROR_ACTIVE
# Send a single message to the receiver, to verify it's working
can.send(_ID, b"PAYLOAD")
active_counters = can.get_counters()
# print(active_counters) # DEBUG
multitest.broadcast("disable receiver")
multitest.wait("receiver disabled")
# Now the receiver shouldn't be ACKing our frames, queue will stay full
# ... we will get the ISR for ERROR_WARNING but it'll go from ERROR_WARNING to ERROR_PASSIVE
# very quickly as all messages are failing
can.send(_ID, b"MORE")
while can.state() in (CAN.STATE_ACTIVE, CAN.STATE_WARNING):
pass
print(state_name(can.state())) # should be ERROR_PASSIVE now
passive_counters = can.get_counters()
# print(passive_counters) # DEBUG
print("tec increased", passive_counters[_IDX_TEC] > active_counters[_IDX_TEC])
print("tec over thresh", passive_counters[_IDX_TEC] >= 128)
# we should have counted exactly one ERROR_WARNING and ERROR_PASSIVE transition
print(
"counted warning",
passive_counters[_IDX_NUM_WARNING] == active_counters[_IDX_NUM_WARNING] + 1,
)
print(
"counted passive",
passive_counters[_IDX_NUM_PASSIVE] == active_counters[_IDX_NUM_PASSIVE] + 1,
)
print("some pending tx", passive_counters[_IDX_PEND_TX] > 0)
# Re-enable the receiver which should allow us to go from ERROR_PASSIVE to ERROR_WARNING
multitest.broadcast("enable receiver")
multitest.wait("receiver enabled")
can.send(_ID, b"MORE")
while can.state() == CAN.STATE_PASSIVE:
pass
print(state_name(can.state())) # should be ERROR_WARNING now
warning_counters = can.get_counters()
# print(warning_counters) # DEBUG
print("tec decreased", warning_counters[_IDX_TEC] < passive_counters[_IDX_TEC])
print(
"tec below thresh", warning_counters[_IDX_TEC] < 128
) # and should be more than error passive threshold
# error warning count should stay the same, as we went "down" in severity not up
print(
"no new warning", warning_counters[_IDX_NUM_WARNING] == passive_counters[_IDX_NUM_WARNING]
)
print(
"no new passive", warning_counters[_IDX_NUM_PASSIVE] == passive_counters[_IDX_NUM_PASSIVE]
)
# Tell the receiver to change to the wrong baud rate, which should create both RX and TX errorxs
multitest.broadcast("switch baud")
multitest.wait("switched baud")
# queue another message. This will keep trying to send until we revert back to ERROR_PASSIVE
idx = can.send(_ID, b"YETMORE")
print("queued yetmore", idx is not None)
while can.state() != CAN.STATE_PASSIVE:
pass
print(state_name(can.state())) # should be ERROR_PASSIVE again
passive_counters = can.get_counters()
# print(passive_counters) # DEBUG
# we can't say for sure which error counter will hit the ERROR_PASSIVE threshold first
print(
"one over thresh", passive_counters[_IDX_TEC] >= 128 or passive_counters[_IDX_REC] >= 128
)
print(
"no new warning", passive_counters[_IDX_NUM_WARNING] == warning_counters[_IDX_NUM_WARNING]
)
print(
"counted passive",
passive_counters[_IDX_NUM_PASSIVE] == warning_counters[_IDX_NUM_PASSIVE] + 1,
)
# Note that we can't get all the way to the most severe BUS_OFF error state
# with this test setup, as Bus Off requires more than just "normal" frame
# transmit errors.
# restarting the controller may cause it to leave its error state, or not, depending
# on the implementation - but it shouldn't cause any recovery issues. Also cancels all pending TX
# (note: have to do this before 'fix baud' or we create a race condition for pending tx)
can.restart()
# tell the receiver to go back to a valid baud rate
multitest.broadcast("fix baud")
multitest.wait("fixed baud")
idx_more = can.send(_ID, b"MOREMORE")
time.sleep_ms(50) # irq_sender should fire during this window
print("queued moremore", idx_more is not None)
print("done")