3. Modbus RTU详解

Modbus RTU 是工业现场最常见的串行通信协议之一,它运行在 RS-485 或 RS-232 物理层之上,以紧凑的二进制帧、明确的地址域和可靠的 CRC 校验,支撑起 PLC、仪表、变频器、网关之间的数据交换。理解 RTU 的关键,不是死记功能码,而是把“串行链路—帧结构—校验—寻址—功能码—异常响应”串成一条完整的通信链。下面从通信基础开始,逐层拆解 RTU 帧的组成与交互逻辑。

一、串行通信基础:RTU 的物理与链路前提

Modbus RTU 本身是应用层协议,它依赖串行链路完成比特传输。现场最常用的是 RS-485 两线半双工总线:主站发送请求,从站应答,同一时刻总线上只能有一个发送方。通信参数必须主从一致,包括波特率、数据位、校验位、停止位,常见组合为 9600/8/N/1、19200/8/E/1 等。若参数不一致,表现为收不到数据、乱码或 CRC 错误。

RTU 与 ASCII 模式的最大区别在于:RTU 用二进制字节传输,帧与帧之间依靠至少 3.5 个字符时间的静默间隔来界定边界;ASCII 模式则用冒号开始、回车换行结束。因此 RTU 的帧同步依赖时间间隔,若主站连续发送过快、从站处理慢,就可能出现帧粘连或截断。实际工程中,网关或串口服务器常通过“帧间隔超时”参数来模拟这一静默时间。

RS-485 总线两端应加终端电阻(通常 120Ω),并采用手拉手拓扑,避免星形分支过长;屏蔽层单点接地,可显著降低 CRC 错误率。

二、RTU 帧结构:地址域、功能码、数据域与 CRC

一个标准 RTU 帧由四部分组成:从站地址(1 字节)、功能码(1 字节)、数据域(N 字节)、CRC 校验(2 字节,低字节在前)。地址域决定“谁应答”,功能码决定“做什么”,数据域携带寄存器地址、数量或写入值,CRC 负责检错。帧长度不固定,最短 4 字节(地址+功能码+CRC),最长 256 字节左右。

以读取保持寄存器为例,主站请求帧为:01 03 00 00 00 02 C4 0B。其中 01 是从站地址,03 是功能码,00 00 是起始寄存器地址,00 02 是读取数量,C4 0B 是 CRC。从站正常应答为:01 03 04 00 0A 00 0B XX XX,其中 04 表示后续数据字节数,00 0A 与 00 0B 是两个寄存器的值。

Modbus RTU 帧结构(请求 / 正常应答 / 异常应答) 主站请求帧 地址域 01 功能码 03 数据域(起始地址 + 数量) 00 00 00 02 CRC 校验 C4 0B 从站正常应答帧 地址域 01 功能码 03 字节数 + 寄存器数据 04 00 0A 00 0B CRC 校验 XX XX 从站异常应答帧 地址域 01 功能码 + 0x80 83 异常码 02(非法数据地址) CRC 校验 XX XX 关键规则 • 地址 0 为广播,从站不应答 • 功能码最高位置 1 表示异常 • CRC 低字节在前,高字节在后 • 帧间静默 ≥ 3.5 字符时间 • 数据域长度随功能码变化 • 从站地址范围 1~247 • 异常码 01~04 为常用类型 • 广播写操作无应答返回 • 超时通常设为 300~1000ms

三、CRC 校验:RTU 的检错核心

CRC(循环冗余校验)是 RTU 帧可靠性的最后一道防线。发送方对“地址+功能码+数据域”逐字节计算 CRC-16,得到 2 字节校验值附加在帧尾;接收方用同样算法重新计算,若结果不一致则丢弃整帧。RTU 使用的 CRC-16 多项式为 0xA001(反向表示),初始值 0xFFFF,不进行最终异或。

下面是一段常用的 CRC 计算代码,可用于嵌入式或网关脚本中:

uint16_t modbus_crc16(uint8_t *buf, uint16_t len) {
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i < len; i++) {
        crc ^= buf[i];
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc = (crc >> 1) ^ 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

注意:计算范围不包含 CRC 本身;发送时低字节先发。若现场频繁出现 CRC 错误,优先排查波特率偏差、线缆质量、终端电阻和电磁干扰,而不是先怀疑从站程序。

四、地址域、功能码与异常响应

地址域是 RTU 的寻址基础。主站发送的地址决定哪个从站应答,从站地址通常为 1~247,0 为广播地址,广播写操作所有从站执行但不返回应答。地址冲突是现场常见故障:两个从站设为同一地址时,总线上会出现数据碰撞或应答混乱,表现为“有时通、有时不通”。

功能码定义操作类型,常用功能码如下表所示:

功能码 名称 作用 典型数据域
0x01 读线圈 读取开关量输出状态 起始地址 + 数量
0x02 读离散输入 读取开关量输入状态 起始地址 + 数量
0x03 读保持寄存器 读取模拟量/参数值 起始地址 + 数量
0x04 读输入寄存器 读取只读测量值 起始地址 + 数量
0x05 写单个线圈 控制单路开关 线圈地址 + FF00/0000
0x06 写单个寄存器 写入单个参数 寄存器地址 + 值
0x0F 写多个线圈 批量控制开关量 地址 + 数量 + 字节数 + 数据
0x10 写多个寄存器 批量写入参数 地址 + 数量 + 字节数 + 数据

当从站无法完成请求时,会返回异常响应:功能码最高位置 1(即原功能码 + 0x80),随后跟一个异常码。例如请求功能码 0x03,异常应答功能码为 0x83。常见异常码包括:01 非法功能、02 非法数据地址、03 非法数据值、04 从站设备故障。网关集成时,必须把异常码映射为上层可读的错误信息,而不是简单丢弃。

异常响应不是通信失败,而是从站“明确拒绝”。若主站把异常帧当作正常数据解析,可能把 0x83 误判为功能码 0x03 的数据,导致寄存器值错乱。解析时应先判断功能码最高位。

五、案例与复盘:一次典型的 RTU 轮询故障

某项目网关轮询 8 台电表,前 3 台正常,第 4 台开始频繁超时。排查发现:第 4 台电表地址被误设为 3,与第 3 台冲突;同时总线末端未接终端电阻,CRC 错误率偏高。修正地址并加装 120Ω 电阻后,轮询恢复稳定。这个案例说明:RTU 故障往往不是单一原因,地址冲突、CRC 错误、超时设置需要联合排查。

建议的排查顺序为:先确认物理层(接线、终端电阻、屏蔽接地),再核对通信参数(波特率、校验位、停止位),然后检查地址唯一性,最后用串口助手抓取原始帧,逐字节比对地址、功能码、数据域和 CRC。只有把帧结构看透,才能在网关集成中快速定位问题。

RTU 的本质是“带地址和校验的二进制问答”:主站按帧提问,从站按帧回答,CRC 保证可信,异常码说明原因。掌握这五要素,就掌握了 Modbus RTU 的通信主线。