6.26. CAN bus in code¶
machine.CAN wraps a hardware CAN controller. Bring
it up with the bus id and a bit rate:
from machine import CAN
can = CAN(1, 500_000)
can.set_filters(None) # accept all incoming IDs
The bit rate must match every other node on the bus exactly –
125_000, 250_000, 500_000 and 1_000_000 are the
common values. set_filters() configures
which IDs the controller will pass through; None means
accept everything (useful while bringing up a link, less useful
once the bus is busy).
6.26.1. Inside the controller¶
Three pieces of hardware sit between the camera’s Python code
and the bus – the TX mailboxes, an acceptance filter, and
the RX FIFO. Each one maps to a piece of the
CAN API used below.
The controller’s TX mailboxes, acceptance filter, and RX FIFO between the software and the bus.¶
TX mailboxes. A small set of hardware slots (typically three) that hold outgoing frames the camera has handed off via
send()but that have not yet reached the bus. When the bus is idle, the controller picks the mailbox with the lowest-numbered ID (highest priority) and arbitrates for the bus on its behalf. The camera does not pick the mailbox; the controller assigns one and returns its index fromsend().Acceptance filter. Configurable hardware that compares each incoming frame’s ID against a list of patterns and discards anything that does not match. Frames that pass continue into the RX FIFO; rejected frames are thrown away by the controller without ever reaching Python.
set_filters()configures these patterns.RX FIFO. A first-in, first-out queue – the first frame in is also the first frame out, like a line at a ticket counter. The controller appends received frames to the back of the queue as they arrive, and
recv()pulls them off the front in the same order. The queue matters because the bus catches frames in the background while Python is busy elsewhere; the camera then drains them one at a time without losing any, as long as the FIFO has not overflowed.
6.26.2. Sending a frame¶
send() queues a frame for transmission:
can.send(0x123, b"\x01\x02\x03\x04")
The first argument is the ID (an 11-bit integer for a standard frame); the second is the payload (0 to 8 bytes for CAN Classic). The call returns a small integer index identifying the hardware mailbox the frame went into. The controller arbitrates with any other transmitters on the bus and re-transmits as needed without further help from software.
For extended (29-bit) IDs, OR the CAN.FLAG_EXT_ID flag
into the third argument:
can.send(0x18FF1234, b"hello", CAN.FLAG_EXT_ID)
6.26.3. Receiving frames¶
recv() returns the next frame in the receive
FIFO, or None if nothing is waiting:
msg = can.recv()
if msg is not None:
can_id, data, flags, errs = msg
print("got", hex(can_id), bytes(data))
The bus fills the RX FIFO in the background, so the main loop just drains it as fast as it iterates. As long as the FIFO is deeper than the longest gap between drains, no frames are lost.
6.26.4. Filters¶
A real bus is usually busy with frames the camera does not
care about. Hardware filters let the controller drop unwanted
IDs before they reach the FIFO. set_filters()
takes a list of (id, mask, flags) tuples; a frame passes
the filter if its ID, masked by mask, matches the
configured id:
# Accept only IDs 0x100 - 0x10F (mask off the bottom 4 bits)
can.set_filters(((0x100, 0x7F0, 0),))
# Accept IDs 0x300 and 0x700 exactly
can.set_filters(((0x300, 0x7FF, 0),
(0x700, 0x7FF, 0)))
Unmatched frames are discarded by the controller and never
appear at recv(), which saves both buffer
space and CPU time.
6.26.5. Error states and recovery¶
A real CAN bus picks up transmission errors – shorts to ground, missing nodes, electrical noise corrupting bits. The controller keeps two counters that track this behaviour: a Transmit Error Counter (TEC) and a Receive Error Counter (REC), each incremented when the controller detects an error and decremented after a successful transfer. The counter values put the controller into one of four states:
Error Active (TEC and REC both below 96). Normal operation. When the node detects a bus error it transmits a dominant active error frame, which forces every other node to discard the in-progress frame so the sender can retry.
Error Warning (either counter reaches 96). Still fully active on the bus – the warning state is a software signal that errors are accumulating, not a behaviour change.
Error Passive (either counter reaches 128). The node is still on the bus but stops sending dominant error frames; errors are now signalled with passive (recessive) error frames so a faulty node cannot keep tearing up the bus for everyone else.
Bus Off (TEC reaches 256). The controller has decided this node is too unreliable to participate. It disconnects from the bus, stops transmitting and acknowledging, and stays out until software explicitly restarts it.
The first three transitions are entirely automatic – as the counters decrement after successful frames, the controller moves itself back toward Error Active without intervention.
Bus Off is the one state that requires software action.
restart() resets the controller and
returns it to Error Active. A typical pattern is to check
the state from the main loop and restart after a short delay
to give the bus time to settle:
import time
from machine import CAN
can = CAN(1, 500_000)
can.set_filters(None)
while True:
if can.state() == CAN.STATE_BUS_OFF:
time.sleep_ms(100)
can.restart()
# ... rest of the loop
The current counter values are available from
get_counters() for diagnostics – a TEC
that climbs steadily on an otherwise-quiet bus usually points
at wiring, termination, or a misconfigured bit rate.