3. CAN总线数据链路层:帧结构与仲裁机制

数据链路层,说白了就是CAN总线的“交通规则”。物理层只管把0和1发到线上,但这一层才真正决定了谁能说话、说什么、说错了怎么办。我刚开始接触CAN时,总觉得帧结构很枯燥,直到有一次调试一个电机控制器,发现报文老是丢——最后查出来是帧ID配错了。嗯,从那以后我再也不敢小看这一层了。

3.1 CAN帧结构:标准帧 vs 扩展帧

CAN总线上的数据以“帧”为单位传输。每一帧就像一封信,有信封(帧头)、正文(数据)、和校验(CRC)。CAN 2.0规范定义了两种帧格式:标准帧和扩展帧。

对比项 标准帧 (CAN 2.0A) 扩展帧 (CAN 2.0B)
ID长度 11位 29位
可标识节点数 2048个 5.36亿个
帧头开销 较小(47位) 较大(67位)
IDE位 0(显性) 1(隐性)
兼容性 所有CAN控制器 仅2.0B控制器
我的经验: 在乘用车动力域,标准帧完全够用。但在工程车辆或工业控制中,节点一多(比如超过100个),我建议直接用扩展帧。我曾经在一个农机项目里用标准帧规划ID,结果后期加传感器时发现ID不够用,不得不重新分配——那叫一个痛苦。

3.2 帧类型详解

CAN总线定义了四种帧类型。你想想看,就像马路上有不同种类的车:私家车、公交车、救护车、洒水车。每种帧干的事不一样。

3.2.1 数据帧

数据帧是干活的主力。它负责把数据从发送节点传到接收节点。一个标准数据帧的完整结构如下:

SOF + 11位ID + RTR + IDE + r0 + DLC + 0~8字节数据 + CRC + ACK + EOF

关键字段说明:

  • SOF (Start of Frame):1位显性,告诉所有节点“我要发帧了”
  • ID:标识符,决定优先级(数值越小优先级越高)
  • RTR (Remote Transmission Request):数据帧中为0(显性)
  • DLC (Data Length Code):4位,表示数据字节数(0~8)
  • 数据段:0~8字节,实际载荷
  • CRC:15位校验 + 1位定界符
  • ACK:2位(应答槽 + 定界符)
  • EOF (End of Frame):7位隐性
避坑指南: 我曾经遇到一个案例,DLC写的是8,但实际只发了3个字节的数据。接收方读到的后5个字节全是垃圾值。记住:DLC必须与实际数据长度一致,否则就是给自己挖坑。

3.2.2 远程帧

远程帧是“请求帧”。节点A发一个远程帧,意思就是“节点B,请把你的数据发给我”。远程帧和数据帧结构几乎一样,唯一的区别是RTR位为1(隐性)。

远程帧没有数据段,但DLC必须填上请求的数据长度。比如你请求8字节数据,DLC就写8。

我个人习惯: 远程帧在简单的主从系统中很好用。但在多主系统中,我建议少用远程帧。为什么?因为远程帧的应答机制容易造成总线拥堵。我见过一个项目,远程帧发得太频繁,结果总线利用率飙到80%以上。

3.2.3 错误帧

错误帧是CAN总线的“警报器”。任何节点检测到错误,就会立刻发送错误帧。错误帧由两部分组成:

  • 错误标志:6个连续相同位(显性或隐性,取决于错误类型)
  • 错误定界符:8个隐性位

错误帧一旦发出,当前正在传输的帧就会被强制终止。所有节点都会丢弃这个帧,然后重新仲裁。

关键认知: 错误帧不是“惩罚”,而是“保护机制”。CAN总线的可靠性,很大程度上就靠错误帧和重发机制撑着。

3.2.4 过载帧

过载帧是“慢点,我跟不上了”的信号。当接收节点来不及处理数据时,它会发送过载帧,要求发送方延迟发送。过载帧的结构和错误帧类似,但触发条件不同。

过载帧在实际项目中用得不多。我做了这么多年,只在一些低速的工业控制总线上见过。在高速车载网络中,过载帧的出现往往意味着系统设计有问题——要么是接收缓冲区太小,要么是中断处理太慢。

3.3 仲裁机制:CSMA/CR

CSMA/CR 的全称是 Carrier Sense Multiple Access with Collision Resolution,翻译过来就是“载波监听多路访问/冲突解决”。

说白了,CAN总线的仲裁机制就是:谁优先级高,谁先说话。

3.3.1 工作原理

  1. 监听:每个节点在发送前,先监听总线是否空闲
  2. 发送:总线空闲时,节点开始发送帧(从SOF开始)
  3. 逐位仲裁:多个节点同时发送时,每个节点逐位比较自己的ID和总线上的电平
  4. 胜负判定:发送显性位(0)的节点获胜,发送隐性位(1)的节点自动退出
核心规则: ID数值越小,优先级越高。0x000是最高优先级,0x7FF(标准帧)或0x1FFFFFFF(扩展帧)是最低优先级。

3.3.2 仲裁过程图解

CAN总线逐位仲裁过程 节点A (ID=0x321) 节点B (ID=0x123) 节点C (ID=0x456) CAN总线(双绞线) 逐位仲裁:从ID最高位开始比较 节点B的ID=0x123 → 二进制 0001 0010 0011 仲裁胜出:节点B(ID最小,优先级最高) 节点A和节点C自动退出,等待下次重发

3.3.3 为什么CAN能做到无损仲裁?

这个问题我当年想了很久。以太网用的是CSMA/CD,冲突了就要退避重发,效率低。CAN为什么不用退避?

原因在于CAN总线的电气特性:显性位(0)会覆盖隐性位(1)。多个节点同时发送时,只要有一个节点发0,总线上就是0。发送1的节点发现总线和自己发的不一致,就知道自己输了,立刻停止发送。

整个过程在几个微秒内完成,而且胜出的节点可以继续发送完整帧,没有任何时间浪费。这就是“无损位仲裁”的精髓。

我曾经踩过的坑: 在一个工程机械项目中,我把两个节点的ID设成了相同的值。结果仲裁时两个节点同时胜出,数据完全乱套。记住:同一总线上,每个帧的ID必须唯一。这不是建议,是铁律。

3.4 帧间空间

帧和帧之间需要一段“休息时间”,叫做帧间空间(Interframe Space)。它由三部分组成:

  • 间歇场:3个隐性位
  • 总线空闲:任意长度的隐性位(直到有节点开始发送)
  • 暂停发送场:仅被动错误节点需要,8个隐性位

帧间空间的作用是给接收节点一点时间处理刚收到的帧,也为下一次仲裁做好准备。

3.5 本章小结

知识点 一句话总结
标准帧 vs 扩展帧 11位ID够用就用标准帧,节点多就用扩展帧
数据帧 主力帧,带0~8字节数据
远程帧 请求帧,无数据,RTR=1
错误帧 警报器,检测到错误立刻发送
过载帧 “慢点”,接收方忙不过来时发送
CSMA/CR仲裁 ID越小优先级越高,无损仲裁,胜者继续
给新手的建议: 刚开始学CAN,先把数据帧和仲裁机制吃透。这两个是CAN总线的灵魂。远程帧和错误帧可以边做项目边理解。我当年就是先跑通数据帧通信,再慢慢啃其他帧类型的。