3. CAN总线数据链路层:帧结构与帧类型

数据链路层,说白了就是CAN协议最核心的“语法规则”。物理层只管把0和1发到总线上,但这一层才决定了这些0和1到底是什么意思。我刚开始接触CAN时,总觉得帧结构很枯燥,直到有一次在调试一个电机控制器时,发现总线莫名其妙挂死——后来查出来是ACK场出了问题。从那以后,我对每一帧的每个位都不敢马虎。

3.1 CAN帧结构:从SOF到EOF

一个标准的CAN数据帧,由7个不同的场组成。我习惯把它想象成一封挂号信:有信封、有地址、有内容、还有回执。下面这张图可以帮你快速建立整体印象。

CAN标准数据帧(11位ID)结构图 SOF 1位 仲裁场 11位ID + RTR 12位 控制场 IDE+r0+DLC 6位 数据场 0~8字节 0~64位 CRC场 15位+定界符 16位 ACK场 ACK+定界符 2位 EOF 7位隐性 7位 关键说明: • 总帧长:1 + 12 + 6 + 0~64 + 16 + 2 + 7 = 44~108位 • 仲裁场决定谁能赢得总线:ID越小,优先级越高 • CRC场负责检错,ACK场负责确认接收 • EOF必须是7个隐性位,否则会触发错误帧

3.2 各场详解

3.2.1 SOF(帧起始)

SOF就是一个显性位(逻辑0)。它告诉总线上所有节点:“我要开始发数据了”。总线空闲时是隐性电平,SOF这个显性位一出现,所有节点都会同步到这一时刻。嗯,这里要注意:SOF也是硬同步的触发点,所有节点的时钟都会在SOF的下降沿重新对齐。

3.2.2 仲裁场

仲裁场包含标识符(ID)和RTR位。标准帧是11位ID,扩展帧是29位ID。ID越小,优先级越高。为什么?因为显性位(0)会覆盖隐性位(1)。

仲裁机制的核心逻辑:

  • 多个节点同时发送时,从ID的最高位开始逐位比较
  • 谁先发出显性位(0),谁就赢得总线
  • 输的节点自动转为接收模式,不破坏总线数据

我在一个项目里遇到过这样的坑:两个ECU的ID设成了0x123和0x124,结果0x123总是抢到总线,0x124的报文经常发不出去。后来我们重新规划了ID分配策略,把紧急报文放在小ID上。

3.2.3 控制场

控制场包含IDE位、保留位r0和DLC(数据长度码)。DLC用4位表示数据场有多少字节,范围是0~8。IDE位用来区分是标准帧还是扩展帧——标准帧的IDE是显性,扩展帧是隐性。

DLC值 数据字节数 常见用途
0 0字节 远程帧请求
1~7 1~7字节 短数据包
8 8字节 标准数据包

3.2.4 数据场

数据场就是真正要传输的数据,最多8个字节。为什么是8字节?这是早期设计时的一个权衡——既要保证实时性,又要能传输足够的信息。说实话,在现在的车载网络中,8字节有时候真不够用,所以后来才有了CAN FD(后面章节会讲)。

3.2.5 CRC场

CRC场包含15位CRC校验码和1位隐性定界符。发送节点计算CRC,接收节点重新计算并比对。如果不一致,接收节点会发送错误帧。

我个人的经验:CRC校验是发现总线噪声干扰的第一道防线。在实验室里可能很少触发,但上了实车,尤其是靠近电机控制器的地方,CRC错误会明显增多。我曾经在一条CAN线上加了铁氧体磁环,CRC错误率从千分之三降到了几乎为零。

3.2.6 ACK场

ACK场只有2位:ACK槽和ACK定界符。发送节点在ACK槽发送隐性位,任何正确接收到帧的节点,都会在ACK槽把总线拉成显性。如果发送节点没检测到显性,就知道没人收到——它会重发。

注意:ACK场是“线与”逻辑。只要有一个节点正确接收,ACK槽就是显性。但这也意味着:如果总线上只有一个节点(比如调试时只接了一个ECU),它自己发自己收,ACK永远成功——这可能会掩盖一些接收问题。

3.2.7 EOF(帧结束)

EOF是7个连续的隐性位。为什么是7位?这是为了和错误帧的6个显性位区分开。如果接收节点检测到EOF不是7个隐性位,就会认为总线有错误。

3.3 帧类型

CAN总线有4种帧类型。我经常把它们分成两组:数据帧和远程帧是“正常通信”,错误帧和过载帧是“异常处理”。

帧类型 用途 关键特征
数据帧 发送数据 包含数据场,RTR=0
远程帧 请求数据 无数据场,RTR=1
错误帧 报告总线错误 6个显性位+8个隐性位
过载帧 请求延迟 与错误帧结构类似

3.3.1 数据帧

数据帧就是最常用的帧类型。它携带数据从发送节点到接收节点。标准帧和扩展帧的区别主要在ID长度上。

3.3.2 远程帧

远程帧没有数据场。它的作用是:一个节点向另一个节点请求数据。比如,发动机ECU可以发一个远程帧请求车速传感器的数据。收到远程帧的节点,会发一个同ID的数据帧作为响应。

我记得有一次,一个同事发现总线负载率异常高,查了半天才发现是某个节点在不停地发远程帧,而对应的节点根本没响应——结果就是远程帧一直重发,把总线塞满了。

3.3.3 错误帧

错误帧由6个显性位和8个隐性位组成。任何节点检测到总线错误,都会立即发送错误帧。错误帧会破坏当前正在传输的帧,迫使发送节点重发。

错误帧触发的条件:

  • 位错误:发送节点发现总线电平与自己发送的不一致
  • 填充错误:连续6个相同电平(违反位填充规则)
  • CRC错误:接收节点计算的CRC与发送的不一致
  • 形式错误:固定格式的位(如EOF)不是预期值
  • ACK错误:发送节点没检测到ACK显性位

3.3.4 过载帧

过载帧和错误帧结构很像,但用途完全不同。过载帧是接收节点主动发出的,意思是:“我还没准备好,请等一下”。过载帧通常在两种情况下触发:

  • 接收节点内部缓冲区满了,需要时间处理
  • 总线空闲时,接收节点检测到显性位(可能是噪声)

说实话,过载帧在实际车载网络中很少见。现在的CAN控制器都有足够的硬件缓冲,很少会因为软件来不及处理而主动发过载帧。如果你在总线上频繁看到过载帧,那大概率是硬件设计有问题。

3.4 位填充机制

CAN总线有一个很有意思的规则:连续发送5个相同电平后,必须插入一个相反电平。这就是位填充。从SOF到CRC场结束,所有位都受这个规则约束。

为什么要这么做?说白了就是为了保证时钟同步。如果没有位填充,连续十几个隐性位,接收节点的时钟可能就漂走了。插入一个翻转电平,相当于给接收节点一个重新同步的机会。

避坑指南:我曾经在写CAN驱动时,自己组帧发送,忘了在位填充后重新计算CRC。结果发出去的帧总是被对方拒绝。查了两天才发现——CRC是在位填充之前计算的,而不是之后。这个顺序千万别搞反。

3.5 本章小结

CAN帧结构是理解整个CAN协议的基础。SOF负责同步,仲裁场决定优先级,数据场承载信息,CRC场保证完整性,ACK场确认接收,EOF标记结束。四种帧类型各司其职,共同构成了一个可靠、实时的通信系统。

我个人觉得,理解CAN帧结构最好的方法就是拿一个CAN分析仪,抓一段真实的CAN总线数据,然后一帧一帧地解析。看着示波器上的波形和协议分析器上的字段一一对应,那种感觉比看十遍书都管用。