3、Modbus RTU帧结构:地址域、功能码、数据域、CRC校验、帧间隔时间
好,咱们今天来聊聊Modbus RTU的帧结构。说白了,这就是PLC和仪表之间通信的“语言格式”。你发出去的数据,对方得能看懂;对方回给你的,你也得能解析。我刚开始接触这玩意儿的时候,总觉得不就是发一串数字嘛,有啥难的?结果第一次调试就栽了跟头——CRC算错了,设备死活没反应。嗯,咱们今天就把这五个部分掰开揉碎了讲清楚。
3.1 地址域:谁在说话?
地址域只有1个字节,取值范围是1到247。0是广播地址,所有从站都得听,但从站不会回复。248到255是保留的,别乱用。
我个人习惯是把PLC的地址设成1,触摸屏设成2,变频器设成3,这样好记。但有一次在项目现场,我发现有个老工程师把地址设成了0,结果整个网络都乱套了——所有设备都在响应,但谁都不确认。嗯,这里要注意:从站地址必须唯一,否则通信会冲突。
关键点:
- 地址域长度:1字节
- 有效范围:1-247(0为广播)
- 每个从站必须有唯一地址
3.2 功能码:你想干啥?
功能码也是1个字节,告诉从站你要执行什么操作。常用的就那么几个:
| 功能码 | 含义 | 我常用的场景 |
|---|---|---|
| 01 | 读取线圈状态 | 读取继电器输出 |
| 02 | 读取离散输入 | 读取按钮状态 |
| 03 | 读取保持寄存器 | 读取模拟量数值 |
| 04 | 读取输入寄存器 | 读取传感器数据 |
| 05 | 写单个线圈 | 控制单个输出 |
| 06 | 写单个寄存器 | 设定单个参数 |
| 15 | 写多个线圈 | 批量控制输出 |
| 16 | 写多个寄存器 | 批量设定参数 |
你想想看,如果功能码是03,从站就知道你要读保持寄存器。如果从站不支持这个功能码,它会返回一个异常码。我曾经遇到过一台老仪表,它只支持03和06,我发了01过去,它直接回了异常——嗯,这就是功能码不匹配的典型情况。
避坑指南:我曾经在调试时,把功能码05(写单个线圈)误写成01(读线圈),结果PLC一直报错。后来才发现是功能码写错了。所以,发之前一定要确认功能码对不对。
3.3 数据域:具体内容
数据域是变长的,最多252个字节。它包含了你真正要读写的地址、数量,以及要写入的数据。
举个例子,你要读取从站地址为1的PLC,从寄存器地址100开始读2个保持寄存器:
请求帧:01 03 00 64 00 02 CRC高 CRC低
解释:
01 - 从站地址
03 - 功能码(读保持寄存器)
00 64 - 起始地址(100的十六进制)
00 02 - 读取数量(2个寄存器)
CRC高 CRC低 - 校验码
从站回复:
响应帧:01 03 04 00 0A 00 14 CRC高 CRC低
解释:
01 - 从站地址
03 - 功能码
04 - 数据字节数(4个字节)
00 0A - 第一个寄存器的值(10)
00 14 - 第二个寄存器的值(20)
CRC高 CRC低 - 校验码
这里要注意:数据域里的地址和数量都是大端格式,也就是高位在前,低位在后。我刚开始做的时候,把地址写成了64 00,结果读出来的数据全不对。嗯,这个坑我踩过,你们别踩了。
警告:数据域长度不能超过252字节。如果你要读写大量数据,记得分批操作。我曾经见过有人一次读200个寄存器,结果帧长度超了,通信直接中断。
3.4 CRC校验:数据对不对?
CRC校验是Modbus RTU的“安全锁”。它占用2个字节,放在帧的末尾。计算方法是CRC-16/MODBUS,多项式是0x8005。
说实话,CRC的计算公式挺复杂的,我从来不去手算。直接用现成的函数库或者在线工具就行。但有一点必须注意:CRC的低字节在前,高字节在后。这和Modbus ASCII正好相反。
举个例子,上面那个请求帧的CRC计算:
原始数据:01 03 00 64 00 02
CRC计算结果:CRC低 = 0x39, CRC高 = 0xC1
完整帧:01 03 00 64 00 02 39 C1
我曾经在项目现场,发现PLC和变频器通信时断时续。排查了半天,最后发现是CRC计算函数写错了——多项式用成了0x8005,但初始值应该是0xFFFF,我写成了0x0000。嗯,这种低级错误,犯过一次就再也不会忘了。
CRC校验要点:
- 占用2字节,放在帧尾
- 低字节在前,高字节在后
- 多项式:0x8005
- 初始值:0xFFFF
- 结果异或值:0x0000
3.5 帧间隔时间:别抢话
帧间隔时间,说白了就是两帧之间的“静默期”。Modbus RTU规定:帧与帧之间至少要有3.5个字符的静默时间。如果静默时间小于1.5个字符,接收方会认为这是同一帧的数据。
这个时间怎么算?以9600波特率为例:
1个字符 = 1起始位 + 8数据位 + 1停止位 = 10位
1个字符时间 = 10 / 9600 ≈ 1.04ms
3.5个字符时间 ≈ 3.64ms
1.5个字符时间 ≈ 1.56ms
所以,在9600波特率下,帧间隔至少要3.64ms。如果波特率是19200,时间减半,约1.82ms。
你想想看,如果帧间隔太短,接收方会把两帧数据当成一帧来处理,CRC肯定算不对。我遇到过一台国产仪表,它的帧间隔只有2个字符时间,结果和西门子PLC通信时老是报错。后来我在PLC程序里加了个延时,问题就解决了。
实战建议:我个人习惯在发送完一帧后,加一个5ms的延时,这样不管波特率是多少,都能保证帧间隔足够。当然,如果你用的是高速通信(比如115200),延时可以适当缩短。
3.6 完整帧结构总结
好了,咱们把五个部分串起来,一个完整的Modbus RTU帧长这样:
| 地址域(1B) | 功能码(1B) | 数据域(NB) | CRC低(1B) | CRC高(1B) |
| 从站地址 | 操作类型 | 具体内容 | 校验码低字节 | 校验码高字节 |
举个例子,读取从站1的2个保持寄存器(地址100开始):
请求:01 03 00 64 00 02 39 C1
响应:01 03 04 00 0A 00 14 3A 5B
嗯,到这里,Modbus RTU的帧结构就讲完了。记住这五个部分:地址域、功能码、数据域、CRC校验、帧间隔时间。下次调试的时候,拿着这个结构去对,基本不会出大问题。
最后提醒:我曾经在项目验收时,发现通信偶尔会丢包。排查了三天,最后发现是帧间隔时间设置得太短。所以,帧间隔时间宁可长一点,也别短。安全第一。