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 字符时间的静默间隔。
Modbus RTU 帧结构分解 Modbus RTU 帧结构 地址域 1 字节 功能码 1 字节 数据域 N 字节(0 ~ 252) CRC 校验 2 字节 低字节在前 高字节在后

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 计算步骤:

  1. 将 16 位 CRC 寄存器初始化为 0xFFFF。
  2. 取帧中第一个字节(地址域)与 CRC 寄存器低 8 位异或,结果放回 CRC 寄存器。
  3. CRC 寄存器右移 1 位,高位补 0;检查移出的最低位:若为 1,则与多项式 0xA001(反转后的 0x8005)异或。
  4. 重复步骤 3 共 8 次(处理完一个字节)。
  5. 取下一个字节,重复步骤 2~4,直到所有字节处理完毕。
  6. 最终 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)
CRC-16 Modbus 计算流程 CRC-16 Modbus 计算流程 CRC = 0xFFFF 取下一个字节 CRC ^= byte 循环 8 次移位与异或 还有字节? 是 否 输出 CRC

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。