5.16 CAN设备
2026/7/20大约 10 分钟设备驱动驱动CAN总线车联网
5.16 CAN设备
📚 本节导读
学习时长: 约 40 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: C 语言基础、CAN 总线协议基础、5.1 设备驱动框架
🎯 学习目标
- 理解 OneOS CAN 设备驱动框架的分层设计
- 掌握 CAN 消息的发送与接收(阻塞/非阻塞)
- 掌握 CAN 过滤器(Filter)的配置与使用
- 理解 CAN 波特率、工作模式(Normal/Loopback/Listen)的配置
- 了解 CAN 状态监控与总线错误处理
- 掌握 CAN 中断 ISR 处理(
os_hw_can_isr_rxdone/os_hw_can_isr_txdone)
一、概述
CAN(Controller Area Network)是一种广泛应用于汽车电子、工业控制等领域的现场总线协议。OneOS 将 CAN 控制器抽象为设备,提供统一的收发、配置、控制接口。CAN 设备驱动位于 drivers/can/ 目录下,核心头文件为 can.h,实现文件为 can.c。
CAN 设备框架的特点:
- 统一抽象:将不同 MCU 平台的 CAN 控制器统一封装为
os_can_device_t - 消息邮箱机制:内置 RX/TX FIFO 消息队列,支持配置邮箱大小
- 过滤器支持:支持标准帧/扩展帧/远程帧的硬件过滤器配置,可选
OS_CAN_USING_HDR回调模式 - 状态监控:支持总线错误计数、收发统计、状态变化通知
- 多模式支持:Normal(正常)、Loopback(回环)、Listen(监听)、Loopback+Listen 四种模式
二、核心数据结构
2.1 CAN 消息结构体 struct os_can_msg
struct os_can_msg
{
uint32_t id : 29; /* CAN ID,标准帧 11bit,扩展帧 29bit */
uint32_t ide : 1; /* 扩展帧标识位:0=标准帧,1=扩展帧 */
uint32_t rtr : 1; /* 远程帧标识位:0=数据帧,1=远程帧 */
uint32_t rsv : 1; /* 保留位 */
uint32_t len : 8; /* 数据长度(0-8 字节) */
uint32_t priv : 8; /* 消息发送优先级 */
int32_t hdr : 8; /* 硬件过滤器表编号(HDR 模式) */
uint32_t reserved : 8; /* 保留位 */
uint8_t data[8]; /* 数据段(最多 8 字节) */
};| 字段 | 说明 |
|---|---|
id | CAN 消息 ID,标准帧使用 11bit(0x000-0x7FF),扩展帧使用 29bit |
ide | 扩展帧标识:OS_CAN_STDID(0) 标准帧,OS_CAN_EXTID(1) 扩展帧 |
rtr | 远程帧标识:OS_CAN_DTR(0) 数据帧,OS_CAN_RTR(1) 远程帧 |
len | 数据长度,CAN 2.0 支持 0-8 字节 |
data[8] | 8 字节数据缓冲区 |
2.2 CAN 设备结构体 struct os_can_device
struct os_can_device
{
struct os_device parent; /* 继承自 os_device_t */
const struct os_can_ops *ops; /* CAN 操作函数表 */
struct can_configure config; /* CAN 配置参数 */
struct os_can_status status; /* CAN 状态统计信息 */
struct os_can_status_ind_type status_indicate; /* 状态变化回调 */
#ifdef OS_CAN_USING_HDR
struct os_can_hdr *hdr; /* HDR 过滤器控制块数组 */
#endif
#ifdef OS_CAN_USING_BUS_HOOK
os_can_bus_hook bus_hook; /* 总线错误回调钩子 */
#endif
os_mutex_id lock; /* 互斥锁,保护设备并发访问 */
struct os_can_rx_fifo *can_rx; /* 接收 FIFO 队列 */
struct os_can_tx_fifo *can_tx; /* 发送 FIFO 队列 */
};2.3 CAN 配置结构体 struct can_configure
struct can_configure
{
uint32_t baud_rate; /* 波特率 */
uint32_t rx_msgbox_size; /* 接收邮箱大小(默认 16) */
uint32_t tx_msgbox_size; /* 发送邮箱大小(默认 1) */
uint32_t mode : 8; /* 工作模式 */
uint32_t privmode : 8; /* 优先级模式 */
uint32_t reserved : 16;
uint32_t ticks; /* 超时 tick 数 */
#ifdef OS_CAN_USING_HDR
uint32_t maxhdr; /* 最大 HDR 过滤器数量 */
#endif
};波特率枚举:
| 枚举值 | 实际波特率 | 说明 |
|---|---|---|
CAN1MBaud | 1000 Kbps | 1 Mbps |
CAN500kBaud | 500 Kbps | 500 Kbps |
CAN250kBaud | 250 Kbps | 250 Kbps |
CAN125kBaud | 125 Kbps | 125 Kbps |
CAN100kBaud | 100 Kbps | 100 Kbps |
CAN50kBaud | 50 Kbps | 50 Kbps |
CAN20kBaud | 20 Kbps | 20 Kbps |
CAN10kBaud | 10 Kbps | 10 Kbps |
工作模式:
| 宏定义 | 值 | 说明 |
|---|---|---|
OS_CAN_MODE_NORMAL | 0 | 正常收发模式 |
OS_CAN_MODE_LISEN | 1 | 只听模式(不发送 ACK,用于总线监听) |
OS_CAN_MODE_LOOPBACK | 2 | 回环模式(自发自收,用于测试) |
OS_CAN_MODE_LOOPBACKANLISEN | 3 | 回环+监听模式 |
2.4 CAN 过滤器结构体 struct os_can_filter_item
struct os_can_filter_item
{
uint32_t id : 29; /* 过滤 ID */
uint32_t ide : 1; /* 扩展帧标识位 */
uint32_t rtr : 1; /* 远程帧标识位 */
uint32_t mode : 1; /* 过滤模式:0=ID 列表模式,1=ID 掩码模式 */
uint32_t mask; /* ID 掩码,1 表示对应位必须匹配 */
int32_t hdr; /* 对应的过滤器控制块编号 */
#ifdef OS_CAN_USING_HDR
os_err_t (*ind)(os_device_t dev, void *args, int32_t hdr, os_size_t size);
void *args; /* 回调参数 */
#endif
};过滤器初始化宏:
| 宏 | 说明 |
|---|---|
OS_CAN_FILTER_STD_INIT(id) | 标准帧过滤器,ID 完全匹配 |
OS_CAN_FILTER_EXT_INIT(id) | 扩展帧过滤器,ID 完全匹配 |
OS_CAN_STD_RMT_FILTER_INIT(id) | 标准远程帧过滤器 |
OS_CAN_EXT_RMT_FILTER_INIT(id) | 扩展远程帧过滤器 |
2.5 CAN 操作函数表 struct os_can_ops
struct os_can_ops
{
os_err_t (*configure)(struct os_can_device *can, struct can_configure *cfg);
os_err_t (*control)(struct os_can_device *can, int cmd, void *arg);
int (*start_send)(struct os_can_device *can, const struct os_can_msg *msg);
int (*stop_send)(struct os_can_device *can);
int (*start_recv)(struct os_can_device *can, struct os_can_msg *msg);
int (*stop_recv)(struct os_can_device *can);
int (*recv_state)(struct os_can_device *can);
};| 函数指针 | 说明 |
|---|---|
configure | 配置 CAN 控制器(波特率、模式等) |
control | 控制命令(设置过滤器、获取状态等) |
start_send | 启动硬件发送一帧 CAN 消息 |
stop_send | 停止硬件发送 |
start_recv | 启动硬件接收(提供缓冲区) |
stop_recv | 停止硬件接收 |
recv_state | 获取接收状态 |
2.6 CAN 状态结构体 struct os_can_status
struct os_can_status
{
uint32_t rcverrcnt; /* 接收错误计数 */
uint32_t snderrcnt; /* 发送错误计数 */
uint32_t errcode; /* 错误码 */
uint32_t rcvpkg; /* 接收成功包数 */
uint32_t dropedrcvpkg; /* 接收丢弃包数 */
uint32_t sndpkg; /* 发送成功包数 */
uint32_t dropedsndpkg; /* 发送丢弃包数 */
uint32_t bitpaderrcnt; /* 位填充错误计数 */
uint32_t formaterrcnt; /* 格式错误计数 */
uint32_t ackerrcnt; /* ACK 错误计数 */
uint32_t biterrcnt; /* 位错误计数 */
uint32_t crcerrcnt; /* CRC 错误计数 */
uint32_t rcvchange; /* 接收状态变化标记 */
uint32_t sndchange; /* 发送状态变化标记 */
uint32_t lasterrtype; /* 最后错误类型 */
};总线错误类型:
| 枚举值 | 说明 |
|---|---|
OS_CAN_BUS_NO_ERR | 无错误 |
OS_CAN_BUS_BIT_PAD_ERR | 位填充错误 |
OS_CAN_BUS_FORMAT_ERR | 格式错误 |
OS_CAN_BUS_ACK_ERR | ACK 错误 |
OS_CAN_BUS_IMPLICIT_BIT_ERR | 隐位错误 |
OS_CAN_BUS_EXPLICIT_BIT_ERR | 显位错误 |
OS_CAN_BUS_CRC_ERR | CRC 错误 |
三、设备操作接口
CAN 设备继承自 os_device_t,通过设备框架的标准接口进行访问:
| 接口 | 实现函数 | 说明 |
|---|---|---|
init | os_can_init | 初始化 CAN 设备:分配 RX/TX FIFO,配置硬件 |
deinit | os_can_deinit | 去初始化:停止收发,释放 FIFO 内存 |
read | os_can_read → _can_int_rx | 从 RX FIFO 读取 CAN 消息 |
write | os_can_write → _can_int_tx | 向 TX FIFO 写入 CAN 消息 |
control | os_can_control | 控制命令分发(过滤器/波特率/模式/状态) |
四、关键 API
4.1 设备注册
os_err_t os_hw_can_register(struct os_can_device *can,
const char *name,
const struct os_can_ops *ops,
void *data);- can: CAN 设备控制块指针
- name: 设备名称(如
"can0"、"can1") - ops: CAN 操作函数表,由 HAL 层实现
- data: 用户私有数据
- 返回:
OS_SUCCESS注册成功
注册后设备类型为 OS_DEVICE_TYPE_CAN,可通过 os_device_open("can0") 打开使用。
4.2 中断 ISR 处理函数
/* 接收完成中断:由 HAL 层在 CAN RX 中断中调用 */
void os_hw_can_isr_rxdone(struct os_can_device *can);
/* 发送完成中断:由 HAL 层在 CAN TX 中断中调用 */
void os_hw_can_isr_txdone(struct os_can_device *can, int event);os_hw_can_isr_rxdone: 从空闲链表取一个消息缓冲区,填充数据后加入数据链表,通知等待读取的任务os_hw_can_isr_txdone: 更新发送结果(OS_CAN_SND_RESULT_OK或OS_CAN_SND_RESULT_ERR),归还缓冲区,通知等待发送完成的任务
事件类型:
| 宏定义 | 值 | 说明 |
|---|---|---|
OS_CAN_EVENT_RX_IND | 0x01 | 接收指示 |
OS_CAN_EVENT_TX_DONE | 0x02 | 发送完成 |
OS_CAN_EVENT_TX_FAIL | 0x03 | 发送失败 |
OS_CAN_EVENT_RX_TIMEOUT | 0x05 | 接收超时 |
OS_CAN_EVENT_RXOF_IND | 0x06 | 接收溢出 |
4.3 控制命令
通过 os_device_control() 发送 CAN 专用控制命令:
| 命令 | 说明 |
|---|---|
OS_CAN_CMD_SET_FILTER | 设置硬件过滤器 |
OS_CAN_CMD_SET_BAUD | 设置波特率 |
OS_CAN_CMD_SET_MODE | 设置工作模式 |
OS_CAN_CMD_SET_PRIV | 设置优先级模式 |
OS_CAN_CMD_GET_STATUS | 获取 CAN 总线状态 |
OS_CAN_CMD_SET_STATUS_IND | 设置状态变化回调 |
OS_CAN_CMD_SET_BUS_HOOK | 设置总线错误钩子 |
五、使用示例
5.1 CAN 发送示例
#include <can/can.h>
#include <device.h>
void can_send_example(void)
{
os_device_t *can_dev;
struct os_can_msg msg;
/* 1. 打开 CAN 设备 */
can_dev = os_device_open("can0");
if (can_dev == OS_NULL)
{
os_kprintf("open can0 failed!\r\n");
return;
}
/* 2. 构造 CAN 消息 */
msg.id = 0x123; /* 标准帧 ID = 0x123 */
msg.ide = OS_CAN_STDID; /* 标准帧 */
msg.rtr = OS_CAN_DTR; /* 数据帧 */
msg.len = 8; /* 8 字节数据 */
msg.data[0] = 0x01;
msg.data[1] = 0x02;
msg.data[2] = 0x03;
msg.data[3] = 0x04;
msg.data[4] = 0x05;
msg.data[5] = 0x06;
msg.data[6] = 0x07;
msg.data[7] = 0x08;
/* 3. 发送消息 */
os_device_write(can_dev, 0, &msg, sizeof(msg));
}5.2 CAN 接收示例(阻塞等待)
#include <can/can.h>
#include <device.h>
void can_recv_task(void *arg)
{
os_device_t *can_dev;
struct os_can_msg msg;
os_size_t size;
can_dev = os_device_open("can0");
if (can_dev == OS_NULL)
{
os_kprintf("open can0 failed!\r\n");
return;
}
while (1)
{
/* 阻塞读取 CAN 消息 */
size = os_device_read(can_dev, 0, &msg, sizeof(msg));
if (size == sizeof(msg))
{
os_kprintf("CAN RX: ID=0x%X, len=%d, data=",
msg.id, msg.len);
for (int i = 0; i < msg.len; i++)
{
os_kprintf("%02X ", msg.data[i]);
}
os_kprintf("\r\n");
}
}
}5.3 CAN 过滤器配置示例
#include <can/can.h>
#include <device.h>
void can_filter_example(void)
{
os_device_t *can_dev;
struct os_can_filter_config filter_cfg;
struct os_can_filter_item filter_items[2];
can_dev = os_device_open("can0");
if (can_dev == OS_NULL)
return;
/* 配置过滤器 1:只接收标准帧 ID=0x123 */
filter_items[0].id = 0x123;
filter_items[0].ide = OS_CAN_STDID;
filter_items[0].rtr = OS_CAN_DTR;
filter_items[0].mode = 0; /* ID 列表模式 */
filter_items[0].mask = 0xFFFFFFFF; /* 全部位必须匹配 */
filter_items[0].hdr = -1;
/* 配置过滤器 2:接收标准帧 ID=0x200~0x2FF */
filter_items[1].id = 0x200;
filter_items[1].ide = OS_CAN_STDID;
filter_items[1].rtr = OS_CAN_DTR;
filter_items[1].mode = 1; /* ID 掩码模式 */
filter_items[1].mask = 0x700; /* 高 3 位匹配,低 8 位不关心 */
filter_items[1].hdr = -1;
filter_cfg.count = 2;
filter_cfg.actived = 1; /* 激活过滤器 */
filter_cfg.items = filter_items;
os_device_control(can_dev, OS_CAN_CMD_SET_FILTER, &filter_cfg);
}5.4 CAN 状态监控示例
void can_status_monitor(os_device_t *can_dev)
{
struct os_can_status status;
os_device_control(can_dev, OS_CAN_CMD_GET_STATUS, &status);
os_kprintf("=== CAN Status ===\r\n");
os_kprintf("RX packets: %d (dropped: %d)\r\n",
status.rcvpkg, status.dropedrcvpkg);
os_kprintf("TX packets: %d (dropped: %d)\r\n",
status.sndpkg, status.dropedsndpkg);
os_kprintf("RX errors: %d, TX errors: %d\r\n",
status.rcverrcnt, status.snderrcnt);
os_kprintf("Bus errors: bit_pad=%d format=%d ack=%d bit=%d crc=%d\r\n",
status.bitpaderrcnt, status.formaterrcnt,
status.ackerrcnt, status.biterrcnt, status.crcerrcnt);
}5.5 HAL 层注册示例
#include <can/can.h>
#include <device.h>
/* HAL 层配置:波特率 500K,Normal 模式 */
struct can_configure can_cfg =
{
.baud_rate = CAN500kBaud,
.rx_msgbox_size = 16,
.tx_msgbox_size = 8,
.mode = OS_CAN_MODE_NORMAL,
};
/* HAL 层需实现 os_can_ops 函数表 */
static const struct os_can_ops stm32_can_ops =
{
.configure = stm32_can_configure,
.control = stm32_can_control,
.start_send = stm32_can_start_send,
.stop_send = stm32_can_stop_send,
.start_recv = stm32_can_start_recv,
.stop_recv = stm32_can_stop_recv,
.recv_state = stm32_can_recv_state,
};
/* 注册 CAN 设备 */
void can_device_init(void)
{
static struct os_can_device can_dev;
/* 设置默认配置 */
can_dev.config = can_cfg;
/* 注册到设备框架 */
os_hw_can_register(&can_dev, "can0", &stm32_can_ops, OS_NULL);
}六、内部实现机制
6.1 RX FIFO 机制
CAN 框架使用 RX FIFO 管理接收消息,核心是两个链表:
freelist(空闲缓冲区) datalist(已接收数据)
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ buf0 │→│ buf1 │→... │ buf2 │→│ buf3 │→...
└──────┘ └──────┘ └──────┘ └──────┘
↑ ISR 取走 │ os_device_read() 取走
│ ↓
└────────── 归还 ──────────────┘接收流程:
- HAL 层 CAN RX 中断触发 → 调用
os_hw_can_isr_rxdone() - 从
rx_fifo->freelist取一个空闲缓冲区 - 填充数据后加入
rx_fifo->datalist can->parent.rx_count++,调用os_device_recv_notify()唤醒等待读取的任务- 应用层调用
os_device_read()时从datalist取出数据,归还到freelist
6.2 TX FIFO 机制
freelist(空闲缓冲区) datalist(排队发送)
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ buf0 │→│ buf1 │→... │ buf2 │→│ buf3 │→...
└──────┘ └──────┘ └──────┘ └──────┘
↑ os_device_write() 取走 │ ISR 发送完成
│ ↓
└────────── 归还 ──────────────┘发送流程:
- 应用层调用
os_device_write()→_can_int_tx() - 从
tx_fifo->freelist取一个空闲缓冲区,填入数据 - 加入
tx_fifo->datalist,如果 datalist 之前为空,立即调用ops->start_send()启动硬件发送 - HAL 层 CAN TX 中断触发 → 调用
os_hw_can_isr_txdone() - 更新发送结果(OK/ERR),归还缓冲区,检查 datalist 中是否还有排队消息,有则继续发送
6.3 并发保护
- 设备级互斥锁:
can->lock互斥量,在os_can_init/os_can_deinit中使用 - 中断安全锁:
gs_device_lock自旋锁,在 RX/TX FIFO 操作中使用,保护中断上下文与任务上下文之间的并发访问
七、注意事项
- 中断上下文:
os_hw_can_isr_rxdone和os_hw_can_isr_txdone在 ISR 上下文中调用,内部操作应尽量简短 - 邮箱大小:
rx_msgbox_size决定了接收缓冲区数量,过小可能导致丢包;tx_msgbox_size决定了发送缓冲区数量,影响发送吞吐量 - 过滤器配置: 过滤器配置通过
OS_CAN_CMD_SET_FILTER控制命令下发到 HAL 层,硬件支持情况因 MCU 而异 - HDR 模式: 启用
OS_CAN_USING_HDR后,每个过滤器条目可绑定回调函数,匹配的消息直接通过回调处理,不经过 FIFO - 总线错误处理: 启用
OS_CAN_USING_BUS_HOOK后,可注册bus_hook回调处理总线错误事件(Bus-Off 恢复等)
八、驱动文件位置
| 文件 | 路径 | 说明 |
|---|---|---|
can.h | drivers/can/can.h | CAN 框架头文件,数据结构定义、API 声明 |
can.c | drivers/can/can.c | CAN 框架实现,设备注册、中断处理、FIFO 管理 |
Kconfig | drivers/can/Kconfig | 编译配置选项 |
📖 延伸阅读
- 5.1 设备驱动框架 — 理解设备驱动框架基础
- 5.4 Serial设备 — 类似的字节流设备驱动模型
- CAN 2.0 协议规范 — CAN 总线协议标准