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);
allocate_buffer(): Finds the first free slot, clears it, marks it allocated, and returns the pointer. ReturnsNULLif exhausted.free_buffer(): Clears the allocated flag. PassingNULLis safe.- No reference counting -- CAN frame buffers are simpler than OpenLCB message buffers because they are never shared between queues.
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);
push(): Adds a frame pointer to the tail. Returnstrueon success,falseif the FIFO is full (frame is dropped).pop(): Removes and returns the oldest frame from the head. Caller is responsible for freeing the buffer withCanBufferStore_free_buffer()after processing.
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:
Key points in this flow:
- The ISR allocates from the CAN pool and pushes to the CAN FIFO. This is the minimal work needed in interrupt context.
- 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.
- 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.
- 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.
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:
- Lost event reports (no retry mechanism for events)
- Datagram timeouts (sender retries, but wastes bandwidth)
- Stalled multi-frame assembly (partial messages time out)
Use CanBufferStore_messages_max_allocated() to monitor peak usage and adjust USER_DEFINED_CAN_MSG_BUFFER_DEPTH accordingly.