第1章:Modbus RTU协议深度解析
各位工程师朋友,咱们今天聊聊Modbus RTU。这协议在工业现场太常见了,几乎每个做SCADA的人都要跟它打交道。我刚开始接触时也觉得它简单,不就是读读写写嘛。但真正出问题的时候,报文结构、CRC校验这些小细节,往往就是坑你的地方。
说白了,Modbus RTU就是个主从协议。一个主站,多个从站。主站发请求,从站回响应。就这么简单。但简单不代表容易,咱们一步步拆开看。
1.1 报文结构:从站地址到数据校验
一条完整的Modbus RTU报文,长这样:
| 从站地址 (1字节) | 功能码 (1字节) | 数据区 (N字节) | CRC校验 (2字节) |
嗯,就这么四个部分。我见过不少新手,上来就写数据区,结果地址码写错了,从站根本不搭理你。记住,地址码是第一个字节,范围1-247。0是广播地址,所有从站都收,但不会回复。
数据区呢?它取决于功能码。比如读线圈,数据区就是起始地址和数量。写寄存器,数据区就是地址加数据值。CRC校验放在最后,低字节在前,高字节在后。这个顺序别搞反了,我吃过这个亏。
关键点:报文必须连续发送,字节之间不能有超过1.5个字符时间的间隔。否则从站会认为报文结束,导致解析错误。
1.2 功能码详解:01、03、05、06、15、16
功能码是Modbus的灵魂。常用的就那么几个,我列个表,大家一目了然:
| 功能码 | 名称 | 操作对象 | 说明 |
|---|---|---|---|
| 01 (0x01) | 读线圈 | DO (数字量输出) | 读取开关量状态,0或1 |
| 02 (0x02) | 读离散输入 | DI (数字量输入) | 只读,不能写 |
| 03 (0x03) | 读保持寄存器 | AO (模拟量输出) | 最常用,读16位数据 |
| 04 (0x04) | 读输入寄存器 | AI (模拟量输入) | 只读,比如温度值 |
| 05 (0x05) | 写单个线圈 | DO | 控制一个开关 |
| 06 (0x06) | 写单个寄存器 | AO | 设置一个数值 |
| 15 (0x0F) | 写多个线圈 | DO | 批量控制 |
| 16 (0x10) | 写多个寄存器 | AO | 批量设置 |
我个人习惯,调试时先用03功能码读几个寄存器,确认通讯通了,再用05或06去写。这样一步步来,出问题也好定位。
举个例子,读保持寄存器的请求报文:
01 03 00 00 00 02 C4 0B
解释一下:01是从站地址,03是功能码,00 00是起始地址(从0开始),00 02是读2个寄存器,C4 0B是CRC校验。响应报文呢?
01 03 04 00 0A 00 14 3A 9F
01还是地址,03功能码,04表示后面有4个字节数据(2个寄存器,每个2字节),00 0A是第一个寄存器的值(10),00 14是第二个(20),3A 9F是CRC。
小技巧:调试时可以用串口助手抓报文。我习惯先发一个读请求,看从站回不回。不回的话,先查地址和波特率。回了但数据不对,再查地址映射。
1.3 CRC校验计算:手算与代码实现
CRC校验是Modbus RTU的防错机制。它用CRC-16算法,多项式是0x8005。计算过程不复杂,但手算容易出错。我一般用代码算,但原理得懂。
计算步骤:
- 预置一个16位寄存器为0xFFFF
- 取第一个字节与寄存器低8位异或
- 右移一位,高位补0。如果移出的位是1,则与0xA001异或
- 重复第3步8次
- 取下一个字节,重复2-4步
- 最后得到的寄存器值取反,就是CRC
代码实现(C语言):
unsigned int crc16_modbus(unsigned char *buf, int len) {
unsigned int crc = 0xFFFF;
for (int i = 0; i < len; i++) {
crc ^= buf[i];
for (int j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
注意,返回的CRC是低字节在前,高字节在后。比如计算结果是0x0BC4,报文里要写成C4 0B。我曾经在项目里忘了这个顺序,结果从站一直报错,查了半天才发现。
避坑指南:CRC计算时,数据区包含地址码和功能码,但不包含CRC本身。另外,有些从站对CRC要求很严格,差一个位都不行。我建议用在线CRC计算器验证一下,省得浪费时间。
1.4 常见错误码分析:01、02、03、04
当从站收到错误请求时,它会返回异常响应。格式是:地址码 + (功能码 | 0x80) + 错误码 + CRC。错误码有几种:
| 错误码 | 名称 | 含义 | 常见原因 |
|---|---|---|---|
| 01 (0x01) | 非法功能码 | 从站不支持该功能 | 功能码写错了,或者从站没实现 |
| 02 (0x02) | 非法数据地址 | 地址超出范围 | 起始地址+数量超过了从站的最大地址 |
| 03 (0x03) | 非法数据值 | 数据值不合理 | 比如写线圈时,数据不是0xFF00或0x0000 |
| 04 (0x04) | 从站设备故障 | 从站内部错误 | 硬件问题,或者从站正忙 |
举个例子,你发了个读请求,从站回的是:
01 83 02 C0 F1
01是地址,83是03|0x80(表示异常),02是错误码(非法数据地址),C0 F1是CRC。这说明你读的地址超出了从站的范围。我遇到过这种情况,明明手册上说地址是0-100,但实际从站只支持0-50。后来一问厂家,才知道是固件版本不同。
实战建议:收到异常码时,别慌。先查地址范围,再查功能码是否支持。如果都对了,可能是从站配置问题。我曾经在项目里遇到04错误,折腾了半天,最后发现是从站的电源线松了。
1.5 知识体系总览
下面这张图,把Modbus RTU的核心逻辑串起来了。从报文结构到功能码,再到CRC和错误码,每一步都环环相扣。
这张图把咱们今天讲的内容都串起来了。报文结构是基础,功能码是操作指令,CRC保证数据完整,错误码帮你定位问题。四者缺一不可。
好了,关于Modbus RTU的深度解析就到这里。记住,协议本身不复杂,但细节决定成败。下次遇到通讯问题,先查报文结构,再看功能码,然后算CRC,最后分析错误码。按这个顺序来,大部分问题都能解决。
公众号:蓝海资料掘金营,微信deep3321