3. CAN总线仲裁机制:线与逻辑、逐位仲裁、ID优先级、非破坏性仲裁
CAN总线的仲裁机制,是它最精妙的设计之一。说白了,就是多个节点同时抢着发消息时,谁说了算。
我刚开始接触CAN时,觉得这玩意儿不就是个“谁先发谁赢”吗?后来踩了坑才明白,它用的是“线与逻辑”和“逐位仲裁”,而且整个过程是非破坏性的——赢了的节点继续发,输了的自动退出发送,不丢数据。
3.1 线与逻辑:物理层的基础
先看物理层。CAN总线有两根线:CAN_H和CAN_L。它有两种状态:
- 显性(Dominant):逻辑0,CAN_H ≈ 3.5V,CAN_L ≈ 1.5V,差分电压约2V
- 隐性(Recessive):逻辑1,CAN_H ≈ 2.5V,CAN_L ≈ 2.5V,差分电压约0V
关键来了:显性位会覆盖隐性位。只要有一个节点输出显性,总线就是显性。这就是“线与逻辑”——多个输出端直接连在一起,谁拉低谁说了算。
核心规则:显性(0)> 隐性(1)。总线状态 = 所有节点输出状态的“线与”结果。
我在项目中遇到过一个问题:某个节点在隐性状态下把CAN_H拉到了3.3V,结果总线一直显性,其他节点全发不出去。排查了半天,发现是收发器的隐性电平偏了。嗯,物理层的问题,有时候比协议层更隐蔽。
3.2 逐位仲裁:一位一位地比
仲裁过程发生在帧起始(SOF)之后,仲裁场(Identifier + RTR)期间。每个节点在发送每一位时,都会同时监听总线电平。
流程是这样的:
- 所有等待发送的节点,在总线空闲时同时开始发送SOF位(显性)。
- 从仲裁场的第一位(ID的MSB)开始,每个节点逐位发送自己的ID位。
- 发送完一位后,节点立即读取总线电平。
- 如果自己发送的是隐性(1),但读到的是显性(0),说明有更高优先级的节点在发送——自己立刻退出仲裁,转为接收模式。
- 如果自己发送的是显性(0),或者发送隐性且读到隐性,则继续发送下一位。
- 重复直到仲裁场结束,最后剩下的那个节点赢得总线。
关键点:仲裁是“边发边听”的。节点在发送每一位的同时,也在监听总线。这种机制叫“回读(Read-back)”。
举个例子。假设三个节点同时发送:
| 节点 | ID(二进制) | 优先级 |
|---|---|---|
| A | 0b1010 0011 00 | 中 |
| B | 0b0110 0101 10 | 高 |
| C | 0b1100 1001 01 | 低 |
仲裁过程:
- 第1位:A发1(隐性),B发0(显性),C发1(隐性)。总线显性。A和C读到显性,知道自己输了,退出。B继续。
- 后续位:B独占总线,完成发送。
你想想看,整个过程只用了几个位时间就完成了仲裁,效率极高。
3.3 ID优先级:数字越小,优先级越高
CAN的ID优先级规则很简单:ID值越小,优先级越高。因为显性(0)优先于隐性(1),所以ID中高位为0的节点,在仲裁中占优势。
标准帧(11位ID)的优先级范围:0x000(最高)~ 0x7FF(最低)。
扩展帧(29位ID)的优先级范围:0x00000000(最高)~ 0x1FFFFFFF(最低)。
注意:扩展帧的ID虽然长,但仲裁时是从ID的MSB开始逐位比较的。所以扩展帧的优先级并不比标准帧高——实际上,标准帧的RTR位位置更靠前,在某些情况下标准帧反而有优势。
我个人习惯在项目初期就规划好ID分配策略:
- 紧急消息(如刹车、安全气囊):ID 0x000~0x0FF
- 实时控制消息(如发动机转速、车速):ID 0x100~0x2FF
- 状态信息(如温度、故障码):ID 0x300~0x4FF
- 诊断和配置消息:ID 0x500~0x7FF
我曾经在一个项目中,把刹车消息的ID设成了0x700,结果在总线负载高时,刹车消息被其他消息堵住了。嗯,从那以后我再也不敢把关键消息的ID设大了。
3.4 非破坏性仲裁:赢了继续,输了不丢
这是CAN仲裁最优雅的地方。传统的总线仲裁(比如以太网的CSMA/CD)是“破坏性”的——冲突发生后,所有节点都停止发送,然后随机等待重试。这会导致带宽浪费和延迟不确定。
CAN的非破坏性仲裁不同:
- 赢了的节点:继续发送完整帧,不受任何影响。
- 输了的节点:在仲裁失败的位立即停止发送,自动转为接收模式。它不会重发,而是等待下一次总线空闲时重新尝试。
整个过程:
- 没有数据损坏
- 没有带宽浪费
- 延迟可预测(优先级高的消息总能及时发出)
非破坏性仲裁的三大好处:
- 实时性:高优先级消息的延迟有上界,适合硬实时系统。
- 效率:仲裁过程不浪费带宽,总线利用率高。
- 确定性:系统行为可预测,便于分析和测试。
我建议你在设计CAN网络时,一定要利用好这个特性。把时间关键的消息分配小ID,把非关键消息分配大ID。这样即使总线负载达到80%以上,关键消息也能准时到达。
3.5 仲裁过程图解
下面这张图展示了三个节点同时发送时的仲裁过程:
从图中可以看到:节点B的ID最小(0x196),在第二位就赢了仲裁。节点A和C在读到总线显性后,立即退出。整个过程干净利落。
3.6 避坑指南
我曾经踩过的坑:
- ID分配不合理:把高频率消息的ID设得太大,导致低优先级消息饿死。后来我改用“优先级分组+动态ID调整”策略。
- 忽略RTR位的影响:远程帧的RTR位是隐性(1),数据帧的RTR位是显性(0)。所以相同ID的数据帧优先级高于远程帧。如果你用远程帧请求数据,要小心这个优先级差异。
- 扩展帧的误判:有些人以为扩展帧优先级更高,其实不是。仲裁只看位序列,不看帧类型。
我的建议:
- 项目初期就制定ID分配规范,留出扩展空间。
- 关键消息的ID尽量用0x000~0x0FF。
- 用CAN分析仪抓一下总线,看看实际仲裁过程中有没有异常。
- 如果总线负载超过60%,考虑增加波特率或拆分网络。
3.7 小结
CAN的仲裁机制,核心就四点:
- 线与逻辑:显性覆盖隐性,物理层保证。
- 逐位仲裁:边发边听,一位一位比。
- ID优先级:数字越小,优先级越高。
- 非破坏性仲裁:赢了继续,输了自动退,不丢数据。