3. CAN总线仲裁机制:CSMA/CA与位仲裁原理、优先级判定
各位,咱们今天聊一个硬核话题——CAN总线怎么解决“多个人同时抢着说话”的问题。
你想想看,CAN总线上一堆节点,谁想发数据就发。要是两个节点同时往总线上丢数据,那不就乱套了?
我刚开始接触CAN的时候,也觉得这问题挺棘手。后来看了它的仲裁机制,不得不服——这设计确实巧妙。
3.1 CSMA/CA:先听再说,边说边听
CAN用的不是CSMA/CD(碰撞检测),而是CSMA/CA(碰撞避免)。这两个词就差一个字母,但思路完全不同。
- CSMA/CD:先听总线,没人用就发。发完发现撞了,退避重发。以太网就是这套路。
- CSMA/CA:先听总线,没人用就发。但发的时候还在听,发现“撞了”立刻用优先级压住对方。
说白了,CAN的仲裁不是“撞了再处理”,而是“在撞的一瞬间就分出胜负”。
核心要点:CAN的仲裁是无损位仲裁。赢的节点继续发,输的节点自动退出发送。整个过程不丢数据,不浪费带宽。
我记得第一次在示波器上看到仲裁波形时,确实挺震撼的。两个节点同时发,波形在某个位突然“合并”成一条线——那就是赢家诞生了。
3.2 位仲裁原理:显性位“吃掉”隐性位
要理解仲裁,先得搞清楚CAN总线的两个电平状态:
| 电平状态 | 逻辑值 | 物理表现 |
|---|---|---|
| 显性(Dominant) | 0 | CAN_H高,CAN_L低,差分电压约2V |
| 隐性(Recessive) | 1 | CAN_H和CAN_L都约2.5V,差分电压0V |
关键规则就一句话:显性位(0)会覆盖隐性位(1)。
怎么理解?你想象一下:总线就像一根绳子,谁拉它就往哪边偏。显性位是“用力拉”,隐性位是“松手”。两个人同时拉,绳子肯定往用力的人那边偏。
所以,当两个节点同时发送时:
- 如果一个发0,另一个发1,总线最终呈现0(显性胜出)
- 发1的那个节点发现“我发的明明是1,怎么总线上是0?”——立刻知道自己输了
我的经验:调试时经常用逻辑分析仪抓仲裁段。你看到ID位上有“毛刺”或“竞争”波形,别慌——那是正常仲裁过程。只要最终数据帧完整,就没问题。
3.3 仲裁过程:逐位PK,ID小的赢
仲裁发生在帧起始(SOF)之后的仲裁段,也就是11位(或29位)标识符的位置。
流程是这样的:
- 总线空闲时,多个节点同时发送SOF(显性位)
- 从ID的最高位(bit 10)开始,逐位比较
- 每个节点发送一位,同时读取总线电平
- 如果发送的位与读取的一致,继续下一位
- 如果发送的是1(隐性),但读到的是0(显性),立刻退出
举个例子:
- 节点A:ID = 0x321(二进制:011 0010 0001)
- 节点B:ID = 0x123(二进制:001 0010 0011)
从最高位开始比:
| 位位置 | 节点A | 节点B | 总线结果 | 说明 |
|---|---|---|---|---|
| bit 10 | 0 | 0 | 0 | 相同,继续 |
| bit 9 | 1 | 0 | 0 | 节点A发1但读到0,A退出 |
| ... | ... | ... | ... | 节点B继续发送 |
结果:节点B(ID=0x123)胜出,节点A自动转为接收状态。
注意:ID值越小,优先级越高。0x000是最高优先级,0x7FF是最低(标准帧)。我见过有人把ID设成0x7FF,结果每次仲裁都输,数据死活发不出去——这就是典型的“优先级饥饿”。
3.4 扩展帧与标准帧的仲裁差异
CAN 2.0B支持两种帧格式:
- 标准帧:11位ID,仲裁段共12位(SOF + 11位ID + RTR)
- 扩展帧:29位ID,仲裁段共32位(SOF + 11位基本ID + SRR + IDE + 18位扩展ID + RTR)
两种帧混用时,仲裁规则有讲究:
| 比较位置 | 标准帧 | 扩展帧 | 仲裁结果 |
|---|---|---|---|
| SOF | 0 | 0 | 相同 |
| ID bit 10-0 | 11位ID | 11位基本ID | 按位比较 |
| SRR位 | (无此位) | 1(隐性) | 标准帧RTR位(0)胜出 |
| IDE位 | 1(隐性) | 0(显性) | 扩展帧IDE(0)胜出 |
简单说:标准帧的优先级高于扩展帧。因为标准帧在SRR位位置上是RTR位(显性),而扩展帧的SRR是隐性。所以标准帧先赢一局。
但IDE位又让扩展帧扳回一城——扩展帧的IDE是显性,标准帧的IDE是隐性。不过这时候标准帧已经赢了,IDE位根本轮不到比。
嗯,这里要注意:实际项目中,我建议不要混用标准帧和扩展帧。除非你有明确的兼容性需求,否则统一用一种格式,省得给自己挖坑。
3.5 仲裁失败后的行为:自动重发机制
节点仲裁失败后,会做什么?
- 立即停止发送,转为接收模式
- 记录当前发送失败的帧
- 等待总线空闲后,自动重发该帧
这个重发是硬件自动完成的,不需要软件干预。你只管把数据往发送缓冲区一丢,硬件会帮你搞定仲裁和重发。
避坑指南:我曾经遇到一个情况——某个节点频繁仲裁失败,导致数据延迟很大。查了半天,发现是ID设得太大了(优先级低),再加上总线负载高,它几乎每次都要重发好几次才能成功。解决方案:要么提高它的ID优先级,要么降低发送频率。
另外,重发次数不是无限的。CAN控制器有发送错误计数器(TEC),每次发送失败都会累加。如果TEC超过255,节点会进入总线关闭(Bus-Off)状态,彻底停止通信。这个后面讲错误处理时会细说。
3.6 仲裁机制的知识图谱
下面这张图把仲裁机制的核心逻辑串起来了:
3.7 实际调试中的仲裁问题
最后聊几个我在现场踩过的坑:
- 总线负载过高导致仲裁频繁:当总线利用率超过70%时,仲裁冲突概率急剧上升。我建议把负载控制在50%以下,留出余量。
- ID分配不合理:实时性要求高的节点(如电机控制)用低ID,非实时节点(如温度采集)用高ID。别搞反了。
- 隐性位干扰:如果总线终端电阻或电平有问题,隐性位可能读成显性位,导致误判。检查CAN收发器和终端电阻是第一步。
重要提醒:仲裁机制依赖所有节点的位时序严格同步。如果某个节点的时钟偏差太大,仲裁会出错。CAN的位同步机制(硬同步+软同步)就是为了解决这个问题。下一章我们会详细讲。
好了,仲裁机制就聊到这儿。记住一句话:CAN的仲裁,就是一场“谁更低调(ID更小)谁赢”的比赛。理解了这个,后面CANopen的优先级管理就好办了。