CAN
Overview
The CAN subsystem has two layers:
- Generated definitions: structs and macros auto-generated from the DBC file. These define CAN message structures, IDs, and scaling factors.
- Generic CAN driver: hardware-agnostic driver handling TX scheduling, TX queuing, and RX ring buffering. Interfaced to HAL via function pointers provided at initialization.
Each board project provides two files to integrate the driver:
can_driver.c: allocates buffers, configures TX frame slots, sets up HAL callbacks, and initializes the generic driver.can_comm.c: contains the RX message handler (process_can_frames) and the TX payload packer (set_can_frames).
TIP
See the CAN Integration Guide for a step-by-step walkthrough of setting this up on a new board using ECU examples.
Generated Definitions
Generated from MAIN_DBC.dbc by dbc_to_c_builder.py.
can_signal_defs.h: per-message structs, message IDs, DLC constants, and signal scaling factor macros.can_board_defs.h: groups messages by sender board into convenience structs.
WARNING
Do not edit the generated headers by hand. Re-run dbc_to_c_builder.py instead.
#include "can_signal_defs.h"
#include "can_board_defs.h"
can_msg_GF_Wheel_Sensors_t gf = {0};
can_board_ECU_t ecu = {0};
gf.Wheel_Speed_FR = 1200;
ecu.ECU_Inverter_Sig.Calculated_Speed = 42.0f;Generic CAN Driver
Architecture and Execution Flow
The driver manages two ring buffers and a periodic scheduler:
ISR context Main loop context
───────────── ──────────────────
HAL RX interrupt CAN_send_frames()
└─► CAN_driver_rx_callback() └─► enqueues due periodic frames
└─► writes to RX ring buf into the TX ring buffer
CAN_process_tx_queue()
└─► drains TX ring buffer
into HW FIFO via add_to_fifo_fn()
process_can_frames() [board code]
└─► reads from RX ring buffer
via head/tail iterationTX scheduling: Each TX frame slot specifies a scheduler_timer_value in ms. CAN_send_frames() checks which frames are due and enqueues them. Frames marked CAN_DRIVER_NON_PERIODIC_FRAME are skipped by the scheduler and sent manually via CAN_send_single_frame().
TX deduplication: If a frame with a matching CAN ID is already queued in the software ring buffer, the driver overwrites it with the newest frame payload instead of appending.
RX flow: The ISR callback writes incoming frames into the RX ring buffer. process_can_frames() in the main loop reads them out by iterating from tail to head.
NOTE
The driver uses void* for the HAL handle and TX headers to remain hardware-agnostic. Cast to platform-specific types (e.g., FDCAN_HandleTypeDef*) inside can_driver.c.
Key Types
| Type | Purpose |
|---|---|
CAN_Driver_t | Main driver instance holding buffer pointers, frame configs, and HAL function pointers |
CAN_Tx_Message_Frame_t | TX frame slot configuration (ID, payload, scheduler timer, HAL header pointer) |
CAN_Rx_Message_Frame_t | RX frame structure (ID, payload, timestamp) |
CAN_Tx_Ring_Buffer_t | Circular buffer for queued TX frames |
CAN_Rx_Ring_Buffer_t | Circular buffer for received RX frames |
Utility Macros
LOW_BYTE(x) // Extract bits [7:0]
HIGH_BYTE(x) // Extract bits [15:8]
CAN_COMBINE_16(high, low) // Reconstruct uint16 from two bytes
CAN_COMBINE_32(b3,b2,b1,b0) // Reconstruct uint32 from four bytes
CAN_DRIVER_NON_PERIODIC_FRAME // Scheduler ignore flag for manual transmissionAPI Reference
void CAN_set_structures(driver, add_to_fifo_fn, get_tx_fifo_level_fn, hfdcan_instance)
Initializes internal indices and sets up function pointers. Must be called after configuring tx_message_frames, rx_ring_buffer, and tx_ring_buffer pointers and sizes.
| Parameter | Description |
|---|---|
driver | CAN_Driver_t* pointer to driver instance |
add_to_fifo_fn | CanTxFn_t HAL callback to push frame into HW FIFO (e.g. HAL_FDCAN_AddMessageToTxFifoQ) |
get_tx_fifo_level_fn | CanTxFifoLevelFn_t HAL callback to check free space in HW TX FIFO |
hfdcan_instance | void* pointer to peripheral HAL handle |
void CAN_driver_rx_callback(driver, data, hdr_rx, msg_id, num_values, timestamp)
ISR-safe callback. Pushes incoming frame into the RX ring buffer and sets can_new_message_flag. Call from peripheral RX interrupt (e.g. HAL_FDCAN_RxFifo0Callback).
WARNING
Runs in interrupt context. Do not perform complex processing inside this function.
| Parameter | Description |
|---|---|
data | uint8_t* received payload buffer |
hdr_rx | void* RX header handle |
msg_id | CAN message ID |
num_values | Data length in bytes |
timestamp | System tick timestamp (HAL_GetTick()) |
void CAN_send_frames(driver, current_tick)
Checks all registered periodic TX frame slots and enqueues due frames into the TX ring buffer. Call from main loop context.
Rotates starting index on each call to prevent fixed-priority scheduling bias.
uint16_t CAN_process_tx_queue(driver, amount)
Pushes queued frames from the software TX ring buffer to the hardware FIFO until FIFO is full or amount frames are processed (pass 0 to process all queued frames).
Returns the number of frames transmitted to the hardware FIFO.
uint32_t CAN_send_single_frame(driver, frame)
Enqueues a non-periodic frame into the TX ring buffer for transmission.
Returns 0 on success, 1 if the TX ring buffer is full.
if (CAN_send_single_frame(&can_driver, &frame) != 0) {
CAN_process_tx_queue(&can_driver, 5);
CAN_send_single_frame(&can_driver, &frame);
}