3. Modbus RTU帧结构
Modbus RTU 采用二进制编码传输,每一帧数据紧凑且高效。理解其帧结构是进行数据采集与协议开发的基础。RTU 帧由四个核心域组成:地址域、功能码、数据域 和 CRC 校验域,并严格遵循帧间隔时间要求。
3.1 帧结构总览
一个完整的 Modbus RTU 报文帧(主机请求或从机响应)最大长度为 256 字节。其基本格式如下:
| 域名称 | 长度(字节) | 描述 |
|---|---|---|
| 地址域 | 1 | 从机地址(1~247),0 为广播地址 |
| 功能码 | 1 | 指示操作类型(读/写/诊断等) |
| 数据域 | N(0~252) | 寄存器地址、数量、数据值等 |
| CRC 校验 | 2 | 循环冗余校验,低字节在前 |
关键原则:RTU 帧内字节之间不允许有大于 1.5 字符时间的间隔;帧与帧之间必须保持至少 3.5 字符时间的静默间隔。
3.2 地址域
地址域标识目标从机设备。主机发送请求时,地址域指定要通信的从机;从机响应时,地址域回显自身地址。
- 有效范围:1 ~ 247(十进制)。地址 0 为广播地址,所有从机接收但不响应。
- 地址 248 ~ 255:保留,不建议使用。
- 唯一性:同一 RS-485 总线上每个从机地址必须唯一。
3.3 功能码
功能码定义操作类型。常用功能码如下:
| 功能码(十六进制) | 功能名称 | 作用 |
|---|---|---|
| 0x01 | 读线圈 | 读取 DO 状态 |
| 0x02 | 读离散输入 | 读取 DI 状态 |
| 0x03 | 读保持寄存器 | 读取 AO / 参数 |
| 0x04 | 读输入寄存器 | 读取 AI 值 |
| 0x05 | 写单线圈 | 控制单个 DO |
| 0x06 | 写单寄存器 | 写入单个 AO |
| 0x0F | 写多线圈 | 批量控制 DO |
| 0x10 | 写多寄存器 | 批量写入 AO |
异常响应:当从机无法执行请求时,会在响应帧中将功能码的最高位置 1(即加 0x80),并附带异常码。
3.4 数据域
数据域承载请求或响应的具体参数。其内容与功能码相关:
- 读操作请求:包含起始地址(2 字节)和寄存器数量(2 字节)。
- 写操作请求:包含起始地址(2 字节)、数据值(2 字节)或数据长度 + 数据体。
- 响应帧:包含字节计数(1 字节)和实际数据。
数据域采用 大端序(Big-Endian):高字节在前,低字节在后。
3.5 CRC 校验算法详解
CRC(循环冗余校验)是 RTU 帧的最后一环,用于检测传输错误。Modbus RTU 使用 CRC-16(Modbus 多项式):
- 多项式:0x8005(x¹⁶ + x¹⁵ + x² + 1)
- 初始值:0xFFFF
- 结果异或:0x0000
- 输出顺序:低字节在前,高字节在后
CRC 计算步骤:
- 将 16 位 CRC 寄存器初始化为 0xFFFF。
- 取帧中第一个字节(地址域)与 CRC 寄存器低 8 位异或,结果放回 CRC 寄存器。
- CRC 寄存器右移 1 位,高位补 0;检查移出的最低位:若为 1,则与多项式 0xA001(反转后的 0x8005)异或。
- 重复步骤 3 共 8 次(处理完一个字节)。
- 取下一个字节,重复步骤 2~4,直到所有字节处理完毕。
- 最终 CRC 寄存器值即为校验码,先发送低字节,再发送高字节。
Python 实现示例:
def crc16_modbus(data: bytes) -> int:
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
# 返回低字节在前、高字节在后的 16 位值
return ((crc & 0xFF) << 8) | ((crc >> 8) & 0xFF)
3.6 帧间隔时间要求
RTU 模式依赖时间间隔来区分帧边界,这是与 TCP 模式最大的区别之一。
| 间隔类型 | 时间要求 | 说明 |
|---|---|---|
| 帧内字节间隔 | < 1.5 字符时间 | 同一帧内字节间停顿超过此值,接收方认为帧不完整并丢弃 |
| 帧间间隔 | ≥ 3.5 字符时间 | 两帧之间必须保持此静默时间,用于帧同步 |
字符时间计算:
字符时间 = 1 个起始位 + 8 个数据位 + 1 个校验位(可选) + 1 个停止位。以常见配置 9600 bps、8N1 为例:
- 1 字符时间 = 10 位 / 9600 bps ≈ 1.04 ms
- 1.5 字符时间 ≈ 1.56 ms
- 3.5 字符时间 ≈ 3.64 ms
常见陷阱:许多 RS-485 转换器或软件库默认使用超时机制代替严格的时间间隔检测。在高速率(如 115200 bps)下,3.5 字符时间仅为约 0.3 ms,普通操作系统线程调度难以精确满足,此时建议使用硬件流控或专用协议芯片。
3.7 帧结构示例
以下是一个完整的 RTU 请求帧示例:读取从机地址 1 的保持寄存器,起始地址 0x0000,读取 2 个寄存器。
字节索引 值(十六进制) 说明
0 0x01 地址域:从机地址 1
1 0x03 功能码:读保持寄存器
2 0x00 数据域:起始地址高字节
3 0x00 数据域:起始地址低字节
4 0x00 数据域:寄存器数量高字节
5 0x02 数据域:寄存器数量低字节
6 0xC4 CRC 低字节
7 0x0B CRC 高字节
发送顺序:01 03 00 00 00 02 C4 0B。