3. Modbus RTU协议详解:报文结构、寄存器映射与读写操作实例
各位同学,咱们今天来啃一块硬骨头——Modbus RTU协议。说实话,这玩意儿在工业现场太常见了,几乎每个做SCADA的人都要跟它打交道。我最早接触它是在一个水处理项目上,当时被CRC校验折腾得够呛,后来摸透了才发现,其实核心就那么几块东西。
3.1 报文结构:四个部分,一个都不能少
Modbus RTU的报文,说白了就是一条指令。它由四个部分组成:地址码、功能码、数据区、CRC校验。我习惯把这四个部分比作一封信——地址码是收件人,功能码是你要干啥,数据区是具体内容,CRC校验就是信封上的火漆封印,保证没人动过手脚。
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 地址码 | 1 | 从站地址,范围1-247,0为广播地址 |
| 功能码 | 1 | 指示操作类型,如读线圈、读寄存器等 |
| 数据区 | N | 根据功能码不同,长度可变 |
| CRC校验 | 2 | 循环冗余校验,低字节在前 |
地址码:每个从站都有一个唯一地址。我见过有人把地址设成0,结果所有从站都响应了,现场乱成一锅粥。记住,0是广播地址,从站不应答。
功能码:这是协议的核心。常用的就那么几个:
- 0x01:读线圈状态
- 0x02:读离散输入
- 0x03:读保持寄存器
- 0x04:读输入寄存器
- 0x05:写单个线圈
- 0x06:写单个寄存器
- 0x0F:写多个线圈
- 0x10:写多个寄存器
数据区:这里放的是具体的操作参数。比如读寄存器时,数据区包含起始地址和读取数量。
CRC校验:嗯,这里要注意。CRC校验是Modbus RTU的命根子。我曾经在一个项目中,因为CRC计算错误,折腾了整整两天才发现是字节序搞反了。CRC的低字节在前,高字节在后,这个顺序千万别搞错。
核心要点:Modbus RTU报文是连续的字节流,帧与帧之间必须有至少3.5个字符时间的间隔。这个时间间隔很重要,如果间隔太短,从站会把两帧当成一帧处理。
3.2 寄存器映射:搞清楚数据在哪
Modbus协议定义了四种数据对象,说白了就是四种不同的“存储区域”。我刚开始做的时候,经常把保持寄存器和输入寄存器搞混,后来画了个表才彻底明白。
| 数据对象 | 访问类型 | 地址范围(PLC习惯) | Modbus地址 | 功能码 |
|---|---|---|---|---|
| 线圈(Coil) | 读写 | 00001-09999 | 0x0000-0x270E | 01, 05, 0F |
| 离散输入(Discrete Input) | 只读 | 10001-19999 | 0x0000-0x270E | 02 |
| 输入寄存器(Input Register) | 只读 | 30001-39999 | 0x0000-0x270E | 04 |
| 保持寄存器(Holding Register) | 读写 | 40001-49999 | 0x0000-0x270E | 03, 06, 10 |
你想想看,PLC里的地址是40001,对应Modbus地址就是0x0000。为什么?因为PLC地址是从1开始编号的,而Modbus地址是从0开始。这个偏移量,坑过不少人。
我的经验:在配置SCADA时,一定要搞清楚设备手册里用的是PLC地址还是Modbus地址。我见过有人把40001直接当成0x40001去读,结果读回来的数据全是乱的。
3.3 读写操作实例:动手试试
光说不练假把式。咱们来看几个实际例子。假设有一个温控器,地址是0x01,它的当前温度保存在保持寄存器地址0x0000(PLC地址40001)中。
3.3.1 读保持寄存器(功能码0x03)
我们要读取温度值,发送的报文是这样的:
请求报文:01 03 00 00 00 01 84 0A
解析:
01 - 从站地址
03 - 功能码:读保持寄存器
00 00 - 起始地址:0x0000
00 01 - 读取数量:1个寄存器
84 0A - CRC校验
响应报文:01 03 02 00 1E 39 84
解析:
01 - 从站地址
03 - 功能码
02 - 数据字节数:2个字节
00 1E - 数据值:0x001E = 30(十进制),表示30°C
39 84 - CRC校验
你看,响应里的0x001E就是30°C。如果温度是负数怎么办?Modbus用补码表示。比如-10°C就是0xFFF6。
3.3.2 写单个寄存器(功能码0x06)
现在我们要把温度设定值改成50°C,假设设定值寄存器地址是0x0001(PLC地址40002)。
请求报文:01 06 00 01 00 32 18 0A
解析:
01 - 从站地址
06 - 功能码:写单个寄存器
00 01 - 寄存器地址:0x0001
00 32 - 写入值:0x0032 = 50(十进制)
18 0A - CRC校验
响应报文:01 06 00 01 00 32 18 0A
解析:写成功的响应,就是把请求原封不动返回
这里有个细节:写成功的响应和请求报文一模一样。如果写失败了,从站会返回异常响应。
3.3.3 异常响应处理
我曾经在一个项目中,发现设备偶尔不响应。后来抓包一看,原来是发了非法地址。Modbus的异常响应格式是这样的:
异常响应:01 83 02 C0 F1
解析:
01 - 从站地址
83 - 功能码 + 0x80(表示异常)
02 - 异常码:02表示非法数据地址
C0 F1 - CRC校验
常见的异常码有:
- 01:非法功能码
- 02:非法数据地址
- 03:非法数据值
- 04:从站设备故障
避坑指南:我曾经遇到过一个问题,从站返回了异常码04,我以为是设备坏了,折腾了半天才发现是电源供电不足。所以,看到异常码先别急着怀疑协议,检查一下硬件环境。
3.4 知识体系结构图
下面这张图,是我自己总结的Modbus RTU知识体系。你看一遍,心里就有谱了。
3.5 实战中的几个坑
最后,我分享几个实战中遇到的坑,你们以后遇到了心里有数:
- 字节序问题:Modbus RTU默认是大端模式(高字节在前),但有些设备偏偏用小端。我遇到过一台进口设备,手册上写的是大端,实际却是小端,害我排查了一整天。
- 寄存器地址偏移:PLC地址和Modbus地址差1,这个前面说过了。但有些设备厂商会自己定义偏移量,比如40001对应Modbus地址0x0001而不是0x0000。遇到这种情况,只能看手册。
- CRC计算工具:我建议你们用现成的CRC计算工具,别自己手算。网上有很多在线计算器,或者用Python的
modbus_tk库,几行代码就搞定了。 - 超时设置:Modbus RTU的响应超时一般设置在100ms到1000ms之间。如果网络环境差,可以适当调大,但别超过3秒,否则会影响整体效率。
我的习惯:每次调试Modbus设备,我都会先用串口助手抓一下报文,看看发送和接收的数据对不对。这一步能省下很多排查时间。
好了,Modbus RTU协议的核心内容就这些。报文结构、寄存器映射、读写操作,这三块搞明白了,剩下的就是多练。你们在实际项目中遇到什么问题,随时可以翻翻这一章的内容。