第2章 CAN协议数据链路层:帧结构详解

好,咱们直接进入正题。数据链路层,说白了就是CAN协议最核心的部分。我当年刚接触CAN时,对着帧结构看了三天三夜,后来发现——其实没那么玄乎,关键是把每个字段的「为什么」搞明白。

2.1 标准帧 vs 扩展帧

先解决一个基础问题。CAN总线有两种帧格式:标准帧(11位ID)和扩展帧(29位ID)。

我个人习惯这么记:

  • 标准帧:ID短,适合节点少的系统(比如车门模块)
  • 扩展帧:ID长,适合复杂网络(比如整车CAN)

你想想看,如果车上只有几个ECU,用11位ID完全够用。但要是像现代汽车那样动辄几十个节点,29位ID才能避免ID冲突。

重要提醒:标准帧和扩展帧的仲裁场结构不同,混用时必须保证ID不重叠。我在一个项目中就吃过这个亏——标准帧的11位ID和扩展帧的高11位ID冲突了,结果总线乱成一锅粥。

2.2 SOF(帧起始)

SOF就是一个显性位(逻辑0)。它告诉所有节点:「我要开始发消息了,大家准备好监听。」

嗯,这里要注意:SOF是同步信号。所有节点的时钟都靠它对齐。如果SOF被干扰了,整帧数据都可能废掉。

2.3 仲裁场

这是CAN协议最巧妙的设计之一。仲裁场包含ID和RTR位。

ID(标识符)

  • 标准帧:11位,ID10~ID0
  • 扩展帧:29位,ID28~ID0

RTR(远程传输请求位)

  • 显性(0):数据帧
  • 隐性(1):远程帧

为什么说它巧妙?因为仲裁过程是「无损」的。多个节点同时发送时,ID值最小的节点自动获胜。我曾经调试过一个故障:两个节点ID只差1位,结果低优先级节点永远发不出数据。后来我把ID重新分配了才解决。

实战技巧:分配ID时,把优先级高的节点(比如刹车控制)分配小ID,优先级低的(比如车窗)分配大ID。这是CAN网络设计的基本功。

2.4 控制场

控制场包含IDE位、保留位和DLC(数据长度码)。

字段 位数 说明
IDE 1 标识扩展帧(1=扩展,0=标准)
保留位 1~2 标准帧1位,扩展帧2位
DLC 4 数据场字节数(0~8)

DLC的取值范围是0~8,但实际可以编码0~15。为什么只用到8?因为CAN协议规定数据场最多8字节。我见过有人试图用DLC=9发送9字节数据,结果接收节点直接丢弃——这是协议规定的硬限制。

2.5 数据场

数据场就是实际要传输的数据,0~8字节。长度由DLC决定。

举个例子:

// 发送一个2字节的数据
// 数据场内容:0x12, 0x34
// DLC = 2
// 帧结构:SOF + ID + RTR + IDE + r0 + DLC(2) + DATA(0x12,0x34) + CRC + ACK + EOF

数据场的排列顺序是MSB优先。我在项目中遇到过一个问题:发送方和接收方对字节序理解不一致,导致数据解析错误。后来我们统一用CANdb文件定义信号格式,才彻底解决。

2.6 CRC场

CRC场包含15位CRC序列和1位CRC界定符(隐性位)。

CRC的计算范围包括:SOF、仲裁场、控制场、数据场。生成多项式是:

x^15 + x^14 + x^10 + x^8 + x^7 + x^4 + x^3 + 1

这个多项式能检测出:

  • 所有单比特错误
  • 所有双比特错误
  • 所有奇数个错误
  • 所有长度≤15的突发错误

我曾经在实验室用CAN分析仪抓包,发现CRC错误率大约在0.01%左右。别小看这个数字,在汽车上,0.01%的错误率意味着每10000帧就有一帧出错——所以CAN协议还设计了错误帧和重发机制。

避坑指南:我曾经遇到一个案例,某ECU的CRC计算逻辑有bug,导致它发送的帧总是CRC错误。其他节点收到后不断发送错误帧,整个总线瘫痪。排查了整整两天才发现是固件问题。所以,CRC计算一定要用硬件模块,别自己写软件实现。

2.7 ACK场

ACK场只有2位:ACK槽和ACK界定符。

  • ACK槽:发送节点发送隐性位(1),接收节点如果正确接收,就拉成显性(0)
  • ACK界定符:必须是隐性位(1)

这个机制很有意思。发送节点只负责发,不负责确认。接收节点通过拉低ACK槽来告诉发送方:「我收到了,没问题。」

如果没有任何节点拉低ACK槽,发送节点就知道——要么总线上没有其他节点,要么所有节点都接收失败。这时候发送节点会重发。

2.8 EOF(帧结束)

EOF是7个连续的隐性位(逻辑1)。它标志着帧的结束。

为什么是7位?因为位填充机制最多连续5个相同位,7位隐性位足够让所有节点识别出帧结束。嗯,这里其实有个细节:EOF之后还有3位的ITM(帧间空间),但那是下一章的内容了。

2.9 位填充机制

这是CAN协议保证同步的关键机制。规则很简单:

发送节点在连续发送5个相同位后,自动插入一个相反位。

举个例子:

原始数据:11111 00000 11111
填充后:  111110 000001 111110

接收节点在接收时,检测到5个相同位后,自动删除后面的那个相反位。这样就能恢复原始数据。

为什么需要位填充?因为CAN总线是异步通信,节点靠跳变沿来同步时钟。如果长时间没有跳变(比如连续发送30个0),节点时钟就会漂移,导致采样错误。

关键点:位填充覆盖的范围是SOF到CRC场。EOF、ACK场、ITM不进行位填充。这意味着EOF的7个隐性位是「干净」的,不会被填充干扰。

我在项目中遇到过位填充导致的问题:某个节点发送的数据恰好是连续的5个相同位,但填充位被干扰了,接收节点误判为错误帧。后来我们在数据场中加入了随机化处理,才避免这种情况。

2.10 完整帧结构一览

把上面所有字段串起来,标准数据帧的完整结构是:

SOF(1) + ID(11) + RTR(1) + IDE(1) + r0(1) + DLC(4) + DATA(0~64) + CRC(15) + CRC界定符(1) + ACK槽(1) + ACK界定符(1) + EOF(7)

总位数:44~108位(不含位填充)。加上位填充后,最大约130位左右。

扩展帧的结构类似,只是ID变成29位,控制场多了一个保留位:

SOF(1) + ID(29) + RTR(1) + SRR(1) + IDE(1) + r1(1) + r0(1) + DLC(4) + DATA(0~64) + CRC(15) + CRC界定符(1) + ACK槽(1) + ACK界定符(1) + EOF(7)

2.11 小结

这一章内容比较多,但核心就三点:

  1. 帧结构:SOF→仲裁场→控制场→数据场→CRC场→ACK场→EOF,每个字段都有明确职责
  2. 仲裁机制:ID越小优先级越高,无损仲裁是CAN的杀手锏
  3. 位填充:保证时钟同步,但也会增加总线负载

下一章我们会讲错误帧和错误处理机制。到时候你会看到,CAN协议为了可靠性做了多少「冗余设计」。嗯,那些设计在实战中真的能救命。