Skip to main content

<alp/can.h> — CAN / CAN-FD

Controller Area Network bus support, including CAN-FD on hardware that supports it.

#include <alp/can.h>

:::warning Breaking change in v0.10 — dlc is now payload_len alp_can_frame_t::dlc was renamed to payload_len, and ALP_CAN_MAX_DLC_CLASSIC / ALP_CAN_MAX_DLC_FD became ALP_CAN_MAX_PAYLOAD_BYTES_CLASSIC / ALP_CAN_MAX_PAYLOAD_BYTES_FD. This is a deliberate pre-1.0 correction, landed before the v1 API freeze — update your call sites; there is no compatibility alias.

The old name was simply wrong. payload_len is a decoded byte count, not a wire CAN/CAN-FD DLC (Data Length Code) nibble. A real CAN-FD DLC is a 4-bit encoded value (0..15) that maps non-linearly onto 0..64 bytes above 8; that mapping is a backend-internal concern (see e.g. Zephyr's can_dlc_to_bytes() / can_bytes_to_dlc()) and never appears in this portable struct. :::

Send and receive

alp_can_t *can = alp_can_open(&(alp_can_config_t){
.bus_id = ALP_E1M_CAN0,
.bitrate_nominal_hz = 500000, // arbitration phase
.bitrate_data_hz = 2000000, // CAN-FD data phase
.mode = ALP_CAN_MODE_FD,
});

alp_can_start(can);

alp_can_frame_t tx = {
.id = 0x123,
.ext_id = false,
.payload_len = 8,
.data = { 1, 2, 3, 4, 5, 6, 7, 8 },
};
alp_can_send(can, &tx, /* timeout_ms */ 100);

void on_frame(const alp_can_frame_t *f, void *user) { /* ... */ }
alp_can_add_filter(can,
&(alp_can_filter_t){ .id = 0x123, .mask = 0x7FF },
on_frame, NULL, /* filter_id_out */ NULL);

Default-initialiser macro

v0.10 adds ALP_CAN_CONFIG_DEFAULT(id), which fills the identity field from id and every other field with its canonical default:

alp_can_config_t cfg = ALP_CAN_CONFIG_DEFAULT(ALP_E1M_CAN0);
cfg.mode = ALP_CAN_MODE_FD; // override what you need
cfg.bitrate_data_hz = 2000000;

alp_can_t *can = alp_can_open(&cfg);
FieldDefaultRationale
bus_idid (the macro arg)Identity.
bitrate_nominal_hz500000A widely-interoperable classic-CAN rate.
bitrate_data_hz0Classic — no data-phase rate.
modeALP_CAN_MODE_CLASSIC
loopbackfalseOn the wire, not local self-test.

For CAN-FD, set mode = ALP_CAN_MODE_FD and a non-zero bitrate_data_hz.

See the shared macro contract for the C++ compound-literal caveat that applies to every ALP_*_CONFIG_DEFAULT.

Frame struct

FieldTypeNotes
iduint32_t11-bit (standard) or 29-bit (extended).
ext_idbooltrue = 29-bit, false = 11-bit.
rtrboolRemote-transmission request.
fdboolCAN-FD frame.
brsboolBit-rate switch (FD only).
payload_lenuint8_tPayload bytes, 0..ALP_CAN_MAX_PAYLOAD_BYTES_FD. Must respect the mode.
datauint8_t[ALP_CAN_MAX_PAYLOAD_BYTES_FD]Payload bytes.
MacroValueNotes
ALP_CAN_MAX_PAYLOAD_BYTES_CLASSIC8Max payload, classic CAN.
ALP_CAN_MAX_PAYLOAD_BYTES_FD64Max payload, CAN-FD.

Functions

CallReturns
alp_can_open(const alp_can_config_t *cfg)Open handle, or NULL — read alp_last_error().
alp_can_start(can) / alp_can_stop(can)Bring the bus up / down.
alp_can_send(can, const alp_can_frame_t *frame, timeout_ms)ALP_OK; ALP_ERR_NOT_READY if can is NULL or stopped; ALP_ERR_INVAL if frame is NULL or payload_len is out of range; ALP_ERR_TIMEOUT if all TX mailboxes are occupied; ALP_ERR_IO on bus error (e.g. error-passive state). Blocks up to timeout_ms for a free TX mailbox.
alp_can_add_filter(can, filter, cb, user, filter_id_out)ALP_OK / ALP_ERR_NOT_READY / ALP_ERR_INVAL / ALP_ERR_NOMEM (filter slots exhausted) / ALP_ERR_IO. Stores the filter in the controller's hardware filter bank when possible, else falls back to software filtering. cb must not be NULL; filter_id_out may be NULL if the caller never removes the filter.
alp_can_remove_filter(can, int32_t filter_id)ALP_OK / ALP_ERR_INVAL (unknown id) / ALP_ERR_NOT_READY.
alp_can_close(can)void. Stops the bus and releases the handle. NULL is a no-op.
alp_can_capabilities(const alp_can_t *can)const alp_capabilities_t *, valid for the handle's lifetime; NULLNULL.

A frame matches a filter when (frame.id & mask) == (id & mask).

v0.10 behaviour fixes

  • Sending on a stopped handle is now rejected with ALP_ERR_NOT_READY rather than being attempted.
  • Filter slots are freed on remove and on close — previously a long-running app that cycled filters could exhaust the bank and start failing alp_can_add_filter with ALP_ERR_NOMEM.

See also

Questions about this page? Discuss in Community Forum