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校验、帧间隔时间。下次调试的时候,拿着这个结构去对,基本不会出大问题。

最后提醒:我曾经在项目验收时,发现通信偶尔会丢包。排查了三天,最后发现是帧间隔时间设置得太短。所以,帧间隔时间宁可长一点,也别短。安全第一。