3. CAN总线数据链路层:帧结构总览与五种帧类型

数据链路层,说白了就是CAN协议最核心的战场。物理层只管把0和1发到总线上,但这一层才真正定义了“什么是有效的一帧数据”。我刚开始接触CAN时,总觉得帧结构很复杂,后来拆开一看,其实就那几类。

CAN总线上一共定义了五种帧,外加一个帧间隔。你想想看,一个通信系统要正常工作,至少需要:正常发数据、请求数据、报告错误、调节流量、以及帧与帧之间的喘息时间。CAN把这五件事分别用五种帧来实现。

五种帧类型速览:
  • 数据帧:发送节点把数据放到总线上
  • 远程帧:请求其他节点发送数据
  • 错误帧:检测到总线错误时主动报告
  • 过载帧:接收节点还没准备好,要求延迟
  • 帧间隔:帧与帧之间的分隔区

3.1 标准帧与扩展帧:ID长度的差异

CAN 2.0A定义了标准帧,标识符(ID)只有11位。CAN 2.0B引入了扩展帧,ID扩展到29位。我当年在做一个车载网关项目时,发现老款ECU只认标准帧,新款ECU两种都认。结果混装时出了不少通信故障——嗯,这里要注意,扩展帧和标准帧可以在同一条总线上共存,但前提是所有节点都得支持2.0B。

对比项 标准帧(CAN 2.0A) 扩展帧(CAN 2.0B)
ID长度 11位 29位
仲裁场结构 ID + RTR + IDE + r0 ID(11) + SRR + IDE + ID(18) + RTR
最大节点数(理论) 211 = 2048 229 = 5.3亿
帧头开销 较小 较大(多出SRR+IDE+18位ID)
兼容性 扩展帧节点可收标准帧 标准帧节点无法收扩展帧

为什么会这样?标准帧节点在IDE位位置看到的是显性(0),而扩展帧的IDE是隐性(1)。标准帧节点一看到IDE=1,直接认为帧格式不对,就丢弃了。所以混用时,标准帧节点永远收不到扩展帧。

我的经验: 设计新系统时,我建议统一用扩展帧。29位ID足够你按“优先级+消息类型+源地址+目标地址”来编码,后期扩展也方便。如果为了兼容老设备必须用标准帧,那就在协议层做映射,别让两种帧混着发。

3.2 数据帧:最常用的帧

数据帧就是用来装数据的。它由七个段组成:帧起始、仲裁场、控制场、数据场、CRC场、ACK场、帧结束。我习惯把数据帧想象成一个快递包裹——帧起始是快递单的起始标记,仲裁场是收件地址和优先级,数据场就是包裹里的东西。

数据场最多8字节。为什么是8?CAN总线设计时考虑到实时性,8字节刚好够一个控制命令或传感器数据,又不至于占用总线太久。我在做电机控制器时,一个数据帧里塞了电流值、转速、温度三个参数,刚好7个字节,很紧凑。

// 标准数据帧结构(位顺序)
// SOF | ID(11) | RTR | IDE | r0 | DLC(4) | Data(0-64) | CRC(15) | CRC Delim | ACK Slot | ACK Delim | EOF(7)
// 示例:发送ID=0x123,数据=0x55 0xAA
// 帧起始:1位显性
// 仲裁场:0x123(二进制 0001 0010 0011)
// RTR:0(数据帧)
// IDE:0(标准帧)
// 控制场:r0=0, DLC=0010(2字节)
// 数据场:01010101 10101010
// CRC:15位校验
// ACK:发送方发隐性,接收方拉显性
// EOF:7位隐性
避坑指南: 我曾经遇到过一个bug,发送节点DLC填了8,但数据场只填了3个有效字节。接收方按DLC=8去读,结果读到5个随机值。记住:DLC必须与实际数据长度一致,否则就是协议违规。

3.3 远程帧:请求数据

远程帧长得跟数据帧几乎一样,唯一的区别是RTR位。数据帧的RTR=0(显性),远程帧的RTR=1(隐性)。远程帧没有数据场,DLC填的是请求的数据长度。

举个例子:节点A想获取温度传感器节点B的数据,就发一个ID=温度传感器ID、RTR=1的远程帧。节点B收到后,发现这个ID是自己的,而且RTR=1,就知道有人要数据,于是发一个数据帧回去。

我个人习惯尽量少用远程帧。为什么?因为远程帧的响应时间不确定。如果总线上有多个节点同时请求同一个ID的数据,仲裁机制会让优先级最高的先响应,其他请求可能被延迟甚至丢失。

远程帧 vs 数据帧:
  • 远程帧:RTR=1,无数据场,用于请求
  • 数据帧:RTR=0,有数据场,用于发送
  • 两者ID相同的情况下,数据帧优先级更高(显性RTR赢得仲裁)

3.4 错误帧:总线的自我修复机制

CAN总线最厉害的地方,就是它的错误检测和自动重发。任何节点只要检测到错误,就会立刻发错误帧。错误帧由6个显性位(主动错误标志)加8个隐性位(错误界定符)组成。

错误帧一出现,所有节点都会检测到总线异常,然后丢弃当前帧。发送节点会自动重发刚才那帧。你想想看,这个机制保证了即使有干扰,数据也不会丢失——最多延迟几个位时间。

我记得有一次在工厂调试,现场电机一启动,CAN总线就疯狂报错。后来发现是屏蔽层接地没做好,共模干扰把CANH/CANL的差分信号打乱了。错误帧一直在发,但系统始终在重试,数据最终都传过去了——这就是CAN的鲁棒性。

注意: 如果错误帧频繁出现,说明总线质量有问题。不要指望错误重发机制来兜底,它只是应急手段。正常工作的总线,错误帧应该极少出现。

3.5 过载帧:接收方的“慢一点”信号

过载帧跟错误帧结构很像,也是6个显性位加8个隐性位。但触发条件不同:过载帧是接收节点主动发的,表示“我还没处理完上一帧,你慢点发”。

实际项目中,过载帧用得很少。因为CAN总线的速率通常远高于应用层的处理速度,接收方一般都能跟上。只有在极端情况下——比如接收缓冲区满了,或者CPU被高优先级任务占满——才会发过载帧。

我建议在设计时不要依赖过载帧来调节流量。更好的做法是:发送方根据ACK确认来调整发送间隔,或者用应用层流控协议。

3.6 帧间隔:帧与帧之间的喘息空间

帧间隔是连续两帧之间的空闲时间。它由3个隐性位(间歇场)和总线空闲组成。间歇场是强制性的,总线空闲可长可短。

为什么需要帧间隔?因为节点需要时间从接收状态切换到发送状态,或者处理刚收到的帧。如果没有这个间隔,节点可能来不及响应下一帧。

帧间隔还有一个作用:错误被动节点在发送前必须等待8个隐性位(挂起传输),这是错误限制机制的一部分。我当年调试时发现一个节点总是发不出数据,查了半天才发现它进入了错误被动状态,被帧间隔卡住了。

3.7 本章知识结构图

下面这张图把五种帧和帧间隔的关系画清楚了,你可以对照着理解整个数据链路层的帧体系。

CAN总线数据链路层帧结构总览 CAN帧体系 数据帧 远程帧 错误帧 过载帧 帧间隔 SOF + 仲裁 + 控制 + 数据 + CRC + ACK + EOF RTR=1,无数据场 6显性 + 8隐性 6显性 + 8隐性 间歇场(3) + 总线空闲 标准帧:11位ID | 扩展帧:29位ID 数据帧优先级 > 远程帧(RTR位仲裁)

3.8 小结

这一章我们把CAN数据链路层的帧结构过了一遍。五种帧各有各的用途:数据帧装数据,远程帧要数据,错误帧修错误,过载帧控节奏,帧间隔做分隔。标准帧和扩展帧的区别就是ID长度,但直接影响系统兼容性。