2. CAN数据帧结构:从SOF到EOF,逐位拆解

各位同学,今天我们来聊聊CAN总线最核心的东西——数据帧结构。说实话,我刚入行那会儿,看着CAN帧的七段结构,感觉就像在看天书。但干这行十几年后,我越来越觉得,搞懂这七个字段,就等于拿到了CAN总线的钥匙。

CAN总线上的数据,不是一股脑儿扔出去的。它被包装成一个“帧”,就像寄快递要填快递单一样。这个帧有固定的格式,一共7个部分:SOF、仲裁场、控制场、数据场、CRC场、ACK场、EOF。咱们一个一个来拆。

CAN标准数据帧结构(2.0A) SOF 1 bit 仲裁场 12/32 bit 控制场 6 bit 数据场 0~64 bit CRC场 16 bit ACK场 2 bit EOF 7 bit ← 帧起始 帧结束 → 总线空闲(隐性位) 一个完整CAN数据帧的传输过程 帧间空间(ITM)≥ 3个隐性位 SOF 仲裁 控制 数据 CRC ACK EOF

2.1 SOF(帧起始)—— 一声号令

SOF全称Start of Frame,就1个显性位(逻辑0)。它干的事很简单:告诉总线上所有节点,“我要发数据了,大家注意!”

为什么是显性位?因为总线空闲时是隐性位(逻辑1)。SOF用显性位,就像在安静的教室里突然拍一下桌子——所有人都知道有动静了。

💡 我的小经验: 用Wireshark抓CAN包时,SOF你是看不到的。因为Wireshark解析的是已经完整接收的帧,SOF已经被硬件处理掉了。但你要知道,没有SOF,后面的一切都不存在。

2.2 仲裁场 —— 谁说了算?

仲裁场是CAN总线最精彩的设计。它包含两部分:标识符(ID)RTR位(远程帧请求位)。

标准帧(2.0A)的ID是11位,扩展帧(2.0B)是29位。ID越小,优先级越高。为什么?因为显性位(0)会覆盖隐性位(1)。

举个例子:节点A发ID=0x100,节点B发ID=0x200。在仲裁过程中,谁先出现1(隐性)而对方是0(显性),谁就退出。0x100的二进制是0001 0000 0000,0x200是0010 0000 0000。比较到第3位时,0x100是0(显性),0x200是1(隐性)——0x100胜出。

🔑 核心要点: 仲裁场实现了“无损逐位仲裁”。胜出的节点继续发,失败的节点自动转为接收。整个过程不浪费一丁点带宽。

RTR位呢?如果是数据帧,RTR=0(显性);如果是远程帧,RTR=1(隐性)。远程帧是用来请求对方发数据的,实际项目中用得不多。我记得有一次调试一个电机控制器,对方一直发远程帧请求转速数据,我这边没配置好,结果总线上一堆远程帧在空转……排查了半天。

2.3 控制场 —— 数据有多长?

控制场共6位:IDE位(1位)、保留位r0(1位)、DLC(4位)。

IDE位:标识是标准帧(0)还是扩展帧(1)。保留位留给未来扩展,目前必须发显性位(0)。

DLC(Data Length Code):告诉接收方数据场有多少字节。取值范围0~8,对应0~8字节。注意,DLC是4位二进制,理论上能表示0~15,但CAN协议规定数据场最多8字节。为什么是8?早期设计时觉得8字节够用了,后来发现确实够用——汽车上大部分信号也就几个字节。

DLC值 二进制 数据场长度
000000字节
100011字节
200102字节
300113字节
401004字节
501015字节
601106字节
701117字节
810008字节
⚠️ 避坑指南: 我曾经见过一个项目,DLC填了8,但实际数据只有3个字节有效。接收方读8个字节,后面5个全是随机值。结果ECU解析出离谱的数据,直接报故障。记住:DLC必须与实际数据长度一致。

2.4 数据场 —— 真正的内容

数据场就是你要传输的实际数据,0~8字节。怎么编排这些字节,由上层协议决定。比如J1939、CANopen、或者你们自己定义的私有协议。

在Wireshark里,数据场会以十六进制显示。比如:01 02 03 04 05 06 07 08。但你要知道,这些字节的排列顺序(大小端)取决于协议定义。有些协议用Motorola格式(大端),有些用Intel格式(小端)。

我个人习惯:拿到一个CAN报文,先看DLC,再看数据场。如果DLC=8但数据场全是0x00或0xFF,多半是空报文或者初始化值,别被误导了。

2.5 CRC场 —— 数据对不对?

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

CRC算法从SOF开始算,一直算到数据场结束。生成多项式是:x¹⁵ + x¹⁴ + x¹⁰ + x⁸ + x⁷ + x⁴ + x³ + 1。接收方收到后重新算一遍CRC,如果和发送方的不一致,就知道数据被破坏了。

CRC界定符固定为隐性位(1)。为什么?为了和后面的ACK场区分开。

💡 实际经验: 在Wireshark里,如果看到CRC错误,别急着怀疑硬件。有时候是软件配置错了——比如波特率不匹配,或者采样点位置不对。我遇到过一台设备,CRC错误率高达30%,最后发现是晶振偏差太大。

2.6 ACK场 —— 有人收到吗?

ACK场就2位:ACK槽(1位)和ACK界定符(1位)。

发送方在ACK槽发隐性位(1)。所有接收方如果正确收到帧,就在ACK槽发显性位(0)来应答。这样,发送方看到总线被拉成显性,就知道至少有一个节点收到了。

如果发送方发现ACK槽还是隐性位(1),说明没人应答——帧发送失败。这时候发送方会重发。

你想想看,这个设计多巧妙。不需要单独的应答帧,一个位就搞定了确认机制。

🔑 注意: ACK界定符必须是隐性位(1)。它和CRC界定符一样,都是用来做位填充的边界标记。

2.7 EOF(帧结束)—— 收工

EOF共7位,全部是隐性位(1)。它告诉总线上所有节点:这帧发完了,大家可以准备接收下一帧了。

为什么是7位?因为位填充机制最多连续出现5个相同位。EOF用7个连续的隐性位,不可能被误认为是填充位。这样接收方就能明确知道帧的结束位置。

EOF之后,总线进入帧间空间(ITM),至少3个隐性位。然后才能开始下一帧的SOF。

2.8 位填充 —— 隐藏的规则

从SOF到CRC场(包括CRC界定符之前),如果出现连续5个相同位,发送方会自动插入一个相反位。这叫位填充。

为什么要有位填充?为了保证时钟同步。CAN总线没有单独的时钟线,接收方靠跳变沿来同步时钟。如果长时间没有跳变,时钟就会漂移。

举个例子:如果数据是00000000,发送方会变成0000010000(在第5个0之后插入1)。接收方看到这个1,就知道是填充位,把它去掉。

⚠️ 我曾经踩过的坑: 有一次写CAN驱动,位填充逻辑写错了。发送方插入了填充位,但接收方没去掉。结果数据全乱了。后来用Wireshark抓包对比,才发现多了一个位。所以,位填充这个细节,千万别忽略。

2.9 总结一下

一个完整的CAN数据帧,从SOF到EOF,总共加起来:

  • 标准帧(11位ID):1 + 12 + 6 + 0~64 + 16 + 2 + 7 = 44~108位
  • 扩展帧(29位ID):1 + 32 + 6 + 0~64 + 16 + 2 + 7 = 64~128位

再加上位填充,实际长度会略有增加。但不管怎么变,这7个字段的结构是固定的。搞懂了它们,你就能看懂Wireshark里任何一条CAN报文。

下一章,我们聊聊CAN的帧类型——数据帧、远程帧、错误帧、过载帧。嗯,到时候你会发现,错误帧才是真正让人头疼的东西。


专注资料整理