3. 数据链路层:CAN帧结构

好,咱们进入数据链路层。这一层说白了,就是CAN协议最核心的部分。你想想看,物理层只负责把0和1变成电信号发出去,但怎么组织这些0和1,怎么知道哪条消息是发给谁的,怎么保证不出错——这些事儿,全归数据链路层管。

我个人习惯把这一层叫做“CAN的骨架”。骨架搭不好,上层应用再牛也白搭。我在BMS项目里就吃过这个亏,后面会讲到。

3.1 CAN帧结构:一个比特一个比特拆开看

一个标准的CAN数据帧,长什么样?我直接给你画出来:

CAN标准数据帧结构(2.0A) SOF ID (11位) RTR IDE r0 DLC Data (0~64位) CRC (15位) DEL ACK DEL EOF (7位) 1位 11位 1位 1位 1位 4位 0~64位 15位 1位 2位 1位 7位 帧起始 标识符 远程 扩展 保留 数据长度 数据 循环冗余 定界 应答 定界 帧结束 显性位 决定优先级 0=数据 0=标准 保留 0~8字节 实际载荷 校验和 1位 应答槽 1位 隐性位 报文传输方向 → 总帧长:SOF(1) + ID(11) + RTR(1) + IDE(1) + r0(1) + DLC(4) + Data(0~64) + CRC(15+1) + ACK(2) + EOF(7) = 44~108位

这张图我建议你保存下来。每次调试CAN总线时,对着它看,心里就有底了。

3.2 逐个字段拆解

3.2.1 SOF(帧起始)

就1个显性位。说白了就是告诉总线上所有节点:“我要发消息了,大家注意!”

SOF的作用很关键——它同步了所有节点的时钟。我在调试一个BMS项目时,发现有时候总线会莫名其妙丢帧。查了半天,最后发现是SOF位被一个电容耦合干扰拉成了隐性位。嗯,硬件设计上这里一定要加滤波。

3.2.2 ID(标识符)

11位,决定了这条消息的优先级。ID越小,优先级越高。你想想看,如果总线上同时有两个节点要发消息,谁的ID小谁就先发。

这里有个坑:ID不是地址!很多初学者以为ID就是“发给谁”,其实不是。CAN是广播式的,所有节点都能收到所有消息,节点自己决定要不要处理。ID只是标识消息的内容,比如“电池电压”用ID 0x100,“电池温度”用ID 0x200。

重要原则:在BMS和PMS通信中,紧急消息(如过压告警)一定要分配小ID。我见过一个设计,把告警ID设成了0x7FF,结果总线上其他消息一多,告警就发不出去——这要出大事的。

3.2.3 RTR(远程发送请求)

1位。0表示数据帧,1表示远程帧。远程帧是干嘛的?就是一个节点问另一个节点:“你那个数据发给我看看?”

实际项目中,远程帧用得不多。我建议你尽量少用,因为远程帧的响应时序不好控制,容易造成总线拥堵。

3.2.4 IDE 和 r0

IDE是扩展标识符位,标准帧里IDE=0。r0是保留位,必须为0。这两个位加起来2个bit,但别小看它们——IDE决定了你是用标准帧还是扩展帧。

3.2.5 DLC(数据长度码)

4位,表示数据段有多少个字节。取值范围0~8。注意:DLC可以设成0,那就是一个空数据帧,只带ID不带数据。这种帧在心跳检测里很常见。

我的习惯:DLC一定要和实际数据长度严格匹配。有些控制器允许DLC设成8但只发3个字节,但我不建议这么做。曾经有个项目,因为DLC和实际数据不匹配,导致接收方CRC校验一直报错,排查了两天才找到原因。

3.2.6 Data(数据段)

0~8个字节,这就是真正的载荷。BMS和PMS通信中,大部分数据都在这里。比如总电压、总电流、SOC、SOH等等。

数据怎么排列?大端还是小端?这完全由应用层协议决定。CAN协议本身不管这个。我个人习惯用Intel格式(小端),因为大部分MCU都是小端处理器,省去转换的麻烦。

3.2.7 CRC(循环冗余校验)

15位CRC校验码,加上1位CRC定界符。这是CAN协议最牛的地方之一——硬件自动计算和校验,不需要软件干预。

CRC能检测出什么?单比特错、双比特错、奇数个错、突发长度≤15的突发错。基本上,只要不是运气太差,CRC都能抓住错误。

注意:CRC校验的是从SOF到数据段的所有位。如果CRC校验失败,接收节点会自动发送错误帧。我曾经在实验室里用CAN分析仪抓包,发现某个节点每隔几秒就发一次错误帧,最后发现是它的晶振频率偏了,导致位时序不对。

3.2.8 ACK(应答)

2位:ACK槽 + ACK定界符。发送节点在ACK槽发隐性位,接收节点如果正确收到,就把这个位拉成显性。这就是CAN的“一收一应”机制。

你想想看,如果总线上没有任何节点接收,ACK槽就会保持隐性,发送节点就知道“没人理我”。这个机制在诊断时特别有用。

3.2.9 EOF(帧结束)

7个隐性位。告诉总线:“我的消息发完了。”之后总线进入空闲状态,等待下一个SOF。

3.3 四种帧类型

CAN总线上一共有四种帧:数据帧、远程帧、错误帧、过载帧。咱们一个一个说。

帧类型 用途 谁发的 关键特征
数据帧 传输数据 任意节点 RTR=0,带数据
远程帧 请求数据 任意节点 RTR=1,无数据
错误帧 报告总线错误 检测到错误的节点 6个显性位 + 8个隐性位
过载帧 请求延迟 接收节点 6个显性位 + 8个隐性位

3.3.1 数据帧

最常用的帧。BMS上报电压、电流、温度,PMS下发充放电指令,全用数据帧。咱们上面拆解的就是数据帧的结构。

3.3.2 远程帧

RTR=1,没有数据段。一个节点发远程帧,另一个节点收到后,会发一个同ID的数据帧作为响应。

实际项目中,远程帧用得少。为什么?因为响应时间不确定。你发个远程帧,对方可能正在处理其他中断,等它响应时黄花菜都凉了。我一般用定时发送数据帧来代替远程帧,更可靠。

3.3.3 错误帧

这是CAN协议的自我修复机制。任何节点检测到错误,就会发错误帧。错误帧分两种:主动错误帧和被动错误帧。

  • 主动错误帧:6个显性位。发送节点会打断当前传输,强制报错。
  • 被动错误帧:6个隐性位。节点知道自己“状态不好”,不敢太强势。

错误帧发完,总线会自动重发被中断的那条消息。这就是CAN的“自动重发”机制——硬件帮你做了,软件不用管。

避坑指南:我曾经在一个项目中,发现总线每隔几秒就出现一次错误帧。用CAN分析仪一抓,发现是一个节点的CAN控制器进入了“总线关闭”状态。原因是它的错误计数器超过了255。最后排查出来,是它的CAN收发器供电不稳,导致发送的电平信号畸变。换了电源模块后,问题解决。

3.3.4 过载帧

过载帧和错误帧长得一模一样,但用途不同。过载帧是接收节点发的,意思是:“我处理不过来了,你慢点发。”

过载帧在实际中很少见。现在的CAN控制器处理速度都很快,很少会出现“来不及收”的情况。但如果你用低速MCU(比如8位机)做CAN节点,就要小心了——处理不过来时,过载帧是最后的救命稻草。

3.4 位填充机制

CAN协议有个很有意思的机制:位填充。规则很简单——连续发送5个相同电平的位后,自动插入一个相反电平的位。

为什么要这么做?因为CAN节点靠电平跳变来同步时钟。如果连续发太多相同的位,节点之间的时钟就会漂移,导致采样出错。

举个例子:如果你要发00000,CAN控制器会自动变成000001。接收方收到后,会自动把那个插入的1去掉,还原成00000。

注意:位填充覆盖的范围是从SOF到CRC段。ACK、EOF和帧间空间不填充。这个细节在计算总线负载率时很重要——实际发送的位数比理论值要多一些。

3.5 帧间空间

两个CAN帧之间,必须有至少3个隐性位作为间隔。这叫帧间空间(IFS)。没有这个间隔,节点来不及处理上一帧,下一帧就来了,容易出问题。

IFS是硬件自动插入的,软件不用管。但你在设计通信时序时,要考虑到IFS占用的时间。尤其是在高波特率(1Mbps)下,IFS的3个位时间虽然短,但累积起来也不可忽视。

3.6 小结

数据链路层是CAN协议的灵魂。你掌握了帧结构,就掌握了CAN的“语法”。SOF是句号,ID是主语,Data是宾语,CRC是标点符号,ACK是对方的回应。把这些搞清楚了,CAN通信对你来说就没有秘密了。

下一层,咱们要进入应用层了。BMS和PMS之间到底传什么数据?怎么定义报文格式?这些才是真正干活的东西。


公众号:蓝海资料掘金营,微信deep3321