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编码之间的关系。

CAN 2.0协议核心要素结构图 CAN 2.0协议核心要素 CAN 2.0 协议 帧格式 ID过滤 DLC编码 标准帧(11位ID) 扩展帧(29位ID) 期望ID 掩码 0~8字节 升级协议设计建议 • 帧格式:优先使用标准帧(11位ID),简化过滤配置 • ID过滤:将升级ID集中在一个连续范围,用单一掩码覆盖 • DLC编码:固定使用8字节数据场,不足部分用0xFF填充

4.5 实战要点总结

好了,CAN协议基础就讲这些。最后给你几个我实战中总结的要点:

  • 帧格式选择:升级场景用标准帧就够了。别为了“扩展性”上29位ID,徒增复杂度。
  • ID过滤配置:把升级ID放在一个连续段,用掩码一次搞定。我习惯用0x100~0x10F,掩码0x7F0。
  • DLC固定为8:统一处理,避免边界情况。最后一块用0xFF填充,接收端根据总长度截断。
  • 避坑:别用DLC=0的帧做控制信号,很多硬件不触发中断。用DLC=1放一个字节的控制码更靠谱。

个人习惯:我在每个CAN节点的初始化代码里,都会先打印出当前配置的过滤ID和掩码。调试时一眼就能看出过滤有没有配错。这个习惯帮我省了不少排查时间。