3、Modbus RTU 帧结构:RTU 报文格式、地址域、功能码域、数据域、CRC校验原理与计算示例

Modbus RTU 是工业现场最常用的串行通信协议之一。理解其报文帧结构是开发数据采集驱动、边缘网关和协议转换器的核心基础。RTU 模式采用二进制编码,报文紧凑、效率高,但要求严格的时序控制。

3.1 RTU 报文帧格式

Modbus RTU 的报文以“帧”为单位进行传输。每一帧由四个主要域组成:地址域、功能码域、数据域和CRC 校验域。帧与帧之间通过至少 3.5 个字符时间的静默间隔(Silent Interval)来区分。

域名称 长度(字节) 说明
地址域(Address) 1 从站设备地址,范围 1~247
功能码域(Function Code) 1 指示读/写操作类型(如 0x03 读保持寄存器)
数据域(Data) N(可变) 包含起始地址、寄存器数量、数据值等
CRC 校验域(CRC) 2 循环冗余校验,低字节在前,高字节在后
关键时序约束:RTU 帧的起始和结束必须由总线静默间隔(≥ 3.5 字符时间)界定。如果两个字节之间的间隔超过 1.5 字符时间,接收方将认为帧不完整并丢弃。
Modbus RTU 帧结构全景图 Modbus RTU 报文帧结构 ≥ 3.5 字符 静默间隔 地址域 1 字节 功能码域 1 字节 数据域 N 字节(可变) CRC 校验域 2 字节 ≥ 3.5 字符 静默间隔 传输顺序:地址 → 功能码 → 数据 → CRC(低字节先发) 帧间间隔 ≥ 3.5 字符时间;字节间间隔 ≤ 1.5 字符时间

3.2 地址域与功能码域

3.2.1 地址域(Address Field)

地址域长度为 1 字节,用于标识总线上的目标从站设备。主站发送的请求帧中包含目标从站地址;从站响应帧中则包含自身地址,以便主站识别响应来源。

  • 有效地址范围:1 ~ 247(0x01 ~ 0xF7)
  • 广播地址:0(0x00),所有从站接收但不回复
  • 保留地址:248 ~ 255(0xF8 ~ 0xFF)

3.2.2 功能码域(Function Code Field)

功能码域长度为 1 字节,指示主站希望从站执行的操作类型。功能码分为公共功能码、用户定义功能码和保留功能码三类。以下是工业数据采集中最常用的功能码:

功能码 名称 操作对象 典型用途
0x01 Read Coils 线圈(DO) 读取开关量输出状态
0x02 Read Discrete Inputs 离散输入(DI) 读取开关量输入状态
0x03 Read Holding Registers 保持寄存器 读取模拟量、参数、设定值
0x04 Read Input Registers 输入寄存器 读取只读模拟量(如传感器值)
0x05 Write Single Coil 单个线圈 控制单个 DO 输出
0x06 Write Single Register 单个保持寄存器 写入单个参数
0x0F Write Multiple Coils 多个线圈 批量控制 DO 输出
0x10 Write Multiple Registers 多个保持寄存器 批量写入参数
异常响应:当从站无法执行请求时,会在响应帧中将功能码的最高位置 1(即功能码 + 0x80),并附带一个异常码(1 字节)说明错误原因。例如:请求功能码 0x03,异常响应功能码为 0x83。

3.3 数据域(Data Field)

数据域是帧中长度可变的部分,其内容和长度取决于功能码。对于请求帧,数据域通常包含起始地址和寄存器/线圈数量;对于响应帧,数据域包含字节计数和实际数据值。

3.3.1 请求帧数据域示例(功能码 0x03)

读取从站地址 0x01 的保持寄存器,起始地址 0x0000,读取 2 个寄存器:

字节索引:  0       1       2       3       4       5       6       7
内容:     0x01    0x03    0x00    0x00    0x00    0x02    0xC4    0x0B
含义:     地址    功能码   起始地址高  起始地址低  数量高    数量低    CRC低   CRC高

3.3.2 响应帧数据域示例

从站正常响应,返回 4 字节数据(2 个寄存器,每个寄存器 2 字节):

字节索引:  0       1       2       3       4       5       6
内容:     0x01    0x03    0x04    0x00    0x64    0x00    0xC8    0x??    0x??
含义:     地址    功能码   字节数   寄存器1高  寄存器1低  寄存器2高  寄存器2低   CRC低   CRC高
字节序规则:Modbus RTU 协议规定多字节数据采用大端序(Big-Endian),即高字节在前,低字节在后。例如寄存器值 0x0064 表示十进制 100。

3.4 CRC 校验原理与计算示例

CRC(Cyclic Redundancy Check)是 RTU 帧中最重要的错误检测机制。Modbus RTU 使用 CRC-16(Modbus 多项式),多项式为 x¹⁶ + x¹⁵ + x² + 1(即 0x8005),初始值为 0xFFFF,输出时进行异或 0x0000 处理。

3.4.1 CRC 计算步骤

  1. 将 16 位 CRC 寄存器初始化为 0xFFFF。
  2. 取帧中第一个字节(地址域)与 CRC 寄存器低 8 位进行异或,结果放回 CRC 寄存器。
  3. 将 CRC 寄存器右移 1 位,高位补 0。
  4. 检查移出的最低位:如果为 1,则将 CRC 寄存器与多项式 0xA001(0x8005 的反射形式)进行异或;如果为 0,则继续下一步。
  5. 重复步骤 3~4 共 8 次(处理完一个字节的所有位)。
  6. 取下一个字节,重复步骤 2~5,直到所有字节处理完毕。
  7. 最终 CRC 寄存器中的值即为 CRC 校验码,先发送低字节,后发送高字节。

3.4.2 Python 计算示例

以下 Python 函数演示了标准的 Modbus RTU CRC-16 计算过程:

def calculate_crc(data: bytes) -> int:
    """
    计算 Modbus RTU CRC-16 校验码。
    :param data: 待校验的字节序列(地址 + 功能码 + 数据)
    :return: 16 位 CRC 值
    """
    crc = 0xFFFF
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 0x0001:
                crc = (crc >> 1) ^ 0xA001
            else:
                crc >>= 1
    return crc

# 示例:计算请求帧 [0x01, 0x03, 0x00, 0x00, 0x00, 0x02] 的 CRC
frame_data = bytes([0x01, 0x03, 0x00, 0x00, 0x00, 0x02])
crc_value = calculate_crc(frame_data)
print(f"CRC 值: 0x{crc_value:04X}")
# 输出: CRC 值: 0xC40B
# 发送顺序: 低字节 0x0B 先发,高字节 0xC4 后发

3.4.3 手动验算示例

以请求帧 01 03 00 00 00 02 为例,CRC 计算结果为 0xC40B。验证步骤:

  • 初始 CRC = 0xFFFF
  • 处理 0x01:CRC = 0x1E0B
  • 处理 0x03:CRC = 0x3A0B
  • 处理 0x00:CRC = 0x740B
  • 处理 0x00:CRC = 0xE80B
  • 处理 0x00:CRC = 0xD00B
  • 处理 0x02:CRC = 0xC40B

最终 CRC 为 0xC40B,低字节 0x0B 先发送,高字节 0xC4 后发送。

Modbus RTU CRC-16 计算流程图 CRC-16 (Modbus) 计算流程 初始化 CRC = 0xFFFF 取下一个字节 CRC ^= byte 循环 8 次:右移 & 条件异或 0xA001 还有字节? 是 否 输出 CRC(低字节先发) 关键参数 多项式: 0x8005 反射多项式: 0xA001 初始值: 0xFFFF 输出异或: 0x0000

3.5 完整报文示例

以下是一个完整的 Modbus RTU 请求-响应交互示例,使用功能码 0x03 读取从站地址 0x01 的 2 个保持寄存器(起始地址 0x0000):

方向 地址 功能码 数据域 CRC 低字节 CRC 高字节 完整报文(Hex)
主站→从站 0x01 0x03 0x00 0x00 0x00 0x02 0x0B 0xC4 01 03 00 00 00 02 0B C4
从站→主站 0x01 0x03 0x04 0x00 0x64 0x00 0xC8 0x?? 0x?? 01 03 04 00 64 00 C8 ?? ??
常见错误:
  • CRC 计算错误:未包含地址和功能码,或使用了错误的反射多项式。
  • 字节序错误:多字节数据(如起始地址、寄存器值)未按大端序排列。
  • 帧间隔不足:连续帧之间未保持 ≥ 3.5 字符时间的静默间隔,导致帧粘连。
  • 数据域长度不匹配:响应帧中声明的字节数与实际数据长度不一致。

3.6 边缘计算场景中的帧处理要点

在边缘计算网关或数据采集终端中,处理 Modbus RTU 帧时需特别注意以下工程实践:

  • 帧完整性检测:利用帧间静默间隔和 CRC 校验双重确认帧完整性,避免解析不完整或损坏的报文。
  • 超时重传机制:主站发送请求后,应设置超时定时器(通常 100~1000 ms),超时未收到响应则重试。
  • 多从站轮询调度:在边缘节点中,使用非阻塞 I/O 或多线程管理多个从站的轮询任务,确保实时性。
  • 数据缓存与转发:解析后的数据应缓存到本地时序数据库或内存队列,再通过 MQTT/HTTP 转发到云端。
推荐实践:在嵌入式或边缘计算平台上,使用硬件 CRC 模块或查表法加速 CRC 计算,避免在低性能 MCU 上逐位计算导致 CPU 占用过高。