RT-Thread RTOS
An open source embedded real-time operating system
CAN device

CAN bus device framework

RT-Thread exposes CAN controllers as **RT_Device_Class_CAN** devices. The framework in **components/drivers/can/dev_can.c** provides interrupt-driven RX/TX FIFOs, optional hardware filters, CAN-FD message layout, blocking and non-blocking send paths, and periodic bus status polling. Board and SoC drivers implement **struct rt_can_ops** and register with **rt_hw_can_register()**.

**can_dm.c** is not the bus core — it only supplies ISO 11898 DLC/length helpers (can_dlc2len, can_len2dlc). See CAN FD DLC helpers (can_dm).

Header: components/drivers/include/drivers/dev_can.h. Core: components/drivers/can/dev_can.c.

Architecture

Application / FinSH
│ rt_device_find / open / read / write / control
struct rt_can_device (dev_can.c)
│ software RX FIFO, TX mailboxes, nb_tx ringbuffer
│ rt_hw_can_isr() ← BSP ISR
struct rt_can_ops
│ configure / control / sendmsg / recvmsg / sendmsg_nonblocking
SoC CAN / CAN-FD controller (BSP or bsp/*/dm/can/)
Component File Role
Framework dev_can.c Device ops, FIFOs, ISR demux, rt_hw_can_register
FD DLC helpers can_dm.c, can_dm.h DLC ↔ byte length (FD payloads)
DM SoC example bsp/rockchip/dm/can/canfd-rockchip.c OFW platform driver + rt_hw_can_register
Legacy BSP e.g. bsp/stm32/.../drv_can.c, drv_fdcan.c HAL-based rt_can_ops

With **RT_USING_DM**, SoC-specific CAN drivers may live under **SOC_DM_CAN_DIR** (see can/Kconfig).


Kconfig

Option Role
**RT_USING_CAN** Build components/drivers/can/
**RT_CAN_USING_HDR** Per-filter RX lists and filter callbacks (rt_can_hdr)
**RT_CAN_USING_CANFD** 64-byte payload, fd_frame / brs, bit-timing fields in can_configure
**RT_CANMSG_BOX_SZ** Software RX buffer depth (messages)
**RT_CANSND_BOX_NUM** Concurrent blocking TX slots (match HW mailboxes)
**RT_CANSND_MSG_TIMEOUT** Blocking send completion timeout (ticks)
**RT_CAN_NB_TX_FIFO_SIZE** Non-blocking TX ring buffer (bytes); size ≥ N × sizeof(struct rt_can_msg)
**RT_CAN_MALLOC_NB_TX_BUFFER** Allocate NB ring at open instead of static pool in rt_can_device

Optional (not in core Kconfig): **RT_CAN_USING_BUS_HOOK** — periodic bus_hook from status timer.


Access CAN devices (applications)

Find and open

#define CAN_DEV_NAME "can1"
rt_device_t candev = rt_device_find(CAN_DEV_NAME);
rt_device_open(candev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX);
#define RT_DEVICE_FLAG_INT_RX
Definition: rtdef.h:1296
#define RT_DEVICE_FLAG_INT_TX
Definition: rtdef.h:1298
Definition: rtdef.h:1369
Open flag Effect
**RT_DEVICE_FLAG_INT_RX** Allocates software RX FIFO; driver enables RX IRQ via control(SET_INT, INT_RX)
**RT_DEVICE_FLAG_INT_TX** Allocates TX mailbox pool + semaphore; enables TX IRQ
**RT_DEVICE_FLAG_RDWR** Set at register time (rt_hw_can_register)

**read** only works with **INT_RX** and ref_count > 0. **write** requires the device to be opened; size must be a multiple of **sizeof(struct rt_can_msg)**.

Register **rt_device_set_rx_indicate()** to wake a thread when frames arrive (see example in dev_can.h Doxygen block).

Receive

struct rt_can_msg rx_msg;
rx_msg.hdr_index = -1; /* -1: any frame; >=0: specific filter bank (RT_CAN_USING_HDR) */
rt_ssize_t n = rt_device_read(candev, 0, &rx_msg, sizeof(rx_msg));
The CAN message structure.
Definition: dev_can.h:525
rt_int32_t hdr_index
Definition: dev_can.h:532

Batch read: pass an array of rt_can_msg and divide returned bytes by sizeof(struct rt_can_msg).

Send — blocking vs non-blocking

Mode How Behavior
Blocking Default (nonblocking == 0), thread context, INT_TX open Waits for HW mailbox + RT_CAN_EVENT_TX_DONE / FAIL
Non-blocking msg.nonblocking = 1, or call from ISR Uses sendmsg_nonblocking; overflow → nb_tx_rb ring
Private mailbox RT_CAN_CMD_SET_PRIV + msg.priv = mailbox index _can_int_tx_priv — fixed TX slot per message
struct rt_can_msg tx = {0};
tx.id = 0x123;
tx.ide = RT_CAN_STDID;
tx.rtr = RT_CAN_DTR;
tx.len = 8;
tx.data[0] = 0x11;
rt_device_write(candev, 0, &tx, sizeof(tx)); /* blocking */
tx.nonblocking = 1;
rt_device_write(candev, 0, &tx, sizeof(tx)); /* accept or enqueue */
rt_uint32_t id
Definition: dev_can.h:526
rt_uint32_t ide
Definition: dev_can.h:527
rt_uint32_t rtr
Definition: dev_can.h:528
rt_uint32_t len
Definition: dev_can.h:530
rt_uint8_t data[8]
Definition: dev_can.h:546
rt_uint32_t nonblocking
Definition: dev_can.h:542

**RT_CANSND_MSG_TIMEOUT** applies to blocking waits. Non-blocking path needs **ops->sendmsg_nonblocking** implemented in the low-level driver.

Control commands (<tt>rt_device_control</tt>)

Command Value Argument Role
**RT_DEVICE_CTRL_CONFIG** struct can_configure * Re-configure (baud, mode, box sizes)
**RT_CAN_CMD_SET_BAUD** 0x14 baud (driver-defined) Set arbitration bitrate
**RT_CAN_CMD_SET_BAUD_FD** 0x1B baud CAN-FD data phase bitrate
**RT_CAN_CMD_SET_BITTIMING** 0x1C struct rt_can_bit_timing_config * Custom prescaler/segments
**RT_CAN_CMD_SET_MODE** 0x15 mode RT_CAN_MODE_NORMAL / LISTEN / LOOPBACK / LOOPBACKANLISTEN
**RT_CAN_CMD_SET_PRIV** 0x16 RT_CAN_MODE_PRIV / NOPRIV Private TX mailbox mode
**RT_CAN_CMD_SET_FILTER** 0x13 struct rt_can_filter_config * HW filters (needs **RT_CAN_USING_HDR**)
**RT_CAN_CMD_SET_CANFD** 0x1A enable flag Enable FD in driver
**RT_CAN_CMD_START** 0x1D non-zero / zero Start or stop controller
**RT_CAN_CMD_GET_STATUS** 0x17 struct rt_can_status * Error counters, pkg stats
**RT_CAN_CMD_SET_STATUS_IND** 0x18 rt_can_status_ind_type_t User callback on timer tick
**RT_CAN_CMD_SET_BUS_HOOK** 0x19 rt_can_bus_hook Periodic hook (if enabled)
**RT_DEVICE_CTRL_SET_INT / CLR_INT** RT_DEVICE_FLAG_INT_RX/TX, RT_DEVICE_CAN_INT_ERR IRQ mask (used internally on open/close)

Typical bring-up sequence:

  1. rt_device_open(..., INT_TX | INT_RX)
  2. RT_CAN_CMD_SET_BAUD / SET_BITTIMING / SET_CANFD as needed
  3. RT_CAN_CMD_SET_FILTER (optional)
  4. RT_CAN_CMD_SET_MODE
  5. RT_CAN_CMD_START with non-zero argument

On **close**, the framework calls **RT_CAN_CMD_START** with **RT_FALSE** to stop the controller.

Baud presets (<tt>enum CANBAUD</tt>)

CAN1MBaud, CAN800kBaud, CAN500kBaud, CAN250kBaud, CAN125kBaud, CAN100kBaud, CAN50kBaud, CAN20kBaud, CAN10kBaud — used in **can_configure.baud_rate**.

Default configuration macro

struct can_configure cfg = CANDEFAULTCONFIG;
cfg.baud_rate = CAN500kBaud;
cfg.msgboxsz = RT_CANMSG_BOX_SZ;
cfg.sndboxnumber = RT_CANSND_BOX_NUM;
CAN device configuration structure.
Definition: dev_can.h:396
rt_uint32_t msgboxsz
Definition: dev_can.h:398
rt_uint32_t baud_rate
Definition: dev_can.h:397
rt_uint32_t sndboxnumber
Definition: dev_can.h:399

Message layout (<tt>struct rt_can_msg</tt>)

Field Meaning
**id** (29 bits) CAN ID
**ide** RT_CAN_STDID (0) or RT_CAN_EXTID (1)
**rtr** RT_CAN_DTR (data) or RT_CAN_RTR (remote)
**len** DLC or payload length (driver may expect bytes or DLC; FD drivers often use can_len2dlc)
**priv** TX mailbox index in private mode
**hdr_index** RX: matched filter bank; set -1 before read for any frame
**nonblocking** 1 → non-blocking send path
**fd_frame / brs** CAN-FD (RT_CAN_USING_CANFD)
**data[]** 8 bytes (classic) or 64 bytes (FD)

Hardware filters (<tt>RT_CAN_USING_HDR</tt>)

struct rt_can_filter_item items[] = {
RT_CAN_FILTER_STD_INIT(0x100, RT_NULL, RT_NULL),
{0x555, 0, 0, 0, 0x7FF, 7}, /* fixed bank #7 */
};
struct rt_can_filter_config cfg = {
.count = sizeof(items)/sizeof(items[0]),
.actived = 1,
.items = items,
};
rt_device_control(candev, RT_CAN_CMD_SET_FILTER, &cfg);
CAN hardware filter configuration structure. This structure is passed to the driver via rt_device_con...
Definition: dev_can.h:362
struct rt_can_filter_item * items
Definition: dev_can.h:365
CAN filter item structure.
Definition: dev_can.h:280
Filter field Role
**mode** RT_CAN_MODE_MASK (0) or RT_CAN_MODE_LIST (1)
**mask** Mask mode: 1 = bit must match
**hdr_bank** -1 = driver assigns; ≥0 = fixed bank
**rxfifo** CAN_RX_FIFO0 or CAN_RX_FIFO1
**ind / args** Optional per-filter RX callback

Framework mirrors filters into **can->hdr[]** and routes RX frames to per-bank lists when **hdr_index** matches.

Helper macros: **RT_CAN_FILTER_ITEM_INIT**, **RT_CAN_FILTER_EXT_INIT**, **RT_CAN_STD_RMT_FILTER_INIT**, etc. (dev_can.h).


CAN-FD

Enable **RT_CAN_USING_CANFD** in Kconfig and in driver:

  • **can_configure.enable_canfd**, **baud_rate_fd**
  • **use_bit_timing** + **can_timing** / **canfd_timing** (struct rt_can_bit_timing)
  • **RT_CAN_CMD_SET_CANFD**, **RT_CAN_CMD_SET_BAUD_FD**, **RT_CAN_CMD_SET_BITTIMING**

Set **rt_can_msg.fd_frame** and **brs** on TX; on RX use **can_dlc2len()** if HW returns DLC nibble (CAN FD DLC helpers (can_dm)).


Status and errors

**struct rt_can_status** — REC/TEC, **errcode** (RT_CAN_BUS_*), packet/drop counters, error type histogram.

Periodic timer (**can_configure.ticks**, default from driver) calls **RT_CAN_CMD_GET_STATUS** and optional **status_indicate** callback.

**enum RT_CAN_STATUS_MODE**: NORMAL, ERRWARNING, ERRPASSIVE, BUSOFF (derived from **errcode** in FinSH **canstat**).

FinSH (when **RT_USING_FINSH**): **canstat can1** — exported from dev_can.c.


Driver author guide

<tt>struct rt_can_ops</tt>

Op Required Role
**configure** Yes Apply struct can_configure (baud, mode, FD)
**control** Yes IOCTL commands above, SET_INT/CLR_INT
**sendmsg** For blocking TX boxno = mailbox index
**recvmsg** For IRQ RX fifo = HW RX FIFO index; fill rt_can_msg
**sendmsg_nonblocking** For NB TX / ISR Return **RT_EOK** or **-RT_EBUSY**

Register

struct rt_can_device my_can;
struct can_configure cfg = CANDEFAULTCONFIG;
/* fill cfg, set my_can.config */
my_can.config = cfg;
rt_err_t rt_hw_can_register(struct rt_can_device *can,
const char *name,
const struct rt_can_ops *ops,
void *user_data);
rt_err_t rt_hw_can_register(struct rt_can_device *can, const char *name, const struct rt_can_ops *ops, void *data)
This function registers a CAN device with the device framework.
The core CAN device structure.
Definition: dev_can.h:559
The CAN device driver operations structure.
Definition: dev_can.h:661

Device name is typically **can0**, **can1**, … Auto-naming: **rt_dm_dev_set_name_auto(&can->parent, "can")** in DM drivers (Rockchip).

ISR contract — <tt>rt_hw_can_isr()</tt>

BSP ISR calls **rt_hw_can_isr(can, event)**:

event & 0xff Meaning
**RT_CAN_EVENT_RX_IND** (0x01) Frame received; **event >> 8** = RX FIFO index
**RT_CAN_EVENT_RXOF_IND** (0x06) RX overflow (still tries one recv)
**RT_CAN_EVENT_TX_DONE** (0x02) Mailbox **event >> 8** completed OK
**RT_CAN_EVENT_TX_FAIL** (0x03) Mailbox transmission failed

After **TX_DONE**, framework drains **nb_tx_rb** via **sendmsg_nonblocking**.

**recvmsg** must return byte count or -1 if no frame. Populate **hdr_index** when hardware reports filter match.

DM platform driver pattern (Rockchip)

  1. **RT_PLATFORM_DRIVER_EXPORT** with **rt_ofw_node_id** (compatible)
  2. **probe**: map reg/IRQ, **rt_clk_get**, **rt_reset_control_get**, deassert reset
  3. Fill **can_configure** (bit timing from DT or constants)
  4. **rt_hw_interrupt_install**, **rt_hw_can_register**
  5. **remove**: mask IRQ, **rt_device_unregister**

Reference: bsp/rockchip/dm/can/canfd-rockchip.c — uses **can_dm.h** for FD DLC encoding.

BSP drivers using this framework (examples)

BSP / path Notes
STM32 drv_can.c / drv_fdcan.c Classic + FDCAN
NXP i.MX / MCX drv_can.c FlexCAN
WCH, Renesas, Phytium, Synwit, Nuvoton Various rt_hw_can_register
Rockchip canfd-rockchip.c DM + OFW

Engineer checklist

  1. Probe: reset → clock → pinmux → transceiver enable/regulator → init HW → **rt_hw_can_register**
  2. ISR: keep minimal — **recvmsg** + **rt_hw_can_isr**; no blocking in ISR
  3. Filters / baud: configure before **RT_CAN_CMD_START**
  4. Mailbox count: set **sndboxnumber** to match hardware TX buffers
  5. FD: increase **RT_CAN_NB_TX_FIFO_SIZE** — sizeof(struct rt_can_msg) is much larger with 64-byte payload
  6. Termination & transceiver STB: board-level; not handled by framework

Pitfalls

  • Open without INT_RX/INT_TXread/write return **-RT_ENOSYS**
  • Non-blocking without sendmsg_nonblockingwrite with nonblocking=1 fails
  • RX FIFO full — oldest frame dropped (dropedrcvpkg); increase **msgboxsz** or read faster
  • Bus-off — check **RT_CAN_CMD_GET_STATUS**; recovery is driver/BSP specific
  • DLC vs length on FD — use **can_len2dlc/can_dlc2len** consistently (CAN FD DLC helpers (can_dm))
  • Multiple openref_count > 1 skips FIFO teardown on close; design single-owner stacks

See also