Modbus 从站的地址冲突与重复应答:柜门里并排两块表,拨码都是 3——一个月开了三次柜门,我每次都只看了要换的那块
一、现象:抄表 12.4,EMS 36.8
2025 年 9 月,一个 1.2MW 工商业屋顶光伏。8 台并网柜,每柜一块三相多功能表,485 手拉手串到采集器,采集器 4G 上云,EMS 做功率曲线和发电量报表。
运维报上来两件事,看起来不搭界:
- 3 号柜抄表对不上。 柜门上那块表显示 12.4kW,EMS 通道 3 显示 36.8kW。站一会儿再看,又回到 12.4。一个月里被抓到两次,都拍了照。
- 功率曲线每天有几个点翻三倍。 5 分钟一个点,一天 288 个点,总有七八个点从 12kW 窜到 36kW,下一个点自己回来。
采集器上的通信质量统计:100.0%。没有超时,没有 CRC 错,没有告警。整站的日报表里,发电量和逆变器汇总差 1~3%,方向随机,有时多有时少。
这就是这类故障最气人的地方:它不制造“读不到”,它制造“读到了别的”。读不到会被发现,读到了别的不会。
二、一个月里排除的四件事
- 换表。 3 号柜的表换了两块:第一块怀疑采样通道坏,第二块是刚到的备件。换完照旧。
- 换 CT。 怕是 400/5 那组二次侧接触不良,整组换了。照旧。
- 换采集器。 怀疑 485 口带载能力不足,换了一台同型号。照旧。
- 物理层按 RS485/Modbus 抗干扰实战 那篇重做了一遍:加 120Ω 终端、换双绞屏蔽线、单点接地、把 485 和动力线分开走。照旧。
到这一步,常规排查已经走完了。剩下的解释只有两个:要么表本身有问题,要么“这个数根本不是这块表给的”。
第二个解释一直没人提,因为它听起来不像故障——表就在柜子里,地址就是 3,还能是谁给的?
三、把 485 摘下来,量一次响应时间
把采集器的 485 摘掉,USB 转 485 直接挂到总线末端,用 Modbus Poll 读 addr 3 的 0x0000 起 16 个寄存器,读 200 次,每次记录“请求最后一个字节发完”到“响应第一个字节到达”的时间。
| 响应延迟 | 次数 | 占比 |
|---|---|---|
| 3.0~3.4ms | 197 | 98.5% |
| 105~108ms | 3 | 1.5% |
两个峰,中间是空的。
抖动不会长这样。干扰、重传、网关排队,都是把分布摊宽;重传就算长出第二个峰,也落在超时值上(1000ms 那个量级),不会是 105ms。两个峰的意思是:有两个设备在回答同一个地址,一个 3ms 就答,一个要 105ms。
打开 3 号柜门。并排两块表:左边发电量表,计量组串逆变器的输出;右边关口表,计量这个并网点的上网电量。施工时图纸上这个并网点都标着“3#”,两块表的拨码都拨成了 3。
一个月里开了三次柜门,每次都只看了要换的那块。
四、电气层:两个驱动器对撞,差分去哪了
先解释一个反直觉的现象:两台同时驱动,为什么不是每次 CRC 都错?
RS485 是差分推挽驱动。正常一台发送时,A、B 之间是 1.5~3V 的差分方波。两台同时发送,且某一位电平相反时,两个驱动器一个往上拉、一个往下拽:
- 每只驱动器的单端输出阻抗 Rout ≈ 40Ω;
- 总线两端各一只 120Ω 终端,并联 60Ω;
- 差分开路电压 Vod ≈ 3.3V。
节点方程一列就清楚了:A 点被驱动器 1 拉到 Vcm+Vod/2、被驱动器 2 拉到 Vcm−Vod/2,两个源内阻相同,A 点正好落在中点 Vcm=2.5V;B 点同理,也落在 2.5V。
差分严格为 0。 不是“被拉低到某个值”,是两个驱动器把 A、B 一起拽到共模中点上,差分归零。
真实器件有失配,残余差分是:
Vab = Vod × (R2 − R1) / (R1 + R2)
| Rout 失配 | R2 | 残余 Vab | 落在哪 |
|---|---|---|---|
| 0% | 40.0Ω | 0.000V | 不确定区正中 |
| 5% | 42.0Ω | 0.080V | 不确定区(−200~+200mV) |
| 10% | 44.0Ω | 0.157V | 不确定区 |
| 15% | 46.0Ω | 0.230V | 刚过门限,可能被读成固定 1 |
| 20% | 48.0Ω | 0.300V | 读成固定电平 |
| 30% | 52.0Ω | 0.430V | 读成固定电平,但远低于 1.5V 有效电平 |
这就是“不是每次都对、也不是每次都错”的物理来源:残余差分只有几十到几百毫伏,落在接收器的判决门限附近,谁赢取决于器件失配、温度、终端和线长。同一对表,上午错得多、下午错得少,是正常的。
顺带一个常被忽略的数:对撞时每只驱动器要灌/拉
I = (Vod/2) / Rout = 1.65V / 40Ω = 41.25mA
41mA 在驱动器短路保护能力(一般 35~250mA)之内,不会立刻烧,但长期对着灌会热。现场见过“这对表一年换三块”的,交易对手就是另一台同地址的表——换的那块每次都在替别人扛电流。
示波器怎么接:
- CH1 接 A、CH2 接 B,数学通道算 A−B;
- 正常帧:|A−B| ≥ 1.5V,方波干净;
- 冲突段:|A−B| < 0.3V,而且 A、B 两条单端波形同时向 2.5V 收敛;
- 冲突段的起点,就是两台数据第一个不同的位。
最后一条最有用:如果 A、B 没有同时往 2.5V 走,那就不是地址冲突,老老实实回去查干扰、终端和地环流。
五、字节层:前三个字节永远是对的
Modbus RTU 的响应帧,前三个字节是:从站地址、功能码、字节数。
两台同地址、同功能码的表,这三个字节逐位相同。所以:
- 冲突损伤从第 4 个字节开始,前 3 个字节永远是好字节;
- 主站先收到长度字段,再收数据 → 长度可信,内容不可信;
- 冲突不会表现成“帧头乱码”,它表现成“帧长度正确、数据值不对”。
CRC-16 对随机损坏的漏检率是 1/65536。但冲突不是随机损坏,它是“前 k 个字节来自 A、后面来自 B”的混血帧,切点有 40 个(41 字节的响应)。每个切点都有 1/65536 的概率恰好 CRC 正确:
40 / 65536 = 0.061% = 1/1638 次冲突
也就是说,每 1638 次冲突,就有一次会产出一帧 CRC 完全正确、数值完全错误的“合法帧”。按现场每天 374 次冲突算,平均 4.4 天出一次。这种帧任何统计都抓不到,只能靠“值和表面读数对不上”来发现——而抄表一个月才抄两次。
还有一个更极端的特例:两台表的读数完全相同。同一条馈线、同变比、同负载,两块表读出来一模一样,整帧字节全同,冲突不产生任何 CRC 错,也不产生任何错值。通信、数据、波形全都正常,只有写命令会双执行。 这种站永远不会因为读数被发现,只会在某次“远程跳闸”时跳错柜。
六、时序层:三种结局,一条谱系
把两台从站的响应延迟记作 d1、d2,响应帧时长 Tframe=46.98ms(41 字节 @9600 8E1)。结局只有三种:
| 条件 | 总线上发生什么 | 主站看到什么 |
|---|---|---|
| d1 + Tframe < d2 | A 整帧发完,B 才开口,两帧首尾相接 | 收到 A 的完整帧,CRC 对;B 的帧成为“多余字节”被清掉 |
| d2 + Tframe < d1 | 反过来 | 收到 B 的完整帧,CRC 对 |
| 其余(两帧重叠) | 字节级对撞 | CRC 错(99.94%),或极偶然的混血合法帧 |
所以“正确率”完全由延迟分布决定。同一套代码、同一个故障,四种现场组合给出四个完全不同的数字:
| 场景 | d1 (ms) | d2 (ms) | 正确 | 合法错值 | CRC 错 |
|---|---|---|---|---|---|
| 两台都挂 200ms 计量节拍 | U(0,200) | U(0,200) | 29.3% | 29.3% | 41.5% |
| 两台同型号直答 | U(2,60) | U(2,60) | 1.8% | 1.8% | 96.4% |
| 一快一慢、慢表可能抢在前 | U(60,180) | U(95,115) | 1.3% | 23.4% | 75.3% |
| 现场:快表 + 关口表 | 99%×U(2,8) + 1%×U(100,220) | U(95,115) | 99.0% | 0.57% | 0.43% |
(U 表示均匀分布,数字由 slave_addr_conflict_calc.py 网格积分算出,见文末。)
这张表解释了为什么这个故障在站内挂了整整一个月:它的正确率是 99%。一天 1440 次采样,只有 8 次错值;一个月两次抄表,正好抓到两次。
七、“读十次三次对”只是它的一种形态
很多资料把“读十次有三次对”当成地址冲突的典型症状。对,但只对其中一行。
第一行那种组合——两块表都把应答挂在 200ms 的计量节拍上(请求落在周期内任意位置,答在下一个节拍点)——延迟就是 U(0,200),正确率 29.3%,正好“十次三次”。这类表在配电网里很常见:电表要等 DSP 算完一个周波窗口才给数,响应不是“立刻”,而是“下一个节拍”。
第二行是两台同型号的 MCU 直答表,延迟都挤在 2~60ms 的窄带里,重叠概率 96.4%——十次错九次半。这种站一天就会被发现,反而活不长。
第四行才是现场这种:一块 3ms 的快表,一块 105ms 的关口表,延迟差 102ms,绝大多数时候两帧根本不重叠,快表永远先到——永远读到同一台,看起来完全正常。只有当快表偶然撞上自己的计量窗口(约 1% 的请求),延迟跳到 100~220ms,两台才在时间上撞到一起。
所以判据不是“对几次”,而是这两个:
- CRC 错和“合法错值”同时存在。纯干扰只产生前者,纯配置错只产生后者,两者并存几乎只能是同地址双机。
- 响应时间双峰。这是唯一一个不依赖“这次谁赢”的判据,因为延迟是设备属性,不是随机结果。
前者要靠长期统计,后者 200 个样本、12 秒就能看出来。能一次定性的事,别熬一个月。
八、指标为什么全绿:三重掩盖
第一重:重试。 该通道 CRC 错率 0.43%,重试 1 次后剩 0.0019%,重试 2 次后剩 8×10⁻⁶%。整站 8 路,只有 1 路冲突,成功率算下来 100.00000%。EMS 上就是 100.0%,任何阈值都不会响。
第二重:稀释。 即使不重试,整站成功率也是 (7 + 0.9957)/8 = 99.95%。一个 99.95% 的指标,在“通信质量”这种常年 99.x% 的栏目里毫无异常感。
第三重:合法错值。 那 0.57% 的错值帧,CRC 正确、长度正确、功能码正确,在统计里是“成功”。它连“错误”这个桶都进不去。
三重叠加的结果:故障持续 30 天,所有监控指标正常,唯一异常的是“抄表对不上”——而抄表是人做的,人会怀疑自己看错了。
顺带算一下冲突对总线负载的影响:374 次/天 × 60.16ms = 22.5 秒/天,占空比 0.026%。所以它也不会通过“周期变长”“轮询变慢”露出马脚。
九、伤害落在哪:四条通道,四种命运
同一个故障,同一个 0.57% 的错值率,在四条数据通道上的表现完全不同:
(1)通信质量:完全无感。 见上一节。
(2)功率曲线:满身毛刺。 5 分钟一个点,288 点/天,含错值 8.2 个,占 2.8%。错值来自关口表(全厂总负荷,36.8kW),本表 12.4kW,倍数 2.97。曲线看上去就是“每天七八个点莫名翻三倍”。
若这条曲线进 15 分钟需量统计,一个桶 15 个样本里 1 个错,桶平均被抬高:
(2.97 − 1) / 15 = 13.1%
如果这个桶恰好是当月最大需量,整月的基本电费就按抬高后的数收。1250kVA 的变压器、需量 800kW、单价 40 元/kW·月,多交约 4200 元;一年踩中两次就是八千。钱不多,但性质是“数据错误直接变成账单”。
(3)发电量报表(增量累加):几乎无感。 按“逐次增量累加”算,一次错值只丢掉一个 60s 增量:
12.4kW × 60s = 0.207 kWh
8.2 次/天 = 1.7 kWh/天,日发电量 4800 kWh,误差 0.035%。这就是为什么日报表和逆变器汇总只差 1~3%,且方向随机——那是限幅丢弃后残留的边角。
(4)日电量(末值 − 首值):一个月崩一次。 如果哪位把日电量写成 “末值 − 首值”:
- 末值踩中错值的概率 0.57%/天;
- 30 天内至少踩中一次的概率 15.7%;
- 踩中那天,末值是另一块表的累计值,两个基数完全不同,差出来是个几万度的数——要么被限幅丢弃(当天电量归零),要么直接算成负数(表倒走告警)。
同一个故障,四条通道四种命:指标 100%、曲线满身刺、电量几乎无感、换个数法就崩。 这也是为什么“先看指标”这种排查顺序,在这种故障上必然失败——指标恰恰是受损最轻的那条通道。
十、写命令:一次跳闸会跳两个柜
读错了只是数据问题,写错了是安全问题。同地址两台从站,对任何写命令都是“收到就执行”:
| 功能码 | 用途 | 发生什么 | 后果 |
|---|---|---|---|
| FC05 单线圈 | 远程跳闸/合闸 | 两台都动作 | 跳错柜 |
| FC06 单寄存器 | 写 CT 变比 | 两台都写 | 本该 200/5 的被写成 400/5,计量差一倍 |
| FC06 单寄存器 | 复位需量 | 两台都清 | 另一台的需量记录永久丢失,不可恢复 |
| FC16 多寄存器 | 写费率时段 | 两台都写 | 分时电量错段,结算错 |
| FC08 诊断 | 改波特率/校验 | 两台都改 | 下次上电两台一起失联 |
| FC03 读 | — | 两台都答 | 看谁先到(延迟接近时各半) |
一次 FC06 是 8 字节请求 + 8 字节响应 = 18.33ms,两台执行不增加任何总线时间——写完一点痕迹都没有。等发现“怎么这台表的变比也变了”,已经是几天后。
所以发现同地址之后,第一条纪律不是“定位”,是 先停写。把该地址在点表里改成只读,或者直接把采集器的写功能关掉,再动手排查。这一条比后面所有技术手段都重要。
十一、五种现场检测方法
| 方法 | 耗时(8 台) | 判据 | 盲区 |
|---|---|---|---|
| 钳形表夹 A/B(mA 档) | 40 min | 一次请求出现两次发送电流脉冲 | 脉冲只有 47ms,要峰值保持或示波器 |
| 逐台拔 485 端子 | 240 s | 拔掉某台后另一台仍有数 | 要停该路数据,影响报表 |
| 响应时间直方图 | 12.0 s(200 样本) | 双峰,峰间距 = 两台延迟差 | 峰间距 < 6σ 时要更多样本 |
| 连读 SN 20 次 | 1.2 s | 出现两个不同的 SN | 延迟稳定时永远同一台,假阴性 |
| 从站上电静默监听 | 2 s | 听到本地址请求、自己没答却有数据 | 必须固件支持 |
现场这一例(错值率 0.57%)的命中率:
- 钳表 / 拔线 / 监听:接近 100%,物理与语义判据;
- 直方图:100%,峰间 102ms、抖动 3ms,34σ;
- 连读 SN 20 次:只有 10.7%(1−(1−0.0057)²⁰)。这是最容易踩的坑——很多人第一反应是“读 SN 对一下”,读完发现 20 次全一样,就排除了地址冲突,然后继续换表。
SN 法只在“两台延迟接近”时好用(第六节那张谱系表的第一、二行);延迟差稳定的站,SN 永远是同一个,因为永远是同一台在答。
钳表那条要补一句实用细节:夹单根 A 或 B,mA 档,让采集器保持轮询。正常一台表被问到时有一个 47ms 的发送电流;同地址两台,一次请求会出现两个脉冲(间隔 = 延迟差),或者一个幅值翻倍的脉冲(两台几乎同时开口)。前者比后者好认。
十二、从站侧:上电听两秒,发送时回读
上面五种都是外部手段,从站自己其实可以做两件事。
(1)上电静默监听。 上电后 2 秒内不驱动 DE,只听总线:
/* 上电静默窗口:自己不发送,只看总线上有没有“替我答”的帧 */
static uint8_t addr_conflict;
void bus_listen_boot(uint8_t my_addr, uint32_t ms)
{
uint32_t t0 = HAL_GetTick();
uint8_t buf[64];
while (HAL_GetTick() - t0 < ms) {
int n = uart_read_frame(buf, sizeof(buf), 20); /* 20ms 空闲判帧 */
if (n < 5) continue;
/* 以本地址开头 + CRC 正确 + 帧形态是“响应”(不是请求)*/
if (buf[0] == my_addr && crc16_ok(buf, n) && is_response(buf, n)) {
addr_conflict = 1; /* 我一个字都没发,却有人在答 */
break;
}
}
if (addr_conflict) {
led_fast_blink(); /* 面板可见 */
report_flag(FLAG_ADDR_CONFLICT);
}
}
判帧阈值该取多少,从站接收状态机那篇 里算过:IDLE 的阈值是 1 个字符,比 T3.5 早 71.4%。这里用 20ms 是因为监听只关心“有没有一整帧”,不关心 3.5 字符的规范边界,宁可慢一点、别切碎。
判据的关键在 is_response():请求帧和响应帧都以地址开头,靠“FC 后的字节语义 + 长度”区分(FC03 请求 8 字节、响应 5+2N 字节;FC06 请求和响应都是 8 字节,就要靠“值是否等于刚发的值”来区分,实际实现里 FC06 的监听判据要放宽)。区分不了的功能码,宁可不判,也别误报。
(2)发送时回读。 半双工 485 在 DE 有效期间,多数收发器的接收器仍然工作,RX 上看到的就是总线实际电平:
/* 边发边比:自己发出去的字节,和自己听到的字节 */
static uint16_t tx_conflict_bytes;
void rs485_send_verified(const uint8_t *p, uint16_t n)
{
while (USART1->SR & USART_SR_RXNE) (void)USART1->DR; /* 清掉上一次的残留 */
DE_HIGH();
for (uint16_t i = 0; i < n; i++) {
USART1->DR = p[i];
while (!(USART1->SR & USART_SR_TXE)) ;
while (!(USART1->SR & USART_SR_RXNE)) ; /* 等回读字节到 */
if ((uint8_t)USART1->DR != p[i]) tx_conflict_bytes++;
}
while (!(USART1->SR & USART_SR_TC)) ;
DE_LOW();
}
两个前提必须先验证,否则这段代码只会给你一堆假阳性:
- 收发器要支持回读。有些型号 DE 有效时会把接收器关掉,回读恒为 0xFF 或 0x00。先做一次自环:正常发送时回读应逐字节等于发送值,不等就说明器件不支持,这段代码直接放弃。
- 阈值要抗噪。单字节不一致可能来自反射或终端失配,至少“连续 2 字节不一致”或“整帧 ≥3 字节不一致”才计一次冲突。
它的盲区必须说清楚:回读只能抓“我发送期间被覆盖”。如果对方先发完、我后发(第六节的第一、二行),我的帧完好无损,回读一字不差——但主站拿的是对方的数。从站自检抓不到“串行双帧”,只有主站侧的响应时间直方图能兜底。 这两件事必须配着上,缺一个就有一半的故障漏在缝里。
十三、上电扫描与直方图:软件能做的两件事
(1)扫描。 单事务 60.16ms,扫描成本很好算:
| 范围 | 耗时 |
|---|---|
| 配置表 8 个地址 × 5 次 | 2.4 s |
| 全地址 1~247 × 2 次 | 29.7 s |
| 全地址 1~247 × 5 次 | 74.3 s |
两条纪律:
- 地址 0 不扫。 广播会让所有从站执行写命令,扫描器不该有写能力,更不该扫 0。
- 扫描只能读。 读到 SN 也不能据此改地址——同地址两台会一起改,改完还是冲突。
(2)直方图。 这是投入产出比最高的一件事:主站本来就知道每次请求发出和响应到达的时刻,只是没记。
/* 每次事务记一条,环形缓冲 200 条足够 */
typedef struct { uint16_t dt_ms; uint8_t addr; uint8_t fc; } rsp_sample_t;
判据:
- 按 5ms 分桶做直方图;
- 主峰之外某个桶的占比 > 0.5%,且是该桶邻域本底的 3 倍以上,且与主峰间隔 > 20ms → 双峰;
- 峰间距就是两台从站的延迟差,可以直接算出冲突概率(第六节那张表)。
可分性由 Δ/σ 决定:
| 峰间距 Δ | 抖动 σ | Δ/σ | 需要的样本(每峰) |
|---|---|---|---|
| 102ms | 3ms | 34.0 | 5 |
| 20ms | 3ms | 6.7 | 5 |
| 10ms | 3ms | 3.3 | 15 |
| 5ms | 3ms | 1.7 | 60 |
| 2ms | 3ms | 0.7 | 300(基本分不开) |
现场这一例 Δ=102ms、σ≈3ms,不到 10 个样本就能看出两个峰。而同型号两块表那种 Δ=5ms 的组合,要 200 个样本,还得按“请求发出时刻”对齐(否则计量节拍会把分布抹平)。
十四、现场处置:六步,别跳
- 停写。 点表里把该地址改只读,或关掉采集器的写功能。先做这个,再动别的。
- 定位。 直方图看双峰,或者开柜门逐台拔 485 端子。拔线比断电好——断电会丢计量,拔 485 只丢通信。
- 隔离后改地址。 同地址的两台不能同时在线改,改了还是冲突。必须物理断开其中一台,单独改,改完读一次 SN 确认。
- 改完复验。 直方图回到单峰、钳表每次请求只剩一个电流脉冲;再连读 SN 20 次确认(这条只当辅助,它的假阴性率很高);表面读数和 EMS 读数一致。
- 补台账。 把“物理柜号 / 柜门丝印 / 表 SN / 从站地址 / 点表通道”五列对齐,写进竣工资料。网关 unit id 与地址映射 那篇里那张三方对照表,就是为这一步准备的。
- 改流程。 备件表入库前统一拨到 255(或 247)并贴标;装表时“按图纸拨码”这个动作,必须改成“按台账拨码 + 拨完读一次 SN”。图纸给的是位置,台账给的才是身份。
验收只需要五个动作:
- ☐ 1. 该地址直方图单峰;连读 SN 20 次只有一个值(辅助)
- ☐ 2. 该通道表面读数与 EMS 读数一致,连续看 10 分钟
- ☐ 3. 该通道 CRC 错计数在 24 小时内不增长
- ☐ 4. 钳表夹一次,每次请求只有一个发送电流脉冲
- ☐ 5. 台账里该表的 SN 与实读一致
判据树:
| 现象 | 判定 | 下一步 |
|---|---|---|
| 读数稳定,但和表面读数不一样 | 地址冲突(延迟差稳定,永远是同一台赢) | 拔线 / 钳表 |
| 读数在两个值之间跳,跳幅=另一台表 | 地址冲突 | 直方图 / 拔线 |
| CRC 错多,错帧位置随机 | 冲突 或 干扰 | 看 A、B 是否同时向 2.5V 收敛 |
| CRC 错多,A/B 波形正常 | 干扰 / 反射 / 终端 | 回 RS485/Modbus 抗干扰实战 |
| 通信质量 100%,但写参数后另一台也变了 | 地址冲突(写双执行) | 读两台表面值 |
| 某地址完全没数 | 无路由 / 未上电 / 地址错 | 查映射表与拨码(网关 unit id 与地址映射) |
| 超时随机、重启能好 | 并发排队 | 回 网关 unit id 与地址映射 |
| 读十次三次对 | 两台都挂 200ms 节拍 | 直方图 |
十五、三个可以带走的结论
一、地址冲突不制造“读不到”,它制造“读到了别的”。 所以它不会被通信质量、超时率、CRC 计数这些指标抓住——现场这一例,整站成功率 100.00000%,冲突额外占用总线 22.5 秒/天,占空比增量 0.026%。它只会以“抄表对不上”“曲线毛刺”“报表差 1~3%”这种业务层症状出现。而业务层症状的第一反应,永远是“换表”。
二、正确率不是判据,延迟分布才是。 同一个故障,正确率可以是 1.8%、29.3%、99.0%,取决于两台从站的响应延迟落在哪里。所以“读十次三次对”只是谱系里的一行,把它当通用判据会漏掉 99% 正确率的那种站——而那种站恰恰最难发现。真正稳的判据只有两个:CRC 错与合法错值并存,以及响应时间双峰。后者 200 个样本、12 秒,比换三次表便宜得多。
三、读的伤害是数据,写的伤害是设备。 读错只是报表难看;写错是跳错柜、改错变比、清掉别人的需量。而写命令在任何统计里都不留痕——一次 FC06 只有 18.33ms,两台执行,总线时间一点没多。所以发现同地址后第一件事是停写,不是定位。这条顺序反了,排查过程本身就可能制造事故。
这篇里所有数字都来自 slave_addr_conflict_calc.py(纯标准库、离线可跑,selftest 25 项全过)。延迟分布、帧长、波特率、重试次数都是参数,改一行就能套到自己现场。模型里的均匀分布是量级近似,结论对“方向”和“量级”有效,对小数点后两位无效——真实的延迟分布要靠直方图去测,测出来再代进来。
下一篇写《把报警位塞进保持寄存器:一个 200ms 的故障脉冲,为什么轮询永远抓不到》——扫描周期的采样定理、事件位与状态位的区别、锁存位与清除握手、边沿计数与“一次都不许丢”的实现代价,以及为什么“我加了 100ms 轮询”并不能解决问题。
本文涉及的具体项目参数已做脱敏处理,数值保留原量级。