4. RTU帧格式详解

Modbus RTU 是工业现场应用最广泛的串行通信协议之一,其帧结构紧凑、无格式开销,但同时对字节顺序、校验算法和时序要求极为严格。理解 RTU 帧的四个组成部分——地址码、功能码、数据域和 CRC 校验——是进行设备调试、协议解析和故障定位的基础。本章将从帧结构出发,逐一拆解每个字段的编码规则、字节序约定和 CRC 计算原理,并结合实际报文案例,帮助读者建立从物理层字节流到应用层数据的完整映射关系。

Modbus RTU 帧结构总览(从站视角) 地址码 1 Byte 功能码 1 Byte 数据域 N Byte(可变) CRC 校验 2 Byte(低字节在前) 字节发送顺序: 地址码 → 功能码 → 数据域(高字节先发) → CRC 低字节 → CRC 高字节 CRC-16 (Modbus) 计算流程 预置 CRC = 0xFFFF 取 1 字节数据 CRC ^= 数据字节 右移 1 位(LSB 为 0) LSB=1? 异或 0xA001 循环 8 次 所有字节处理完,取反得到 CRC 报文示例 01 03 00 00 00 02 C4 0B 地址=01 功能=03 数据=00 00 00 02 CRC=C4 0B CRC 低字节 C4 先发

4.1 地址码:从站寻址与广播机制

地址码是 RTU 帧的第一个字节,取值范围为 0x00~0xF7(0~247),其中 0x00 保留为广播地址,所有从站必须接收并执行广播命令,但不需要回复响应帧。实际项目中,从站地址通常设置为 1~247,主站通过地址码唯一确定通信对象。地址码的编码规则简单,但工程中容易忽略两个细节:一是地址 0x00 的广播帧不能用于写操作(如写保持寄存器),因为从站无法确认是否执行成功;二是地址码与功能码共同构成帧头,接收方在解析时必须先校验地址匹配,再校验 CRC,否则可能被错误帧干扰。

经验提醒: 在多从站总线中,建议将地址 1 保留给网关或主站调试工具,从站地址从 2 开始分配,便于后续扩展和维护。同时,地址码的分配应记录在设备台账中,避免冲突。

4.2 功能码:操作类型与异常响应

功能码占用 1 个字节,定义了主站请求的操作类型。常用功能码包括:0x01 读线圈、0x02 读离散输入、0x03 读保持寄存器、0x04 读输入寄存器、0x05 写单线圈、0x06 写单寄存器、0x0F 写多线圈、0x10 写多寄存器。功能码的高位(bit7)用于异常响应标志:当从站返回的功能码最高位为 1 时(如 0x83 对应 0x03),表示异常响应,数据域的第一个字节为异常码(如 0x01 非法功能、0x02 非法数据地址、0x03 非法数据值)。

功能码名称数据域方向典型应用
0x03读保持寄存器请求:起始地址(2B)+数量(2B);响应:字节数(1B)+寄存器值(N×2B)读取 PLC 的设定参数
0x06写单寄存器请求:地址(2B)+值(2B);响应:原样返回请求帧修改单个设定值
0x10写多寄存器请求:起始地址(2B)+数量(2B)+字节数(1B)+数据(N×2B)批量下发参数表
0x83异常响应(读保持寄存器)异常码(1B)报告非法地址或非法功能

功能码的选择直接影响数据域的编码方式。例如,读保持寄存器(0x03)的请求数据域固定为 4 个字节(起始地址高字节、起始地址低字节、寄存器数量高字节、寄存器数量低字节),而响应数据域则包含字节计数字段和寄存器值。理解功能码与数据域的对应关系,是编写协议解析代码的前提。

4.3 数据域:字节序规则与多寄存器编码

数据域是帧中长度可变的部分,承载实际读写数据。Modbus RTU 规定,所有多字节数据(如寄存器地址、寄存器值、数量)均采用大端字节序(Big-Endian),即高字节先发送,低字节后发送。例如,寄存器地址 0x0102 在帧中表现为 01 02,而不是 02 01。这一规则在读写多个寄存器时尤为重要:每个寄存器值占 2 个字节,先发高字节,再发低字节。

// 读保持寄存器请求帧示例(读取地址 0x0000 开始的 2 个寄存器)
// 地址码: 0x01
// 功能码: 0x03
// 起始地址: 0x0000 → 发送 0x00 0x00
// 寄存器数量: 0x0002 → 发送 0x00 0x02
// CRC: 计算后得到 0xC4 0x0B(低字节先发)
// 完整帧: 01 03 00 00 00 02 C4 0B

对于浮点数或 32 位整数等跨寄存器数据,Modbus 协议本身未定义字节序,通常由设备厂商在寄存器映射表中说明。常见的有两种方式:AB CD(寄存器顺序 + 寄存器内大端)和 CD AB(寄存器顺序交换)。工程中必须查阅设备手册确认,否则会出现数据解析错误。

风险提示: 字节序错误是 Modbus 调试中最常见的问题之一。例如,读取到的寄存器原始值为 12 34,若按小端解析则为 0x3412,而正确值应为 0x1234。建议在解析代码中统一封装字节序转换函数,并在测试阶段用已知值验证。

4.4 CRC 校验:字节顺序与计算原理

CRC(循环冗余校验)是 RTU 帧的最后两个字节,用于检测传输过程中的数据错误。Modbus RTU 采用 CRC-16/IBM 算法,多项式为 x^16 + x^15 + x^2 + 1(即 0x8005,反射形式为 0xA001),初始值为 0xFFFF,计算完成后结果取反。CRC 的字节顺序为低字节在前,高字节在后,与数据域的大端序相反。

CRC 计算步骤如下:

  1. 预置 16 位寄存器为 0xFFFF。
  2. 取帧中第一个字节(地址码)与 CRC 寄存器的低 8 位异或,结果放回 CRC 寄存器。
  3. 将 CRC 寄存器右移 1 位,最高位补 0。若移出的最低位为 1,则与 0xA001 异或。
  4. 重复步骤 3 共 8 次,完成一个字节的处理。
  5. 对帧中所有字节(地址码、功能码、数据域)重复步骤 2~4。
  6. 最终得到的 CRC 寄存器值取反,即为校验码。
// CRC 计算 C 语言示例(查表法可提高效率)
uint16_t crc16_modbus(uint8_t *data, uint16_t len) {
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i < len; i++) {
        crc ^= data[i];
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc = (crc >> 1) ^ 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;  // 返回值为取反前的 CRC,发送时需取反并低字节在前
}

发送时,将计算得到的 CRC 值取反,先发送低字节,再发送高字节。例如,计算得到 CRC 为 0x0BC4,则帧中发送 C4 0B。接收方对收到的完整帧(包括 CRC 字段)重新计算 CRC,若结果为 0x0000,则校验通过;否则丢弃该帧。

4.5 实战案例:从报文到数据的完整解析

假设某温度采集模块地址为 0x11,使用功能码 0x03 读取保持寄存器 0x0000 和 0x0001 中的温度值(浮点数,AB CD 格式)。主站发送请求帧:11 03 00 00 00 02 C4 0B(CRC 为示例值)。从站响应帧为:11 03 04 41 20 00 00 8A 9C。解析过程如下:

  • 地址码:0x11,确认是发给本设备的帧。
  • 功能码:0x03,读保持寄存器,且最高位为 0,表示正常响应。
  • 字节计数:0x04,表示后续有 4 个数据字节。
  • 寄存器值:41 20 00 00,按大端序组合为 0x41200000,即 IEEE 754 浮点数 10.0。
  • CRC:8A 9C,接收方重新计算前 6 个字节的 CRC,取反后与帧中 CRC 比较,一致则接受。

若响应帧的 CRC 校验失败,可能原因包括:波特率不匹配导致字节错位、RS-485 总线干扰导致数据翻转、从站发送时序异常(如帧间隔不足)。此时应使用示波器或逻辑分析仪抓取物理层波形,确认每个字节的起始位和停止位是否完整。

排障要点: 当 CRC 校验失败时,不要急于修改从站程序。首先检查主站发送的请求帧 CRC 是否正确(可用 Modbus Poll 等工具验证),然后检查从站响应帧的字节数是否符合功能码规范。若字节数正确但 CRC 仍失败,重点排查总线终端电阻和接地情况。

4.6 常见误区与总结

在实际工程中,以下误区需要特别注意:

  • 误区一: 认为 CRC 的字节顺序与数据域一致。实际上 CRC 低字节在前,而数据域高字节在前,两者相反。
  • 误区二: 忽略广播地址 0x00 的特殊性。广播帧不产生响应,若主站程序等待从站回复,会超时。
  • 误区三: 将功能码 0x03 与 0x04 混淆。0x03 读保持寄存器(可写),0x04 读输入寄存器(只读),地址空间不同。
  • 误区四: 在计算 CRC 时忘记对结果取反。取反是 Modbus RTU 与标准 CRC-16/IBM 的差异点,直接使用标准库函数可能导致校验错误。

总结而言,RTU 帧格式的精髓在于“字节顺序”和“校验算法”两个维度。地址码和功能码决定了通信对象和操作类型,数据域承载具体参数,CRC 则保障了传输可靠性。掌握这些规则后,无论是编写主站程序、调试从站设备,还是分析总线报文,都能做到有据可依。建议读者在开发过程中,使用 Modbus 调试助手或自写脚本对每一帧进行 CRC 验证,逐步形成对帧结构的直觉判断。