4、CAN FD帧结构深度解析:帧起始(SOF)、仲裁场、控制场、数据场、CRC场、ACK场、EOF
好,咱们今天来啃一块硬骨头——CAN FD的帧结构。说实话,很多工程师学了几年CAN,一看到FD帧就头大。其实没那么复杂,咱们把它拆开来看,每个字段都讲清楚。
我个人习惯把CAN FD帧比作一列火车。车头是SOF,车厢是数据场,车尾是EOF。中间那些控制位,就是调度员。咱们一节一节看。
4.1 帧起始(SOF)—— 一列火车的汽笛
SOF就是一个显性位(逻辑0)。它告诉总线上所有节点:“我要发消息了,你们听着。”
这里有个细节:SOF的下降沿是所有节点同步的基准。CAN总线没有单独的时钟线,全靠这个边沿来对齐。我在调试一个混合网络时遇到过,某个节点因为晶振偏差太大,SOF采样就出错,整个网络直接瘫痪。嗯,后来换了高精度晶振才解决。
关键点:SOF必须是显性位。如果总线上同时有多个节点发送,最先发送SOF的节点会赢得仲裁。
4.2 仲裁场 —— 谁嗓门大谁先走
仲裁场包含ID和几个控制位。CAN FD支持两种ID长度:11位(标准帧)和29位(扩展帧)。
你想想看,11位ID最多支持2^11=2048个不同消息。29位呢?5亿多个。实际项目中,11位基本够用,但遇到复杂的车载网络,29位是必须的。
仲裁场的结构是这样的:
- ID(11或29位):标识消息优先级。数值越小,优先级越高。
- IDE(Identifier Extension):1位。0表示标准帧,1表示扩展帧。
- EDL(Extended Data Length):1位。这是CAN FD新增的。0表示经典CAN,1表示CAN FD。
- BRS(Bit Rate Switch):1位。0表示不切换速率,1表示数据段切换到高速。
- ESI(Error State Indicator):1位。0表示发送节点处于主动错误状态,1表示被动错误状态。
避坑指南:我曾经在项目中把IDE和EDL搞混了。IDE是区分标准/扩展帧,EDL是区分经典CAN/CAN FD。两个位紧挨着,但功能完全不同。写代码时一定要看清楚寄存器定义。
4.3 控制场 —— 调度员的指令
控制场包含DLC、FDF、BRS、ESI这几个位。咱们一个一个说。
4.3.1 DLC(Data Length Code)—— 数据长度编码
经典CAN的DLC是4位,只能表示0-8字节。CAN FD的DLC也是4位,但编码方式变了:
| DLC值 | 经典CAN数据长度(字节) | CAN FD数据长度(字节) |
|---|---|---|
| 0-8 | 0-8 | 0-8 |
| 9 | 8 | 12 |
| 10 | 8 | 16 |
| 11 | 8 | 20 |
| 12 | 8 | 24 |
| 13 | 8 | 32 |
| 14 | 8 | 48 |
| 15 | 8 | 64 |
说白了,经典CAN的DLC超过8就无效了,但CAN FD利用这些“无效值”来表示更大的数据长度。我刚开始学的时候觉得这设计很巧妙,后来发现其实挺坑的——如果你用经典CAN的控制器去解析CAN FD帧,DLC=9会被误认为8字节,数据直接截断。
4.3.2 FDF(FD Format)—— FD格式标志
FDF位是CAN FD帧和经典CAN帧的“分水岭”。
- 经典CAN:FDF = 0(显性)
- CAN FD:FDF = 1(隐性)
接收节点看到FDF=1,就知道这是FD帧,要用FD的规则来解析。如果FDF=0,就按经典CAN处理。
注意:FDF位在经典CAN中必须是显性(0)。如果某个经典CAN节点收到FDF=1的帧,它会直接报错。这就是为什么CAN FD和经典CAN不能直接混用——你发一个FD帧,老节点会认为格式错误。
4.3.3 BRS(Bit Rate Switch)—— 速率切换
BRS位控制是否在数据段切换到高速模式。
- BRS = 0:不切换,全程用仲裁段速率(通常125k-1Mbps)
- BRS = 1:切换,数据段用高速率(最高8Mbps)
为什么要切换?因为仲裁段需要所有节点都能听到,速率不能太高。但数据段只有发送和接收两个节点在通信,可以跑快一点。我建议在长距离总线(比如超过10米)上慎用BRS,信号反射会导致误码率飙升。
4.3.4 ESI(Error State Indicator)—— 错误状态指示
ESI位告诉接收节点:“我现在的错误状态如何。”
- ESI = 0:发送节点处于主动错误状态(Error Active)
- ESI = 1:发送节点处于被动错误状态(Error Passive)
这个位有什么用?如果接收节点发现ESI=1,就知道发送节点快“不行了”,可能会频繁出错。这时候接收节点可以采取一些措施,比如降低通信频率,或者切换到冗余通道。
我记得有一次在台架测试中,某个ECU频繁发送ESI=1的帧,其他节点就开始忽略它的消息。后来发现是那个ECU的CAN控制器寄存器配置错了,导致错误计数器一直增加。嗯,查了两天才找到原因。
4.4 数据场 —— 真正的货物
数据场是帧的核心,存放实际要传输的数据。
- 经典CAN:0-8字节
- CAN FD:0-64字节
数据场长度由DLC决定。注意,数据场在BRS=1时使用高速率传输,所以数据段比仲裁段快得多。
我个人习惯在数据场里放结构体,而不是裸字节。比如:
typedef struct {
uint16_t speed; // 车速,单位0.1km/h
uint8_t direction; // 方向:0-前进,1-后退
uint8_t gear; // 档位
uint32_t timestamp; // 时间戳,单位ms
} VehicleData_t;
这样代码可读性高,也方便调试。但要注意字节序——CAN总线默认是大端(Motorola格式),如果你的MCU是小端(比如ARM Cortex-M),需要做转换。
4.5 CRC场 —— 数据的“指纹”
CRC场用于检测传输错误。CAN FD的CRC比经典CAN复杂得多。
- 经典CAN:15位CRC + 1位CRC分隔符
- CAN FD(数据长度≤16字节):17位CRC + 填充位计数
- CAN FD(数据长度>16字节):21位CRC + 填充位计数
为什么CAN FD要用更长的CRC?因为数据场变大了,错误概率也增加了。17位CRC可以检测最多6位错误,21位CRC可以检测最多8位错误。
这里有个新东西——填充位计数(Stuff Bit Count)。经典CAN的填充位是随机插入的,接收端需要自己数。CAN FD把填充位的数量直接编码在CRC后面,接收端可以快速验证。这算是一个小优化,但确实提高了效率。
避坑指南:我曾经在移植CAN FD驱动时,CRC计算一直不对。后来发现是填充位计数没处理好——CAN FD的CRC计算要包含填充位,而经典CAN不包含。这个差异很容易被忽略。
4.6 ACK场 —— 确认收货
ACK场只有2位:ACK Slot(1位)和ACK Delimiter(1位)。
- ACK Slot:发送节点发送隐性位(1),接收节点如果正确收到,就拉成显性(0)。
- ACK Delimiter:必须是隐性位(1),用于分隔ACK和EOF。
如果发送节点在ACK Slot检测到显性位,说明至少有一个节点正确收到了。如果还是隐性,说明没人确认,发送节点会重发。
嗯,这里要注意:CAN FD的ACK机制和经典CAN完全一样。所以从ACK角度看,两者是兼容的。
4.7 EOF(End of Frame)—— 列车到站
EOF是7个连续的隐性位(1)。它告诉所有节点:“这帧结束了,准备接收下一帧。”
为什么是7个?因为填充位规则规定,连续5个相同位就要插入一个相反位。7个隐性位保证了不会触发填充位插入,也给了节点足够的时间准备接收下一帧。
EOF之后,总线会进入3位的ITM(Intermission,间歇场),然后才能开始下一帧。这个间歇场是必须的,否则节点来不及处理。
总结
CAN FD帧结构其实不复杂,关键是要理解每个字段的作用和变化。我建议你拿一个实际的CAN FD波形,对照着本文的字段逐个分析。纸上得来终觉浅,绝知此事要躬行。
下一章咱们讲CAN FD的位定时和同步机制,那是真正考验功底的地方。