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.
Practical integration examples are maintained in a separate repository:
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.
- 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
The core of CAN CoreX is not a transport layer or diagnostics module. It is the RX/TX table model.
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_tper CAN controller or logical CAN channel
That gives you a clean split between:
- message definitions
- application logic
- hardware send/receive glue
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.
- 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
FrameFormatmatching 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
CCX_RX_table_t rx_table[] = {
{.ID = 0x200, .DLC = 8, .IDE_flag = 0, .Parser = parse_status, .TimeOut = 1000, .TimeoutCallback = on_status_timeout}
};
CCX_TX_table_t tx_table[] = {
{.ID = 0x300, .Data = heartbeat_data, .DLC = 2, .IDE_flag = 0, .SendFreq = 100, .Parser = update_heartbeat}
};
CCX_tick_variable_register(&system_tick_ms);
CCX_Init(&can1, rx_table, tx_table, 1, 1, hw_send_can_message, hw_can_bus_check, NULL);
while (1) {
CCX_Poll(&can1);
}The point is not fewer lines at all costs. The point is that message handling becomes explicit and centralized.
The second big reason to use this library is the ISO-TP layer.
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.
- ISO-TP for classic CAN and CAN FD
- TX and RX instances with explicit callbacks and state handling
- padding control
TxDL/FC_TxDLsupport for FD sessions- timeout diagnostics split by phase
- abort APIs
- support for sub-millisecond
STminwhen HR timing is enabled
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 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_timeuses the primary timebaserecovery_delaycan use HR only for short delays viaCCX_BUS_RECOVERY_US(...)
Header organization in 2.2.1 and newer:
can_corex.hremains the main public headercan_corex_bus.hcontains bus-monitoring types, macros, and API declarations- including only
can_corex.hremains sufficient for normal integrations
Typical integration flow:
- Register the primary tick source.
- Initialize one
CCX_instance_twith 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_instance_t can1;
CCX_tick_variable_register(&system_tick_ms);
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);
CCX_ISOTP_TX_Poll(&isotp_tx);
CCX_ISOTP_RX_Poll(&isotp_rx);
}CCX_Status_t CCX_Init(
CCX_instance_t *Instance,
CCX_RX_table_t *CCX_RX_table,
CCX_TX_table_t *CCX_TX_table,
uint16_t RxTableSize,
uint16_t TxTableSize,
void (*SendFunction)(const CCX_instance_t *Instance, const CCX_message_t *msg),
CCX_BusIsFree_t (*BusCheck)(const CCX_instance_t *Instance),
void (*ParserUnregMsg)(const CCX_instance_t *Instance, CCX_message_t *Msg)
);Description: Initializes a CAN CoreX instance with specified tables and callbacks.
Parameters:
Instance: Pointer to instance structure to initializeCCX_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 tableTxTableSize: Number of entries in TX tableSendFunction: 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 successfulCCX_NULL_PTR: Invalid parameter combination
Important Notes:
- Must call
CCX_tick_variable_register()BEFORECCX_Init() - Initializes all buffer pointers to zero
- Clears all buffers
- Sets LastTick in tables to current time
- In FD builds, does not zero
FrameFormatin RX table entries -CCX_FRAME_FORMAT_CLASSIC=0is the natural C aggregate-initializer default; if using field-by-field assignment (no aggregate initializer), callmemset(rx_table, 0, sizeof(rx_table))before populating
Example:
CCX_instance_t can_instance;
CCX_RX_table_t rx_table[5];
CCX_TX_table_t tx_table[3];
// Register tick source first
CCX_tick_variable_register(&system_tick);
// Initialize instance
CCX_Status_t status = CCX_Init(
&can_instance,
rx_table, tx_table,
5, 3,
my_send_function,
my_bus_check,
NULL /* ParserUnregMsg - optional unregistered message handler */
);uint8_t CCX_FD_LenToDLC(uint8_t len);
uint8_t CCX_MsgPayloadLen(const CCX_message_t *msg);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 dlc = CCX_FD_LenToDLC(48); // returns 14 (CCX_FD_DLC_48B)
uint8_t len = CCX_MsgPayloadLen(&msg); // returns actual byte countvoid CCX_RX_RebuildHash(CCX_instance_t *Instance);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:
// Initial setup - hash built automatically
CCX_Init(&can_instance, rx_table, ...);
// Later: modify RX table at runtime
rx_table[5].ID = 0x456;
rx_table[5].Parser = new_parser;
// Rebuild hash to reflect changes
CCX_RX_RebuildHash(&can_instance);#if CCX_TICK_FROM_FUNC
void CCX_tick_function_register(CCX_TIME_BASE_SCALAR (*Function)(void));
#else
void CCX_tick_variable_register(CCX_TIME_t *Variable);
#endifDescription: Registers the primary system tick source used by core CAN RX/TX logic and all base-domain timeouts.
Parameters:
Function: Callback returning the current primary tick whenCCX_TICK_FROM_FUNC=1Variable: Pointer to a volatile primary tick variable whenCCX_TICK_FROM_FUNC=0
Important: Must be called BEFORE CCX_Init().
Example:
volatile uint32_t system_tick_ms = 0;
CCX_tick_variable_register(&system_tick_ms);Or with a callback:
static uint32_t get_system_tick_ms(void)
{
return system_tick_ms;
}
CCX_tick_function_register(get_system_tick_ms);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);
#endifImportant semantics:
CCX_TICK_FROM_FUNCandCCX_HR_TICK_FROM_FUNCare independent- core CAN logic always uses the primary timebase
- ISO-TP uses HR only for sub-millisecond
STminvalues (0xF1..0xF9) - bus monitor uses HR only when recovery delay is configured in
usand the value is<= 3000 us CCX_IsPrimaryTickRegistered()andCCX_IsHighResTickRegistered()expose registration state for tests and diagnostics
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_ttimebases: 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.
CCX_Status_t CCX_RX_PushMsg(
CCX_instance_t *Instance,
const CCX_message_t *msg
);Description: Adds a received message to the RX buffer.
Parameters:
Instance: Pointer to CAN instancemsg: Pointer to message to add
Returns:
CCX_OK: Message added successfullyCCX_NULL_PTR: NULL pointer providedCCX_WRONG_ARG: Invalid DLC (> 8 in classic build; > 15 in FD build); also returned for FD frames pushed to a classic instanceCCX_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:
CCX_message_t msg = {
.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) {
// Message accepted
} else if (status == CCX_BUS_TOO_BUSY) {
// Buffer full, message dropped
}CCX_Status_t CCX_TX_PushMsg(
CCX_instance_t *Instance,
const CCX_message_t *msg
);Description: Adds a message to the TX buffer for transmission.
Parameters:
Instance: Pointer to CAN instancemsg: Pointer to message to transmit
Returns:
CCX_OK: Message queued successfullyCCX_NULL_PTR: NULL pointer providedCCX_WRONG_ARG: Invalid DLC (> 8 in classic build; > 15 in FD build); also returned for FD frames pushed to a classic instanceCCX_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:
CCX_message_t msg = {
.ID = 0x456,
.DLC = 4,
.IDE_flag = 0,
.Data = {0xAA, 0xBB, 0xCC, 0xDD}
};
CCX_Status_t status = CCX_TX_PushMsg(&can_instance, &msg);CCX_Status_t CCX_Poll(CCX_instance_t *Instance);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 successfullyCCX_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);
// Other main loop tasks
}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.
CCX_UTILS_Status_t CCX_SLCAN_Parse(const char *line, CCX_message_t *msg);
CCX_UTILS_Status_t CCX_SLCAN_Format(const CCX_message_t *msg, char *out, size_t out_size);Supported frame lines are classic CAN data frames:
tIIILDD...for 11-bit standard IDsTIIIIIIIILDD...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.
CCX_message_t msg;
char line[CCX_SLCAN_CLASSIC_MAX_LINE_LEN];
CCX_SLCAN_Parse("t12381122334455667788\r", &msg);
CCX_SLCAN_Format(&msg, line, sizeof(line));CCX_UTILS_Status_t CCX_BytesToHex(const uint8_t *bytes, size_t byte_count, char *out, size_t out_size);
CCX_UTILS_Status_t CCX_HexToBytes(const char *hex, size_t hex_len, uint8_t *out, size_t out_size,
size_t *bytes_written);CCX_BytesToHex() emits uppercase hex without separators. CCX_HexToBytes()
accepts uppercase and lowercase hex and requires an even number of input
characters.
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);typedef enum {
CCX_OK = 0, // Operation successful
CCX_NULL_PTR, // NULL pointer provided
CCX_WRONG_ARG, // Invalid argument (e.g., DLC > 8 in classic, DLC > 15 in FD)
CCX_BUS_TOO_BUSY // Buffer full, message dropped
} CCX_Status_t;Classic build (CCX_ENABLE_CANFD=0, default):
typedef struct {
uint32_t ID; // CAN message ID
uint8_t Data[8]; // Message data (0-8 bytes)
uint8_t DLC : 4; // Data Length Code (0-8)
uint8_t IDE_flag : 1; // 0=Standard, 1=Extended ID
} CCX_message_t;FD build (CCX_ENABLE_CANFD=1):
typedef struct {
uint32_t ID; // CAN message ID
uint8_t Data[64]; // Message data (up to 64 bytes)
uint8_t DLC : 4; // DLC code (0-15; use CCX_FD_DLC_t constants)
uint8_t IDE_flag : 1; // use CCX_ide_t: CCX_ID_STANDARD / CCX_ID_EXTENDED
uint8_t FrameFormat : 2; // use CCX_frame_format_t: CLASSIC / FD / FD_BRS
uint8_t ESI : 1; // Error State Indicator (set by RX hardware)
} CCX_message_t;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;Classic build (CCX_ENABLE_CANFD=0, default):
typedef struct {
uint32_t ID; // Expected message ID
uint8_t DLC : 4; // Expected DLC (0-8); CCX_DLC_ANY (15) = accept any
uint8_t IDE_flag : 1; // Expected ID type
void *UserData; // User context pointer passed to Parser and TimeoutCallback
CCX_TIME_t TimeOut; // Timeout period (0 = disabled)
void (*Parser)(const CCX_instance_t *Instance,
CCX_message_t *Msg,
uint16_t Slot,
void *UserData);
void (*TimeoutCallback)(CCX_instance_t *Instance, uint16_t Slot, void *UserData);
CCX_TIME_t LastTick; // Last receive time (auto-managed)
} CCX_RX_table_t;FD build (CCX_ENABLE_CANFD=1):
typedef struct {
uint32_t ID; // Expected message ID
uint8_t DLC : 5; // Expected DLC (0-15); CCX_DLC_ANY (16) = accept any
uint8_t IDE_flag : 1; // use CCX_ide_t: CCX_ID_STANDARD / CCX_ID_EXTENDED
uint8_t FrameFormat : 2; // use CCX_frame_format_t: CLASSIC->match classic; FD/FD_BRS->match FD
void *UserData; // User context pointer passed to Parser and TimeoutCallback
CCX_TIME_t TimeOut; // Timeout period (0 = disabled)
void (*Parser)(const CCX_instance_t *Instance,
CCX_message_t *Msg,
uint16_t Slot,
void *UserData);
void (*TimeoutCallback)(CCX_instance_t *Instance, uint16_t Slot, void *UserData);
CCX_TIME_t LastTick; // Last receive time (auto-managed)
} CCX_RX_table_t;Usage:
- Define expected messages with ID, DLC, and IDE_flag
- Set
TimeOutto enable timeout detection (in ticks) - Provide
Parsercallback to process matched messages - Provide
TimeoutCallbackto handle timeout events (optional) LastTickis automatically updated by library- In FD builds:
FrameFormatdefaults toCCX_FRAME_FORMAT_CLASSIC=0via C aggregate initialization ({.ID=..., .DLC=...}); if using field-by-field assignment, callmemset(rx_table, 0, sizeof(rx_table))first - All entries check
FrameFormat- a CLASSIC entry will not match an FD frame and vice versa
Classic build (CCX_ENABLE_CANFD=0, default):
typedef struct {
uint32_t ID; // Message ID to send
uint8_t *Data; // Pointer to data buffer
uint8_t DLC : 4; // Data length (0-8)
uint8_t IDE_flag : 1; // ID type
void *UserData; // User context pointer passed to Parser
CCX_TIME_t SendFreq; // Send period in ticks
void (*Parser)(const CCX_instance_t *Instance,
uint8_t *DataToSend,
uint16_t Slot,
void *UserData);
CCX_TIME_t LastTick; // Last send time (auto-managed)
} CCX_TX_table_t;FD build (CCX_ENABLE_CANFD=1):
typedef struct {
uint32_t ID; // Message ID to send
uint8_t *Data; // Pointer to data buffer (up to 64 bytes)
uint8_t DLC : 4; // DLC code (0-15)
uint8_t IDE_flag : 1; // use CCX_ide_t: CCX_ID_STANDARD / CCX_ID_EXTENDED
uint8_t FrameFormat : 2; // use CCX_frame_format_t: CLASSIC / FD / FD_BRS
void *UserData; // User context pointer passed to Parser
CCX_TIME_t SendFreq; // Send period in ticks
void (*Parser)(const CCX_instance_t *Instance,
uint8_t *DataToSend,
uint16_t Slot,
void *UserData);
CCX_TIME_t LastTick; // Last send time (auto-managed)
} CCX_TX_table_t;Usage:
- Define periodic messages with ID, DLC, IDE_flag
- Set
SendFreqto transmission period (in ticks) Datapoints to data buffer (must be at leastCCX_FD_DLC_TO_LEN[DLC]bytes in FD builds)- Optional
Parsercallback to update data before sending LastTickis automatically updated by library- In FD builds: set
FrameFormat = CCX_FRAME_FORMAT_FDorCCX_FRAME_FORMAT_FD_BRSfor FD periodic frames
#include "can_corex.h"
// System tick (incremented by timer interrupt)
volatile uint32_t system_tick_ms = 0;
// CAN instance
CCX_instance_t can1;
// Callback: Send message to hardware
void hw_send_can_message(const CCX_instance_t *inst, const CCX_message_t *msg) {
// Write to CAN hardware registers
CAN->TX_ID = msg->ID;
CAN->TX_DLC = msg->DLC;
memcpy(CAN->TX_DATA, msg->Data, msg->DLC);
CAN->TX_REQ = 1; // Trigger transmission
}
// Callback: Check if CAN bus is free
CCX_BusIsFree_t hw_can_bus_check(const CCX_instance_t *inst) {
return (CAN->STATUS & CAN_TX_BUSY) ? CCX_BUS_BUSY : CCX_BUS_FREE;
}
int main(void) {
// Initialize hardware
can_hardware_init();
// Register tick source
CCX_tick_variable_register(&system_tick_ms);
// Initialize CAN CoreX
CCX_Init(&can1, NULL, NULL, 0, 0,
hw_send_can_message, hw_can_bus_check, NULL);
while (1) {
// Poll CAN library
CCX_Poll(&can1);
// Send a message
CCX_message_t msg = {
.ID = 0x100,
.DLC = 2,
.IDE_flag = 0,
.Data = {0xAA, 0xBB}
};
CCX_TX_PushMsg(&can1, &msg);
delay_ms(10);
}
}
// Timer interrupt - increment tick
void SysTick_Handler(void) {
system_tick_ms++;
}// Parser callback for specific message
void parse_sensor_data(const CCX_instance_t *inst,
CCX_message_t *msg,
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);
}
// Timeout callback
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();
}
// Define RX table
CCX_RX_table_t rx_table[] = {
{
.ID = 0x200,
.DLC = 2,
.IDE_flag = 0,
.TimeOut = 1000, // 1000ms timeout
.Parser = parse_sensor_data,
.TimeoutCallback = sensor_timeout_handler
}
};
// Initialize with RX table
CCX_tick_variable_register(&system_tick_ms);
CCX_Init(&can1, rx_table, NULL, 1, 0,
hw_send_can_message, hw_can_bus_check, NULL);// Data buffer for heartbeat message
uint8_t heartbeat_data[2] = {0x00, 0x00};
// Parser to update data before sending
void update_heartbeat(const CCX_instance_t *inst,
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();
}
// Define TX table
CCX_TX_table_t tx_table[] = {
{
.ID = 0x100,
.Data = heartbeat_data,
.DLC = 2,
.IDE_flag = 0,
.SendFreq = 100, // Send every 100ms
.Parser = update_heartbeat
}
};
// Initialize with TX table
CCX_Init(&can1, NULL, tx_table, 0, 1,
hw_send_can_message, hw_can_bus_check, NULL);
// Heartbeat will be sent automatically by CCX_Poll()// Two CAN instances on different physical buses
CCX_instance_t can1, can2;
// Network structure
CCX_net_t can_network;
// Initialize both instances
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);
// Configure network
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;
// Initialize network
CCX_net_init(&can_network);
// Now messages received on can1 are automatically forwarded to can2CAN CoreX includes ISO 15765-2 (ISO-TP) for classic CAN and CAN FD transports.
- Classic CAN instances: Standard
SFup to 7 bytes, standardFF + CFup to 4095 bytes - CAN FD instances: Standard
SFup to 7 bytes, extendedSFfor larger single-frame payloads, standardFFup to 4095 bytes, extendedFFabove 4095 bytes - Flow Control (FC): CTS/WAIT/OVFLW support
- Configurable Padding:
- classic instances pad to
8 - FD instances pad to configured
TxDL
- classic instances pad to
- Timeout Monitoring: enforced
N_Bs,N_Cs,N_Cr - Separated Timing Domains:
N_Bs,N_Cs,N_Cruse the primarymstimebaseSTminvalues0x00..0x7Fuse the primarymstimebaseSTminvalues0xF1..0xF9(100-900us) 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:
- classic instance:
CCX_ISOTP_MAX_CLASSIC_DATA_SIZE(4095by default) - FD instance:
CCX_ISOTP_MAX_FD_DATA_SIZE(UINT32_MAXby default, bounded by application buffers)
- classic instance:
- 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_tisuint32_t; in classic-only builds it remainsuint16_t - In FD builds, a classic ISO-TP instance still keeps the classic
4095-byte limit OnReceiveProgressreports a delta since the previous callback, not an absolute offset- To print
received/total, accumulateBytesReceivedyourself or readInstance->RxDataOffset; do not interpretBytesReceivedas the total bytes received so far N_Crmeasures 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_Crexpires, RX will typically end withCCX_ISOTP_ERROR_SEQUENCE, notCCX_ISOTP_ERROR_TIMEOUT_CF_RX OnReceiveStartfires once after a validFFis accepted and the total payload length is knownCCX_ISOTP_ERROR_TIMEOUTremains a legacy alias ofCCX_ISOTP_ERROR_TIMEOUT_FC- In dual-timebase builds, sub-millisecond
STminrequires a registered HR tick source - In single-timebase builds (
CCX_DISABLE_HIGH_RES_TIMEBASE), RX configuration must not request sub-millisecondSTmin; incoming FC values in the0xF1..0xF9range are still rounded up to the primarymstick on TX
#include "can_corex.h"
#include "can_corex_isotp.h"
// Initialize CAN CoreX
CCX_instance_t can;
CCX_tick_variable_register(&system_tick);
// Create ISO-TP TX instance
CCX_ISOTP_TX_t isotp_tx;
// RX table - only for receiving Flow Control frames
CCX_RX_table_t rx_table[] = {
CCX_ISOTP_TX_FC_TABLE_ENTRY(&isotp_tx, 0x321, 0) // Receive FC on 0x321 (Standard ID)
};
CCX_Init(&can, rx_table, NULL, 1, 0, hw_send, hw_bus_check, NULL);
// Configure ISO-TP TX
CCX_ISOTP_TX_Config_t tx_cfg;
CCX_ISOTP_TX_Config_Init(&tx_cfg, 0x123, CCX_ID_STANDARD, 1000, 1000, CCX_ISOTP_PADDING(0xAA), NULL, tx_complete_callback,
tx_error_callback);
tx_cfg.MaxWaitFrames = 10; // Optional FC.WAIT tolerance (0 = library default)
CCX_ISOTP_TX_Init(&isotp_tx, &can, &tx_cfg);
// Transmit large message
uint8_t data[200];
// ... fill data ...
CCX_ISOTP_Transmit(&isotp_tx, data, 200);
// Main loop
while (1) {
CCX_Poll(&can); // Process CAN messages
CCX_ISOTP_TX_Poll(&isotp_tx); // Handle ISO-TP TX state machine
}Use the _EX table macros when the ISO-TP session uses FD frames:
CCX_RX_table_t rx_table[] = {
CCX_ISOTP_TX_FC_TABLE_ENTRY_EX(&isotp_tx, 0x321, 0, CCX_FRAME_FORMAT_FD_BRS)
};
CCX_ISOTP_TX_Config_t tx_cfg;
CCX_ISOTP_TX_Config_InitFD(&tx_cfg, 0x123, CCX_ID_STANDARD, CCX_FRAME_FORMAT_FD_BRS, CCX_ISOTP_TX_DL_64, 1000,
1000, CCX_ISOTP_PADDING(0xAA), NULL, tx_complete_callback, tx_error_callback);
tx_cfg.MaxWaitFrames = 10;TxDL accepts only legal FD payload sizes: 8, 12, 16, 20, 24, 32, 48, 64.
#include "can_corex.h"
#include "can_corex_isotp.h"
// Initialize CAN CoreX
CCX_instance_t can;
CCX_tick_variable_register(&system_tick);
// Create ISO-TP RX instance
CCX_ISOTP_RX_t isotp_rx;
uint8_t rx_buffer[512];
// RX table - for receiving data frames
CCX_RX_table_t rx_table[] = {
CCX_ISOTP_RX_TABLE_ENTRY(&isotp_rx, 0x123, 0) // Receive data on 0x123 (Standard ID)
};
CCX_Init(&can, rx_table, NULL, 1, 0, hw_send, hw_bus_check, NULL);
// Configure ISO-TP RX
CCX_ISOTP_RX_Config_t rx_cfg;
CCX_ISOTP_RX_Config_Init(&rx_cfg, 0x321, CCX_ID_STANDARD, 0, 10, 1000, rx_buffer, sizeof(rx_buffer),
CCX_ISOTP_PADDING(0xAA), 0, NULL, NULL, rx_complete_callback, NULL, rx_error_callback);
CCX_ISOTP_RX_Init(&isotp_rx, &can, &rx_cfg);
// Main loop
while (1) {
CCX_Poll(&can); // Process CAN messages
CCX_ISOTP_RX_Poll(&isotp_rx); // Handle ISO-TP RX timeouts
}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);
// Process received data...
}
void tx_complete_callback(CCX_ISOTP_TX_t *Instance, void *UserData) {
(void)Instance;
(void)UserData;
printf("Transmission complete!\n");
}In FD sessions the RX-side FC_TxDL controls only transmitted Flow Control frames:
CCX_ISOTP_RX_Config_t rx_cfg;
CCX_ISOTP_RX_Config_InitFD(&rx_cfg, 0x321, CCX_ID_STANDARD, CCX_FRAME_FORMAT_FD_BRS, CCX_ISOTP_TX_DL_64, 0, 0,
1000, rx_buffer, sizeof(rx_buffer), CCX_ISOTP_PADDING(0xAA), 512, NULL,
rx_start_callback, rx_complete_callback, rx_progress_callback, rx_error_callback);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_900USExample:
.STmin = CCX_ISOTP_STMIN_500USIn 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:
// With padding - FC will have DLC=8
.Padding = CCX_ISOTP_PADDING(0xAA)
// Example with BS=0, STmin=10: [30 00 0A AA AA AA AA AA]
// Without padding - FC will have DLC=3
.Padding = CCX_ISOTP_NO_PADDING
// Example with BS=0, STmin=10: [30 00 0A]Both variants work correctly thanks to CCX_DLC_ANY wildcard matching in the RX table macros.
Use CCX_ISOTP_TX_Config_Init() and CCX_ISOTP_RX_Config_Init() to seed the config with the common defaults:
CCX_ISOTP_TX_Config_t tx_cfg;
CCX_ISOTP_TX_Config_Init(&tx_cfg, 0x700, CCX_ID_STANDARD, 1000U, 1000U, CCX_ISOTP_NO_PADDING, user_ctx,
tx_complete_cb, tx_error_cb);
CCX_ISOTP_RX_Config_t rx_cfg;
CCX_ISOTP_RX_Config_Init(&rx_cfg, 0x701, CCX_ID_STANDARD, 0, 0, 1000U, rx_buffer, sizeof(rx_buffer),
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.
CCX_ISOTP_TX_Abort()andCCX_ISOTP_RX_Abort()cancel an active transfer and reset the instance toIDLE- Active aborts emit
OnError(..., CCX_ISOTP_ERROR_ABORTED, ...) - Timeout errors are phase-specific:
CCX_ISOTP_ERROR_TIMEOUT_FCforN_BsCCX_ISOTP_ERROR_TIMEOUT_CF_TXforN_CsCCX_ISOTP_ERROR_TIMEOUT_CF_RXforN_CrCCX_ISOTP_ERROR_WAIT_EXCEEDEDfor exhaustedFC.WAIT
CCX_ISOTP_ERROR_TIMEOUTis kept as a legacy alias ofCCX_ISOTP_ERROR_TIMEOUT_FC
For large transfers, you can monitor reception progress:
CCX_ISOTP_RX_Config_t rx_cfg = {
// ... other config ...
.ProgressCallbackInterval = 512, // Call every 512 bytes
.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; // BytesReceived is a delta from the previous callback
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.
ISO-TP supports both Standard (11-bit) and Extended (29-bit) CAN identifiers.
Standard ID (11-bit) - Range: 0x000 to 0x7FF:
CCX_RX_table_t rx_table[] = {
CCX_ISOTP_RX_TABLE_ENTRY(&isotp_rx, 0x123, 0), // Standard ID
CCX_ISOTP_TX_FC_TABLE_ENTRY(&isotp_tx, 0x321, 0) // Standard ID
};
CCX_ISOTP_TX_Config_t tx_cfg = {
.TxID = 0x123,
.IDE_TxID = 0, // Standard ID
// ... other config
};
CCX_ISOTP_RX_Config_t rx_cfg = {
.TxID = 0x321,
.IDE_TxID = 0, // Standard ID
// ... other config
};Extended ID (29-bit) - Range: 0x00000000 to 0x1FFFFFFF:
CCX_RX_table_t rx_table[] = {
CCX_ISOTP_RX_TABLE_ENTRY(&isotp_rx, 0x18DA00F1, 1), // Extended ID
CCX_ISOTP_TX_FC_TABLE_ENTRY(&isotp_tx, 0x18DAF100, 1) // Extended ID
};
CCX_ISOTP_TX_Config_t tx_cfg = {
.TxID = 0x18DA00F1,
.IDE_TxID = 1, // Extended ID
// ... other config
};
CCX_ISOTP_RX_Config_t rx_cfg = {
.TxID = 0x18DAF100,
.IDE_TxID = 1, // Extended ID
// ... other config
};Mixed Configuration (TX uses Standard, RX uses Extended):
CCX_RX_table_t rx_table[] = {
CCX_ISOTP_RX_TABLE_ENTRY(&isotp_rx, 0x18DA00F1, 1), // Extended ID
CCX_ISOTP_TX_FC_TABLE_ENTRY(&isotp_tx, 0x321, 0) // Standard ID
};
CCX_ISOTP_TX_Config_t tx_cfg = {
.TxID = 0x123,
.IDE_TxID = 0, // Standard ID
// ... other config
};
CCX_ISOTP_RX_Config_t rx_cfg = {
.TxID = 0x18DAF100,
.IDE_TxID = 1, // Extended ID for FC
// ... other config
};- Normal addressing only: Extended and Mixed addressing modes not yet implemented
- Single-timebase builds: Submillisecond
STminvalues are rounded up to the primarymstick
CAN CoreX supports wildcard DLC matching in RX tables using CCX_DLC_ANY.
By default, RX table entries match exact DLC values. Using CCX_DLC_ANY allows accepting messages with any DLC:
CCX_RX_table_t rx_table[] = {
// Exact match - only DLC=8
{.ID = 0x100, .DLC = 8, .IDE_flag = 0, .Parser = my_parser},
// Wildcard - accepts any DLC
{.ID = 0x200, .DLC = CCX_DLC_ANY, .IDE_flag = 0, .Parser = my_parser}
};| Build | CCX_DLC_ANY |
Matches |
|---|---|---|
Classic (CCX_ENABLE_CANFD=0) |
15 | Any DLC 0-8 |
FD (CCX_ENABLE_CANFD=1) |
16 | Any DLC 0-15 (format filtered by FrameFormat) |
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.
CCX_message_t msg1 = {.ID = 0x200, .DLC = 3, .Data = {1, 2, 3}};
CCX_message_t msg2 = {.ID = 0x200, .DLC = 8, .Data = {1, 2, 3, 4, 5, 6, 7, 8}};
CCX_RX_PushMsg(&can, &msg1); // Parser called (DLC=3 accepted)
CCX_RX_PushMsg(&can, &msg2); // Parser called (DLC=8 accepted)In FD builds, CCX_DLC_ANY combined with the FrameFormat field selects which frame format the wildcard matches:
// Matches any FD frame (with BRS) on ID 0x300
CCX_RX_table_t rx_table[] = {
{.ID = 0x100, .DLC = 8, .FrameFormat = CCX_FRAME_FORMAT_CLASSIC, .Parser = classic_parser},
{.ID = 0x300, .DLC = CCX_DLC_ANY, .FrameFormat = CCX_FRAME_FORMAT_FD_BRS, .Parser = fd_brs_parser},
{.ID = 0x400, .DLC = CCX_DLC_ANY, .FrameFormat = CCX_FRAME_FORMAT_CLASSIC, .Parser = classic_any_parser},
};
CCX_Init(&can, rx_table, NULL, 3, 0, send_fn, bus_fn, NULL);
// FrameFormat is part of the aggregate initializer - no post-Init fixup neededFrameFormat uses the CCX_frame_format_t enum values:
CCX_FRAME_FORMAT_CLASSIC(0) - matches classic (non-FD) frames onlyCCX_FRAME_FORMAT_FD(1) - matches FD frames (any BRS setting)CCX_FRAME_FORMAT_FD_BRS(2) - matches FD frames with BRS
All entries check FrameFormat - a CLASSIC entry will not match an FD frame carrying the same DLC, and vice versa.
- 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
- ISO-TP macros automatically use
CCX_DLC_ANYfor flexibility - In FD builds, DLC values 0-15 are all valid; only 16 is the wildcard sentinel
CCX_FRAME_FORMAT_CLASSIC=0is the natural C aggregate-initializer default; field-by-field tables requirememsetbefore population
CAN CoreX includes comprehensive bus health monitoring and statistics tracking.
Always-on statistics tracking with minimal overhead. Automatically maintained by the library.
Available Metrics:
total_rx_messages- Total messages received and pushed to RX buffertotal_tx_messages- Total messages successfully transmitted (requiresCCX_OnMessageTransmitted()call from ISR)rx_buffer_overflows- Number of times RX buffer was fulltx_buffer_overflows- Number of times TX buffer was fullpeak_rx_depth- Highest observed RX queue depth since statistics resetpeak_tx_depth- Highest observed TX queue depth since statistics resetparser_calls_count- Total parser function invocationstimeout_calls_count- Total timeout callback invocations
Usage:
// Get statistics
const CCX_GlobalStats_t *stats = CCX_GetGlobalStats(&can_instance);
printf("RX: %lu, TX: %lu, Overflows: %lu/%lu\n",
stats->total_rx_messages,
stats->total_tx_messages,
stats->rx_buffer_overflows,
stats->tx_buffer_overflows);
printf("Peak queue depth: RX=%u, TX=%u\n",
stats->peak_rx_depth,
stats->peak_tx_depth);
// Reset statistics
CCX_ResetGlobalStats(&can_instance);Important: For accurate TX counting, call CCX_OnMessageTransmitted() from your CAN TX complete interrupt:
void CAN_TX_IRQHandler(void) {
// Clear interrupt flag
// ...
// Notify library
CCX_OnMessageTransmitted(&can_instance, NULL);
}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); // clear only RX queue indices
CCX_FlushTx(&can_instance); // clear only TX queue indices
CCX_Flush(&can_instance); // clear RX and TX queues
CCX_Reset(&can_instance); // clear RX/TX queues and reset global statsCCX_Flush*() does not reset global statistics or ISO-TP session state. CCX_Reset() resets global statistics, including peak_rx_depth and peak_tx_depth.
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_t bus_monitor;
CCX_BusMonitor_Init(
&can_instance,
&bus_monitor,
my_get_bus_state, // Read state from hardware
my_get_error_counters, // Read TEC/REC (optional)
my_request_recovery, // Trigger recovery
CCX_BUS_RECOVERY_MS(10), // recovery_delay: 10ms between attempts
60000, // successful_run_time: 60s grace period
1, // auto_recovery_enabled
5 // max_recovery_attempts before grace period
);
// Optional callbacks
bus_monitor.OnBusStateChange = my_state_callback;
bus_monitor.OnRecoveryAttempt = my_recovery_callback;
bus_monitor.OnRecoveryFailed = my_failed_callback;
bus_monitor.OnErrorCountersUpdate = my_counters_callback;Recovery Strategy:
-
Active Recovery Phase:
- Attempts recovery up to
max_recovery_attemptstimes - Waits
recovery_delaybetween attempts - Calls
OnRecoveryAttemptbefore 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 whenx <= 3000 us
- Attempts recovery up to
-
Grace Period:
- After max attempts, waits
successful_run_timebefore trying again - Calls
OnRecoveryFailedwhen entering grace period - Prevents aggressive retry loops
- After max attempts, waits
-
Counter Reset:
- After successful operation for
successful_run_time, attempt counter resets to 0
- After successful operation for
Recovery delay selection:
- Classic CAN constants up to
250 kbpsuseCCX_BUS_RECOVERY_MS(...) 500 kbps,800 kbps,1000 kbps, and fast FD data-phase constants useCCX_BUS_RECOVERY_US(...)CCX_BUS_RECOVERY_US(x)uses HR only forx <= 3000 us; above that it falls back to basemssuccessful_run_timealways uses the primarymstimebase
Manual Recovery:
// Trigger recovery manually (resets counter and grace period)
CCX_Status_t status = CCX_BusMonitor_TriggerRecovery(&can_instance);Statistics:
// Access bus statistics
const CCX_BusStats_t *stats = &bus_monitor.stats;
printf("Bus-off count: %lu\n", stats->bus_off_count);
printf("TEC: %u, REC: %u\n",
stats->error_counters.TEC,
stats->error_counters.REC);
printf("Peak TEC: %u, Peak REC: %u\n",
stats->peak_error_counters.TEC,
stats->peak_error_counters.REC);
// Reset bus monitoring statistics
CCX_BusMonitor_ResetStats(&can_instance);Hardware Interface Functions:
You must implement three functions for your hardware:
// Read current bus state
CCX_BusState_t my_get_bus_state(const CCX_instance_t *inst) {
// Read from CAN controller registers
// Return CCX_BUS_STATE_ACTIVE/WARNING/PASSIVE/OFF
}
// Read error counters (optional - can be NULL)
void my_get_error_counters(const CCX_instance_t *inst, CCX_ErrorCounters_t *cnt) {
// Read TEC/REC from CAN controller
cnt->TEC = /* read transmit error counter */;
cnt->REC = /* read receive error counter */;
}
// Trigger bus-off recovery
void my_request_recovery(const CCX_instance_t *inst) {
// Trigger recovery in CAN controller
// e.g., clear bus-off bit, request re-initialization
}Callbacks:
void my_state_callback(CCX_instance_t *inst,
CCX_BusState_t old_state,
CCX_BusState_t new_state,
void *user_data) {
if (new_state == CCX_BUS_STATE_OFF) {
// Bus-off occurred - recovery will start automatically
} else if (new_state == CCX_BUS_STATE_ACTIVE &&
old_state == CCX_BUS_STATE_OFF) {
// Recovery successful
}
}
void my_recovery_callback(CCX_instance_t *inst,
uint8_t attempt,
void *user_data) {
printf("Recovery attempt %u\n", attempt);
}
void my_failed_callback(CCX_instance_t *inst, void *user_data) {
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); // Automatically updates bus monitor
// ...
}Most users should stay with the default build and only decide whether CAN FD is needed.
Main build switch:
CCX_ENABLE_CANFD=1enables 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_BINARYswitches RX lookup to binary search and requires the RX table to stay sorted by CAN IDCCX_RX_SEARCH_HASHenables hashed RX lookup andCCX_RX_RebuildHash()for runtime RX table changesCCX_TICK_FROM_FUNCandCCX_HR_TICK_FROM_FUNCswitch tick registration from variables to callbacks- custom primary / HR tick widths can be selected with
CCX_TIME_BASE_TYPE_CUSTOM_IS_UINT16/32/64andCCX_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
// CORRECT
CCX_tick_variable_register(&system_tick);
CCX_Init(&can1, ...);
// WRONG - will cause undefined behavior
CCX_Init(&can1, ...);
CCX_tick_variable_register(&system_tick);CCX_Status_t status = CCX_TX_PushMsg(&can1, &msg);
if (status == CCX_BUS_TOO_BUSY) {
// Implement retry logic or drop message
log_error("TX buffer full");
}// In main loop (non-RTOS)
while (1) {
CCX_Poll(&can1);
// Poll frequency should be >> highest TX frequency
}
// Or in timer (RTOS)
void timer_callback(void) {
CCX_Poll(&can1);
}uint8_t dlc = user_input;
if (dlc > 8) {
dlc = 8; // Clamp to maximum
}
msg.DLC = dlc;// For TX table, ensure Data pointer is valid
static uint8_t data_buffer[8]; // Static or global, not stack!
CCX_TX_table_t tx_table[] = {
{
.Data = data_buffer, // Valid for entire lifetime
// ...
}
};// For safety-critical sensors, always enable timeout
rx_table[0].TimeOut = 100; // 100ms max between messages
rx_table[0].TimeoutCallback = critical_sensor_timeout_handler;// Size buffers based on traffic:
// RX buffer >= max burst size
// TX buffer >= (max burst + periodic messages)
#define CCX_RX_BUFFER_SIZE 48 // e.g., 48 messages
#define CCX_TX_BUFFER_SIZE 48- Tick source should match timing requirements
- For 1ms timeouts, use 1ms tick
- Higher resolution = more precise timeouts
- Call
CCX_Poll()at least 2x faster than shortest timeout
- Timeout triggers when:
current_tick - LastTick >= TimeOut - For 100ms timeout with 10ms poll: actual = 100-110ms
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.
void can_task(void *param) {
while (1) {
CCX_Poll(&can1);
vTaskDelay(10);
}
}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);
}
}- Buffer Size: Fixed at compile time (
CCX_RX_BUFFER_SIZE,CCX_TX_BUFFER_SIZE) - ISO-TP effective payload limit depends on instance format:
- classic instance:
CCX_ISOTP_MAX_CLASSIC_DATA_SIZE(4095by default) - FD instance:
CCX_ISOTP_MAX_FD_DATA_SIZE(UINT32_MAXby default, still bounded by application buffer sizes and available memory)
- classic instance:
- Timeout Range: Limited by
CCX_TIME_ttype (default:uint32_t) - Not Thread-Safe: Requires external synchronization in multi-threaded environments
Mozilla Public License 2.0 - see LICENSE file for details.
Adrian Pietrzak
- GitHub: @AdrianPietrzak1998
- Initialization hardening:
CCX_Init()now returnsCCX_MISSING_TIMEBASEwhen the primary tick source has not been registered- prevents silent
0tick fallback during tableLastTickinitialization
- Version metadata:
- added public
CCX_VERSION_MAJOR,CCX_VERSION_MINOR,CCX_VERSION_PATCH, andCCX_VERSION_STRINGmacros
- added public
- Periodic TX reliability:
- TX table
LastTickis updated only afterCCX_TX_PushMsg()accepts the generated message - periodic messages retry on the next
CCX_Poll()when the TX queue was full
- TX table
- 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
- Optional utilities module:
- added
can_corex_utils.handcan_corex_utils.c - utility module is opt-in and does not change core CAN, CAN FD, ISO-TP, network, or bus-monitoring behavior
- added
- Classic SLCAN helpers:
- added
CCX_SLCAN_Parse()andCCX_SLCAN_Format()for classic CAN data-frame text conversion - supports standard and extended data frames compatible with Linux
slcan/slcandworkflows - remote frames are reported as unsupported because
CCX_message_tdoes not represent RTR
- added
- Hex helpers:
- added
CCX_BytesToHex()andCCX_HexToBytes() - conversion is allocation-free and caller-buffer based
- added
- 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
_Clampedbehavior - 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
- Queue observability:
- added
CCX_RX_GetDepth(),CCX_TX_GetDepth(),CCX_RX_GetFree(), andCCX_TX_GetFree() - added
peak_rx_depthandpeak_tx_depthto global statistics
- added
- Queue control:
- added
CCX_FlushRx(),CCX_FlushTx(),CCX_Flush(), andCCX_Reset() - flush helpers clear queue indices without resetting statistics
CCX_Reset()clears RX/TX queues and global statistics
- added
- 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
STminvalues0xF1..0xF9and advertises the raw value in Flow Control frames - TX behavior for sub-millisecond
STminin single-timebase builds remains rounded up to the nearest full millisecond
- TX enqueue failures for SF, FF, and CF now abort transmission, return/report failure, and raise
- C++ compatibility:
- public headers now use
extern "C"guards when included from C++
- public headers now use
- Documentation:
- added local Doxygen configuration and public API module groups
- Breaking Changes: None to existing function signatures, enum values, or initialization APIs
- Bus-monitoring header split:
- bus-monitoring public types, macros, and API declarations moved from
can_corex.htocan_corex_bus.h can_corex.hstill includescan_corex_bus.h, so existing integrations that include onlycan_corex.hremain valid
- bus-monitoring public types, macros, and API declarations moved from
- Bus-monitoring implementation split:
- bus-monitoring runtime logic now lives in
can_corex_bus.c can_corex.ckeeps core RX/TX runtime and global statistics
- bus-monitoring runtime logic now lives in
- Documentation:
- README and implementation guide updated to reflect the new public module layout
- Breaking Changes: None for normal users of the public API
- Timebase contract cleanup:
- core CAN RX/TX logic now always uses the primary
mstimebase - optional HR timing is a separate domain with its own type family and independent tick registration
CCX_TICK_FROM_FUNCandCCX_HR_TICK_FROM_FUNCare fully independentCCX_IsPrimaryTickRegistered()andCCX_IsHighResTickRegistered()expose registration state
- core CAN RX/TX logic now always uses the primary
- Timebase type restrictions:
- allowed custom widths are now
uint16_t,uint32_t,uint64_t uint8_ttimebases 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
- allowed custom widths are now
- ISO-TP timing cleanup:
- enforced ISO-TP runtime timers
N_Bs,N_Cs, andN_Cruse the primarymstimebase - HR timing is used only for sub-millisecond
STminvalues (0xF1..0xF9) - in single-timebase builds, incoming FC
STminvalues in the0xF1..0xF9range are rounded up to the primarymstick
- enforced ISO-TP runtime timers
- Bus monitoring timing cleanup:
successful_run_timenow always uses the primarymstimebaserecovery_delayis now configured throughCCX_BUS_RECOVERY_MS(...)orCCX_BUS_RECOVERY_US(...)CCX_BUS_RECOVERY_US(x)uses HR only forx <= 3000 us; above that it falls back to basems- classic recovery constants up to
250 kbpsare expressed inms; faster classic/FD constants use preciseusvalues where it matters 500 kbpsrecovery constant corrected to2816 us
- Testing:
- added explicit bus recovery threshold coverage for
2999 us,3000 us, and3001 us - current verified totals: classic linear
352, FD linear529
- added explicit bus recovery threshold coverage for
- CAN FD Support (
-DCCX_ENABLE_CANFD=1): 64-byte payloads, BRS, ESI - zero overhead when disabledCCX_frame_format_t(3 values):CCX_FRAME_FORMAT_CLASSIC=0,CCX_FRAME_FORMAT_FD=1,CCX_FRAME_FORMAT_FD_BRS=2- replaces separateFDF:1 + BRS:1bitfields; present inCCX_message_t,CCX_RX_table_t,CCX_TX_table_t; per-message/per-entry (not per-instance)CCX_ide_tenum:CCX_ID_STANDARD=0,CCX_ID_EXTENDED=1- named constants for allIDE_flagfieldsCCX_RX_table_textended: 5-bitDLCfield (sentinelCCX_DLC_ANY=16);FrameFormatcontrols which format the wildcard matches- All RX entries check
FrameFormat- CLASSIC entry will not match FD frame and vice versa CCX_Initdoes not zeroFrameFormat-CCX_FRAME_FORMAT_CLASSIC=0is the C aggregate-init default; field-by-field tables requirememset(rx_table, 0, sizeof(rx_table))before populationCCX_Init_Ex()removed - no per-instance frame format; useCCX_Init()for all instancesCCX_FD_DLC_tenum: named constantsCCX_FD_DLC_0B...CCX_FD_DLC_64BCCX_FD_LenToDLC()/CCX_MsgPayloadLen(): helpers for FD length <-> DLC conversionCCX_FD_DLC_TO_LEN[16]LUT: compile-time array mapping DLC codes to byte lengths
- Network layer (
can_corex_net): no FD-to-classic frame filtering - FD frames replicate freely;dropped_mixedstat removed - ISO-TP (
can_corex_isotp):- unified classic/FD API
FrameFormatselects classic / FD / FD_BRS session behavior- TX uses
TxDL; RX usesFC_TxDLfor transmitted Flow Control frames TxDL/FC_TxDLselect 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, extendedFF, and payload lengths above4095 CCX_ISOTP_Length_tisuint32_tin FD builds- padding is format-aware: classic pads to
8, FD pads toTxDL CCX_ISOTP_TX_Abort()/CCX_ISOTP_RX_Abort()allow explicit transfer cancellationOnReceiveStartfires after a validFF;OnReceiveProgressreports deltas- phase-specific timeout diagnostics:
TIMEOUT_FC,TIMEOUT_CF_TX,TIMEOUT_CF_RX,WAIT_EXCEEDED - RX configuration advertises
BSandSTminthrough 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_MAXISO-TP path - current verified totals: classic linear/binary
340, FD linear/binary516, FD hash520
- Breaking Changes (FD builds only):
FDF/BRSfields replaced byFrameFormat;CCX_Init_Exremoved; 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
- CAN FD Support (
-DCCX_ENABLE_CANFD=1): 64-byte payloads, BRS, ESI support added to the CAN coreCCX_frame_format_tintroduced withCCX_FRAME_FORMAT_CLASSIC,CCX_FRAME_FORMAT_FD, andCCX_FRAME_FORMAT_FD_BRSCCX_message_t,CCX_RX_table_t, andCCX_TX_table_textended for FD operation- strict
FrameFormatmatching added on RX entries CCX_FD_DLC_t,CCX_FD_LenToDLC(),CCX_MsgPayloadLen(), andCCX_FD_DLC_TO_LEN[16]added for FD DLC handling
- 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/BRSbitfields replaced byFrameFormatCCX_Init_Ex()removed- all RX entries began enforcing frame-format matching
- 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 == 0treated as disabled
- When
- ISO-TP FC.WAIT LastTick ordering (MODERATE): Fixed
LastTickbeing updated afterOnErrorcallback when FC.WAIT frames are exhausted- If a new transmission was started inside the
OnErrorcallback, itsLastTickwas immediately overwritten LastTickis now updated before invoking the callback
- If a new transmission was started inside the
- 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
- Re-adding the last node in the linked list set
- ISO-TP N_Cs timeout logic (CRITICAL): Fixed N_Cs timeout check incorrectly nested inside STmin condition
- 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 safetyCCX_BusMonitor_ResetStats()- counter zeroing, state preservation, NULL safetyCCX_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)
- 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 separateData[1] - Block Size moved from
Data[2]toData[1], STmin moved fromData[3]toData[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
- Flow Status now encoded in lower nibble of PCI byte (
- Custom Time Type Fix: Fixed typedef
CC_TIME_t->CCX_TIME_tincan_corex.h- Any user defining
CCX_TIME_BASE_TYPE_CUSTOMwould get a compilation error in v1.4.0-v1.4.2
- Any user defining
- Header Cleanup: Removed leftover
#define DCCX_RX_SEARCH_HASHfromcan_corex.h - Network RX Replication Fix:
CCX_net_RX_PushMsgnow setsRxReceivedTick- Timeout detection for messages received via network replication (
CCX_NET_TX_RX_REPLICATION) was broken -LastTickwas never updated becauseRxReceivedTickstayed at 0
- Timeout detection for messages received via network replication (
- 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)
- 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
- Changed condition from
- Time Type System: Added
CCX_TIME_VALUE_ttypedef 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
- STmin Implementation: Fixed TX consecutive frame timing to use STmin from Flow Control instead of N_Cs
- 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
- Bug-fix
- Hash table size: assert if RxTableSize >= CCX_RX_HASH_SIZE
- 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_SIZEdefine (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:
CCX_RX_RebuildHash()- Rebuild hash table (no-op for non-hash modes)CCX_RX_HASH_SIZEdefine for hash table sizing
- 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
- 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 monitoringCCX_BusMonitor_TriggerRecovery()- Manual recovery triggerCCX_BusMonitor_GetState()- Get current bus stateCCX_BusMonitor_ResetStats()- Reset bus statisticsCCX_GetGlobalStats()- Get global statisticsCCX_ResetGlobalStats()- Reset global statisticsCCX_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
- UserData Context in Parsers: Added
void *UserDatato 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 *UserDataparameter) - 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_ANYconstant 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
- Per-message timeout callbacks:
TimeoutCallbackmoved fromCCX_instance_ttoCCX_RX_table_tfor better flexibility - API Change:
CCX_Init()parameter count reduced from 9 to 8 (removed globalTimeoutCallbackparameter) - 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