4、CAN帧结构详解(下):数据场、CRC场、ACK场、帧结束

上一讲我们把CAN帧的前半部分拆了个干净。今天接着聊剩下的四个场:数据场、CRC场、ACK场和帧结束。

这几个场,说白了就是“你到底要传什么”、“传得对不对”、“别人收到了没”以及“收工”。看起来简单,但坑不少。我这些年调试CAN总线,至少有一半的疑难杂症都出在CRC和ACK这两个环节上。

4.1 数据场:你要传的真东西

数据场是CAN帧里唯一一个由用户完全控制的区域。标准帧最多8字节,扩展帧也一样——8字节,没得商量。

有人问我:“为什么非得是8字节?我传个9字节不行吗?”

不行。这是CAN协议定死的。当年博世设计CAN时,考虑到实时性和总线负载,8字节是个折中方案。你想想看,如果允许传几百字节,一个节点占着总线不放,别的节点就别玩了。

帧类型 数据场长度 DLC编码(4位)
数据帧 0~8字节 0000~1000
远程帧 无数据场 DLC仅表示期望的数据长度
个人习惯:我一般把DLC和实际数据长度严格对应。比如传4字节数据,DLC就设4。有些工程师偷懒,传2字节数据却把DLC设成8,这在某些CAN控制器上会触发错误。

数据场的字节顺序呢?CAN协议本身没规定是大端还是小端。这完全取决于你的应用层协议。我在做J1939协议栈时,遇到过两个ECU因为字节序不一致,导致车速显示翻倍——一个认为是0x1234,另一个读成0x3412。

嗯,这里要注意:CAN协议只保证帧的完整性,不保证数据的解释方式。字节序、数据类型、物理单位,这些都是应用层的事。

4.2 CRC场:15位CRC的硬核计算

CRC场是CAN帧的“体检医生”。它负责检查帧在传输过程中有没有被干扰。

CAN用的是15位CRC,生成多项式是:

x¹⁵ + x¹⁴ + x¹⁰ + x⁸ + x⁷ + x⁴ + x³ + 1

写成二进制就是:

1 1000 1001 1001 1001

这个多项式可不是随便选的。我查过资料,博世当年做了大量仿真,确保这个多项式在CAN总线常见的噪声模型下,漏检率最低。

CRC覆盖范围:从帧起始(SOF)开始,一直到数据场结束。注意,不包含CRC场本身、ACK场和帧结束。

计算过程是这样的:

  1. 发送节点把待校验的位序列左移15位
  2. 用生成多项式做模2除法(其实就是异或)
  3. 余数就是15位CRC
  4. CRC后面加一个隐性位(CRC delimiter)

接收节点收到帧后,用同样的多项式对同样的位序列做计算。如果算出来的余数是0,说明帧没问题。如果不是0——嗯,那就说明帧被破坏了。

我曾经踩过的坑:有一次调试,发现某个节点总是报CRC错误。查了半天,发现是那个节点的晶振频率偏了0.5%。CAN的位时序对时钟精度要求很高,晶振不准会导致采样点偏移,进而把正确的位采样成错误的。CRC校验自然就过不了。

15位CRC的检错能力有多强?

  • 能检测所有≤5位的突发错误
  • 能检测所有奇数个错误
  • 对更长的突发错误,漏检率只有2⁻¹⁵ ≈ 0.003%

说白了,在汽车电子这种高噪声环境里,15位CRC已经够用了。CAN FD后来升级到17位或21位CRC,那是后话。

4.3 ACK场:你到底收到了没?

ACK场只有2位:ACK Slot(应答槽)和ACK Delimiter(应答分隔符)。

但就是这2位,实现了CAN总线最巧妙的设计之一——多节点同时应答。

工作流程是这样的:

  1. 发送节点在ACK Slot位输出隐性位(1)
  2. 所有接收节点如果正确收到帧,就在ACK Slot位输出显性位(0)
  3. 发送节点读到显性位,就知道至少有一个节点收到了

这里的关键是:多个节点可以同时拉低总线。因为CAN总线是“线与”机制,只要有一个节点输出显性位,总线就是显性。发送节点不需要知道具体是谁应答的,只需要知道“有人收到了”。

ACK的隐藏规则:只有数据帧和远程帧需要ACK。错误帧和过载帧不需要——它们本身就是错误处理机制的一部分。

如果发送节点在ACK Slot读到的是隐性位(没人应答),它会怎么做?

它会认为帧发送失败,然后自动重发。重发次数不限,直到成功或总线关闭。

我在实际项目中遇到过一种情况:某个节点只发不收,导致总线上没人应答它的帧。结果那个节点不停地重发,把总线带宽全占满了。排查时发现,是它的接收中断被关掉了。

避坑指南:我曾经调试过一个系统,发现某个节点偶尔收不到应答。示波器一抓,发现它的ACK Slot位输出的是隐性位——它根本没参与应答。原因是它的CAN控制器配置成了“只听模式”(Listen Only Mode)。这种模式下,节点只收不发,自然也不会应答。

ACK Delimiter固定为隐性位。它的作用是给ACK Slot一个明确的结束边界。如果ACK Delimiter被检测到显性位,就会触发格式错误。

4.4 帧结束:7个隐性位收尾

帧结束(EOF)由7个连续的隐性位组成。

为什么是7个?不是8个,也不是6个?

因为CAN协议需要保证帧结束的位模式不会和错误帧(6个显性位)混淆。7个隐性位,足够让所有节点确认帧已经结束。

帧结束之后,总线会进入间歇场(Intermission),持续3个隐性位。然后总线才能开始下一帧的传输。

这里有个细节:帧结束期间,如果有节点检测到位错误(比如读到显性位),它会发送错误帧。但这种情况很少见,因为帧结束是隐性位,总线自然就是隐性状态。

4.5 完整帧结构一览

把四个场串起来,一个完整的数据帧长这样:

SOF + 仲裁场 + 控制场 + 数据场 + CRC场 + ACK场 + EOF
 1      12/32      6       0~64     16      2      7  (位)

标准帧总位数:44~108位(不含位填充)
扩展帧总位数:64~128位(不含位填充)

加上位填充后,实际长度会多一些。位填充的规则是:连续5个相同位后插入一个相反位。这个我们后面专门讲。

下面这张图把整个帧结构串起来了:

CAN数据帧完整结构(标准帧) SOF 1位 仲裁场 11位ID + RTR 12位 控制场 IDE+r0+DLC 6位 数据场 0~8字节 0~64位 CRC场 15位CRC+分隔符 16位 ACK场 槽+分隔符 2位 EOF 7个隐性位 7位 说明: 标准帧总长度 = 1 + 12 + 6 + (0~64) + 16 + 2 + 7 = 44~108位(不含位填充) 扩展帧仲裁场为32位(29位ID + SRR + IDE + RTR),总长度 = 64~128位 关键点总结 数据场:0~8字节,DLC与实际长度应一致 CRC场:15位CRC + 1位分隔符,覆盖SOF到数据场 ACK场:发送节点输出隐性位,接收节点拉低应答 EOF:7个隐性位,确保帧结束不被误判为错误帧 位填充:连续5个相同位后插入相反位(CRC场、ACK场、EOF除外)

这张图把整个帧结构串起来了。你可以看到,每个场都有明确的职责边界。CAN协议的设计哲学就是:简单、可靠、实时。每个位都有它的用途,没有浪费。