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总线高可靠性的来源之一。
重要结论: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总线在硬件上是怎么实现的。但先把数据链路层吃透,后面学起来会轻松很多。