4. CAN协议基础:CAN 2.0帧格式、ID过滤、数据长度编码
好,咱们进入CAN协议的基础部分。做嵌入式固件升级,尤其是车载或者工业现场,CAN总线几乎是绕不开的。我记得刚入行那会儿,第一次调CAN通信,看着示波器上的差分信号一头雾水。后来踩了几个坑,才慢慢摸清楚它的脾气。
说白了,CAN协议就是一套“谁先抢到总线谁说话”的规则。它没有主从之分,任何节点都能发。你想想看,这在升级场景里多重要——你可以随时把升级指令广播出去,所有节点都能收到。
4.1 CAN 2.0帧格式:标准帧 vs 扩展帧
CAN 2.0规范定义了两种帧格式:标准帧(CAN 2.0A)和扩展帧(CAN 2.0B)。它们最核心的区别,就是仲裁场的长度。
| 特性 | 标准帧 (CAN 2.0A) | 扩展帧 (CAN 2.0B) |
|---|---|---|
| ID长度 | 11位 | 29位 |
| IDE位 | 显性(0) | 隐性(1) |
| 数据场最大长度 | 8字节 | 8字节 |
| 应用场景 | 简单网络,节点少 | 复杂网络,需大量ID |
我个人习惯在升级协议里优先用标准帧。为什么?因为11位ID足够给每个节点分配一个唯一的升级指令ID了。而且标准帧的仲裁场短,总线利用率更高。除非你的网络里有上百个节点,否则没必要上扩展帧。
来看一个标准帧的完整结构:
SOF | 11位ID | RTR | IDE | r0 | DLC | 0~8字节数据 | CRC | ACK | EOF
这里有个关键点:RTR位。远程帧的RTR是隐性(1),数据帧是显性(0)。我在做升级协议时,从来不用远程帧——它没有数据场,没法传固件包。你想想看,升级嘛,核心就是传数据,所以咱们只关心数据帧。
核心结论:做CAN升级,标准帧+数据帧是黄金组合。ID用11位,数据场8字节,简单可靠。
4.2 ID过滤:硬件帮你筛掉99%的垃圾
CAN总线是广播式的,所有节点都能收到所有报文。但你的MCU不可能处理每一条消息——那CPU就废了。这时候就需要ID过滤。
我遇到过一个问题:一个节点每秒收到上千条无关报文,中断频繁触发,导致升级超时。后来加了硬件过滤,CPU负载直接降了80%。
ID过滤的原理很简单:
- 每个CAN控制器有一组过滤寄存器
- 你设置一个期望的ID和一个掩码
- 只有匹配的报文才会进入接收缓冲区
掩码的规则:
- 掩码位为0:对应ID位必须匹配
- 掩码位为1:对应ID位忽略(通配)
举个例子。假设你只关心ID为0x100和0x101的报文:
期望ID: 0x100 (二进制: 001 0000 0000)
掩码: 0x7F8 (二进制: 111 1111 1000)
解释:掩码的低3位是0,所以ID的低3位必须匹配(000)。
高8位是1,忽略。
这样0x100和0x101都能通过,因为它们的低3位都是000。
实战技巧:升级时,我建议把升级相关的ID集中在一个连续的范围内。比如0x100~0x10F,这样用一个掩码就能覆盖所有升级报文。别把升级ID散得到处都是,否则你得配好几个过滤组。
4.3 数据长度编码:DLC的玄机
DLC(Data Length Code)是4位字段,表示数据场的字节数。取值范围0~8。但注意,DLC编码不是简单的二进制数——它遵循CAN规范的特殊映射。
| DLC值(二进制) | 数据长度(字节) |
|---|---|
| 0000 | 0 |
| 0001 | 1 |
| ... | ... |
| 1000 | 8 |
| 1001~1111 | 8(CAN 2.0规范) |
嗯,这里要注意:DLC超过8时,数据长度仍然是8字节。但有些老旧的控制器可能会报错。我建议你在代码里做一次校验:
if (dlc > 8) {
dlc = 8; // 安全处理
}
在升级协议里,我通常固定使用8字节数据场。为什么?因为固件包是按块传输的,8字节正好是一个块的大小。如果最后一块不足8字节,用0xFF填充。这样接收端处理起来更统一。
曾经踩过的坑:有一次我偷懒,DLC设成了0,想发一个空数据帧作为心跳。结果接收端的DMA直接没触发,因为数据场长度为0。后来我改成DLC=1,数据场放一个字节的心跳计数,问题解决。记住:DLC=0的帧,很多控制器不会触发接收中断。
4.4 知识结构图:CAN协议核心要素
下面这张图把CAN 2.0协议的核心要素串起来了。你可以看到帧格式、ID过滤和DLC编码之间的关系。
4.5 实战要点总结
好了,CAN协议基础就讲这些。最后给你几个我实战中总结的要点:
- 帧格式选择:升级场景用标准帧就够了。别为了“扩展性”上29位ID,徒增复杂度。
- ID过滤配置:把升级ID放在一个连续段,用掩码一次搞定。我习惯用0x100~0x10F,掩码0x7F0。
- DLC固定为8:统一处理,避免边界情况。最后一块用0xFF填充,接收端根据总长度截断。
- 避坑:别用DLC=0的帧做控制信号,很多硬件不触发中断。用DLC=1放一个字节的控制码更靠谱。
个人习惯:我在每个CAN节点的初始化代码里,都会先打印出当前配置的过滤ID和掩码。调试时一眼就能看出过滤有没有配错。这个习惯帮我省了不少排查时间。