4. Modbus RTU协议详解

Modbus RTU 是工业自动化领域应用最广泛的串行通信协议之一,尤其在 SCADA 系统与 PLC、传感器、执行器之间的数据交换中占据核心地位。本节将深入解析 Modbus RTU 的报文结构(地址码、功能码、数据区、CRC 校验)以及主从站通信机制,帮助学员掌握报文解析与调试的核心技能。

学习目标: 能够独立解析 Modbus RTU 报文,理解主站轮询与从站响应的时序关系,掌握 CRC 校验的计算方法,为后续的厂务系统调试打下基础。

4.1 Modbus RTU 报文结构

Modbus RTU 采用二进制编码,报文紧凑、效率高。一条完整的 RTU 报文由四个部分组成:地址码、功能码、数据区、CRC 校验。报文以至少 3.5 个字符时间的静默间隔作为起始和结束标志。

字段 长度(字节) 描述 示例(十六进制)
地址码 1 从站设备地址,范围 1~247,0 为广播地址 0x01
功能码 1 指示读/写操作类型,如 0x03 读保持寄存器 0x03
数据区 N(可变) 包含起始地址、寄存器数量、数据值等 0x00 0x01 0x00 0x0A
CRC 校验 2 循环冗余校验,低字节在前,高字节在后 0xCC 0x0A
关键点: Modbus RTU 报文采用“低字节在前”的 Little-Endian 字节序,CRC 校验码也是低字节在前发送。这是初学者最容易出错的地方。

4.1.1 地址码(Address Field)

地址码标识目标从站设备。主站发送请求时,地址码指定要通信的从站;从站响应时,地址码回显自身地址,用于主站确认响应来源。广播地址 0x00 用于主站向所有从站发送命令,从站不响应。

4.1.2 功能码(Function Code)

功能码定义操作类型。常用功能码如下:

功能码 名称 作用 数据区方向
0x01 读线圈状态 读取 DO 输出状态 主站 → 从站
0x02 读离散输入 读取 DI 输入状态 主站 → 从站
0x03 读保持寄存器 读取 AO 或参数寄存器 主站 → 从站
0x04 读输入寄存器 读取 AI 模拟量输入 主站 → 从站
0x05 写单线圈 控制单个 DO 输出 主站 → 从站
0x06 写单寄存器 写入单个 AO 或参数 主站 → 从站
0x0F 写多线圈 批量控制 DO 输出 主站 → 从站
0x10 写多寄存器 批量写入 AO 或参数 主站 → 从站

4.1.3 数据区(Data Field)

数据区内容取决于功能码。以功能码 0x03(读保持寄存器)为例,请求报文数据区包含:起始地址(2 字节) + 寄存器数量(2 字节)。响应报文数据区包含:字节计数(1 字节) + 寄存器数据(N 字节)。

// 请求示例:读取从站 0x01,起始地址 0x0001,读取 10 个寄存器
地址码: 0x01
功能码: 0x03
起始地址高字节: 0x00
起始地址低字节: 0x01
寄存器数量高字节: 0x00
寄存器数量低字节: 0x0A
CRC 低字节: 0xCC
CRC 高字节: 0x0A

// 完整报文(十六进制):
01 03 00 01 00 0A CC 0A

4.1.4 CRC 校验(Cyclic Redundancy Check)

CRC 校验用于检测报文传输错误。Modbus RTU 使用 CRC-16(多项式 0x8005),初始值为 0xFFFF。计算时对地址码、功能码、数据区所有字节进行运算,结果低字节在前发送。

常见错误: 很多调试工具或 PLC 库的 CRC 计算默认高字节在前,而 Modbus RTU 要求低字节在前。如果报文被从站拒绝,首先检查 CRC 字节序是否正确。
// CRC-16 计算步骤(伪代码)
uint16_t crc = 0xFFFF;
for each byte in (地址码 + 功能码 + 数据区) {
    crc ^= byte;
    for (int i = 0; i < 8; i++) {
        if (crc & 0x0001) {
            crc = (crc >> 1) ^ 0xA001;  // 0xA001 是 0x8005 的反转
        } else {
            crc = crc >> 1;
        }
    }
}
// crc 低字节在前发送
Modbus RTU 报文结构 报文总长度:地址码(1) + 功能码(1) + 数据区(N) + CRC(2) 地址码 1 字节 功能码 1 字节 数据区(可变长度) N 字节 CRC 校验 2 字节 报文解析要点: • 地址码 0x01~0xF7(1~247),0x00 为广播地址 • 功能码 0x01~0x10 为常用标准功能码,0x80+ 为异常响应 • CRC 校验覆盖地址码+功能码+数据区,低字节在前发送

4.2 主从站通信机制

Modbus RTU 采用严格的主从(Master/Slave)通信模式。在同一总线上,只能有一个主站,最多可挂载 247 个从站。所有通信由主站发起,从站只能被动响应,不能主动发送数据。

4.2.1 通信流程

  1. 主站发送请求: 主站构造包含地址码、功能码、数据区、CRC 的请求报文,通过串口发送到总线。
  2. 所有从站接收: 总线上的所有从站都收到该报文,但只有地址匹配的从站才会处理。
  3. 从站处理并响应: 匹配的从站解析报文,执行操作(如读取寄存器),构造响应报文(包含地址码、功能码、数据、CRC)发回主站。
  4. 主站接收响应: 主站收到响应后,校验 CRC,解析数据,完成一次通信周期。
  5. 超时与重试: 如果主站在规定时间内未收到响应,则判定超时,可选择重试或报错。
调试技巧: 使用串口监听工具(如 ModScan、Serial Port Monitor)抓取报文时,注意观察请求与响应之间的时间间隔。正常响应应在 10~100ms 内返回,超过 1s 通常表示从站故障或通信线路问题。

4.2.2 异常响应机制

当从站无法执行请求时(如非法地址、非法功能码、寄存器不可用),会返回异常响应。异常响应报文的功能码为 请求功能码 + 0x80,数据区包含一个字节的异常码。

异常码 名称 含义
0x01 非法功能码 从站不支持该功能码
0x02 非法数据地址 请求的寄存器地址超出范围
0x03 非法数据值 请求的数据值超出允许范围
0x04 从站设备故障 从站内部硬件或软件错误
0x06 从站忙 从站正在处理其他任务,稍后重试
// 异常响应示例:请求非法地址
请求报文:01 03 00 01 00 0A CC 0A
异常响应:01 83 02 C0 F1
解析:
  地址码: 0x01
  功能码: 0x83 (0x03 + 0x80)
  异常码: 0x02 (非法数据地址)
  CRC: 0xC0 0xF1
Modbus RTU 主从站通信时序 主站 (Master) 从站 1 (Slave) 从站 2 (Slave) 请求报文 地址=0x01 处理请求 响应报文 数据区+CRC 解析响应 • 主站发送请求后,总线上的所有从站都收到报文,但只有地址匹配的从站(本例中为从站1)响应 • 从站2地址不匹配,忽略该报文,不产生任何响应

4.3 报文解析实战案例

以下是一个完整的 Modbus RTU 通信案例,展示主站如何读取从站 0x01 的保持寄存器(起始地址 0x0000,读取 2 个寄存器)。

方向 报文(十六进制) 解析
主站 → 从站 01 03 00 00 00 02 C4 0B 地址=0x01, 功能码=0x03(读寄存器), 起始地址=0x0000, 数量=2, CRC=0xC40B
从站 → 主站 01 03 04 00 0A 00 14 7A 5E 地址=0x01, 功能码=0x03, 字节数=4, 数据=0x000A(10), 0x0014(20), CRC=0x7A5E
// 响应报文逐字节解析
01       → 从站地址
03       → 功能码(读保持寄存器)
04       → 数据区字节数(2个寄存器 × 2字节 = 4字节)
00 0A    → 寄存器1的值 = 0x000A = 10(十进制)
00 14    → 寄存器2的值 = 0x0014 = 20(十进制)
7A 5E    → CRC校验码(低字节0x7A,高字节0x5E)
厂务系统调试要点: 在调试水处理、空调系统等厂务设备时,经常需要读取 PLC 中的温度、压力、流量等模拟量数据。这些数据通常存储在保持寄存器中,使用功能码 0x03 读取。注意寄存器地址与 PLC 内部地址的映射关系(如西门子 PLC 的 Modbus 地址偏移)。

4.4 常见问题与避坑指南

  • CRC 校验错误: 检查字节序(低字节在前)、多项式是否正确(0x8005)、是否遗漏了地址码和功能码。
  • 从站无响应: 检查从站地址是否匹配、波特率/数据位/停止位/校验位是否一致、RS-485 终端电阻是否安装。
  • 数据解析错误: 注意寄存器数据的字节序(高字节在前),以及数据类型(16位无符号、32位浮点等)的转换规则。
  • 广播地址使用: 广播地址 0x00 只能用于写操作(如同时启动所有设备),从站不响应,因此无法确认执行结果。
  • 报文间隔时间: 相邻报文之间必须有至少 3.5 个字符时间的静默间隔,否则从站可能将两条报文误认为一条。
重要提醒: 在厂务系统调试中,切勿在未确认从站地址和寄存器映射表的情况下随意发送写命令(功能码 0x05、0x06、0x0F、0x10),以免误操作导致设备停机或参数紊乱。建议先使用读命令验证通信正常后再执行写操作。