Modbus RTU 转 MQTT:寄存器映射、字节序与缩放
可靠的 Modbus RTU 转 MQTT 集成,需要在设备寄存器映射与使用测量数据的应用之间明确约定。串口成功应答只能证明字节已到达。调试验收还必须确认这些字节的含义,以及转换后的数值是否仍然有效、及时。
本指南讨论只读遥测,包括地址转换、功能码、有符号数值、缩放、多寄存器排列顺序和 MQTT 数据时效性。下文中的所有寄存器分配、数值和载荷均为示例,并非 Obeita 产品规格或现场测量结果。
从一次已验证的 RTU 读取开始
收集设备型号、固件版本、寄存器映射版本、串口设置、站点地址和一个已知参考值。按照安装文档核对 RS-485 接线、终端匹配和偏置配置。确保总线仅由指定主站控制。在尝试通过缩放修正数值之前,先解决超时和 CRC 校验失败问题。
Modbus 串行链路指南规定了 RTU 帧格式、时序和差错检测要求。实际部署的轮询程序必须遵守这些要求,同时满足设备的响应限制。先读取一个文档已明确定义的测点;只有原始响应与解码后的数值相符,才扩大轮询范围。
同时确认寄存器地址和功能码
Modbus 应用协议规范采用从零开始的 PDU 地址。功能码 03 读取保持寄存器,功能码 04 读取输入寄存器。这是两个不同的逻辑表。偏移量的数值相同,并不能证明这两个功能码会返回同一项测量数据。
在常见的文档记法中,40001 表示第一个保持寄存器,30001 表示第一个输入寄存器。首位数字用于标识寄存器表,并不属于实际传输的 PDU 地址。参见 Modbus Organization 的寻址说明。
| 手册条目示例 | 功能码 | PDU 起始地址 |
|---|---|---|
| 保持寄存器 40011 | 03 | 十进制 10,0x000A |
| 输入寄存器 30011 | 04 | 十进制 10,0x000A |
按照上述约定,40011 − 40001 = 10。但手册也可能直接给出从零开始的偏移量、从一开始的寄存器编号或十六进制地址。网关界面还可能自行进行地址转换。应同时记录手册记法和实际 PDU 偏移量,切勿不加判断地减一。除非设备文档明确支持相应映射,否则不要为了得到看似合理的数值而将 FC03 改成 FC04。
第一个示例的请求 PDU 为 03 00 0A 00 01:功能码 03、起始地址 10、数量为一个寄存器。这仅是 PDU,不是完整的 RTU 帧,其中未包含站点地址和 CRC。
先解码有符号数值,再应用缩放
为每个测点建立定义,包含站点地址、功能码、PDU 偏移量、寄存器数量、数据类型、字顺序、缩放系数、偏置、单位、无效值规则和时效性限制。在调试日志中保留原始寄存器字,以便其他工程师复现转换过程。
假设示例测点是一个有符号 16 位温度值,缩放系数为 0.1 °C,偏置为零。响应字 0xFF9C 按无符号数读取时为 65436,按有符号二进制补码解释时则为 65436 − 65536 = −100。应先解码,再应用缩放:
engineering_value = decoded_value × scale + offset
= -100 × 0.1 + 0
= -10.0 °C
若按无符号数解码,则会发布 6543.6 °C。通过更改缩放系数来掩盖这一结果,并不能修复根本的数据类型错误。应测试正值、负值和边界值。在缩放之前检查制造商定义的无效码,不能让哨兵值变成看似可信的测量结果。

区分 16 位字节序与多寄存器字顺序
标准 16 位 Modbus 寄存器内,高字节先传输。因此,0x4148 在链路上表示为 41 48。RTU CRC 是独立字段,低字节先发送;CRC 的顺序不是寄存器解码的规则。
一个 32 位应用数值占用两个寄存器,但字的排列方式由设备决定。Schneider Electric 的交换浮点数字顺序示例说明了为何有时需要调整高有效字和低有效字的顺序。
以可精确表示的 IEEE 754 float32 数值 12.5 为例,其位模式为 0x41480000。高字在前的映射返回 0x4148, 0x0000;低字在前的映射返回 0x0000, 0x4148。应按照手册重新排列字,再将拼接后的位重新解释为 float32。将拼接所得的整数按数值转换为浮点数,则是另一种操作。

网关中名为“little endian”(小端)的选项可能存在歧义。应记录它实际执行的字和字节排列方式。某些厂商映射还规定了应用层字节交换,应逐项明确验证。在设备支持时,用一次请求读取相关的多个字,并检查设备是否保证更新期间读取的是一致快照。
随 MQTT 数值一同发布时效性和质量信息
为每项测量分配稳定的测点名称、单位、质量状态和含义明确的时间戳。如果设备不提供采集时间戳,应如实标注网关成功读取的时间。不能用更晚的发布时间掩盖旧样本。
以下应用载荷示例采用网关读取完成时间,以及在单次启动周期内使用的序列号:
{
"schema_version": 1,
"device_id": "example-device-07",
"point": "temperature",
"value": -10.0,
"unit": "degC",
"quality": "good",
"read_completed_at": "2026-10-03T12:00:00Z",
"time_source": "gateway_read",
"boot_id": "example-boot-01",
"sequence": 1042
}
明确轮询未成功时的处理方式:例如,保留最后一次有效读取的时间戳,并将质量状态标为过期,或显式发布不可用值。规定适合工艺过程的过期阈值、时钟同步要求和队列上限。重放缓冲的样本时,应保留其原始读取时间。
MQTT 保留消息可帮助新订阅者获取最近发布的状态,但保留并不代表数据仍然新鲜。MQTT 5 的 Message Expiry(消息过期)机制可以限制队列中消息的存续时间;消费端仍需在应用层检查数据年龄。参见 MQTT 5.0 第 3.3 和 4.3 节。
选择 QoS 时,不承诺端到端“恰好一次”处理
MQTT QoS 0 提供最多一次交付,QoS 1 提供至少一次交付,QoS 2 在其协议交互范围内提供恰好一次交付。规范将交付范围限定为单个发送方与单个接收方;消息代理向订阅者交付消息是另一次独立交互。仅凭 QoS 2,无法保证整个系统中的传感器采集或数据库更新恰好执行一次。
如果重复数据会产生影响,应将消费端写入设计为幂等操作。由设备、启动 ID 和序列号组成的应用键,在定义好唯一性和持久化规则后,可以支持去重。应使用实际的消息代理、客户端和存储配置,测试重连与下游重试。
调试验收检查清单
- 对照已确认的地址映射检查请求 PDU,包括 FC03 与 FC04 的选择。
- 启用云端计算之前,将原始字与设备上已知的显示值进行核对。
- 测试负值、无效码、字顺序变化以及部分轮询失败的情况。
- 分别断开串口设备和消息代理,确认过期数据仍可识别。
- 重启网关和消费端,检查重放数据的年龄、重复数据处理和序列号行为。
- 记录已测试的固件、映射版本、载荷模式和验收结果。
准备 Modbus RTU 转 MQTT 需求评审
与 Obeita 讨论集成方案前,请准备相关设备手册页面、请求/响应帧示例、预期工程量、测点数量、轮询周期、消息代理要求和中断时的处理方式。隐去密码、密钥、客户标识、私有网络细节,以及无权分享的专有数据。这些资料有助于在确定网关配置之前定义并测试映射。
可查看 Obeita 的传统设备联网服务以及已交付的 STM32 数据采集与 MQTT 集成项目,了解相关工程范围。如需讨论具体的映射需求,请联系 Obeita。
一手参考资料
- Modbus 应用协议规范 V1.1b3。相关章节:4.2、4.3、4.4、6.3、6.4。
- Modbus 简介 — Modbus Organization。相关部分:Modbus 数据寻址与功能码 03。
- Modbus 串行链路规范与实施指南 V1.02。相关章节:2.2、2.5.1、3。
- Schneider Electric — 如何读取 Modbus 中字顺序交换的浮点值。相关部分:Resolution(解决方法)。
- MQTT 5.0 版 — OASIS 标准。相关章节:3.3.1.3、3.3.2.3.3、4.3。