第3章:Modbus帧结构——报文格式解析、地址域、功能码、数据域、CRC校验原理
各位同学,今天我们来聊聊Modbus协议里最核心的东西——帧结构。
说实话,我刚开始接触Modbus时,看着那一串十六进制数,脑袋都是懵的。后来在工厂调试一个温湿度传感器,死活读不到数据,折腾了两天,最后发现是CRC校验算错了。从那以后,我对帧结构的每个字节都格外敏感。
好,咱们直接进入正题。
3.1 报文格式:Modbus RTU的“骨架”
Modbus RTU的报文,说白了就是一段有固定格式的二进制数据。我习惯把它想象成一封挂号信:有收件人地址、信件内容、还有防伪封条。
一个完整的Modbus RTU帧,长这样:
| 地址域 | 功能码 | 数据域 | CRC校验 |
| 1字节 | 1字节 | N字节 | 2字节 |
嗯,就这么简单。总共最少4个字节,最多256个字节。为什么是256?因为数据域最长能到252个字节,加上地址、功能码和CRC,刚好256。
我在项目中见过有人把数据域塞得满满当当,结果从机处理不过来,直接超时。所以啊,别贪心,数据量大的时候分几次读更稳妥。
3.2 地址域:谁是你的通信对象
地址域占1个字节,取值范围是0~247。其中:
- 0:广播地址,所有从机都要接收,但不回复
- 1~247:从机地址,每个设备一个唯一编号
- 248~255:保留,别用
你想想看,如果总线上挂了20个设备,地址乱糟糟的,排查故障时得多痛苦?
3.3 功能码:告诉从机你要干什么
功能码也是1个字节。它定义了主站想让从站做什么。常用的就那么几个:
| 功能码 | 含义 | 我常用的场景 |
|---|---|---|
| 0x01 | 读取线圈状态 | 读取继电器输出 |
| 0x02 | 读取离散输入 | 读取按钮、限位开关 |
| 0x03 | 读取保持寄存器 | 读取传感器数值、参数 |
| 0x04 | 读取输入寄存器 | 读取模拟量输入 |
| 0x05 | 写单个线圈 | 控制单个继电器 |
| 0x06 | 写单个寄存器 | 设置单个参数 |
| 0x0F | 写多个线圈 | 批量控制输出 |
| 0x10 | 写多个寄存器 | 批量设置参数 |
这里有个坑,我曾经踩过:有些国产设备的功能码实现不标准,比如0x03和0x04混用。你发0x03读输入寄存器,它也能响应,但数据是错的。所以拿到新设备,第一件事就是看它的Modbus协议文档。
3.4 数据域:真正干活的地方
数据域是帧的“正文”。它包含:
- 起始地址:从哪个寄存器开始读/写(2字节)
- 寄存器数量:读/写多少个(2字节)
- 数据内容:写操作时,这里放要写入的值
举个例子,我要读取地址为1的从机,从寄存器0x0000开始读2个寄存器:
发送:01 03 00 00 00 02 C4 0B
| | | | |
| | | | +-- CRC校验
| | | +-------- 寄存器数量(2个)
| | +-------------- 起始地址(0x0000)
| +----------------- 功能码(0x03读保持寄存器)
+-------------------- 从机地址(1)
从机回复:
接收:01 03 04 00 64 00 C8 7A 3F
| | | | |
| | | | +-- CRC校验
| | | +----------- 数据(0x0064=100, 0x00C8=200)
| | +-------------- 字节数(4个字节)
| +----------------- 功能码(0x03)
+-------------------- 从机地址(1)
3.5 CRC校验:数据的“指纹”
CRC(循环冗余校验)是Modbus RTU的防伪封条。它占2个字节,放在帧的末尾。
为什么需要CRC?因为工业现场干扰多,电磁噪声、线路老化都可能导致数据出错。没有CRC,你读到100℃可能实际上是150℃,后果嘛...你懂的。
CRC的计算过程,说白了就是:
- 把整个帧(地址+功能码+数据)当成一串二进制数
- 用这个数除以一个固定的多项式(0x8005)
- 余数就是CRC值
嗯,数学上就是这么回事。但实际编程时,我们不会真的去做除法,太慢了。我一般用查表法:
def crc16_modbus(data):
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return crc
这段代码我用了快十年了,从单片机到树莓派,从Python到C,基本没改过。你直接拿去用就行。
3.6 异常响应:当事情不顺利时
如果从机收到请求,但无法执行,它会返回一个异常帧:
| 地址域 | 功能码+0x80 | 异常码 | CRC校验 |
| 1字节 | 1字节 | 1字节 | 2字节 |
常见的异常码:
- 01:非法功能码——从机不支持这个功能
- 02:非法数据地址——你读的寄存器不存在
- 03:非法数据值——你写的值超出范围
- 04:从机故障——设备内部出问题了
我个人习惯,在调试阶段把异常码打印出来。这样能快速定位问题,省得瞎猜。
3.7 本章小结
Modbus帧结构,说白了就是一套约定:谁说话、说什么、怎么说、怎么防伪。掌握了这四点,你就掌握了Modbus通信的精髓。
下一章,我们会用Python代码把这些帧结构变成真正的通信程序。到时候,你就能亲手抓包、解析、发送Modbus报文了。
01 03 00 00 00 02 的CRC值,看看是不是 C4 0B。算不出来没关系,用上面的Python代码跑一遍也行。
好了,帧结构就讲到这里。记住,Modbus通信的本质就是拼帧、发帧、收帧、拆帧。把这四个步骤搞明白,你就入门了。
公众号:蓝海资料掘金营,微信deep3321