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")