Skip to content

CAN

Overview

The CAN subsystem has two layers:

  1. Generated definitions: structs and macros auto-generated from the DBC file. These define CAN message structures, IDs, and scaling factors.
  2. 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.

c
#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 iteration

TX 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

TypePurpose
CAN_Driver_tMain driver instance holding buffer pointers, frame configs, and HAL function pointers
CAN_Tx_Message_Frame_tTX frame slot configuration (ID, payload, scheduler timer, HAL header pointer)
CAN_Rx_Message_Frame_tRX frame structure (ID, payload, timestamp)
CAN_Tx_Ring_Buffer_tCircular buffer for queued TX frames
CAN_Rx_Ring_Buffer_tCircular buffer for received RX frames

Utility Macros

c
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 transmission

API 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.

ParameterDescription
driverCAN_Driver_t* pointer to driver instance
add_to_fifo_fnCanTxFn_t HAL callback to push frame into HW FIFO (e.g. HAL_FDCAN_AddMessageToTxFifoQ)
get_tx_fifo_level_fnCanTxFifoLevelFn_t HAL callback to check free space in HW TX FIFO
hfdcan_instancevoid* 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.

ParameterDescription
datauint8_t* received payload buffer
hdr_rxvoid* RX header handle
msg_idCAN message ID
num_valuesData length in bytes
timestampSystem 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.

c
if (CAN_send_single_frame(&can_driver, &frame) != 0) {
    CAN_process_tx_queue(&can_driver, 5);
    CAN_send_single_frame(&can_driver, &frame);
}

Internal Flow Diagram

Click to expand state and flow diagram

Released under the MIT License.