3. RTU帧结构详解
Modbus RTU 的可靠性来自一套非常朴素的约定:用静默时间划清帧边界,用地址区分从站,用功能码说明意图,用数据域承载参数,用 CRC 兜底校验。理解这五个部分,等于拿到了从站开发最底层的“语法书”。本节把 RTU 帧拆开逐段讲解,并说明 3.5 字符定时为什么是从站状态机的第一驱动力。
一、RTU 帧的整体结构
一条标准 RTU 帧由四段组成,总长度取决于功能码和数据量:
| 字段 | 长度 | 作用 | 从站处理要点 |
|---|---|---|---|
| 地址域 | 1 字节 | 标识目标从站 | 0 为广播,1~247 为单播地址 |
| 功能码 | 1 字节 | 说明操作类型 | 判断是否支持,决定后续数据长度 |
| 数据域 | N 字节 | 寄存器地址、数量、值等 | 按功能码解析,校验范围与数量 |
| CRC 校验 | 2 字节 | 检测传输错误 | 低字节在前,高字节在后 |
注意:RTU 帧没有起始符和结束符,帧与帧之间完全靠时间间隔区分。这也是它与 ASCII 帧最本质的区别。
二、地址域:谁在说话
地址域占 1 字节,取值范围 0~255,但工程上有效单播地址通常为 1~247。地址 0 是广播地址,所有从站都应接收并执行,但不应答;248~255 为保留地址。从站收到一帧后,第一件事就是比较地址:
- 地址等于本机地址:正常处理并回复。
- 地址为 0:执行写操作,但不回复。
- 地址为其他值:直接丢弃,不产生任何响应。
三、功能码:要做什么
功能码占 1 字节,常见的有 0x01 读线圈、0x02 读离散输入、0x03 读保持寄存器、0x04 读输入寄存器、0x05 写单个线圈、0x06 写单个寄存器、0x0F 写多个线圈、0x10 写多个寄存器。从站收到功能码后,需要判断两件事:是否支持该功能码,以及数据域长度是否符合该功能码的格式。
如果功能码不支持,从站应返回异常响应:功能码最高位置 1,并在数据域给出异常码。例如请求 0x03 不支持时,返回 0x83 加异常码 0x01(非法功能)。
四、数据域:参数与边界
数据域长度和内容由功能码决定。以 0x03 读保持寄存器为例,请求帧数据域为 4 字节:起始地址高、低字节,寄存器数量高、低字节。响应帧数据域为 1 字节字节数加 2N 字节寄存器值。从站必须对以下边界做检查:
- 起始地址加数量是否越界。
- 数量是否在 1~125 之间(0x03 的常见限制)。
- 字节数是否与数量匹配。
任何一项不满足,都应返回异常码 0x02(非法数据地址)或 0x03(非法数据值),而不是强行截断或返回错误数据。
五、CRC 校验:最后一道防线
CRC 采用 CRC-16/MODBUS,多项式 0xA001(反向表示),初值 0xFFFF。计算范围是地址域、功能码、数据域的全部字节,不包括 CRC 本身。发送时低字节在前,高字节在后。从站收到帧后,对前面所有字节重新计算 CRC,与帧尾两字节比较,不一致则丢弃。
uint16_t modbus_crc16(const uint8_t *buf, uint16_t len)
{
uint16_t crc = 0xFFFF;
for (uint16_t i = 0; i < len; i++) {
crc ^= buf[i];
for (uint8_t b = 0; b < 8; b++) {
if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001;
else crc >>= 1;
}
}
return crc;
}
六、帧间隔与 3.5 字符定时
RTU 规定:帧内字符间隔不得超过 1.5 个字符时间,帧与帧之间至少需要 3.5 个字符时间的静默。一个字符时间取决于波特率和帧格式。以 9600bps、8 数据位、无校验、1 停止位为例,一个字符共 10 位,字符时间约为 1.042ms,3.5 字符约为 3.65ms。
从站通常用定时器实现:每收到一个字节就重置定时器;当定时器超过 3.5 字符时间且缓冲区非空,就认为一帧结束,进入解析流程。如果帧内出现超过 1.5 字符的间隔,应丢弃当前帧并重新同步。
七、案例:一次读保持寄存器的完整帧
假设从站地址为 0x01,主机读取起始地址 0x0000 的 2 个保持寄存器,请求帧为:
01 03 00 00 00 02 C4 0B
地址 功能码 起始地址 数量 CRC
从站正常响应(假设寄存器值为 0x1234、0x5678):
01 03 04 12 34 56 78 XX XX
地址 功能码 字节数 数据1 数据2 CRC
如果从站不支持 0x03,则返回:
01 83 01 XX XX
地址 异常功能码 异常码 CRC
八、常见误区与复盘
- 误区一:把 3.5 字符定时写成固定毫秒。正确做法是按波特率和字符位数动态计算,否则换波特率后帧边界会错乱。
- 误区二:CRC 校验通过就立即执行写操作。应先完成地址、功能码、数据范围检查,再执行,避免非法参数写入。
- 误区三:广播帧也回复。广播地址 0 的帧不应答,否则总线会出现冲突。
- 误区四:帧内间隔超过 1.5 字符仍继续拼接。应丢弃当前帧并重新同步,否则会把两帧错误地拼成一帧。
把地址、功能码、数据域、CRC 和 3.5 字符定时这五件事逐一落实,RTU 从站的帧处理骨架就立住了。后续所有功能码实现,都是在这套骨架上填充具体的数据映射与异常分支。