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知识体系。你看一遍,心里就有谱了。

Modbus RTU 知识体系 报文结构 地址码 功能码 数据区 CRC校验 寄存器映射 线圈 离散输入 输入寄存器 保持寄存器 读写操作 读操作 写操作 异常处理 CRC计算 核心:报文结构是基础,寄存器映射是地图,读写操作是实战 三者结合,才能玩转Modbus RTU

3.5 实战中的几个坑

最后,我分享几个实战中遇到的坑,你们以后遇到了心里有数:

  1. 字节序问题:Modbus RTU默认是大端模式(高字节在前),但有些设备偏偏用小端。我遇到过一台进口设备,手册上写的是大端,实际却是小端,害我排查了一整天。
  2. 寄存器地址偏移:PLC地址和Modbus地址差1,这个前面说过了。但有些设备厂商会自己定义偏移量,比如40001对应Modbus地址0x0001而不是0x0000。遇到这种情况,只能看手册。
  3. CRC计算工具:我建议你们用现成的CRC计算工具,别自己手算。网上有很多在线计算器,或者用Python的modbus_tk库,几行代码就搞定了。
  4. 超时设置:Modbus RTU的响应超时一般设置在100ms到1000ms之间。如果网络环境差,可以适当调大,但别超过3秒,否则会影响整体效率。

我的习惯:每次调试Modbus设备,我都会先用串口助手抓一下报文,看看发送和接收的数据对不对。这一步能省下很多排查时间。

好了,Modbus RTU协议的核心内容就这些。报文结构、寄存器映射、读写操作,这三块搞明白了,剩下的就是多练。你们在实际项目中遇到什么问题,随时可以翻翻这一章的内容。