3. Modbus数据模型

Modbus 的互操作性之所以强,很大程度上得益于它把从站设备内部千差万别的物理量,抽象成了四种标准的数据对象。主站不需要知道从站内部是 PLC、电表还是变频器,只要按这四类对象去访问,就能完成绝大多数读写操作。理解这四类对象的含义、地址映射方式和读写规则,是排查“读不到数据”“写不进去”“地址差一位”等问题的根本前提。

一、四类数据对象的含义与读写属性

Modbus 数据模型把数据按“位/字”和“只读/可读写”两个维度分成四类。位对象用于表达开关量,字对象用于表达数值量;只读对象通常来自现场采集,可读写对象通常用于控制与参数设置。

  • 线圈(Coils,0x 区):可读写的单比特输出量,对应继电器、接触器、指示灯等可被主站控制的开关。功能码 01 读、05 写单个、15 写多个。
  • 离散输入(Discrete Inputs,1x 区):只读的单比特输入量,对应限位开关、按钮、故障触点等现场状态。功能码 02 读取。
  • 保持寄存器(Holding Registers,4x 区):可读写的 16 位字,用于设定值、控制字、配方参数等。功能码 03 读、06 写单个、16 写多个。
  • 输入寄存器(Input Registers,3x 区):只读的 16 位字,用于测量值、温度、电流、累计量等采集数据。功能码 04 读取。
记忆口诀:0 可写位、1 只读位、3 只读字、4 可写字。数字前缀代表对象类别,而不是真实地址。

二、地址映射:协议地址、PLC 地址与偏移量

这是最容易出错的地方。Modbus 规范中的 PDU 地址从 0 开始,而传统文档常用 1 起始的“逻辑地址”描述,例如 40001 表示第一个保持寄存器。二者相差 1,这就是著名的“差一位”问题。

以保持寄存器为例:文档写 40001,其 PDU 地址是 0x0000;文档写 40100,PDU 地址是 0x0063。很多组态软件要求填写“寄存器地址”,此时应填 0 或 99,而不是 40001 或 40100。若填错,读到的就是相邻寄存器的值,表现为数据整体偏移或数值异常。

对象传统逻辑地址PDU 地址(0 起始)常用功能码读写
线圈000010x000001 / 05 / 15读写
离散输入100010x000002只读
输入寄存器300010x000004只读
保持寄存器400010x000003 / 06 / 16读写

下面是一段典型的 Modbus RTU 请求帧,读取从站 1 的保持寄存器,起始 PDU 地址 0x0000,读取 2 个寄存器:

请求: 01 03 00 00 00 02 C4 0B
      从站 功能码 起始地址Hi Lo 数量Hi Lo CRC

响应: 01 03 04 00 64 01 F4 3A 8B
      从站 功能码 字节数 数据1(100) 数据2(500) CRC

三、读写规则与边界约束

Modbus 对每次请求能访问的对象数量有明确上限,超过上限从站会返回异常码 03(非法数据值)。常见限制如下:

  • 读线圈/离散输入:一次最多 2000 个位(0x07D0)。
  • 读保持/输入寄存器:一次最多 125 个字(0x007D)。
  • 写多个线圈:一次最多 1968 个位(0x07B0)。
  • 写多个寄存器:一次最多 123 个字(0x007B)。

此外,访问地址必须落在从站实际支持的范围内。若请求的起始地址加数量越界,从站通常返回异常码 02(非法数据地址)。因此主站组态时应先确认从站的点表范围,再决定单次读取长度,必要时分段轮询。

优化建议:把地址连续的寄存器合并成一次读取,可显著减少报文数量、提升轮询效率;但不要为了合并而跨越不同从站或跨越功能码区域。

四、典型场景:电表数据采集与远程控制

假设一台 Modbus 电表,点表定义如下:电压在输入寄存器 30001~30003,电流在 30013~30015,有功功率在 30025,继电器输出控制在线圈 00001。主站采集与控制流程为:

  1. 用功能码 04 读取 30001 起始的 3 个输入寄存器,解析三相电压。
  2. 用功能码 04 读取 30013 起始的 3 个输入寄存器,解析三相电流。
  3. 用功能码 03 读取保持寄存器中的电能累计值,用于计费。
  4. 用功能码 05 写线圈 00001 为 ON,闭合继电器实现远程合闸。

注意:电压、电流属于只读的输入寄存器,不能用 03 或 06 去访问;继电器属于线圈,不能用保持寄存器去写。对象类型选错,从站会返回异常码 01 或 02。

五、常见误区与复盘

  • 把 40001 直接当 PDU 地址下发:导致读到 40002 的数据,数值整体错位。应减去 1 或按软件要求填写。
  • 混淆输入寄存器与保持寄存器:测量值误用 03 读取,从站返回异常。应先查点表确认对象类别。
  • 忽略单次读取上限:一次读 200 个寄存器,从站返回异常码 03。应拆分为多次请求。
  • 认为所有从站地址都从 0 开始:部分设备点表本身以 1 起始,需以设备手册为准,不能一概而论。
  • 写入只读对象:对离散输入或输入寄存器执行写操作,必然失败,应改用可写对象或检查控制逻辑。
排障顺序建议:先确认对象类型是否正确,再核对地址偏移是否为 0 起始,最后检查数量是否超限。这三步能覆盖绝大多数“读不到、写不进、数据错位”的问题。
Modbus 数据模型与地址映射 主站 Master 请求 / 响应 PDU 线圈 Coils(0x) 位 · 可读写 · 01/05/15 逻辑 00001 → PDU 0x0000 离散输入(1x) 位 · 只读 · 02 逻辑 10001 → PDU 0x0000 输入寄存器(3x) 字 · 只读 · 04 逻辑 30001 → PDU 0x0000 保持寄存器(4x) 字 · 可读写 · 03/06/16 逻辑 40001 → PDU 0x0000 读写规则与上限 读线圈/离散输入 ≤ 2000 位 读寄存器 ≤ 125 字 写多线圈 ≤ 1968 位 写多寄存器 ≤ 123 字 异常码 01 非法功能码 02 非法数据地址 03 非法数据值(超限) 地址换算 PDU = 逻辑地址 − 1 例:40001 → 0x0000 排障三步:确认对象类型 → 核对 0 起始偏移 → 检查数量是否超限

掌握这四类对象的含义与映射规则后,再看任何一份 Modbus 点表,都能迅速判断该用哪个功能码、地址该填多少、单次能读多长。这是构建高可靠 Modbus 通信的第一块基石,也是后续异常处理与性能优化的判断依据。