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

数据链路层,说白了就是CAN协议最核心的部分。它定义了数据怎么打包、怎么发送、怎么避免冲突。我当年刚接触CAN时,觉得帧结构就是一堆二进制位,后来在项目里吃过亏才明白——每一个位都有它的设计逻辑。

3.1 五种帧类型概览

CAN总线上一共就五种帧。不多不少,每种干每种的事。

帧类型 用途 谁发
数据帧 发送节点把数据传给其他节点 任意节点
远程帧 请求其他节点发送特定ID的数据 任意节点
错误帧 检测到总线错误时通知所有人 任意节点
过载帧 接收节点还没准备好,要求延迟 接收节点
帧间隔 帧与帧之间的分隔,让总线休息 总线空闲

项目中打交道最多的就是数据帧和远程帧。错误帧和过载帧一般不用你主动发,是硬件自动处理的。但你要理解它们——调试时看到示波器上出现错误帧,你得知道发生了什么。

3.2 数据帧:最常用的帧

数据帧分两种格式:标准帧(11位ID)和扩展帧(29位ID)。结构上就差在ID长度和控制位。

先看标准数据帧的完整结构:

SOF | 11位ID | RTR | IDE | r0 | DLC | 数据段(0-8字节) | CRC | ACK | EOF

我来拆开讲每个字段:

  • SOF(Start of Frame):1位显性位。告诉所有人“我要发帧了”。
  • ID(标识符):标准帧11位,扩展帧29位。不光标识数据内容,还决定优先级。
  • RTR(Remote Transmission Request):数据帧里是显性位(0),远程帧里是隐性位(1)。
  • IDE(Identifier Extension):标准帧里是显性位,扩展帧里是隐性位。
  • r0/r1:保留位,显性位。
  • DLC(Data Length Code):4位,表示数据段有多少字节。范围0-8。
  • 数据段:0到8字节。我建议你尽量用满8字节,除非数据量确实小。
  • CRC(Cyclic Redundancy Check):15位CRC校验 + 1位CRC分隔符。硬件自动算。
  • ACK(Acknowledge):2位。发送节点发隐性位,接收节点如果收到正确的帧,就在ACK Slot位拉成显性位。
  • EOF(End of Frame):7位隐性位。帧结束标志。

关键点:数据帧最多带8字节数据。为什么是8?因为CAN设计时考虑实时性,帧太长会影响高优先级帧的响应时间。8字节在汽车电子里够用——一个传感器值、一个状态字、一个控制命令,基本都能塞进去。

3.3 远程帧:请求数据用的

远程帧的结构和数据帧几乎一样,区别就两点:

  • RTR位是隐性位(1),表示这是远程帧。
  • 没有数据段。DLC虽然填了,但实际不传数据。

举个例子:节点A想获取节点B上某个传感器的值。A发一个远程帧,ID填那个传感器的ID。B收到后,发现这个ID是自己发的,就立刻发一个数据帧回去。

我的经验:远程帧在实际项目中用得不多。大部分系统都是节点主动周期性发送数据,或者用应用层协议来请求。远程帧的好处是硬件自动响应,但坏处是——如果多个节点同时请求同一个ID,总线负载会突然升高。我曾经在一个项目里看到远程帧风暴,差点把总线打崩。

3.4 错误帧与过载帧

这两种帧是硬件自动处理的,但你必须理解它们的行为。

错误帧由6个显性位(错误标志)和8个隐性位(错误分隔符)组成。任何节点检测到错误,就会主动发错误帧。错误帧会把当前正在传输的帧打断,所有节点都会收到错误通知。

错误帧分两种:

  • 主动错误帧:6个显性位。节点处于主动错误状态时发这个。
  • 被动错误帧:6个隐性位。节点处于被动错误状态时发这个。

过载帧结构和错误帧类似,但触发条件不同:

  • 接收节点还没处理完上一帧,内部缓冲区满了。
  • 总线空闲时,接收节点检测到非法显性位。

避坑指南:我曾经在一个项目中,发现总线时不时出现错误帧。查了半天,发现是一个节点的CAN控制器配置错了——波特率设成了500k,但其他节点都是250k。这个节点发出来的帧,其他节点CRC校验全错,于是疯狂发错误帧。整个总线几乎瘫痪。所以,波特率一致性是CAN调试的第一步。

3.5 帧间隔

帧间隔就是两帧之间的空闲时间。它由至少3个隐性位组成。为什么要有帧间隔?

  • 给接收节点一点时间处理上一帧。
  • 让总线状态恢复成隐性,避免误判。
  • 给错误帧和过载帧留出插入空间。

帧间隔之后,任何节点都可以开始发送下一帧。如果多个节点同时开始,就进入仲裁环节。

3.6 ID与仲裁机制

这是CAN总线最巧妙的设计之一。没有主从,没有中央调度,所有节点靠ID来抢总线。

仲裁规则很简单:ID越小,优先级越高。

具体怎么实现的?CAN总线是“线与”逻辑:显性位(0)会覆盖隐性位(1)。多个节点同时发送时,每个节点在发送ID的每一位时,都会监听总线电平。如果自己发的是隐性位(1),但总线上是显性位(0),说明有更高优先级的节点在发,自己立刻退出发送。

我画个图帮你理解:

CAN总线仲裁过程示意图 节点A ID=0x123 节点B ID=0x456 节点C ID=0x789 CAN总线(隐性=1,显性=0) 仲裁过程(逐位比较ID) A:0 B:0 C:0 总线:0 → 全部继续 A:0 B:1 C:1 总线:0 → B、C退出 节点A 赢得仲裁 节点B 等待重发 节点C 等待重发 ID越小优先级越高,节点A的0x123 < 0x456 < 0x789,所以A获胜

你看,仲裁过程是分布式的。没有中央仲裁器,每个节点自己判断该不该退让。这就是CAN总线高可靠性的来源之一。

重要结论:ID不仅标识数据内容,还决定总线访问优先级。设计CAN网络时,要把关键信号(如刹车、转向)分配小ID,非关键信号(如车窗、灯光)分配大ID。这样即使总线负载高,关键信号也能优先通过。

3.7 标准帧 vs 扩展帧

扩展帧的ID是29位,标准帧是11位。结构上多了个SRR位和IDE位。

特性 标准帧 扩展帧
ID长度 11位 29位
可用的ID数量 2048个 5.36亿个
帧长度 较短(111位) 较长(131位)
仲裁优先级 标准帧ID < 扩展帧ID(相同ID部分) 扩展帧优先级较低
应用场景 J1939、CANopen、大部分汽车网络 需要大量ID的复杂系统

我的建议:除非你的系统需要超过2000个不同的ID,否则用标准帧就够了。扩展帧的帧长度更长,总线利用率会降低。我在一个工程机械项目里用过扩展帧,因为需要区分几十个ECU和上千个信号,标准帧的ID空间确实不够用。

3.8 位填充机制

这个机制容易被忽略,但很重要。CAN协议规定:连续发送5个相同电平的位后,必须插入一个相反电平的位。

为什么?

  • 保证接收节点能从总线电平变化中提取时钟信号。
  • 防止长时间相同电平导致接收节点失锁。

位填充的范围:从SOF到CRC段结束。ACK、EOF和帧间隔不参与位填充。

举个例子:如果你要发送的数据是 11111 00000,经过位填充后会变成 11111 0 00000 1。多出来的0和1就是填充位,接收节点会自动去掉它们。

避坑指南:我曾经遇到过一个奇怪的问题——某个节点偶尔会发错误帧,但频率很低,很难复现。后来用示波器抓波形发现,这个节点发送的数据段里连续出现了6个相同的位。按理说位填充应该插位,但那个节点的CAN控制器配置有问题,位填充功能被意外关闭了。结果就是其他节点收到这个帧时,检测到位填充错误,立刻发错误帧。所以,检查CAN控制器的位填充配置,是调试时的必修课。

3.9 本章小结

数据链路层是CAN协议的核心。你不需要记住每个位的具体位置,但必须理解:

  • 五种帧类型各自干什么。
  • 数据帧的结构和每个字段的意义。
  • 仲裁机制如何靠ID决定优先级。
  • 标准帧和扩展帧的区别和选择。
  • 位填充为什么存在,以及它可能带来的坑。

下一层我们就要进入物理层了,看看CAN总线在硬件上是怎么实现的。但先把数据链路层吃透,后面学起来会轻松很多。