2. Modbus协议基础

Modbus 是工业自动化领域最广泛使用的现场总线协议之一,它的核心思想非常朴素:一台主站主动发起请求,多台从站被动响应。理解主从架构、功能码、寄存器类型、数据模型与地址映射,是后续做 RTU/TCP 网关映射、点表配置和故障排查的基础。很多现场问题并不是网络不通,而是对“寄存器地址从 0 还是从 1 开始”“功能码对应哪类数据”“网关该做透明转发还是地址重映射”理解不一致造成的。

一、主从架构:谁发起,谁响应

Modbus 采用典型的主从(Master/Slave)或客户端/服务器(Client/Server)模型。主站负责发起请求,从站只有在收到请求后才返回响应,从站之间不会主动通信。RTU 场景下通常一条 RS-485 总线上挂多台从站,通过从站地址区分设备;TCP 场景下通过 IP 和端口定位设备,再用单元标识符(Unit ID)区分同一网关后的多个从站。

  • 主站:PLC、SCADA、上位机、网关的采集侧,负责轮询和写命令。
  • 从站:仪表、变频器、电表、IO 模块,保存寄存器数据并响应请求。
  • 请求帧:从站地址 + 功能码 + 数据区 + 校验(RTU 为 CRC,TCP 为 MBAP 头)。
  • 响应帧:从站地址 + 功能码 + 数据/异常码 + 校验。
RTU 与 TCP 的关键差异:RTU 用 CRC16 校验、以 3.5 字符间隔分帧;TCP 用 MBAP 报文头(事务号、协议号、长度、单元标识),没有 CRC,由 TCP 保证传输可靠性。

二、功能码:请求要做什么

功能码决定“这次操作读还是写、操作哪类寄存器”。工程中最常用的是 01、02、03、04、05、06、15、16。功能码最高位为 1 时表示异常响应,例如请求 03 返回 0x83,表示读保持寄存器出错。

功能码名称操作对象典型用途
01 (0x01)读线圈可读写位读继电器、开关量输出状态
02 (0x02)读离散输入只读位读按钮、限位、DI 状态
03 (0x03)读保持寄存器可读写 16 位读参数、设定值、测量值
04 (0x04)读输入寄存器只读 16 位读温度、电量等实时采集值
05 (0x05)写单个线圈可读写位控制单路继电器
06 (0x06)写单个寄存器可读写 16 位修改单个设定参数
15 (0x0F)写多个线圈可读写位批量控制输出
16 (0x10)写多个寄存器可读写 16 位批量下发参数、配方

三、寄存器类型与数据模型

Modbus 把数据抽象成四类“表”,每类最多 65536 个条目。位数据以 1 bit 为单位,寄存器数据以 16 bit 为单位。需要特别注意:寄存器本身只定义 16 位无符号数,32 位整数、浮点数、字符串都需要占用连续两个或多个寄存器,并约定字节序和字序。

  • 线圈(Coils):可读写位,功能码 01/05/15。
  • 离散输入(Discrete Inputs):只读位,功能码 02。
  • 输入寄存器(Input Registers):只读 16 位,功能码 04。
  • 保持寄存器(Holding Registers):可读写 16 位,功能码 03/06/16。

对于 32 位数据,常见组合有 ABCD(大端)、CDAB(字交换)、BADC(字节交换)、DCBA(小端)。网关做协议转换时,必须和从站手册、上位机点表保持一致,否则会出现“数值对但量级差 65536 倍”或“高低字颠倒”的问题。

四、地址映射:0 基与 1 基的坑

Modbus 协议报文中的地址是从 0 开始的,称为 PDU 地址;而设备手册和组态软件常使用从 1 开始的寄存器编号,并带前缀区分类型,例如 40001 表示保持寄存器第 1 个。二者相差 1,这是现场最常见的地址错位来源。

协议地址(0基)   常用编号(1基)   类型
00000           00001          线圈
10000           10001          离散输入
30000           30001          输入寄存器
40000           40001          保持寄存器

示例:读保持寄存器第 1 个
请求地址 = 0x0000,数量 = 0x0001
若手册写 40001,则协议地址 = 40001 - 40001 = 0
不要混用“寄存器编号”和“协议地址”。配置网关点表时,建议统一记录三列:设备手册编号、协议地址(0 基)、数据类型与字节序。只写“40001”而不写清类型和字节序,后期排障会非常困难。

五、案例:网关采集电表

某 Modbus RTU 电表,从站地址 1,波特率 9600 8N1。手册标注:电压为输入寄存器 30001,电流为 30002,有功功率为保持寄存器 40001(32 位浮点,CDAB)。网关作为 Modbus TCP 服务器,上位机通过 TCP 读取。

RTU 请求(读输入寄存器,电压):
01 04 00 00 00 01 31 CA
从站地址 01 | 功能码 04 | 起始地址 0000 | 数量 0001 | CRC 31 CA

TCP 请求(MBAP + PDU):
00 01 00 00 00 06 01 04 00 00 00 01
事务号 0001 | 协议号 0000 | 长度 0006 | 单元号 01 | 04 00 00 00 01

网关需要完成三件事:把 TCP 请求中的 Unit ID 映射为 RTU 从站地址;把 0 基协议地址原样转发或按点表重映射;把 RTU 响应封装回 MBAP 返回给上位机。若功率为 32 位 CDAB,网关还需按点表做字交换,否则上位机解析出的浮点值会明显异常。

六、常见误区与复盘

  1. 地址差 1:手册 40001 对应协议地址 0,不是 1。批量读取时起始地址和数量都要按 0 基计算。
  2. 功能码用错:只读输入寄存器用 04,保持寄存器用 03;用 03 读只读区可能返回异常码 0x83 或 0x02。
  3. 字节序/字序未确认:32 位数据必须确认 ABCD/CDAB/BADC/DCBA,最好用已知值(如 220.0V)反推验证。
  4. Unit ID 与从站地址混淆:TCP 网关后的 RTU 设备靠 Unit ID 区分,配置错误会导致“能连上但读不到数据”。
  5. 轮询过密:RTU 总线是共享介质,轮询间隔过短会造成超时和丢包,应结合波特率和从站数量估算。
掌握主从架构、功能码、四类寄存器、数据模型和 0 基/1 基地址映射,就掌握了 Modbus 的“语法”。后续做 RTU/TCP 网关时,所有点表配置、字节序处理和异常排查,都是在这套语法上展开的。
Modbus 主从架构 · 功能码 · 寄存器地址映射 主站 / Client PLC · SCADA · 上位机 主动发起请求 轮询 / 写命令 Modbus 网关 TCP ⇄ RTU 转换 Unit ID ↔ 从站地址 地址重映射 / 字节序 从站 / Server 电表 ID=1 变频器 ID=2 IO模块 ID=3 仪表 ID=4 请求 响应 转发 回传 常用功能码与寄存器类型 01 / 05 / 15 线圈(可读写位) 02 离散输入(只读位) 04 输入寄存器(只读16位) 03 / 06 / 16 保持寄存器(可读写16位) 异常响应:功能码最高位置 1 0x83 = 读保持寄存器异常 0x02 非法地址 · 0x03 非法数据值 地址映射:0 基协议地址 vs 1 基编号 类型 协议地址(0基) 常用编号(1基) 线圈 00000 00001 离散输入 10000 10001 输入寄存器 30000 30001 保持寄存器 40000 40001 协议地址 = 常用编号 - 类型基址(如 40001 → 0)