2、冲突的本质:总线电平竞争、数据帧碰撞、响应超时与重试风暴
好,咱们接着聊。上一章我讲了多主站的基本概念,说白了就是多个老大抢一条总线。那问题来了——抢的时候会发生什么?
冲突的本质,其实就三件事:电平打架、数据撞车、以及连锁反应。我做了十几年现场总线,见过太多工程师栽在这三个坑里。今天咱们一个一个拆开看。
2.1 总线电平竞争——物理层的“硬碰硬”
Modbus RS-485 总线,用的是差分信号。逻辑“1”靠 A 线比 B 线高 200mV 以上,逻辑“0”则反过来。正常情况下,总线上只有一个主站在驱动电平。
但两个主站同时发送呢?
一个发“1”,一个发“0”。A 线上,一个往上拉,一个往下拽。结果就是电平不确定。我见过最夸张的一次,示波器上波形直接变成了一条模糊的带状,根本读不出数据。
关键点:RS-485 是电压驱动的,不是电流型。两个驱动器同时使能,轻则数据错误,重则烧毁驱动芯片。
为什么会这样?因为 RS-485 芯片的输出级是推挽结构。一个推高,一个拉低,相当于电源对地短路。电流瞬间飙升。我记得有一次在化工厂调试,现场就烧了三块 485 隔离模块。从那以后,我养成了一个习惯——所有多主站项目,必须加总线仲裁或使能互锁。
2.2 数据帧碰撞——协议层的“车祸现场”
电平竞争是物理现象,数据帧碰撞就是协议层面的结果。
Modbus 的帧结构很简单:地址 + 功能码 + 数据 + CRC。两个主站同时发,字节在总线上交错。接收方收到的就是一堆乱码。CRC 校验基本过不了。
我举个例子:
主站A发送:01 03 00 00 00 02 C4 0B
主站B发送:02 03 00 01 00 01 D5 F1
实际总线: 01 02 03 03 00 00 00 01 ...(完全乱掉)
从站收到这种数据,会怎么处理?
- 地址字节可能被覆盖,从站根本不认领
- 功能码可能变成非法值,从站返回异常码
- CRC 几乎必错,从站直接丢弃
你想想看,从站其实很无辜。它不知道总线上有几个主站在打架,它只知道收到的数据不对。于是它沉默,或者返回异常。
我的经验:判断是否发生了碰撞,最简单的方法是用示波器抓总线空闲时间。正常帧之间应该有 3.5 字符时间的静默。如果频繁出现帧间距小于这个值,大概率是多个主站在抢总线。
2.3 响应超时——从站“装死”了?
主站发完请求,就开始等响应。等多久?一般设个超时时间,比如 100ms 或 500ms。
但碰撞发生后,从站要么没收到完整请求,要么 CRC 校验失败。它不会回复。主站等啊等,超时了。
超时本身不是大问题。问题是——主站怎么处理超时?
很多工程师写代码,超时后就重发。这本身没错。但在多主站场景下,重发可能让情况更糟。
我见过一个项目,三个主站轮询同一个从站。每个主站超时时间都是 200ms,重试次数 3 次。结果呢?
| 时间点 | 事件 |
|---|---|
| T0 | 主站A发送请求,碰撞发生 |
| T0+200ms | 主站A超时,重试 |
| T0+200ms | 主站B也超时,重试 |
| T0+200ms | 再次碰撞 |
| T0+400ms | 两个主站同时第二次重试 |
看到了吗?超时时间一样,重试策略一样,结果就是同步重试。每一次重试都撞车,直到重试次数耗尽。
注意:同步重试是灾难性的。它会让总线陷入死循环,所有主站都拿不到数据,但总线却一直繁忙。
2.4 重试风暴——连锁反应来了
重试风暴,就是上面那个过程的放大版。
一个主站超时重试,导致更多碰撞。更多碰撞导致更多超时。更多超时导致更多重试。最终,总线被重试帧占满,正常通信完全瘫痪。
我曾在一条生产线上遇到过。12 个 PLC 通过 Modbus 读取同一个仪表的数据。每个 PLC 的轮询周期是 100ms,超时 50ms,重试 5 次。
刚开始还能跑,偶尔丢一帧。但某个时刻,一个 PLC 因为干扰丢了一帧,触发重试。重试又撞上另一个 PLC 的正常轮询。连锁反应开始。
30 秒后,总线利用率接近 100%,但有效数据吞吐量几乎为零。所有 PLC 都在重试,没有一台能读到正确数据。
最后怎么解决的?
- 把重试次数从 5 次降到 2 次
- 给每个主站加随机退避时间(10~50ms 随机)
- 把轮询周期错开,避免同步
嗯,其实说白了,就是不要让大家同时说话。如果非要说,那就让每个人等一个随机的时间再开口。
2.5 小结一下
冲突的本质,从物理层到应用层,是一条链:
- 电平竞争:两个驱动器同时使能,电压不确定,甚至烧硬件
- 数据帧碰撞:字节交错,CRC 必错,从站不响应
- 响应超时:主站等不到回复,触发超时处理
- 重试风暴:多个主站同步重试,总线陷入死循环
我曾经在培训课上问学员:你们觉得哪个环节最致命?
有人说是电平竞争,有人说是碰撞。我的答案是——重试风暴。因为前三个是偶发的,但重试风暴是自我放大的。一旦触发,不干预就不会停。
下一章,我会讲怎么从根源上避免这些冲突。咱们聊聊静态仲裁和动态调度这两种思路。到时候我会拿出我实际用过的代码和时序图,咱们一起分析。