4. Modbus RTU协议详解
Modbus RTU 是工业自动化领域应用最广泛的串行通信协议之一,尤其在 SCADA 系统与 PLC、传感器、执行器之间的数据交换中占据核心地位。本节将深入解析 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 |
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。计算时对地址码、功能码、数据区所有字节进行运算,结果低字节在前发送。
// 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 低字节在前发送
4.2 主从站通信机制
Modbus RTU 采用严格的主从(Master/Slave)通信模式。在同一总线上,只能有一个主站,最多可挂载 247 个从站。所有通信由主站发起,从站只能被动响应,不能主动发送数据。
4.2.1 通信流程
- 主站发送请求: 主站构造包含地址码、功能码、数据区、CRC 的请求报文,通过串口发送到总线。
- 所有从站接收: 总线上的所有从站都收到该报文,但只有地址匹配的从站才会处理。
- 从站处理并响应: 匹配的从站解析报文,执行操作(如读取寄存器),构造响应报文(包含地址码、功能码、数据、CRC)发回主站。
- 主站接收响应: 主站收到响应后,校验 CRC,解析数据,完成一次通信周期。
- 超时与重试: 如果主站在规定时间内未收到响应,则判定超时,可选择重试或报错。
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
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)
4.4 常见问题与避坑指南
- CRC 校验错误: 检查字节序(低字节在前)、多项式是否正确(0x8005)、是否遗漏了地址码和功能码。
- 从站无响应: 检查从站地址是否匹配、波特率/数据位/停止位/校验位是否一致、RS-485 终端电阻是否安装。
- 数据解析错误: 注意寄存器数据的字节序(高字节在前),以及数据类型(16位无符号、32位浮点等)的转换规则。
- 广播地址使用: 广播地址 0x00 只能用于写操作(如同时启动所有设备),从站不响应,因此无法确认执行结果。
- 报文间隔时间: 相邻报文之间必须有至少 3.5 个字符时间的静默间隔,否则从站可能将两条报文误认为一条。