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 小结一下

冲突的本质,从物理层到应用层,是一条链:

  1. 电平竞争:两个驱动器同时使能,电压不确定,甚至烧硬件
  2. 数据帧碰撞:字节交错,CRC 必错,从站不响应
  3. 响应超时:主站等不到回复,触发超时处理
  4. 重试风暴:多个主站同步重试,总线陷入死循环

我曾经在培训课上问学员:你们觉得哪个环节最致命?

有人说是电平竞争,有人说是碰撞。我的答案是——重试风暴。因为前三个是偶发的,但重试风暴是自我放大的。一旦触发,不干预就不会停。

下一章,我会讲怎么从根源上避免这些冲突。咱们聊聊静态仲裁动态调度这两种思路。到时候我会拿出我实际用过的代码和时序图,咱们一起分析。