第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 小结
这一章内容比较多,但核心就三点:
- 帧结构:SOF→仲裁场→控制场→数据场→CRC场→ACK场→EOF,每个字段都有明确职责
- 仲裁机制:ID越小优先级越高,无损仲裁是CAN的杀手锏
- 位填充:保证时钟同步,但也会增加总线负载
下一章我们会讲错误帧和错误处理机制。到时候你会看到,CAN协议为了可靠性做了多少「冗余设计」。嗯,那些设计在实战中真的能救命。