2. Modbus RTU协议详解:报文结构(地址码、功能码、数据区、CRC校验)、03/06/16功能码实操、从站地址配置
各位同行,今天我们来聊聊Modbus RTU。这玩意儿在工业现场太常见了,几乎每个做SCADA的人都要跟它打交道。说实话,我刚入行那会儿,觉得Modbus RTU挺简单的,不就是发几个字节嘛。后来在项目里栽过跟头,才明白里面的门道不少。
咱们直接切入正题。Modbus RTU的报文结构,说白了就是四个部分:地址码、功能码、数据区、CRC校验。我习惯把这四个部分比作一封信——地址码是收件人,功能码是你要干什么,数据区是具体内容,CRC校验就是信封上的火漆封印,保证信没被人动过。
2.1 报文结构拆解
先看一个典型的Modbus RTU报文帧:
从站地址 | 功能码 | 数据区 | CRC校验
1字节 | 1字节 | N字节 | 2字节
嗯,这里要注意,整个报文是连续发送的,字节之间没有间隔。如果间隔超过3.5个字符时间,从站就认为报文结束了。我在一个项目中遇到过,因为串口中断处理不及时,导致报文被拆成两段,从站死活不响应。后来查了半天,才发现是软件定时器配置有问题。
地址码
地址码范围是1-247,0是广播地址。你想想看,一个串行总线上挂多个设备,每个设备必须有一个唯一的地址。我建议从站地址尽量从1开始顺序分配,别跳着来,不然以后维护起来头疼。
功能码
功能码告诉从站要做什么。常用的就那几个:
- 03 (0x03):读取保持寄存器
- 06 (0x06):写单个寄存器
- 16 (0x10):写多个寄存器
其他的像01、02、04、05、15也偶尔用,但咱们今天重点讲这三个。
数据区
数据区的内容取决于功能码。比如读寄存器,数据区里放的是起始地址和寄存器数量。写寄存器的话,数据区里放的是地址、数量和要写的数据。
CRC校验
CRC校验是Modbus RTU的保命符。没有它,数据在传输过程中被干扰了都不知道。CRC算法是固定的,我一般直接用现成的查表法,效率高。曾经有个同事自己手写CRC算法,结果算出来总是差几个字节,排查了两天才发现多项式搞反了。
重要:CRC校验的低字节在前,高字节在后。这个顺序别搞反了,否则从站会直接丢弃报文。
2.2 03功能码实操:读取保持寄存器
03功能码是最常用的,没有之一。你想读取PLC里的模拟量、温度值、压力值,都用它。
请求报文格式:
从站地址 | 0x03 | 起始地址高字节 | 起始地址低字节 | 寄存器数量高字节 | 寄存器数量低字节 | CRC低字节 | CRC高字节
举个例子,读取地址为1的从站,从寄存器地址0开始读2个寄存器:
01 03 00 00 00 02 C4 0B
响应报文格式:
从站地址 | 0x03 | 字节数 | 数据1高字节 | 数据1低字节 | 数据2高字节 | 数据2低字节 | CRC低字节 | CRC高字节
假设返回的数据是0x1234和0x5678:
01 03 04 12 34 56 78 3A 9F
你看,字节数=04,表示后面有4个字节的数据。每个寄存器占2个字节,高字节在前。我在调试一个水处理项目时,发现读回来的温度值总是偏大,后来才发现是字节序搞反了——设备用的是低字节在前。所以啊,拿到设备先看手册,别想当然。
2.3 06功能码实操:写单个寄存器
06功能码用来写一个寄存器。比如你要设定一个PID参数、写一个设定值,就用它。
请求报文格式:
从站地址 | 0x06 | 寄存器地址高字节 | 寄存器地址低字节 | 数据高字节 | 数据低字节 | CRC低字节 | CRC高字节
举个例子,向地址1的从站,寄存器地址0写入0x0001:
01 06 00 00 00 01 48 0A
响应报文就是原样返回请求报文。如果返回的不一样,说明写失败了。我曾经在一个项目中,06功能码写下去,从站返回了异常码02(非法数据地址),查了半天才发现寄存器地址写错了——手册上写的是40001,对应Modbus地址是0,我直接写了40001进去,当然不行。
小技巧:很多PLC的寄存器地址是从1开始的,但Modbus协议里地址是从0开始的。比如40001对应Modbus地址0,40002对应1。换算公式:Modbus地址 = PLC地址 - 1。
2.4 16功能码实操:写多个寄存器
16功能码是06的升级版,一次可以写多个连续的寄存器。比如你要批量设置参数、下载配方数据,用16功能码效率高得多。
请求报文格式:
从站地址 | 0x10 | 起始地址高字节 | 起始地址低字节 | 寄存器数量高字节 | 寄存器数量低字节 | 字节数 | 数据... | CRC低字节 | CRC高字节
举个例子,向地址1的从站,从寄存器地址0开始写2个寄存器,数据分别是0x000A和0x000B:
01 10 00 00 00 02 04 00 0A 00 0B 3A 9F
注意这里的字节数=04,表示后面有4个字节的数据(2个寄存器×2字节)。响应报文比较简单:
01 10 00 00 00 02 41 C8
响应里只返回起始地址和寄存器数量,不返回具体数据。我建议写多个寄存器时,一次不要超过120个字节,否则容易超时。有些老旧的从站设备,一次写太多还会死机。
2.5 从站地址配置
从站地址配置是现场调试的第一步,也是最容易出问题的一步。我见过太多因为地址冲突导致通讯失败的案例了。
配置方式一般有三种:
- 拨码开关:最传统的方式,直观但容易拨错。我记得有一次在配电房,光线暗,把拨码开关的二进制看反了,结果地址设成了64而不是1。
- 软件配置:通过串口或网口用配置工具设置。方便但需要先通讯上,有点鸡生蛋蛋生鸡的意思。
- 自动分配:有些新设备支持DHCP或类似机制,但工业现场我建议还是手动指定,稳当。
警告:同一个总线上绝对不能有两个相同地址的设备。我曾经在一个项目中,两台变频器都设成了地址1,结果主站发指令时,两台同时响应,数据全乱了。排查了整整一个下午。
配置从站地址时,我个人的习惯是:
- 先给每个设备贴标签,写上规划的地址。
- 用Modbus扫描工具扫一遍总线,确认没有地址冲突。
- 逐个设备测试通讯,确保每个地址都能正常响应。
- 最后在SCADA系统里把地址和设备名称对应好,方便以后维护。
2.6 知识体系总览
下面这张图把Modbus RTU的核心知识点串起来了,你可以对照着看:
这张图把Modbus RTU的三个核心模块串起来了。报文结构是基础,功能码实操是应用,从站地址配置是前提。三者缺一不可。
2.7 避坑指南
最后,我把自己这些年踩过的坑总结一下,希望能帮你少走弯路:
- CRC校验算不对:我曾经手算CRC,结果多项式搞反了,浪费了半天。建议直接用现成的查表法,网上代码一大把。
- 地址冲突:两台设备设成同一个地址,通讯直接瘫痪。调试前先用扫描工具扫一遍。
- 字节序搞反:有些设备高字节在前,有些低字节在前。拿到设备先看手册,或者用调试工具抓包确认。
- 超时时间设置太短:有些老设备响应慢,超时时间设个500ms比较稳妥。
- 忽略广播地址:地址0是广播,所有从站都会接收,但不会响应。可以用来同步参数,但别指望它返回数据。
好了,Modbus RTU的报文结构和三个核心功能码就讲到这里。这些东西看着简单,但真正用起来,细节决定成败。下次你在现场调试时,不妨对照着这篇文章再捋一遍,应该能省不少事。
公众号:蓝海资料掘金营,微信deep3321