4. Modbus RTU帧结构:地址域、功能码、数据域、CRC校验、报文示例解析
好,咱们今天来聊聊Modbus RTU的帧结构。说实话,这玩意儿是Modbus通信的根基。你如果搞不懂帧结构,后面做协议转换肯定会踩坑。我当年刚入行时,就因为CRC算错了一个字节,现场调试了整整一个下午——嗯,那种滋味,记忆犹新。
4.1 整体帧结构长什么样?
Modbus RTU的报文,说白了就是一串字节流。它没有像TCP那样复杂的头部,结构非常简洁。一个完整的RTU帧,由以下四部分组成:
| 字段 | 长度 | 说明 |
|---|---|---|
| 地址域 | 1字节 | 从站地址,范围1~247 |
| 功能码 | 1字节 | 指示操作类型(读/写等) |
| 数据域 | N字节 | 具体的数据内容,长度可变 |
| CRC校验 | 2字节 | 循环冗余校验,低字节在前 |
你想想看,整个帧就这么简单。但越简单的东西,越容易在细节上出问题。咱们一个一个拆开讲。
4.2 地址域——谁在说话?
地址域只有1个字节。主站发报文时,这个字节告诉所有从站:「这条消息是发给谁的」。从站收到后,先看地址是不是自己——不是自己的,直接扔掉,不处理。
我个人习惯把地址域叫做「门牌号」。0地址是广播地址,所有从站都得处理,但从站不会回复。1到247是单播地址。248到255是保留的,我建议你别用,有些老设备会不认。
关键点:地址0x00是广播,从站收到后执行但不回复。这在批量设置参数时很有用,但要注意——如果某个从站没收到,主站是不知道的。
4.3 功能码——要干什么?
功能码也是1个字节。它告诉从站:你要读线圈?还是写寄存器?常见的功能码就那么几个:
- 0x01:读线圈状态
- 0x02:读离散输入
- 0x03:读保持寄存器(这个最常用)
- 0x04:读输入寄存器
- 0x05:写单个线圈
- 0x06:写单个寄存器
- 0x0F:写多个线圈
- 0x10:写多个寄存器
我在项目中遇到过一个问题:有个设备的功能码0x03和0x04返回的数据长度不一样。后来查手册才发现,0x03读的是掉电保持的寄存器,0x04读的是实时采样值。嗯,功能码选错了,数据就全乱了。
小技巧:如果从站返回的功能码最高位是1(比如0x83),说明发生了异常。这时候数据域的第一个字节就是异常码。
4.4 数据域——具体内容
数据域是帧里最灵活的部分。它可以是0个字节,也可以是252个字节(最大)。具体内容取决于功能码:
- 读操作:数据域包含起始地址和读取数量
- 写操作:数据域包含地址、数据值
- 从站回复:数据域包含字节计数和实际数据
举个例子,读保持寄存器的请求帧:
从站地址: 0x01
功能码: 0x03
起始地址: 0x00 0x00 (高字节在前)
寄存器数: 0x00 0x0A (读10个寄存器)
CRC校验: 0xXX 0xXX
你看,数据域里地址和数量都是16位的,而且是大端序(高字节在前)。这个顺序千万别搞反了,我见过有人把高低字节写反,结果读出来的数据驴唇不对马嘴。
4.5 CRC校验——数据对不对?
CRC校验是Modbus RTU的「守门员」。它占2个字节,校验范围是从地址域到数据域的所有字节。算法是CRC-16/MODBUS,多项式是0x8005。
计算步骤其实不复杂:
- 初始化CRC寄存器为0xFFFF
- 对每个字节,与CRC低字节异或
- 右移8次,每次检测最低位,如果为1则与0xA001异或
- 处理完所有字节后,得到的结果就是CRC值
- 发送时先发低字节,再发高字节
注意:CRC校验是RTU模式独有的。如果你用的是ASCII模式,校验方式完全不同。做协议转换时,一定要确认对方用的是RTU还是ASCII。我曾经在项目里没注意这个,结果两边CRC对不上,折腾了两天才发现是模式不匹配。
4.6 报文示例解析
光说不练假把式。咱们看一个完整的例子。假设主站要读取地址为0x01的从站,从寄存器地址0x0000开始,读5个保持寄存器:
请求帧(主站→从站):
01 03 00 00 00 05 85 C9
拆开来看:
- 01:从站地址
- 03:功能码(读保持寄存器)
- 00 00:起始寄存器地址
- 00 05:读取5个寄存器
- 85 C9:CRC校验(低字节85在前,高字节C9在后)
正常响应帧(从站→主站):
01 03 0A 00 01 00 02 00 03 00 04 00 05 4A 3B
拆开来看:
- 01:从站地址
- 03:功能码(原样返回)
- 0A:数据域有10个字节(5个寄存器×2字节)
- 00 01 00 02 00 03 00 04 00 05:5个寄存器的值
- 4A 3B:CRC校验
你看,每个寄存器占2个字节,高字节在前。如果从站返回的数据是00 01,那实际值就是1,不是256。
避坑指南:我曾经遇到过从站返回的字节数不对。比如我请求读5个寄存器,它应该返回0x0A(10个字节),但它返回了0x0B。这种情况下,CRC校验大概率会失败。所以,收到报文后先检查字节计数,再算CRC,这个顺序别搞反了。
4.7 帧结构知识体系
为了让你更直观地理解,我画了一张图,把整个帧结构的关系串起来:
这张图把帧结构的四个字段和它们的子项都串起来了。你写代码的时候,脑子里要有这张图——先填地址,再填功能码,然后组装数据,最后算CRC。顺序别乱,乱了就收不到正确回复。
4.8 小结
Modbus RTU帧结构,说白了就是四个部分:地址、功能码、数据、CRC。每个部分都有它的规矩。地址不能乱写,功能码不能选错,数据域要按大端序排列,CRC要算对。这些细节,任何一个出问题,通信就断了。
我个人建议,刚开始做Modbus开发时,先用串口助手抓一下报文,看看实际收发的数据长什么样。亲眼看到01 03 00 00 00 05 85 C9这个序列,比看一百遍理论都管用。嗯,今天就到这儿,下一章咱们聊功能码的详细用法。