
Overview
CAN CoreX is built around one practical idea: most CAN applications end up maintaining the same RX parsers, TX messages, timeouts, and periodic traffic by hand. This library moves that work into explicit RX/TX tables and a small polling model.
Hosted documentation is available as the CAN CoreX Doxygen API Reference.
Examples
Practical integration examples are maintained in a separate repository:
CAN CoreX Examples
Use that repository as the starting point for wiring CAN CoreX into real projects. It is intended to show complete application-level usage around the library API, while this repository stays focused on the portable core library, tests, and API documentation.
The main value of the library is:
- table-driven RX handling with parser callbacks
- table-driven periodic TX handling
- one place for message metadata, IDs, DLC, timeouts, and callbacks
- ISO-TP support for both classic CAN and CAN FD
- optional bus monitoring and runtime statistics
Version 2.3.1 hardens initialization and periodic TX behavior: CCX_Init() now reports a missing primary timebase explicitly, and periodic TX table entries retry instead of silently skipping a period when the TX queue is full.
Table of Contents
- Core CAN Model
- ISO-TP
- Bus Monitoring & Statistics
- Quick Start
- API Reference
- Utilities
- Error Codes
- Data Structures
- Usage Examples
- Advanced Build-Time Options
- Best Practices
- Changelog
Core CAN Model
The core of CAN CoreX is not a transport layer or diagnostics module. It is the RX/TX table model.
What You Actually Work With
In a typical integration you define:
- an RX table: what IDs you accept, what DLC you expect, which parser handles the message, and whether a timeout matters
- a TX table: what messages should be sent periodically, how often, and whether data should be refreshed just before sending
- one CCX_instance_t per CAN controller or logical CAN channel
That gives you a clean split between:
- message definitions
- application logic
- hardware send/receive glue
Why It Matters
Without this model, most projects gradually accumulate:
- hand-written switch(ID) parsing logic
- duplicated timeout handling
- ad-hoc periodic transmission code
- per-message state scattered across the application
With CAN CoreX, that state lives in the tables.
Core Features
- RX tables: parser dispatch, optional timeout callback, wildcard DLC support when needed
- TX tables: periodic traffic with optional pre-send parser callback
- Classic CAN and CAN FD: one model, with strict FrameFormat matching in FD builds
- Buffered operation: RX/TX queues decouple hardware callbacks from the application loop
- Runtime statistics: message counters, parser calls, timeout calls, buffer overflows
Minimal Example
{.ID = 0x200, .DLC = 8, .IDE_flag = 0, .Parser = parse_status, .TimeOut = 1000, .TimeoutCallback = on_status_timeout}
};
{.ID = 0x300, .Data = heartbeat_data, .DLC = 2, .IDE_flag = 0, .SendFreq = 100, .Parser = update_heartbeat}
};
CCX_Init(&can1, rx_table, tx_table, 1, 1, hw_send_can_message, hw_can_bus_check, NULL);
while (1) {
CCX_Poll(&can1);
}
void CCX_tick_variable_register(CCX_TIME_t *Variable)
Registers the system tick source variable.
CAN RX table entry structure.
Definition can_corex.h:390
CAN TX table entry structure.
Definition can_corex.h:445
The point is not fewer lines at all costs. The point is that message handling becomes explicit and centralized.
ISO-TP
The second big reason to use this library is the ISO-TP layer.
Why It Is Important Here
The library already gives you structured CAN message handling. ISO-TP extends that model to payloads that do not fit into a single CAN frame, without forcing a separate stack or a completely different integration style.
What You Get
- ISO-TP for classic CAN and CAN FD
- TX and RX instances with explicit callbacks and state handling
- padding control
- TxDL / FC_TxDL support for FD sessions
- timeout diagnostics split by phase
- abort APIs
- support for sub-millisecond STmin when HR timing is enabled
Practical Integration Model
You still work with:
- one CAN CoreX instance
- RX table entries for ISO-TP data or flow-control traffic
- one ISO-TP TX instance and/or one ISO-TP RX instance
- polling from the main loop
That makes ISO-TP feel like an extension of the same library, not a second subsystem bolted on from somewhere else.
ISO-TP interoperability was additionally validated with PEAK PCAN-ISO-TP API tooling.
Bus Monitoring & Statistics
Bus monitoring is useful, but it is not the main reason to adopt the library. It is an operational layer on top of the core CAN model.
What it provides:
- bus state tracking: active, warning, passive, off
- TEC/REC tracking
- configurable recovery callback flow
- recovery delay helpers
- runtime counters and stats reset APIs
In 2.2.0, timing in this area is also more explicit:
- successful_run_time uses the primary timebase
- recovery_delay can use HR only for short delays via CCX_BUS_RECOVERY_US(...)
Header organization in 2.2.1 and newer:
- can_corex.h remains the main public header
- can_corex_bus.h contains bus-monitoring types, macros, and API declarations
- including only can_corex.h remains sufficient for normal integrations
Quick Start
Typical integration flow:
- Register the primary tick source.
- Initialize one CCX_instance_t with RX/TX tables and hardware callbacks.
- Push RX frames from the ISR or driver callback.
- Call CCX_Poll() regularly from the main loop or task.
- If you use ISO-TP, also poll the ISO-TP TX/RX instances.
Minimal sketch:
volatile uint32_t system_tick_ms = 0;
CCX_Init(&can1, rx_table, tx_table, RX_TABLE_SIZE, TX_TABLE_SIZE,
hw_send_can_message, hw_can_bus_check, NULL);
while (1) {
CCX_Poll(&can1);
}
void CCX_ISOTP_TX_Poll(CCX_ISOTP_TX_t *Instance)
Poll ISO-TP TX instance (call periodically from main loop).
void CCX_ISOTP_RX_Poll(CCX_ISOTP_RX_t *Instance)
Poll ISO-TP RX instance (call periodically from main loop).
Definition can_corex.h:460
API Reference
Initialization and Configuration
CCX_Init
CCX_Status_t CCX_Init(
uint16_t RxTableSize,
uint16_t TxTableSize,
);
CCX_BusIsFree_t
Enumeration indicating the status of the CAN bus.
Definition can_corex.h:247
Structure representing a CAN message.
Definition can_corex.h:336
Description: Initializes a CAN CoreX instance with specified tables and callbacks.
Parameters:
- Instance: Pointer to instance structure to initialize
- CCX_RX_table: Pointer to RX message table (can be NULL if RxTableSize is 0)
- CCX_TX_table: Pointer to TX message table (can be NULL if TxTableSize is 0)
- RxTableSize: Number of entries in RX table
- TxTableSize: Number of entries in TX table
- SendFunction: Callback to physically send messages (required if TxTableSize > 0)
- BusCheck: Callback to check if bus is free (required if TxTableSize > 0)
- ParserUnregMsg: Callback for unregistered messages (optional)
Returns:
- CCX_OK: Initialization successful
- CCX_NULL_PTR: Invalid parameter combination
Important Notes:
- Must call CCX_tick_variable_register() BEFORE CCX_Init()
- Initializes all buffer pointers to zero
- Clears all buffers
- Sets LastTick in tables to current time
- In FD builds, does not zero FrameFormat in RX table entries - CCX_FRAME_FORMAT_CLASSIC=0 is the natural C aggregate-initializer default; if using field-by-field assignment (no aggregate initializer), call memset(rx_table, 0, sizeof(rx_table)) before populating
Example:
CCX_Status_t status = CCX_Init(
&can_instance,
rx_table, tx_table,
5, 3,
my_send_function,
my_bus_check,
NULL
);
CCX_FD_LenToDLC / CCX_MsgPayloadLen (CAN FD builds only)
uint8_t CCX_FD_LenToDLC(uint8_t len)
Convert byte length to the smallest valid CAN FD DLC.
CCX_FD_LenToDLC: Converts a byte length (0-64) to the corresponding CAN FD DLC code (0-15).
CCX_MsgPayloadLen: Returns the actual payload length of a message in bytes (uses DLC directly for classic frames; applies the FD length table for FD frames).
Example:
uint8_t len = CCX_MsgPayloadLen(&msg);
CCX_RX_RebuildHash
void CCX_RX_RebuildHash(CCX_instance_t *Instance)
Rebuild hash table for RX message lookup.
Description: Rebuilds the internal hash table for RX lookup when the hash mode is enabled.
Parameters:
- Instance: Pointer to CAN instance
Important Notes:
- Only functional when compiled with -DCCX_RX_SEARCH_HASH
- Hash table is automatically built during CCX_Init()
- Call this function only if you modify RX table after initialization
- Does nothing in linear or binary search modes
When to use:
CCX_Init(&can_instance, rx_table, ...);
rx_table[5].ID = 0x456;
rx_table[5].Parser = new_parser;
Primary Tick Registration
#if CCX_TICK_FROM_FUNC
void CCX_tick_function_register(CCX_TIME_BASE_SCALAR (*Function)(void));
#else
#endif
Description: Registers the primary system tick source used by core CAN RX/TX logic and all base-domain timeouts.
Parameters:
Important: Must be called BEFORE CCX_Init().
Example:
volatile uint32_t system_tick_ms = 0;
Or with a callback:
static uint32_t get_system_tick_ms(void)
{
return system_tick_ms;
}
CCX_tick_function_register(get_system_tick_ms);
High-Resolution Tick Registration
When CCX_DISABLE_HIGH_RES_TIMEBASE is not defined, HR-aware parts of the library can use a separate high-resolution tick source.
#if CCX_HR_TICK_FROM_FUNC
void CCX_high_res_tick_function_register(CCX_HR_TIME_BASE_SCALAR (*Function)(void));
#else
void CCX_high_res_tick_variable_register(CCX_HR_TIME_t *Variable);
#endif
Important semantics:
- CCX_TICK_FROM_FUNC and CCX_HR_TICK_FROM_FUNC are independent
- core CAN logic always uses the primary timebase
- ISO-TP uses HR only for sub-millisecond STmin values (0xF1..0xF9)
- bus monitor uses HR only when recovery delay is configured in us and the value is <= 3000 us
- CCX_IsPrimaryTickRegistered() and CCX_IsHighResTickRegistered() expose registration state for tests and diagnostics
Timebase Type Configuration
The library supports separate unsigned type selection for the primary and HR timebases:
- primary: CCX_TIME_BASE_TYPE_CUSTOM_IS_UINT16, ..._UINT32, ..._UINT64
- HR: CCX_HR_TIME_BASE_TYPE_CUSTOM_IS_UINT16, ..._UINT32, ..._UINT64
The following are intentionally rejected at compile time:
- uint8_t timebases: range is too small for this library
- signed timebase macros (..._IS_INT8/16/32/64): kept only as hard errors because they were an old bug and break timeout arithmetic
If no custom width macro is defined, both domains default to uint32_t.
Message Operations
CCX_RX_PushMsg
CCX_Status_t CCX_RX_PushMsg(
);
Description: Adds a received message to the RX buffer.
Parameters:
- Instance: Pointer to CAN instance
- msg: Pointer to message to add
Returns:
- CCX_OK: Message added successfully
- CCX_NULL_PTR: NULL pointer provided
- CCX_WRONG_ARG: Invalid DLC (> 8 in classic build; > 15 in FD build); also returned for FD frames pushed to a classic instance
- CCX_BUS_TOO_BUSY: Buffer is full
Behavior:
- Validates DLC (0-8 in classic; 0-15 in FD)
- Records receive timestamp
- Triggers network replication if configured
- Returns error if buffer full (non-blocking)
Example:
.ID = 0x123,
.DLC = 8,
.IDE_flag = 0,
.Data = {1, 2, 3, 4, 5, 6, 7, 8}
};
CCX_Status_t status = CCX_RX_PushMsg(&can_instance, &msg);
if (status == CCX_OK) {
} else if (status == CCX_BUS_TOO_BUSY) {
}
CCX_TX_PushMsg
CCX_Status_t CCX_TX_PushMsg(
);
Description: Adds a message to the TX buffer for transmission.
Parameters:
- Instance: Pointer to CAN instance
- msg: Pointer to message to transmit
Returns:
- CCX_OK: Message queued successfully
- CCX_NULL_PTR: NULL pointer provided
- CCX_WRONG_ARG: Invalid DLC (> 8 in classic build; > 15 in FD build); also returned for FD frames pushed to a classic instance
- CCX_BUS_TOO_BUSY: Buffer is full
Behavior:
- Validates DLC (0-8 in classic; 0-15 in FD)
- Queues message for transmission
- Triggers network replication if configured
- Returns error if buffer full (non-blocking)
Example:
.ID = 0x456,
.DLC = 4,
.IDE_flag = 0,
.Data = {0xAA, 0xBB, 0xCC, 0xDD}
};
CCX_Status_t status = CCX_TX_PushMsg(&can_instance, &msg);
CCX_Poll
Description: Processes RX/TX buffers and performs timeout checks. Must be called periodically (e.g., in main loop or timer).
Parameters:
- Instance: Pointer to CAN instance
Returns:
- CCX_OK: Poll completed successfully
- CCX_NULL_PTR: NULL instance provided
Behavior:
- Processes all messages in RX buffer
- Matches against RX table
- Calls parser callbacks
- Updates LastTick on match
- Checks for RX timeouts
- Generates periodic TX messages from TX table
- Sends queued TX messages if bus is free
Example:
while (1) {
CCX_Poll(&can_instance);
}
Utilities
The optional can_corex_utils module is separate from the core runtime. Include can_corex_utils.h and link can_corex_utils.c only when these helpers are needed.
Classic SLCAN
CCX_UTILS_Status_t CCX_SLCAN_Parse(const char *line, CCX_message_t *msg)
Parse a classic SLCAN data-frame line into a CAN CoreX message.
CCX_UTILS_Status_t
Status values returned by CAN CoreX utility helpers.
Definition can_corex_utils.h:37
CCX_UTILS_Status_t CCX_SLCAN_Format(const CCX_message_t *msg, char *out, size_t out_size)
Format a CAN CoreX classic CAN message as an SLCAN data-frame line.
Supported frame lines are classic CAN data frames:
- tIIILDD... for 11-bit standard IDs
- TIIIIIIIILDD... for 29-bit extended IDs
The formatter always emits \r and \0. The parser accepts \r, \n, \r\n, or the end of the C string immediately after the frame. Remote frames are not represented by CCX_message_t and are returned as unsupported.
#define CCX_SLCAN_CLASSIC_MAX_LINE_LEN
Maximum NUL-terminated classic SLCAN data-frame line length.
Definition can_corex_utils.h:51
Hex Helpers
size_t *bytes_written);
CCX_UTILS_Status_t CCX_HexToBytes(const char *hex, size_t hex_len, uint8_t *out, size_t out_size, size_t *bytes_written)
Convert a hex string to bytes.
CCX_UTILS_Status_t CCX_BytesToHex(const uint8_t *bytes, size_t byte_count, char *out, size_t out_size)
Convert bytes to an uppercase hex string.
CCX_BytesToHex() emits uppercase hex without separators. CCX_HexToBytes() accepts uppercase and lowercase hex and requires an even number of input characters.
Linear Encode / Decode
Double-based helpers:
uint16_t CCX_EncodeLinearU16_Clamped(double physical, double factor, double offset);
double CCX_DecodeLinearU16(uint16_t raw, double factor, double offset);
Float-based helpers use the F suffix:
uint16_t CCX_EncodeLinearU16F_Clamped(float physical, float factor, float offset);
float CCX_DecodeLinearU16F(uint16_t raw, float factor, float offset);
The full family covers unsigned and signed 8/16/32/64-bit raw values. Decode uses:
physical = raw * factor + offset;
Encode uses:
raw = round((physical - offset) / factor);
Values outside the raw type range are saturated by the _Clamped variants. If factor is zero, encode returns the minimum value for the target type.
Encode followed by decode preserves the physical value when the input lies on the representable raw grid and the selected floating-point type can represent the intermediate values accurately enough. Off-grid values are quantized to the nearest raw integer first, so decode returns the nearest representable physical value. For 64-bit helpers, remember that float and double cannot represent every 64-bit integer exactly.
double physical = 40.75;
uint16_t raw = CCX_EncodeLinearU16_Clamped(physical, 0.25, 10.0);
double decoded = CCX_DecodeLinearU16(raw, 0.25, 10.0);
Error Codes
typedef enum {
CCX_OK = 0,
CCX_NULL_PTR,
CCX_WRONG_ARG,
CCX_BUS_TOO_BUSY
} CCX_Status_t;
Data Structures
CCX_message_t
Classic build (CCX_ENABLE_CANFD=0, default):
typedef struct {
uint32_t ID;
uint8_t Data[8];
uint8_t DLC : 4;
uint8_t IDE_flag : 1;
FD build (CCX_ENABLE_CANFD=1):
typedef struct {
uint32_t ID;
uint8_t Data[64];
uint8_t DLC : 4;
uint8_t IDE_flag : 1;
uint8_t FrameFormat : 2;
uint8_t ESI : 1;
CCX_FD_DLC_t enum (FD build only) - named DLC constants:
typedef enum {
CCX_FD_DLC_0B = 0, CCX_FD_DLC_1B = 1,
CCX_FD_DLC_2B = 2, CCX_FD_DLC_3B = 3,
CCX_FD_DLC_4B = 4, CCX_FD_DLC_5B = 5,
CCX_FD_DLC_6B = 6, CCX_FD_DLC_7B = 7,
CCX_FD_DLC_8B = 8, CCX_FD_DLC_12B = 9,
CCX_FD_DLC_16B = 10, CCX_FD_DLC_20B = 11,
CCX_FD_DLC_24B = 12, CCX_FD_DLC_32B = 13,
CCX_FD_DLC_48B = 14, CCX_FD_DLC_64B = 15,
CCX_FD_DLC_t
Named CAN FD DLC values.
Definition can_corex.h:642
CCX_RX_table_t
Classic build (CCX_ENABLE_CANFD=0, default):
typedef struct {
uint32_t ID;
uint8_t DLC : 4;
uint8_t IDE_flag : 1;
void *UserData;
CCX_TIME_t TimeOut;
uint16_t Slot,
void *UserData);
void (*TimeoutCallback)(
CCX_instance_t *Instance, uint16_t Slot,
void *UserData);
CCX_TIME_t LastTick;
FD build (CCX_ENABLE_CANFD=1):
typedef struct {
uint32_t ID;
uint8_t DLC : 5;
uint8_t IDE_flag : 1;
uint8_t FrameFormat : 2;
void *UserData;
CCX_TIME_t TimeOut;
uint16_t Slot,
void *UserData);
void (*TimeoutCallback)(
CCX_instance_t *Instance, uint16_t Slot,
void *UserData);
CCX_TIME_t LastTick;
Usage:
- Define expected messages with ID, DLC, and IDE_flag
- Set TimeOut to enable timeout detection (in ticks)
- Provide Parser callback to process matched messages
- Provide TimeoutCallback to handle timeout events (optional)
- LastTick is automatically updated by library
- In FD builds: FrameFormat defaults to CCX_FRAME_FORMAT_CLASSIC=0 via C aggregate initialization ({.ID=..., .DLC=...}); if using field-by-field assignment, call memset(rx_table, 0, sizeof(rx_table)) first
- All entries check FrameFormat - a CLASSIC entry will not match an FD frame and vice versa
CCX_TX_table_t
Classic build (CCX_ENABLE_CANFD=0, default):
typedef struct {
uint32_t ID;
uint8_t *Data;
uint8_t DLC : 4;
uint8_t IDE_flag : 1;
void *UserData;
CCX_TIME_t SendFreq;
uint8_t *DataToSend,
uint16_t Slot,
void *UserData);
CCX_TIME_t LastTick;
FD build (CCX_ENABLE_CANFD=1):
typedef struct {
uint32_t ID;
uint8_t *Data;
uint8_t DLC : 4;
uint8_t IDE_flag : 1;
uint8_t FrameFormat : 2;
void *UserData;
CCX_TIME_t SendFreq;
uint8_t *DataToSend,
uint16_t Slot,
void *UserData);
CCX_TIME_t LastTick;
Usage:
- Define periodic messages with ID, DLC, IDE_flag
- Set SendFreq to transmission period (in ticks)
- Data points to data buffer (must be at least CCX_FD_DLC_TO_LEN[DLC] bytes in FD builds)
- Optional Parser callback to update data before sending
- LastTick is automatically updated by library
- In FD builds: set FrameFormat = CCX_FRAME_FORMAT_FD or CCX_FRAME_FORMAT_FD_BRS for FD periodic frames
Usage Examples
Basic Setup
volatile uint32_t system_tick_ms = 0;
CAN->TX_ID = msg->ID;
CAN->TX_DLC = msg->DLC;
memcpy(CAN->TX_DATA, msg->Data, msg->DLC);
CAN->TX_REQ = 1;
}
return (CAN->STATUS & CAN_TX_BUSY) ? CCX_BUS_BUSY : CCX_BUS_FREE;
}
int main(void) {
can_hardware_init();
CCX_Init(&can1, NULL, NULL, 0, 0,
hw_send_can_message, hw_can_bus_check, NULL);
while (1) {
CCX_Poll(&can1);
.ID = 0x100,
.DLC = 2,
.IDE_flag = 0,
.Data = {0xAA, 0xBB}
};
CCX_TX_PushMsg(&can1, &msg);
delay_ms(10);
}
}
void SysTick_Handler(void) {
system_tick_ms++;
}
Core CAN CoreX public API.
RX Table with Timeout
uint16_t slot,
void *user_data) {
(void)inst; (void)slot; (void)user_data;
uint16_t sensor_value = (msg->Data[0] << 8) | msg->Data[1];
process_sensor_value(sensor_value);
}
void sensor_timeout_handler(
CCX_instance_t *inst, uint16_t slot,
void *user_data) {
(void)inst; (void)user_data;
printf("Sensor timeout on slot %u!\n", slot);
activate_failsafe_mode();
}
{
.ID = 0x200,
.DLC = 2,
.IDE_flag = 0,
.TimeOut = 1000,
.Parser = parse_sensor_data,
.TimeoutCallback = sensor_timeout_handler
}
};
CCX_Init(&can1, rx_table, NULL, 1, 0,
hw_send_can_message, hw_can_bus_check, NULL);
TX Table for Periodic Messages
uint8_t heartbeat_data[2] = {0x00, 0x00};
uint8_t *data,
uint16_t slot,
void *user_data) {
(void)inst; (void)slot; (void)user_data;
static uint8_t counter = 0;
data[0] = counter++;
data[1] = get_system_status();
}
{
.ID = 0x100,
.Data = heartbeat_data,
.DLC = 2,
.IDE_flag = 0,
.SendFreq = 100,
.Parser = update_heartbeat
}
};
CCX_Init(&can1, NULL, tx_table, 0, 1,
hw_send_can_message, hw_can_bus_check, NULL);
Network Replication
CCX_Init(&can1, NULL, NULL, 0, 0, hw_send_can1, hw_bus_check1, NULL);
CCX_Init(&can2, NULL, NULL, 0, 0, hw_send_can2, hw_bus_check2, NULL);
can_network.NodeList[0].NodeInstance = &can1;
can_network.NodeList[0].NodeSettings.Replication = CCX_NET_TX_RX_REPLICATION;
can_network.NodeList[0].NodeSettings.NodeType = CCX_NET_NODE_IN_NET;
can_network.NodeList[1].NodeInstance = &can2;
can_network.NodeList[1].NodeSettings.Replication = CCX_NET_TX_REPLICATION;
can_network.NodeList[1].NodeSettings.NodeType = CCX_NET_NODE_IN_NET;
CCX_net_status_t CCX_net_init(CCX_net_t *net)
Initialize a CAN network.
CAN network structure.
Definition can_corex_net.h:139
ISO-TP Transport Protocol
CAN CoreX includes ISO 15765-2 (ISO-TP) for classic CAN and CAN FD transports.
Features
- Classic CAN instances: Standard SF up to 7 bytes, standard FF + CF up to 4095 bytes
- CAN FD instances: Standard SF up to 7 bytes, extended SF for larger single-frame payloads, standard FF up to 4095 bytes, extended FF above 4095 bytes
- Flow Control (FC): CTS/WAIT/OVFLW support
- Configurable Padding:
- classic instances pad to 8
- FD instances pad to configured TxDL
- Timeout Monitoring: enforced N_Bs, N_Cs, N_Cr
- Separated Timing Domains:
- N_Bs, N_Cs, N_Cr use the primary ms timebase
- STmin values 0x00..0x7F use the primary ms timebase
- STmin values 0xF1..0xF9 (100-900 us) use the HR timebase when enabled
- Lifecycle Hooks: OnReceiveStart, OnReceiveProgress, OnReceiveComplete, OnError
- Abort API: CCX_ISOTP_TX_Abort() / CCX_ISOTP_RX_Abort()
- Separate TX/RX Instances: Independent transmit and receive handling
- Extended ID Support: Full support for both Standard (11-bit) and Extended (29-bit) CAN identifiers
- Per-instance length limits:
Important Semantics
- Header selection is based on ISO-TP payload length before padding
- Padding changes CAN frame payload length, not reported PDU length
- In FD builds, CCX_ISOTP_Length_t is uint32_t; in classic-only builds it remains uint16_t
- In FD builds, a classic ISO-TP instance still keeps the classic 4095-byte limit
- OnReceiveProgress reports a delta since the previous callback, not an absolute offset
- To print received/total, accumulate BytesReceived yourself or read Instance->RxDataOffset; do not interpret BytesReceived as the total bytes received so far
- N_Cr measures inactivity between CF frames, not whether the next received CF is the expected one
- If one CF is lost but a later CF arrives before N_Cr expires, RX will typically end with CCX_ISOTP_ERROR_SEQUENCE, not CCX_ISOTP_ERROR_TIMEOUT_CF_RX
- OnReceiveStart fires once after a valid FF is accepted and the total payload length is known
- CCX_ISOTP_ERROR_TIMEOUT remains a legacy alias of CCX_ISOTP_ERROR_TIMEOUT_FC
- In dual-timebase builds, sub-millisecond STmin requires a registered HR tick source
- In single-timebase builds (CCX_DISABLE_HIGH_RES_TIMEBASE), RX configuration must not request sub-millisecond STmin; incoming FC values in the 0xF1..0xF9 range are still rounded up to the primary ms tick on TX
Basic Usage
Transmitter Node (Sending ISO-TP Messages)
};
CCX_Init(&can, rx_table, NULL, 1, 0, hw_send, hw_bus_check, NULL);
tx_error_callback);
tx_cfg.MaxWaitFrames = 10;
uint8_t data[200];
while (1) {
CCX_Poll(&can);
}
ISO-TP transport layer public API.
@ CCX_ID_STANDARD
Standard 11-bit identifier.
Definition can_corex.h:257
CCX_ISOTP_Status_t CCX_ISOTP_Transmit(CCX_ISOTP_TX_t *Instance, const uint8_t *Data, CCX_ISOTP_Length_t Length)
Transmit data using ISO-TP.
void CCX_ISOTP_TX_Config_Init(CCX_ISOTP_TX_Config_t *Config, uint32_t TxID, uint8_t IDE_TxID, CCX_TIME_t N_Bs, CCX_TIME_t N_Cs, CCX_ISOTP_Padding_t Padding, void *UserData, void(*OnTransmitComplete)(CCX_ISOTP_TX_t *Instance, void *UserData), void(*OnError)(CCX_ISOTP_TX_t *Instance, CCX_ISOTP_Status_t Error, void *UserData))
Initialize ISO-TP TX configuration with defaults.
CCX_ISOTP_Status_t CCX_ISOTP_TX_Init(CCX_ISOTP_TX_t *Instance, CCX_instance_t *CanInstance, const CCX_ISOTP_TX_Config_t *Config)
Initialize ISO-TP TX instance.
#define CCX_ISOTP_TX_FC_TABLE_ENTRY(isotp_tx_ptr, fc_can_id, ide_flag)
Macro to generate CAN CoreX RX table entry for ISO-TP TX Flow Control.
Definition can_corex_isotp.h:657
ISO-TP TX configuration structure.
Definition can_corex_isotp.h:242
ISO-TP TX instance structure.
Definition can_corex_isotp.h:335
CAN FD ISO-TP Example
Use the _EX table macros when the ISO-TP session uses FD frames:
};
1000, CCX_ISOTP_PADDING(0xAA), NULL, tx_complete_callback, tx_error_callback);
tx_cfg.MaxWaitFrames = 10;
@ CCX_FRAME_FORMAT_FD_BRS
CAN FD with bit-rate switch (BRS=1).
Definition can_corex.h:279
void CCX_ISOTP_TX_Config_InitFD(CCX_ISOTP_TX_Config_t *Config, uint32_t TxID, uint8_t IDE_TxID, CCX_frame_format_t FrameFormat, uint8_t TxDL, CCX_TIME_t N_Bs, CCX_TIME_t N_Cs, CCX_ISOTP_Padding_t Padding, void *UserData, void(*OnTransmitComplete)(CCX_ISOTP_TX_t *Instance, void *UserData), void(*OnError)(CCX_ISOTP_TX_t *Instance, CCX_ISOTP_Status_t Error, void *UserData))
Initialize ISO-TP TX FD configuration with defaults.
TxDL accepts only legal FD payload sizes: 8, 12, 16, 20, 24, 32, 48, 64.
Receiver Node (Receiving ISO-TP Messages)
uint8_t rx_buffer[512];
};
CCX_Init(&can, rx_table, NULL, 1, 0, hw_send, hw_bus_check, NULL);
CCX_ISOTP_PADDING(0xAA), 0, NULL, NULL, rx_complete_callback, NULL, rx_error_callback);
while (1) {
CCX_Poll(&can);
}
#define CCX_ISOTP_RX_TABLE_ENTRY(isotp_rx_ptr, can_id, ide_flag)
Macro to generate CAN CoreX RX table entry for ISO-TP RX instance.
Definition can_corex_isotp.h:627
CCX_ISOTP_Status_t CCX_ISOTP_RX_Init(CCX_ISOTP_RX_t *Instance, CCX_instance_t *CanInstance, const CCX_ISOTP_RX_Config_t *Config)
Initialize ISO-TP RX instance.
void CCX_ISOTP_RX_Config_Init(CCX_ISOTP_RX_Config_t *Config, uint32_t TxID, uint8_t IDE_TxID, uint8_t BS, uint8_t STmin, CCX_TIME_t N_Cr, uint8_t *RxBuffer, CCX_ISOTP_Length_t RxBufferSize, CCX_ISOTP_Padding_t Padding, CCX_ISOTP_Length_t ProgressCallbackInterval, void *UserData, void(*OnReceiveStart)(CCX_ISOTP_RX_t *Instance, CCX_ISOTP_Length_t TotalLength, void *UserData), void(*OnReceiveComplete)(CCX_ISOTP_RX_t *Instance, const uint8_t *Data, CCX_ISOTP_Length_t Length, void *UserData), void(*OnReceiveProgress)(CCX_ISOTP_RX_t *Instance, CCX_ISOTP_Length_t BytesReceived, CCX_ISOTP_Length_t TotalLength, void *UserData), void(*OnError)(CCX_ISOTP_RX_t *Instance, CCX_ISOTP_Status_t Error, void *UserData))
Initialize ISO-TP RX configuration with defaults.
ISO-TP RX configuration structure.
Definition can_corex_isotp.h:301
ISO-TP RX instance structure.
Definition can_corex_isotp.h:361
Complete Callback Example:
void rx_complete_callback(
CCX_ISOTP_RX_t *Instance,
const uint8_t *Data, CCX_ISOTP_Length_t Length,
void *UserData) {
(void)Instance;
(void)UserData;
printf("Received %d bytes via ISO-TP\n", Length);
}
(void)Instance;
(void)UserData;
printf("Transmission complete!\n");
}
CAN FD ISO-TP Receiver Example
In FD sessions the RX-side FC_TxDL controls only transmitted Flow Control frames:
1000, rx_buffer, sizeof(rx_buffer), CCX_ISOTP_PADDING(0xAA), 512, NULL,
rx_start_callback, rx_complete_callback, rx_progress_callback, rx_error_callback);
void CCX_ISOTP_RX_Config_InitFD(CCX_ISOTP_RX_Config_t *Config, uint32_t TxID, uint8_t IDE_TxID, CCX_frame_format_t FrameFormat, uint8_t FC_TxDL, uint8_t BS, uint8_t STmin, CCX_TIME_t N_Cr, uint8_t *RxBuffer, CCX_ISOTP_Length_t RxBufferSize, CCX_ISOTP_Padding_t Padding, CCX_ISOTP_Length_t ProgressCallbackInterval, void *UserData, void(*OnReceiveStart)(CCX_ISOTP_RX_t *Instance, CCX_ISOTP_Length_t TotalLength, void *UserData), void(*OnReceiveComplete)(CCX_ISOTP_RX_t *Instance, const uint8_t *Data, CCX_ISOTP_Length_t Length, void *UserData), void(*OnReceiveProgress)(CCX_ISOTP_RX_t *Instance, CCX_ISOTP_Length_t BytesReceived, CCX_ISOTP_Length_t TotalLength, void *UserData), void(*OnError)(CCX_ISOTP_RX_t *Instance, CCX_ISOTP_Status_t Error, void *UserData))
Initialize ISO-TP RX FD configuration with defaults.
Flow Control Frame Layout (ISO 15765-2)
Flow Control frames follow the ISO 15765-2 standard layout:
Data[0] = 0x3N (PCI=0x30 | Flow Status in lower nibble: 0=CTS, 1=WAIT, 2=OVFLW)
Data[1] = BS (Block Size: 0=unlimited, 1-255=CF count before next FC)
Data[2] = STmin (Separation Time minimum: 0-127ms or 0xF1-0xF9 for 100-900us)
Helper macros are available for the sub-millisecond range:
CCX_ISOTP_STMIN_100US
CCX_ISOTP_STMIN_200US
CCX_ISOTP_STMIN_300US
CCX_ISOTP_STMIN_400US
CCX_ISOTP_STMIN_500US
CCX_ISOTP_STMIN_600US
CCX_ISOTP_STMIN_700US
CCX_ISOTP_STMIN_800US
CCX_ISOTP_STMIN_900US
Example:
.STmin = CCX_ISOTP_STMIN_500US
In single-timebase builds (CCX_DISABLE_HIGH_RES_TIMEBASE), these helper macros still exist, but CCX_ISOTP_RX_Init() rejects sub-millisecond STmin values in RX configuration with CCX_ISOTP_ERROR_INVALID_ARG. Incoming FC values in the 0xF1..0xF9 range are still accepted on TX and rounded up to the primary ms tick.
FC frames respect the Padding configuration from RX:
.Padding = CCX_ISOTP_PADDING(0xAA)
.Padding = CCX_ISOTP_NO_PADDING
Both variants work correctly thanks to CCX_DLC_ANY wildcard matching in the RX table macros.
Config Init Helpers
Use CCX_ISOTP_TX_Config_Init() and CCX_ISOTP_RX_Config_Init() to seed the config with the common defaults:
tx_complete_cb, tx_error_cb);
CCX_ISOTP_NO_PADDING, 0U, user_ctx, rx_start_cb, rx_complete_cb, rx_progress_cb,
rx_error_cb);
These helpers now also accept Padding, and RX helpers accept ProgressCallbackInterval.
Defaults typically used by callers:
- TX: classic frame format, default MaxWaitFrames
- RX: classic frame format
Override fields directly after init when needed, for example MaxWaitFrames.
In FD-enabled builds, use CCX_ISOTP_TX_Config_InitFD() and CCX_ISOTP_RX_Config_InitFD() when the session itself is FD, so FrameFormat and TxDL / FC_TxDL stay encapsulated in the helper call.
Abort and Timeout Diagnostics
- CCX_ISOTP_TX_Abort() and CCX_ISOTP_RX_Abort() cancel an active transfer and reset the instance to IDLE
- Active aborts emit OnError(..., CCX_ISOTP_ERROR_ABORTED, ...)
- Timeout errors are phase-specific:
- CCX_ISOTP_ERROR_TIMEOUT_FC for N_Bs
- CCX_ISOTP_ERROR_TIMEOUT_CF_TX for N_Cs
- CCX_ISOTP_ERROR_TIMEOUT_CF_RX for N_Cr
- CCX_ISOTP_ERROR_WAIT_EXCEEDED for exhausted FC.WAIT
- CCX_ISOTP_ERROR_TIMEOUT is kept as a legacy alias of CCX_ISOTP_ERROR_TIMEOUT_FC
Progress Monitoring
For large transfers, you can monitor reception progress:
.ProgressCallbackInterval = 512,
.OnReceiveProgress = rx_progress_callback,
};
void rx_progress_callback(
CCX_ISOTP_RX_t *Instance, CCX_ISOTP_Length_t BytesReceived,
CCX_ISOTP_Length_t TotalLength, void *UserData) {
(void)Instance; (void)UserData;
static CCX_ISOTP_Length_t accumulated = 0;
accumulated += BytesReceived;
printf("Progress: %u/%u bytes (%.1f%%)\n",
(unsigned)accumulated, (unsigned)TotalLength,
(100.0 * accumulated) / TotalLength);
}
Incorrect usage example:
printf("Progress: %u/%u\n", (unsigned)(TotalLength - BytesReceived), (unsigned)TotalLength);
This is wrong because BytesReceived is a delta, so the output will jump around instead of showing monotonic progress.
Extended ID Support
ISO-TP supports both Standard (11-bit) and Extended (29-bit) CAN identifiers.
Standard ID (11-bit) - Range: 0x000 to 0x7FF:
};
.TxID = 0x123,
.IDE_TxID = 0,
};
.TxID = 0x321,
.IDE_TxID = 0,
};
Extended ID (29-bit) - Range: 0x00000000 to 0x1FFFFFFF:
};
.TxID = 0x18DA00F1,
.IDE_TxID = 1,
};
.TxID = 0x18DAF100,
.IDE_TxID = 1,
};
Mixed Configuration (TX uses Standard, RX uses Extended):
};
.TxID = 0x123,
.IDE_TxID = 0,
};
.TxID = 0x18DAF100,
.IDE_TxID = 1,
};
Limitations
- Normal addressing only: Extended and Mixed addressing modes not yet implemented
- Single-timebase builds: Submillisecond STmin values are rounded up to the primary ms tick
Wildcard DLC Matching
CAN CoreX supports wildcard DLC matching in RX tables using CCX_DLC_ANY.
Overview
By default, RX table entries match exact DLC values. Using CCX_DLC_ANY allows accepting messages with any DLC:
{.ID = 0x100, .DLC = 8, .IDE_flag = 0, .Parser = my_parser},
{.ID = 0x200, .DLC =
CCX_DLC_ANY, .IDE_flag = 0, .Parser = my_parser}
};
#define CCX_DLC_ANY
Special DLC value for wildcard matching in RX table.
Definition can_corex.h:110
CCX_DLC_ANY value
The sentinel value is always one above the maximum valid DLC for the build, so it can never collide with a real frame length code.
Classic build wildcard
CCX_message_t msg2 = {.ID = 0x200, .DLC = 8, .Data = {1, 2, 3, 4, 5, 6, 7, 8}};
CCX_RX_PushMsg(&can, &msg1);
CCX_RX_PushMsg(&can, &msg2);
FD build wildcard with frame-format filter
In FD builds, CCX_DLC_ANY combined with the FrameFormat field selects which frame format the wildcard matches:
};
CCX_Init(&can, rx_table, NULL, 3, 0, send_fn, bus_fn, NULL);
@ CCX_FRAME_FORMAT_CLASSIC
Classic CAN 2.0, max 8-byte payload.
Definition can_corex.h:277
FrameFormat uses the CCX_frame_format_t enum values:
Exact-DLC entries
All entries check FrameFormat - a CLASSIC entry will not match an FD frame carrying the same DLC, and vice versa.
Use Cases
- Higher-level protocols: ISO-TP, J1939 where message DLC varies
- Padding flexibility: Accept messages with or without padding
- Monitoring/debugging: Capture all variants of a message ID
- FD/classic separation: Different parsers for FD vs classic traffic on the same ID
Important Notes
- ISO-TP macros automatically use CCX_DLC_ANY for flexibility
- In FD builds, DLC values 0-15 are all valid; only 16 is the wildcard sentinel
- CCX_FRAME_FORMAT_CLASSIC=0 is the natural C aggregate-initializer default; field-by-field tables require memset before population
Detailed Bus Management & Statistics
CAN CoreX includes comprehensive bus health monitoring and statistics tracking.
Global Statistics
Always-on statistics tracking with minimal overhead. Automatically maintained by the library.
Available Metrics:
- total_rx_messages - Total messages received and pushed to RX buffer
- total_tx_messages - Total messages successfully transmitted (requires CCX_OnMessageTransmitted() call from ISR)
- rx_buffer_overflows - Number of times RX buffer was full
- tx_buffer_overflows - Number of times TX buffer was full
- peak_rx_depth - Highest observed RX queue depth since statistics reset
- peak_tx_depth - Highest observed TX queue depth since statistics reset
- parser_calls_count - Total parser function invocations
- timeout_calls_count - Total timeout callback invocations
Usage:
printf("RX: %lu, TX: %lu, Overflows: %lu/%lu\n",
printf("Peak queue depth: RX=%u, TX=%u\n",
void CCX_ResetGlobalStats(CCX_instance_t *Instance)
Reset global statistics.
const CCX_GlobalStats_t * CCX_GetGlobalStats(const CCX_instance_t *Instance)
Get global statistics.
Global statistics for CAN instance.
Definition can_corex.h:299
uint32_t tx_buffer_overflows
Number of times TX buffer was full.
Definition can_corex.h:304
uint16_t peak_tx_depth
Highest TX buffer depth observed since stats reset.
Definition can_corex.h:308
uint32_t total_rx_messages
Total messages received and pushed to RX buffer.
Definition can_corex.h:300
uint32_t rx_buffer_overflows
Number of times RX buffer was full.
Definition can_corex.h:303
uint32_t total_tx_messages
Total messages successfully transmitted (call CCX_OnMessageTransmitted from ISR).
Definition can_corex.h:302
uint16_t peak_rx_depth
Highest RX buffer depth observed since stats reset.
Definition can_corex.h:307
Important: For accurate TX counting, call CCX_OnMessageTransmitted() from your CAN TX complete interrupt:
void CAN_TX_IRQHandler(void) {
}
void CCX_OnMessageTransmitted(CCX_instance_t *Instance, const CCX_message_t *msg)
Notify library that a message was successfully transmitted.
Queue State Helpers
The RX/TX queues can be inspected without touching queue internals:
uint16_t rx_depth = CCX_RX_GetDepth(&can_instance);
uint16_t tx_depth = CCX_TX_GetDepth(&can_instance);
uint16_t rx_free = CCX_RX_GetFree(&can_instance);
uint16_t tx_free = CCX_TX_GetFree(&can_instance);
All four helpers return 0 for NULL.
Queue contents can also be cleared explicitly:
CCX_FlushRx(&can_instance);
CCX_FlushTx(&can_instance);
CCX_Flush(&can_instance);
CCX_Reset(&can_instance);
CCX_Flush*() does not reset global statistics or ISO-TP session state. CCX_Reset() resets global statistics, including peak_rx_depth and peak_tx_depth.
Bus Monitoring
Automatic bus-off detection and recovery with configurable retry strategy according to ISO 11898-1.
Features:
- Automatic bus state monitoring (Active/Warning/Passive/Off)
- TEC/REC error counter tracking
- Configurable recovery delay and retry attempts
- Grace period after max recovery attempts
- State transition callbacks
- Manual recovery trigger
Bus States (ISO 11898-1):
- CCX_BUS_STATE_ACTIVE - Normal operation (TEC < 96 && REC < 96)
- CCX_BUS_STATE_WARNING - Degraded performance (TEC > 96 || REC > 96)
- CCX_BUS_STATE_PASSIVE - Cannot send active error frames (TEC > 127 || REC > 127)
- CCX_BUS_STATE_OFF - Disconnected from bus (TEC > 255)
Initialization:
CCX_BusMonitor_Init(
&can_instance,
&bus_monitor,
my_get_bus_state,
my_get_error_counters,
my_request_recovery,
CCX_BUS_RECOVERY_MS(10),
60000,
1,
5
);
Bus monitor configuration and state.
Definition can_corex_bus.h:150
void(* OnErrorCountersUpdate)(CCX_instance_t *Instance, const CCX_ErrorCounters_t *Counters, void *UserData)
Called when TEC/REC updated.
Definition can_corex_bus.h:183
void(* OnRecoveryAttempt)(CCX_instance_t *Instance, uint8_t AttemptNumber, void *UserData)
Called before each recovery attempt.
Definition can_corex_bus.h:180
void(* OnBusStateChange)(CCX_instance_t *Instance, CCX_BusState_t OldState, CCX_BusState_t NewState, void *UserData)
Called on state transition.
Definition can_corex_bus.h:178
void(* OnRecoveryFailed)(CCX_instance_t *Instance, void *UserData)
Called when max attempts reached.
Definition can_corex_bus.h:182
Recovery Strategy:
- Active Recovery Phase:
- Attempts recovery up to max_recovery_attempts times
- Waits recovery_delay between attempts
- Calls OnRecoveryAttempt before each try
- Use CCX_BUS_RECOVERY_MS(x) for base-domain recovery delays
- Use CCX_BUS_RECOVERY_US(x) for short delays that should use HR when x <= 3000 us
- Grace Period:
- After max attempts, waits successful_run_time before trying again
- Calls OnRecoveryFailed when entering grace period
- Prevents aggressive retry loops
- Counter Reset:
- After successful operation for successful_run_time, attempt counter resets to 0
Recovery delay selection:
- Classic CAN constants up to 250 kbps use CCX_BUS_RECOVERY_MS(...)
- 500 kbps, 800 kbps, 1000 kbps, and fast FD data-phase constants use CCX_BUS_RECOVERY_US(...)
- CCX_BUS_RECOVERY_US(x) uses HR only for x <= 3000 us; above that it falls back to base ms
- successful_run_time always uses the primary ms timebase
Manual Recovery:
CCX_Status_t status = CCX_BusMonitor_TriggerRecovery(&can_instance);
Statistics:
printf("TEC: %u, REC: %u\n",
printf("Peak TEC: %u, Peak REC: %u\n",
CCX_BusMonitor_ResetStats(&can_instance);
CCX_BusStats_t stats
Accumulated statistics.
Definition can_corex_bus.h:152
Bus monitoring statistics.
Definition can_corex_bus.h:131
CCX_ErrorCounters_t error_counters
Current TEC/REC values from hardware.
Definition can_corex_bus.h:137
CCX_ErrorCounters_t peak_error_counters
Peak TEC/REC values since initialization.
Definition can_corex_bus.h:138
uint32_t bus_off_count
Number of bus-off events.
Definition can_corex_bus.h:132
uint8_t REC
Receive Error Counter (0-255).
Definition can_corex_bus.h:121
uint8_t TEC
Transmit Error Counter (0-255).
Definition can_corex_bus.h:120
Hardware Interface Functions:
You must implement three functions for your hardware:
}
}
}
CCX_BusState_t
CAN bus state according to ISO 11898-1.
Definition can_corex_bus.h:98
CAN error counters from hardware controller.
Definition can_corex_bus.h:119
Callbacks:
void *user_data) {
if (new_state == CCX_BUS_STATE_OFF) {
} else if (new_state == CCX_BUS_STATE_ACTIVE &&
old_state == CCX_BUS_STATE_OFF) {
}
}
uint8_t attempt,
void *user_data) {
printf("Recovery attempt %u\n", attempt);
}
printf("Max recovery attempts reached - entering grace period\n");
}
Integration with CCX_Poll:
Bus monitoring is automatic - just call CCX_Poll() as usual:
while (1) {
CCX_Poll(&can_instance);
}
Advanced Build-Time Options
Most users should stay with the default build and only decide whether CAN FD is needed.
Main build switch:
- CCX_ENABLE_CANFD=1 enables CAN FD message support, FD DLC helpers, FD-aware RX/TX tables, and FD ISO-TP sessions
Less common build-time options:
- CCX_RX_SEARCH_BINARY switches RX lookup to binary search and requires the RX table to stay sorted by CAN ID
- CCX_RX_SEARCH_HASH enables hashed RX lookup and CCX_RX_RebuildHash() for runtime RX table changes
- CCX_TICK_FROM_FUNC and CCX_HR_TICK_FROM_FUNC switch tick registration from variables to callbacks
- custom primary / HR tick widths can be selected with CCX_TIME_BASE_TYPE_CUSTOM_IS_UINT16/32/64 and CCX_HR_TIME_BASE_TYPE_CUSTOM_IS_UINT16/32/64
Recommendation:
- treat RX lookup mode selection as an advanced optimization knob
- choose it only when you have a measured reason, not as part of the default integration path
Best Practices
1. Always Register Tick Source First
CCX_Init(&can1, ...);
CCX_Init(&can1, ...);
2. Handle Return Codes
CCX_Status_t status = CCX_TX_PushMsg(&can1, &msg);
if (status == CCX_BUS_TOO_BUSY) {
log_error("TX buffer full");
}
3. Call Poll Regularly
while (1) {
CCX_Poll(&can1);
}
void timer_callback(void) {
CCX_Poll(&can1);
}
4. Validate DLC Before Creating Messages
uint8_t dlc = user_input;
if (dlc > 8) {
dlc = 8;
}
msg.DLC = dlc;
5. Initialize Tables Properly
static uint8_t data_buffer[8];
{
.Data = data_buffer,
}
};
6. Use Timeouts for Critical Messages
rx_table[0].TimeOut = 100;
rx_table[0].TimeoutCallback = critical_sensor_timeout_handler;
7. Buffer Sizing
#define CCX_RX_BUFFER_SIZE 48
#define CCX_TX_BUFFER_SIZE 48
Timing Considerations
Tick Resolution
- Tick source should match timing requirements
- For 1ms timeouts, use 1ms tick
- Higher resolution = more precise timeouts
Poll Frequency
- Call CCX_Poll() at least 2x faster than shortest timeout
Timeout Accuracy
- Timeout triggers when: current_tick - LastTick >= TimeOut
- For 100ms timeout with 10ms poll: actual = 100-110ms
Thread Safety
CAN CoreX is not thread-safe by default. If using RTOS:
RX/TX queue indices are declared volatile so the common bare-metal pattern where an ISR pushes RX frames and the main loop calls CCX_Poll() has proper compiler visibility for queue state. This does not make compound operations atomic and does not support multiple concurrent producers or consumers.
Global statistics are best-effort counters, not atomic counters. If statistics are read or reset from a different context than the one updating them, protect that access with the same critical section, interrupt masking, or mutex used for the CAN instance.
Option 1: Single Thread Access
void can_task(void *param) {
while (1) {
CCX_Poll(&can1);
vTaskDelay(10);
}
}
Option 2: Mutex Protection
SemaphoreHandle_t can_mutex;
void user_code(void) {
xSemaphoreTake(can_mutex, portMAX_DELAY);
CCX_TX_PushMsg(&can1, &msg);
xSemaphoreGive(can_mutex);
}
void can_task(void *param) {
while (1) {
xSemaphoreTake(can_mutex, portMAX_DELAY);
CCX_Poll(&can1);
xSemaphoreGive(can_mutex);
vTaskDelay(10);
}
}
Limitations
- Buffer Size: Fixed at compile time (CCX_RX_BUFFER_SIZE, CCX_TX_BUFFER_SIZE)
- ISO-TP effective payload limit depends on instance format:
- Timeout Range: Limited by CCX_TIME_t type (default: uint32_t)
- Not Thread-Safe: Requires external synchronization in multi-threaded environments
License
Mozilla Public License 2.0 - see LICENSE file for details.
Author
Adrian Pietrzak
Changelog
Current Release: v2.3.1 (2026-05-21)
- Initialization hardening:
- CCX_Init() now returns CCX_MISSING_TIMEBASE when the primary tick source has not been registered
- prevents silent 0 tick fallback during table LastTick initialization
- Version metadata:
- added public CCX_VERSION_MAJOR, CCX_VERSION_MINOR, CCX_VERSION_PATCH, and CCX_VERSION_STRING macros
- Periodic TX reliability:
- TX table LastTick is updated only after CCX_TX_PushMsg() accepts the generated message
- periodic messages retry on the next CCX_Poll() when the TX queue was full
- Tests:
- added regression coverage for missing primary timebase detection
- added regression coverage for periodic TX retry after queue-full enqueue failure
- Breaking Changes: None to existing function signatures, enum values, or initialization APIs
Previous Release: v2.3.0 (2026-05-18)
- Optional utilities module:
- added can_corex_utils.h and can_corex_utils.c
- utility module is opt-in and does not change core CAN, CAN FD, ISO-TP, network, or bus-monitoring behavior
- Classic SLCAN helpers:
- added CCX_SLCAN_Parse() and CCX_SLCAN_Format() for classic CAN data-frame text conversion
- supports standard and extended data frames compatible with Linux slcan/slcand workflows
- remote frames are reported as unsupported because CCX_message_t does not represent RTR
- Hex helpers:
- Linear encode/decode helpers:
- added double-based and float-based helpers for unsigned and signed 8/16/32/64-bit raw values
- encode uses factor/offset scaling, nearest-integer quantization, and saturated _Clamped behavior
- documentation and tests cover exact round trips, quantization, clamping, zero factor, and 64-bit precision limits
- Breaking Changes: None to existing core function signatures, enum values, or initialization APIs
Previous Release: v2.2.2 (2026-04-26)
- Queue observability:
- added CCX_RX_GetDepth(), CCX_TX_GetDepth(), CCX_RX_GetFree(), and CCX_TX_GetFree()
- added peak_rx_depth and peak_tx_depth to global statistics
- Queue control:
- added CCX_FlushRx(), CCX_FlushTx(), CCX_Flush(), and CCX_Reset()
- flush helpers clear queue indices without resetting statistics
- CCX_Reset() clears RX/TX queues and global statistics
- Network replication accounting:
- failed internal replication pushes now increment existing RX/TX overflow counters
- successful internal replication updates target queue peak usage
- ISO-TP robustness:
- TX enqueue failures for SF, FF, and CF now abort transmission, return/report failure, and raise CCX_ISOTP_ERROR_BUSY
- RX initialization accepts STmin values 0xF1..0xF9 and advertises the raw value in Flow Control frames
- TX behavior for sub-millisecond STmin in single-timebase builds remains rounded up to the nearest full millisecond
- C++ compatibility:
- public headers now use extern "C" guards when included from C++
- Documentation:
- added local Doxygen configuration and public API module groups
- Breaking Changes: None to existing function signatures, enum values, or initialization APIs
Previous Release: v2.2.1 (2026-04-24)
- Bus-monitoring header split:
- Bus-monitoring implementation split:
- bus-monitoring runtime logic now lives in can_corex_bus.c
- can_corex.c keeps core RX/TX runtime and global statistics
- Documentation:
- README and implementation guide updated to reflect the new public module layout
- Breaking Changes: None for normal users of the public API
Previous Release: v2.2.0 (2026-04-21)
- Timebase contract cleanup:
- core CAN RX/TX logic now always uses the primary ms timebase
- optional HR timing is a separate domain with its own type family and independent tick registration
- CCX_TICK_FROM_FUNC and CCX_HR_TICK_FROM_FUNC are fully independent
- CCX_IsPrimaryTickRegistered() and CCX_IsHighResTickRegistered() expose registration state
- Timebase type restrictions:
- allowed custom widths are now uint16_t, uint32_t, uint64_t
- uint8_t timebases are rejected because their range is too small for this library
- old signed timebase selection macros are rejected with a hard compile-time error because they were a historical bug and break timeout arithmetic
- ISO-TP timing cleanup:
- enforced ISO-TP runtime timers N_Bs, N_Cs, and N_Cr use the primary ms timebase
- HR timing is used only for sub-millisecond STmin values (0xF1..0xF9)
- in single-timebase builds, incoming FC STmin values in the 0xF1..0xF9 range are rounded up to the primary ms tick
- Bus monitoring timing cleanup:
- successful_run_time now always uses the primary ms timebase
- recovery_delay is now configured through CCX_BUS_RECOVERY_MS(...) or CCX_BUS_RECOVERY_US(...)
- CCX_BUS_RECOVERY_US(x) uses HR only for x <= 3000 us; above that it falls back to base ms
- classic recovery constants up to 250 kbps are expressed in ms; faster classic/FD constants use precise us values where it matters
- 500 kbps recovery constant corrected to 2816 us
- Testing:
- added explicit bus recovery threshold coverage for 2999 us, 3000 us, and 3001 us
- current verified totals: classic linear 352, FD linear 529
Previous Release: v2.1.0 (2026-04-19)
- CAN FD Support (-DCCX_ENABLE_CANFD=1): 64-byte payloads, BRS, ESI - zero overhead when disabled
- Network layer (can_corex_net): no FD-to-classic frame filtering - FD frames replicate freely; dropped_mixed stat removed
- ISO-TP (can_corex_isotp):
- unified classic/FD API
- FrameFormat selects classic / FD / FD_BRS session behavior
- TX uses TxDL; RX uses FC_TxDL for transmitted Flow Control frames
- TxDL / FC_TxDL select FD link-layer payload size (8/12/16/20/24/32/48/64)
- classic instances keep the standard 4095-byte limit
- FD instances support extended SF, extended FF, and payload lengths above 4095
- CCX_ISOTP_Length_t is uint32_t in FD builds
- padding is format-aware: classic pads to 8, FD pads to TxDL
- CCX_ISOTP_TX_Abort() / CCX_ISOTP_RX_Abort() allow explicit transfer cancellation
- OnReceiveStart fires after a valid FF; OnReceiveProgress reports deltas
- phase-specific timeout diagnostics: TIMEOUT_FC, TIMEOUT_CF_TX, TIMEOUT_CF_RX, WAIT_EXCEEDED
- RX configuration advertises BS and STmin through FC; TX enforces the values received in FC at runtime
- Test suite: 7 build flavors
- classic linear / binary / hash
- FD linear / binary / hash
- optional FD 32-bit stress build for the full UINT32_MAX ISO-TP path
- current verified totals: classic linear/binary 340, FD linear/binary 516, FD hash 520
- Breaking Changes (FD builds only): FDF/BRS fields replaced by FrameFormat; CCX_Init_Ex removed; all RX entries now enforce FrameFormat matching
- Breaking Changes (FD ISO-TP users): ISO-TP length-related APIs now use CCX_ISOTP_Length_t
- No breaking changes for classic (CCX_ENABLE_CANFD=0) builds - fully backward compatible with v1.4.x
Previous Release: v2.0.0 (2026-04-18)
- CAN FD Support (-DCCX_ENABLE_CANFD=1): 64-byte payloads, BRS, ESI support added to the CAN core
- Network layer (can_corex_net): FD frames can be replicated between instances; no FD-to-classic filtering in the network layer
- ISO-TP status in 2.0.0:
- classic ISO-TP sessions on FD-capable instances were supported
- FD payload ISO-TP was not implemented yet and returned CCX_ISOTP_ERROR_FD_NOT_SUPPORTED
- Test suite: first FD test coverage added alongside the existing classic builds
- Breaking Changes (FD builds only):
- FDF / BRS bitfields replaced by FrameFormat
- CCX_Init_Ex() removed
- all RX entries began enforcing frame-format matching
v1.4.4 (2026-04-01)
- Bug Fixes:
- ISO-TP N_Cs timeout logic (CRITICAL): Fixed N_Cs timeout check incorrectly nested inside STmin condition
- When STmin > N_Cs, the timeout never fired - CF was sent before N_Cs could trigger
- When N_Cs = 0, any elapsed tick immediately triggered a false timeout instead of sending CF
- N_Cs is now checked independently of STmin, with N_Cs == 0 treated as disabled
- ISO-TP FC.WAIT LastTick ordering (MODERATE): Fixed LastTick being updated after OnError callback when FC.WAIT frames are exhausted
- If a new transmission was started inside the OnError callback, its LastTick was immediately overwritten
- LastTick is now updated before invoking the callback
- Network cyclic list (CRITICAL): Fixed CCX_net_init() not checking the last node for duplicates
- Re-adding the last node in the linked list set node->next = node, creating a cycle
- Any subsequent CCX_net_push() call would then hang in an infinite loop
- Added duplicate check for the final node after the traversal loop
- Test Coverage: Added 4 previously missing tests - 100% of public API now explicitly tested
- CCX_BusMonitor_GetState() - all states (ACTIVE, WARNING, PASSIVE, OFF) and NULL safety
- CCX_BusMonitor_ResetStats() - counter zeroing, state preservation, NULL safety
- CCX_RX_RebuildHash() - dynamic table modification and hash rebuild in hash mode; no-op in other modes
- ISO-TP 400-byte payload - multi-frame segmentation and data integrity for large transfers
- Breaking Changes: None - all changes are internal fixes, fully backward compatible
- Testing: 275/275 tests passing (linear, binary); 279/279 tests passing (hash)
Previous Release: v1.4.3 (2026-02-13)
- ISO-TP Flow Control Frame Fix (CRITICAL): Fixed FC frame layout to comply with ISO 15765-2
- Flow Status now encoded in lower nibble of PCI byte (Data[0] = 0x3N) instead of separate Data[1]
- Block Size moved from Data[2] to Data[1], STmin moved from Data[3] to Data[2]
- FC DLC without padding corrected from 4 to 3 bytes
- Previous versions were incompatible with all external ISO-TP implementations (CANoe, PCAN, any standards-compliant ECU)
- Internal loopback tests passed before because both TX and RX had the same bug
- Custom Time Type Fix: Fixed typedef CC_TIME_t -> CCX_TIME_t in can_corex.h
- Any user defining CCX_TIME_BASE_TYPE_CUSTOM would get a compilation error in v1.4.0-v1.4.2
- Header Cleanup: Removed leftover #define DCCX_RX_SEARCH_HASH from can_corex.h
- Network RX Replication Fix: CCX_net_RX_PushMsg now sets RxReceivedTick
- Timeout detection for messages received via network replication (CCX_NET_TX_RX_REPLICATION) was broken - LastTick was never updated because RxReceivedTick stayed at 0
- New Test: ISO 15765-2 FC frame layout validation with non-zero BS and STmin values
- Verifies byte-level FC format, TX-side parsing of BS/STmin, and end-to-end transfer with BS=5
- Breaking Changes: ISO-TP FC wire format changed - not backward compatible with v1.2.0-v1.4.2 ISO-TP peers
- Now compatible with all standards-compliant ISO-TP implementations
- Testing: 253/253 tests passing across all three search modes (linear, binary, hash)
Previous Release: v1.4.2 (2026-02-10)
- ISO-TP Protocol Fixes: Critical timing implementation corrections
- STmin Implementation: Fixed TX consecutive frame timing to use STmin from Flow Control instead of N_Cs
- STmin (from FC) now correctly used as minimum delay between consecutive frames
- N_Cs now properly used as timeout protection (maximum allowed time), not as delay
- Fixes extremely slow transfers (256 bytes taking ~3 minutes instead of <1 second)
- Complies with ISO 15765-2 specification for separation time handling
- **Timeout Boundary Condition**: Fixed false timeout at exact N_Cs timing
- Changed condition from `>= to > for proper timeout detection
- Allows full use of configured timeout window (e.g., 10ms timeout no longer triggers at exactly 10ms)
- Fixes test failures in boundary conditions
- **Time Type System**: Added CCX_TIME_VALUE_t typedef for non-volatile time values
- Eliminates compiler warning: "type qualifiers ignored on function return type"
- Preserves configurable time type system (uint8/16/32/64 support)
CCX_TIME_t (volatile) for tick variables, CCX_TIME_VALUE_t` (non-volatile) for values/return types
- Maintains type consistency across custom time base configurations
- Breaking Changes: None - all fixes are internal implementation corrections
- Performance Impact: Massive improvement in ISO-TP transfer speed (100-200x faster for typical configurations)
- Testing: All 241 tests passing, including previously failing ISO-TP boundary condition tests
- Hardware Validation: Confirmed working on STM32 CAN peripherals
Previous Release: v1.4.1 (2026-02-05)
- Bug-fix
- Hash table size: assert if RxTableSize >= CCX_RX_HASH_SIZE
Previous Release: v1.4.0 (2026-02-05)
- Compile-Time RX Lookup Strategies: Configurable message search methods
- Linear Search (default): O(n), no extra memory, best for < 15 messages
- Binary Search (-DCCX_RX_SEARCH_BINARY): O(log n), requires sorted RX table, ideal for 15-50+ messages
- Hash Table (-DCCX_RX_SEARCH_HASH): O(1) average, configurable size (default 64 entries/128 bytes), best for 30+ messages
- Compile-time selection via preprocessor flags - no runtime overhead
- Hash Table Management:
- CCX_RX_RebuildHash() - Rebuild hash after runtime RX table modifications
- Automatic hash table initialization during CCX_Init()
- Configurable hash size via CCX_RX_HASH_SIZE define (default: 64)
- Linear probing collision resolution
- Performance Improvements:
- Up to 10x faster RX message lookup for large tables (50+ messages)
- Hash table provides consistent O(1) performance regardless of table size
- Binary search provides 2-3x speedup over linear for medium tables (15-50 messages)
- Memory Efficiency:
- Hash table overhead: only 2 bytes per hash entry (default: 128 bytes total)
- Binary search: zero additional memory overhead
- Linear search: unchanged, still the most compact option
- API Additions:
- Documentation:
- Comprehensive RX lookup strategy guide with performance comparisons
- Hash table sizing recommendations and best practices
- Compilation examples for all three search modes
- Testing: All 241 tests passing across all three search modes
- Breaking Changes: None - fully backward compatible with v1.3.0
- Default behavior unchanged (linear search)
- Opt-in via compilation flags only
Previous Release: v1.3.0 (2026-01-27)
- Bus Management: Automatic bus-off detection and recovery
- Configurable retry strategy with grace period
- TEC/REC error counter monitoring according to ISO 11898-1
- State transition callbacks (OnBusStateChange, OnRecoveryAttempt, OnRecoveryFailed)
- Manual recovery trigger via CCX_BusMonitor_TriggerRecovery()
- Detailed statistics tracking (bus-off count, error states, total bus-off duration)
- Global Statistics: Always-on operational metrics
- RX/TX message counters
- Buffer overflow tracking
- Parser/timeout call counters
- CCX_OnMessageTransmitted() function for accurate TX counting from ISR
- Minimal performance overhead
- API Additions:
- CCX_BusMonitor_Init() - Initialize bus monitoring
- CCX_BusMonitor_TriggerRecovery() - Manual recovery trigger
- CCX_BusMonitor_GetState() - Get current bus state
- CCX_BusMonitor_ResetStats() - Reset bus statistics
- CCX_GetGlobalStats() - Get global statistics
- CCX_ResetGlobalStats() - Reset global statistics
- CCX_OnMessageTransmitted() - Notify library of successful transmission
- Enhanced Testing: 225/225 tests passing
- Global statistics validation (RX/TX counters, buffer overflows)
- Bus monitoring state machine tests
- Auto-recovery and grace period verification
- Manual recovery trigger tests
- TEC/REC tracking validation
- Breaking Changes: None - fully backward compatible with v1.2.0
Previous Release: v1.2.0 (2026-01-26)
- UserData Context in Parsers: Added void *UserData to RX/TX table structures
- Enables multiple ISO-TP instances without global variables
- Parser callbacks receive context pointer for flexible instance management
- Breaking change: Parser signatures updated (added void *UserData parameter)
- TimeoutCallback also receives UserData for consistency
- ISO-TP Protocol: Full ISO 15765-2 transport layer implementation
- Single Frame and Multi-Frame support (up to 4095 bytes)
- Flow Control with CTS/WAIT/OVFLW
- Configurable padding and timeouts
- Progress callbacks for large transfers
- Extended ID Support: Full support for both Standard (11-bit) and Extended (29-bit) CAN identifiers
- Multiple concurrent instances supported via UserData context
- UserData in Callbacks: All ISO-TP callbacks now receive UserData parameter for context
- Breaking change: All callback signatures updated (OnTransmitComplete, OnReceiveComplete, OnReceiveProgress, OnError)
- Consistent with CAN CoreX parser/callback design pattern
- Wildcard DLC Matching: CCX_DLC_ANY constant for accepting any DLC in RX table
- Enables flexible protocol handling (ISO-TP, J1939, etc.)
- Useful for messages with variable padding
- Comprehensive test coverage added
- Enhanced Testing: 143/143 tests passing
- TimeoutCallback with UserData validation
- CCX_DLC_ANY wildcard matching tests (all DLC values 0-8)
- Extended ID support in basic CAN CoreX (Standard vs Extended ID filtering)
- ISO-TP UserData callback verification
- Full protocol compliance testing
- Network initialization and message replication tests
- Improved Documentation:
- Detailed code comments for all ISO-TP helper functions
- UserData usage examples in structure documentation
- Complete API reference with practical code examples
- Clear separation of Standard ID (11-bit) and Extended ID (29-bit) usage
- CAN Network (can_corex_net) fully documented:
- Complete API documentation for all functions
- Network architecture explained (linked list, node types, replication modes)
- Practical examples (ECU network simulation)
- Added missing CCX_net_clear_nodes() to public API
Previous Release: v1.1.0 (2026-01-22)
- Per-message timeout callbacks: TimeoutCallback moved from CCX_instance_t to CCX_RX_table_t for better flexibility
- API Change: CCX_Init() parameter count reduced from 9 to 8 (removed global TimeoutCallback parameter)
- Documentation: Updated all examples and best practices to reflect new timeout callback approach
- Performance: Reduced overhead - timeout callbacks now only called when needed per message
Initial Release: v1.0.0 (2025-04-26)