Modbus 寄存器映射是一套约定,它定义了设备如何对外暴露其位和数值、由哪个功能码读取或写入每一项,以及数据手册中的编号如何转换为真正在总线上传输的地址。大多数集成难题(读到错误的值、地址差一位、浮点数看起来像乱码)都可以追溯到对以下三件事之一的误解:四种数据模型、0 基协议地址与 1 基文档地址之分,以及多寄存器数值的字节序与字序。本指南将逐一讲解,让您第一次就能正确映射设备。

四种 Modbus 数据模型

Modbus 应用协议规范定义了四张主要的数据表。其中两张为单比特,两张为 16 位寄存器;两张为只读,两张为读/写。从历史上看,它们对应一个引导参考数字(0xxxx、1xxxx、3xxxx、4xxxx),您在许多数据手册中仍会看到这个数字,尽管它只是文档的简写约定,并非实际帧的一部分。

数据模型 参考前缀 大小 访问权限 典型用途
线圈 (Coils) 0xxxx 1 位 读 / 写 继电器输出、开关命令、使能标志
离散输入 (Discrete Inputs) 1xxxx 1 位 只读 数字量输入、限位开关、状态位
输入寄存器 (Input Registers) 3xxxx 16 位 只读 实时测量值:电压、电流、温度
保持寄存器 (Holding Registers) 4xxxx 16 位 读 / 写 设定值、配置、累加器,通常也包括测量值

这种划分是逻辑上的,而非物理上的。许多实际设备,包括大多数 Modbus 电能表,会把所有测量值放在保持寄存器而非输入寄存器中,因为保持寄存器被各类轮询主站普遍支持。设备采用哪种数据模型是厂商的决定,因此请始终以设备映射表为准,而非教科书上的预期。关于承载这些模型的 RTU 与 TCP 两种传输方式的区别,请参阅 Modbus RTU 与 Modbus TCP 对比

功能码:把动作与数据对应起来

一个 Modbus 请求由功能码加上起始地址和数量组成。每种数据模型都有自己的读取码,可写模型还增加了单个写入码与多个写入码。以下是您在实践中会用到的这八个。

功能码(十进制 / 十六进制) 名称 作用对象 方向
01 / 0x01 读线圈 (Read Coils) 线圈 (0xxxx) 读位
02 / 0x02 读离散输入 (Read Discrete Inputs) 离散输入 (1xxxx) 读位
03 / 0x03 读保持寄存器 (Read Holding Registers) 保持寄存器 (4xxxx) 读 16 位字
04 / 0x04 读输入寄存器 (Read Input Registers) 输入寄存器 (3xxxx) 读 16 位字
05 / 0x05 写单个线圈 (Write Single Coil) 线圈 (0xxxx) 写一个位
06 / 0x06 写单个寄存器 (Write Single Register) 保持寄存器 (4xxxx) 写一个字
15 / 0x0F 写多个线圈 (Write Multiple Coils) 线圈 (0xxxx) 写位块
16 / 0x10 写多个寄存器 (Write Multiple Registers) 保持寄存器 (4xxxx) 写字块

有两种对应关系最容易导致轮询失败。功能码 04 读取的是输入寄存器,而非保持寄存器,因此把 FC04 指向 4xxxx 映射,要么什么都返回不了,要么返回非法数据地址。而输入寄存器和离散输入都没有写入码,因为二者按定义就是只读的。如果主站需要写入某个值,那么该值必须存放在线圈或保持寄存器中。

0 基与 1 基寻址偏移

这一点是 Modbus 集成中差一位错误最常见的根源。在总线上传输的协议数据单元(PDU)使用 0 基地址。数据手册中的参考编号通常是 1 基的,并包含引导的模型数字。要把文档地址转换为 PDU 地址,需去掉模型前缀并减一。

陷阱在于:有些厂商直接给出 0 基的 PDU 地址,有些给出 1 基的参考编号,还有些在相邻列中两者都列出,却不标明哪一列是哪一种。在信任任何映射表之前,先读取一个您能预知其值的寄存器(如固件版本或某个固定的铭牌值),确认是否需要减一偏移。如果某个已知值出现在高一个或低一个寄存器的位置,您就找到了基址不匹配的问题。关于这一点及相关故障模式,详见 Modbus 通信错误排查

16 位寄存器与跨两个寄存器的 32 位数值

一个 Modbus 寄存器正好是 16 位,因此它可以存放 0 到 65535 的无符号整数,或 -32768 到 32767 的有符号整数。任何更大的数值,包括 32 位整数或 IEEE 754 单精度浮点数,都必须占用两个连续的寄存器,而 64 位数值则占用四个。协议本身没有数据类型的概念。它只搬运 16 位字,其含义完全由寄存器映射所定义的设备与主站之间的约定来决定。

由于规范从未规定这两个寄存器的排序方式,现场中存在四种组合。第一个变量是每个寄存器内部的字节序(大端,即 Modbus 默认方式,先发送高字节)。第二个变量是字序:是高位寄存器在前(大端字序),还是低位寄存器在前(字交换,在工具中常被标记为”小端”或”Daniel/Enron”)。

排序名称 寄存器顺序 0x12345678 的字节布局 常见于
大端 (ABCD) 高字在前 12 34 / 56 78 Modbus 默认,多数电能表
小端 (DCBA) 低字在前,字节交换 78 56 / 34 12 部分控制器
大字节 / 字交换 (BADC) 高字在前,字节交换 34 12 / 78 56 某些 PLC 库
字交换 (CDAB) 低字在前,字节保持不变 56 78 / 12 34 施耐德、许多网关

实用测试方法:读取一对您已知真实值的寄存器,例如电能表读取的某个稳定线电压(接近 230 V)。如果解码出的浮点数读数为 230.1,则排序正确。如果读出像 4.7e34 这样的离谱数字或一个负值,请先尝试字交换,因为 CDAB 与 ABCD 之间的不匹配最为常见。一旦确定排序,就将其锁定,记录在映射表中,并将其应用于该设备上的每一个 32 位字段。诸如 SURIOTA SRT-MGATE-1210 网关 这样可配置的 Modbus 转 MQTT 桥接设备,允许您为每个寄存器块设置字序和字节序,从而让浮点数到达消息代理时就已经被正确组装,免去了上游应用的猜测工作。

Modbus 异常码

当请求格式错误或超出范围时,从站会以功能码与 0x80 进行按位或运算后的值加上一个单字节异常码作为回复。识别这些异常码比单纯抓包能更快地加速调试。

异常码 名称 常见原因
01 非法功能 (Illegal Function) 设备不支持该功能码
02 非法数据地址 (Illegal Data Address) 地址或数量超出设备映射范围(常因 0/1 偏移导致)
03 非法数据值 (Illegal Data Value) 该寄存器不允许的值或数量
04 从站设备故障 (Slave Device Failure) 处理过程中设备内部错误
05 确认 (Acknowledge) 请求已接受,长时间处理正在进行中
06 从站设备忙 (Slave Device Busy) 设备繁忙,请稍后重试
0B 网关目标设备无响应 (Gateway Target Failed) 网关已到达总线,但目标从站未响应

在您认为有效的读取上出现异常 02,几乎总是意味着寻址基址出错了。在 TCP 转 RTU 桥接上出现异常 0B,指向的是下游的串行侧,而非您的 TCP 请求。一旦映射表、功能码和字序都确定下来,下一步就是把组装好的数值传送到物联网平台,这部分内容请参阅我们的 Modbus RTU 转 MQTT 网关指南

常见问题解答

为什么同一台设备上功能码 03 失败而 04 能正常工作?

该设备把测量值存放在输入寄存器(3xxxx)中,这些寄存器需用功能码 04 读取。功能码 03 针对的是保持寄存器(4xxxx)。如果那些地址没有被填充,设备就会返回非法数据地址异常。请将功能码与寄存器映射表中列出的数据模型对应起来。

我怎么知道我的设备使用 0 基还是 1 基寻址?

读取一个具有已知固定值的寄存器,例如固件版本或铭牌常量。如果读出的值出现在比文档标注高一个的地址上,则该映射为 1 基,您必须减一才能得到 PDU 地址。许多主站工具都有针对此偏移的勾选框。

我的 32 位浮点数读出来是一个很大的数或负数。哪里出错了?

这两个寄存器几乎可以肯定处于错误的字序。交换高位与低位寄存器(CDAB 对 ABCD),再针对一个您能预知的值(如已知电压)重新解码。如果仍然不对,再尝试交换每个寄存器内部的字节。

我可以写入输入寄存器或离散输入吗?

不可以。在 Modbus 规范中,输入寄存器(3xxxx)和离散输入(1xxxx)按定义就是只读的,因此不存在针对它们的写入功能码。可写数据必须存放在线圈(使用 FC05 或 FC0F)或保持寄存器(使用 FC06 或 FC10)中。