Modbus 的"字序":一个配错就变成天文数字,或者干脆变成 0
这是我在做工业数据采集 Demo 时踩得最深的一个坑。它不像"连不上""超时"那样会报错,它不报错------数据照常写库、页面照常画曲线,只是那个数字在物理上根本不可能存在。
一、先看现象
我用一台真的 RS485 温湿度变送器(艾克姆 IKM10000WS01,9600-8N1,从站 1),读功能码 03,一次读 4 个保持寄存器。
抓包看到的应答长这样:
00 2f | 00 00 | 00 0b | 01 | 03 | 08 | de cc 42 78 00 00 04 d2
事务ID 协议ID 长度 单元 FC 字节数 reg0 reg1 reg2 reg3
两个点位的正确解是:
| 点位 | 寄存器 | 正确字序 | 结果 |
|---|---|---|---|
| 温度(float32) | reg0 + reg1 = DE CC 42 78 |
CDAB | 0x4278DECC ≈ 62.2 ℃ |
| 运行时长(uint32) | reg2 + reg3 = 00 00 04 D2 |
ABCD | 1234 h |
同一帧、同一台设备、两个点位、两种字序。
如果你按 ABCD 去解温度,0xDECC4278 算出来是 -7.36e18。
更阴的一次:另一帧 reg0=3188(0x0C74)、reg1=17017(0x4279),按 ABCD 解是 1.88e-31 ,四舍五入到界面上就是 0.00。
所以我当时得到的教训是:
字序配错,不一定"看起来很大",也可能"看起来是 0"。靠肉眼发现不了,必须靠量程校验。
二、为什么会这样
Modbus 规范只规定了寄存器是 16 位,没规定"一个 32 位的值怎么放进两个寄存器"。
于是厂商各自发挥,就有了四种常见排法(按内存从低到高的字节顺序命名):
| 名称 | 含义 | 32 位值 0x4278DECC 拆成两个寄存器 |
|---|---|---|
| ABCD(大端) | 高字在前、高字节在前 | reg0=42 78,reg1=DE CC |
| CDAB(字交换) | 低字在前,字内仍大端 | reg0=DE CC,reg1=42 78 |
| BADC(字节交换) | 高字在前,字内小端 | reg0=78 42,reg1=CC DE |
| DCBA(全反) | 低字在前,字内小端 | reg0=CC DE,reg1=78 42 |
国内仪表最常见的是 CDAB (也有人叫"低字在前"),但绝不是所有点位都一样------我这次就是同一台表里温度和运行时长用了不同排法。
三、我最后是怎么设计的
1)字序必须是点位级配置,不是全局开关
点表长这样(纯文本 properties):
properties
device.sensor-01.point.temperature.area=holding
device.sensor-01.point.temperature.offset=1
device.sensor-01.point.temperature.type=float32
device.sensor-01.point.temperature.order=CDAB
device.sensor-01.point.temperature.min=-40
device.sensor-01.point.temperature.max=125
order 挂在每个点位 上,代码里不出现任何设备的私有约定。换一台表,只改这张表,不改代码。
2)解码统一走一个枚举
java
public enum WordOrder {
ABCD, CDAB, BADC, DCBA;
public float decodeFloat(int hi, int lo) { ... } // 按各自规则拼 4 字节
public long decodeUInt32(int hi, int lo) { ... }
}
驱动只负责"把寄存器读回来",怎么拼 交给 WordOrder,避免每个驱动各写一套位运算。
3)量程校验做最后兜底
properties
...temperature.min=-40
...temperature.max=125
解出来的值超出物理量程 → 直接标 BAD,不入正常曲线。
这一步比什么都重要:字序错不一定报错,但一定算不出一个"合理的温度"。
四、怎么验证(别靠猜)
我用了三条互相独立的证据:
- 抓包逐字节核对:Wireshark 抓 FC03,把 8 个字节和点表对照(这也是我后来写进文档的"报文级证`据")。
- 两个点位交叉验证:温度和运行时长在同一帧里,`两种字序都要对得上------只对一个说明是巧合。
- 物理激励 :对着传感器吹热风,看数值是不是朝正确方向、以合理速度变化。字序错了,数值会乱跳或不动。
五、几条可以直接带走的经验
- Modbus 只保证"16 位",不保证"32 位怎么拆"。任何声称"Modbus 就是大端"的说法都是错的。
- 字序错了通常不报错 ,所以必须靠量程校验 + 交叉验证兜底,而不是指望日志。
- 四种字序里,CDAB 在国内仪表里特别常见 ,但同一台设备内也可能混用。
- 点表应该把
区域 / 偏移 / 类型 / 字序 / 量程全部外置------这不是"设计洁癖",是换设备时能不能只改配置的问题。 - 再给你一个排查顺序:先看报文有没有回来 (没回来是地址/波特率/接线)→ 再看字节数对不对 (字节数错是从站实现问题)→ 最后才怀疑字序。
附:这个坑对做数据平台的人意味着什么
如果你只做"采集完写库",字序错了就是一条脏数据。
但如果你做的是在线监测/告警 ,字序错了会变成误报警 或者该报不报 ------这才是工业现场真正怕的:不是数字难看,而是基于错数字做了动作。
所以我在这个项目里给每条数据加了质量码(GOOD / STALE / BAD),后面一篇文章专门讲:为什么"断线补 0"是比"没有数据"更危险的事。