1. CAN 是什么?
CAN(Controller Area Network)是一种面向工业与汽车场景的现场总线,使用CAN_H、CAN_L两根差分线通信。
它不区分固定的主机和从机:总线空闲时,每个节点都可以发送消息,因此属于多主通信。
2. CAN 的核心特点
| 特点 | 说明 |
|---|---|
| 差分传输 | 抗干扰能力强,适合工业现场。 |
| 多节点 | 一条总线可连接多个 CAN 节点。 |
| 自动仲裁 | 多节点同时发送时,ID 数值越小的消息优先级越高。 |
| 错误处理 | 支持 CRC、位错误检测和自动重发。 |
| 速率与距离 | 速率越高,可通信距离通常越短。 |
同一 CAN 总线上的所有节点必须配置为相同的通信速率,否则会持续报错并影响总线通信。
3. CAN 帧基础
CAN 协议包含数据帧、遥控帧、错误帧、过载帧和帧间隔。
日常开发最常用的是数据帧。经典 CAN 的数据帧可使用:
标准帧:11 位 ID。
扩展帧:29 位 ID。
数据区:最多 8 字节。
CAN 的 ID 不仅用于标识消息,也决定仲裁优先级。它不是传统串口通信中的“设备地址”。
4. RT-Thread CAN 设备模型
应用程序 ↓ I/O 设备管理接口 ↓ CAN 驱动框架 ↓ 芯片 CAN 驱动 ↓ CAN 控制器与收发器CAN 设备一般命名为can1、can2等。驱动层负责适配配置、控制、发送和接收操作,应用层统一使用rt_device_*()接口访问。
5. 常用 API
| 接口/命令 | 作用 |
|---|---|
rt_device_find() | 查找 CAN 设备。 |
rt_device_open() | 以中断收发模式打开设备。 |
rt_device_write() | 发送一帧 CAN 消息。 |
rt_device_read() | 读取一帧 CAN 消息。 |
rt_device_set_rx_indicate() | 注册接收通知回调。 |
RT_CAN_CMD_SET_BAUD | 设置通信速率。 |
RT_CAN_CMD_SET_MODE | 设置工作模式。 |
RT_CAN_CMD_SET_FILTER | 设置硬件过滤表。 |
6. 初始化并配置 CAN
#define CAN_DEV_NAME "can1" rt_device_t can_dev; rt_err_t result; can_dev = rt_device_find(CAN_DEV_NAME); if (can_dev != RT_NULL) { result = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_INT_TX); if (result == RT_EOK) { rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD, (void *)CAN500kBaud); rt_device_control(can_dev, RT_CAN_CMD_SET_MODE, (void *)RT_CAN_MODE_NORMAL); } }代码解析:rt_device_find()按名称取得 CAN 设备句柄。打开时同时指定RT_DEVICE_FLAG_INT_RX和RT_DEVICE_FLAG_INT_TX,让收发使用中断模式。RT_CAN_CMD_SET_BAUD把速率设置为 500kbit/s,必须与总线其他节点一致;RT_CAN_MODE_NORMAL表示正常通信模式。示例应继续检查每个rt_device_control()的返回值,以便及时发现 BSP 不支持或参数错误。
7. 发送一帧 CAN 数据
struct rt_can_msg txmsg = {0}; txmsg.id = 0x78; txmsg.ide = RT_CAN_STDID; txmsg.rtr = RT_CAN_DTR; txmsg.len = 2; txmsg.data[0] = 0x11; txmsg.data[1] = 0x22; if (rt_device_write(can_dev, 0, &txmsg, sizeof(txmsg)) == sizeof(txmsg)) { /* 发送请求已成功提交 */ }代码解析:rt_can_msg用于描述一帧报文。id = 0x78是报文 ID;RT_CAN_STDID表示 11 位标准帧;RT_CAN_DTR表示数据帧;len = 2说明只有前两个data字节有效。rt_device_write()的pos对 CAN 无意义,传0即可。返回值等于sizeof(txmsg)才表示该帧已成功交给驱动发送。
8. 中断接收:回调通知,线程读取
static struct rt_semaphore rx_sem; static rt_err_t can_rx_ind(rt_device_t dev, rt_size_t size) { rt_sem_release(&rx_sem); return RT_EOK; } /* 初始化时注册回调 */ rt_sem_init(&rx_sem, "can_rx", 0, RT_IPC_FLAG_FIFO); rt_device_set_rx_indicate(can_dev, can_rx_ind); /* 在接收线程中读取 */ struct rt_can_msg rxmsg = {0}; rxmsg.hdr = -1; rt_sem_take(&rx_sem, RT_WAITING_FOREVER); rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg));代码解析:CAN 中断到来时,驱动调用can_rx_ind();回调只释放信号量,避免在中断中进行复杂处理。接收线程被唤醒后再调用rt_device_read()读取完整报文。hdr = -1表示从默认接收链表取消息;如果设置了指定硬件过滤表号,读取时应填写对应的hdr。实际线程中应循环等待、读取并检查返回值。
9. 硬件过滤表
过滤表让 CAN 控制器只接收关心的报文,减少 CPU 处理无关消息。
过滤条件通常由以下内容组成:
id:要匹配的报文 ID。ide:标准帧或扩展帧。rtr:数据帧或遥控帧。mask:ID 中哪些位必须匹配。
过滤配置不正确时,可能表现为“总线上有数据但应用收不到”。调试初期可先使用较宽松的过滤规则,确认通信后再收紧条件。
总结
CAN 的优势是高可靠、多节点和基于 ID 的优先级仲裁。RT-Thread 中的基本流程是:
查找设备 → 打开并配置 → 设置过滤/接收回调 → 收发 CAN 报文 → 关闭设备初学阶段先掌握标准数据帧的收发与中断接收,再逐步学习过滤规则和总线错误处理。