Chapter 4b — CAN Buffer System

The CAN buffer system mirrors the OpenLCB buffer system from Chapter 4 but operates at the CAN frame level. It consists of a flat buffer pool and a circular FIFO. These live in the drivers/canbus/ directory.

4b.1 CAN Frame Buffer Pool

Unlike the OpenLCB buffer store which has four segregated pools, the CAN buffer pool is flat -- all buffers have the same size (a can_msg_t with an 8-byte payload). The pool depth is controlled by USER_DEFINED_CAN_MSG_BUFFER_DEPTH (default: 10, maximum: 254).

typedef can_msg_t can_msg_array_t[USER_DEFINED_CAN_MSG_BUFFER_DEPTH];

API

void CanBufferStore_initialize(void);
can_msg_t *CanBufferStore_allocate_buffer(void);
void CanBufferStore_free_buffer(can_msg_t *msg);
uint16_t CanBufferStore_messages_allocated(void);
uint16_t CanBufferStore_messages_max_allocated(void);
void CanBufferStore_clear_max_allocated(void);
No Segregation Needed

CAN 2.0 frames always have exactly 8 data bytes maximum, so there is no need for multiple payload sizes. Every can_msg_t has the same structure regardless of how many data bytes are actually used.

4b.2 CAN FIFO

The CAN FIFO is a circular buffer of can_msg_t pointers. It sits between the CAN hardware ISR (producer) and the CAN RX state machine in the main loop (consumer).

void CanBufferFifo_initialize(void);
bool CanBufferFifo_push(can_msg_t *new_msg);
can_msg_t *CanBufferFifo_pop(void);
uint8_t CanBufferFifo_is_empty(void);
uint16_t CanBufferFifo_get_allocated_count(void);

The FIFO uses one extra slot (total = USER_DEFINED_CAN_MSG_BUFFER_DEPTH + 1) so that head == tail always means empty.

4b.3 Data Flow: CAN Buffers to OpenLCB Buffers

The CAN RX pipeline converts CAN frames into OpenLCB messages. This is where the two buffer systems connect:

sequenceDiagram participant ISR as CAN RX ISR participant CPool as CAN Buffer Store participant CFIFO as CAN FIFO participant RX as CAN RX State Machine participant OPool as OpenLCB Buffer Store participant OFIFO as OpenLCB Buffer FIFO Note over ISR: Hardware receives CAN frame ISR->>CPool: CanBufferStore_allocate_buffer() CPool-->>ISR: can_msg_ptr ISR->>ISR: Copy frame data into can_msg_t ISR->>CFIFO: CanBufferFifo_push(can_msg_ptr) Note over RX: Main loop iteration RX->>CFIFO: CanBufferFifo_pop() CFIFO-->>RX: can_msg_ptr RX->>RX: Decode CAN identifier, extract MTI RX->>OPool: OpenLcbBufferStore_allocate_buffer(type) OPool-->>RX: openlcb_msg_ptr RX->>RX: Copy/assemble data into openlcb_msg_t RX->>CPool: CanBufferStore_free_buffer(can_msg_ptr) RX->>OFIFO: OpenLcbBufferFifo_push(openlcb_msg_ptr)

Key points in this flow:

  1. The ISR allocates from the CAN pool and pushes to the CAN FIFO. This is the minimal work needed in interrupt context.
  2. The main loop pops from the CAN FIFO, interprets the frame, allocates from the OpenLCB pool, and pushes the assembled message to the OpenLCB FIFO.
  3. The CAN frame buffer is freed as soon as its data has been copied into the OpenLCB message. This keeps CAN buffers available for the next interrupt.
  4. For multi-frame messages, the CAN RX handler uses the Buffer List (see Chapter 4) to accumulate frames until the final frame arrives.

4b.4 Thread Safety: The ISR/Main-Loop Boundary

The CAN FIFO is the critical boundary between interrupt context and the main loop. The ISR writes to the FIFO tail; the main loop reads from the FIFO head.

flowchart LR subgraph ISR_CTX["Interrupt Context"] A["CAN RX ISR"] B["CanBufferStore_allocate_buffer()"] C["CanBufferFifo_push()"] end subgraph MAIN_CTX["Main Loop Context"] D["CanBufferFifo_pop()"] E["CAN RX State Machine"] F["CanBufferStore_free_buffer()"] end A --> B --> C C -.->|"lock boundary"| D D --> E --> F style ISR_CTX fill:#ffcdd2,stroke:#c62828 style MAIN_CTX fill:#c8e6c9,stroke:#2e7d32
Lock Requirement

Both the CAN buffer store and CAN FIFO are marked as NOT thread-safe in their API documentation. The platform must use the lock_shared_resources / unlock_shared_resources callbacks (typically disable/enable interrupts on bare metal) to protect concurrent access. See Chapter 6 — Thread Safety for details.

4b.5 Sizing the CAN Buffer Pool

The CAN buffer pool must be large enough to absorb bursts of incoming frames while the main loop is busy processing previous messages. A too-small pool causes frame drops, which may result in:

Use CanBufferStore_messages_max_allocated() to monitor peak usage and adjust USER_DEFINED_CAN_MSG_BUFFER_DEPTH accordingly.

← Previous: Ch 4 — OpenLCB Buffer System Next: Ch 5 — Node Management →