<alp/can.h> — CAN / CAN-FD
Controller Area Network bus support, including CAN-FD on hardware that supports it.
Header
#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);
| Field | Default | Rationale |
|---|---|---|
bus_id | id (the macro arg) | Identity. |
bitrate_nominal_hz | 500000 | A widely-interoperable classic-CAN rate. |
bitrate_data_hz | 0 | Classic — no data-phase rate. |
mode | ALP_CAN_MODE_CLASSIC | |
loopback | false | On 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
| Field | Type | Notes |
|---|---|---|
id | uint32_t | 11-bit (standard) or 29-bit (extended). |
ext_id | bool | true = 29-bit, false = 11-bit. |
rtr | bool | Remote-transmission request. |
fd | bool | CAN-FD frame. |
brs | bool | Bit-rate switch (FD only). |
payload_len | uint8_t | Payload bytes, 0..ALP_CAN_MAX_PAYLOAD_BYTES_FD. Must respect the mode. |
data | uint8_t[ALP_CAN_MAX_PAYLOAD_BYTES_FD] | Payload bytes. |
| Macro | Value | Notes |
|---|---|---|
ALP_CAN_MAX_PAYLOAD_BYTES_CLASSIC | 8 | Max payload, classic CAN. |
ALP_CAN_MAX_PAYLOAD_BYTES_FD | 64 | Max payload, CAN-FD. |
Functions
| Call | Returns |
|---|---|
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; NULL → NULL. |
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_READYrather 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_filterwithALP_ERR_NOMEM.
See also
<alp/peripheral.h>— error model and the shared config-macro contract- Examples: can-loopback