Chapter 11 — CAN RX and TX Pipelines
The receive and transmit pipelines convert between raw CAN frames (8 bytes max) and OpenLCB messages (up to 512 bytes). The RX path classifies, assembles multi-frame messages, and pushes to the OpenLCB FIFO. The TX path fragments OpenLCB messages into CAN frames and transmits them.
can_rx_statemachine.c, can_rx_message_handler.c,
can_tx_statemachine.c, can_tx_message_handler.c
(all in src/drivers/canbus/)
11.1 RX: Frame Classification
CanRxStatemachine_incoming_can_driver_callback() is called directly from
the CAN receive ISR (or receive thread). It first checks whether the frame is an
OpenLCB message or a CAN control frame:
11.2 RX: Multi-Frame Assembly
OpenLCB messages that exceed 8 bytes are split across multiple CAN frames. The RX path reassembles them using the BufferList as scratch space:
Framing Bits (addressed messages)
The high nibble of payload byte 0 carries framing bits for addressed standard frames:
| Value | Meaning | Handler |
|---|---|---|
0x00 | Only frame (complete message) | single_frame() |
0x10 | First frame of multi-frame | first_frame() |
0x20 | Middle frame | middle_frame() |
0x30 | Last frame | last_frame() |
Assembly Flow
- first_frame: Allocate an OpenLCB buffer (sized by type: BASIC, SNIP, or DATAGRAM).
Load the message header (source alias, dest alias, MTI). Copy payload bytes.
Set
state.inprocess = true, stamptimer.assembly_ticks, and add to the BufferList. - middle_frame: Find the in-progress buffer in BufferList by source/dest/MTI. Check the timeout (30 ticks = 3 seconds). Append payload bytes.
- last_frame: Find the buffer, append final bytes, clear
state.inprocess, release from BufferList, and push to the OpenLCB FIFO.
Assembly Timeout
If a middle or last frame arrives more than 30 ticks (3 seconds) after the first frame,
the assembly is considered stale. The buffer is freed and a reject message is sent.
The timeout constant is CAN_RX_INPROCESS_TIMEOUT_TICKS = 30.
11.3 RX: Datagram Assembly
Datagrams use a different framing mechanism than standard messages. Instead of framing bits in the payload, the CAN frame type field (bits 27:24) indicates the position:
CAN_FRAME_TYPE_DATAGRAM_ONLY-- single frame, up to 8 bytesCAN_FRAME_TYPE_DATAGRAM_FIRST-- first of a multi-frame datagramCAN_FRAME_TYPE_DATAGRAM_MIDDLE-- continuation framesCAN_FRAME_TYPE_DATAGRAM_FINAL-- last frame
The assembly logic is the same (first/middle/last handlers) but with
offset = 0 since the destination alias is in the CAN identifier rather
than in the payload.
11.4 RX: Legacy SNIP
Early SNIP implementations did not include framing bits. The handler
CanRxMessageHandler_can_legacy_snip() uses null-byte counting to determine
frame boundaries: a SNIP reply is complete when 6 null bytes have been accumulated
across all frames.
11.5 TX: Message Routing
CanTxStatemachine_send_openlcb_message() is the TX entry point. It routes
each outgoing OpenLCB message to the correct frame handler:
11.6 TX: Fragmentation
The TX handlers fragment messages into CAN frames, looping until the entire payload is sent as an atomic sequence:
Datagram Fragmentation
Datagrams can be up to 72 bytes. The frame type selection in
CanTxMessageHandler_datagram_frame():
| Condition | Frame Type |
|---|---|
| Total payload <= 8 bytes | Datagram Only |
| payload_index < 8 | Datagram First |
| More data remains after this frame | Datagram Middle |
| This frame completes the payload | Datagram Last |
Addressed Message Fragmentation
Standard addressed messages carry the 12-bit destination alias in payload bytes 0-1, leaving only 6 bytes per frame for actual data. The framing bits are set in the high nibble of byte 0:
| Condition | Framing Bits |
|---|---|
| Total payload <= 6 bytes | MULTIFRAME_ONLY (0x00) |
| payload_index < 6 | MULTIFRAME_FIRST (0x10) |
| More data remains | MULTIFRAME_MIDDLE (0x20) |
| Last frame | MULTIFRAME_FINAL (0x30) |
11.7 TX: CAN Header Construction
CAN identifiers are built from templates. For example, a datagram-only frame:
identifier = RESERVED_TOP_BIT
| CAN_OPENLCB_MSG
| CAN_FRAME_TYPE_DATAGRAM_ONLY
| (dest_alias << 12)
| source_alias;
A standard addressed frame places the MTI (not the destination alias) in bits 23:12. The destination alias goes in payload bytes 0-1 instead.
11.8 TX: Retry Mechanism
Before transmitting, the TX state machine checks is_tx_buffer_empty().
If the hardware TX buffer is busy, the function returns false and the
caller retries on the next main-loop iteration. For multi-frame sequences, the
payload_index is only advanced on successful transmission, ensuring no
data is lost.
11.9 RX: CAN Control Frame Handling
Control frames are handled by dedicated functions in can_rx_message_handler.c:
| Frame | Handler | Action |
|---|---|---|
| CID | cid_frame() | If alias matches one of ours, reply with RID to defend the alias. |
| RID | rid_frame() | Check for duplicate alias; flag if found. |
| AMD | amd_frame() | Check duplicate; update listener alias table; release held attach messages. |
| AME | ame_frame() | Check duplicate; respond with AMD for matching aliases. |
| AMR | amr_frame() | Check duplicate; scrub BufferList and FIFO for stale source alias; clear listener table. |
| Error Info | error_info_report_frame() | Check for duplicate alias. |