第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。计算过程不复杂,但手算容易出错。我一般用代码算,但原理得懂。

计算步骤:

  1. 预置一个16位寄存器为0xFFFF
  2. 取第一个字节与寄存器低8位异或
  3. 右移一位,高位补0。如果移出的位是1,则与0xA001异或
  4. 重复第3步8次
  5. 取下一个字节,重复2-4步
  6. 最后得到的寄存器值取反,就是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和错误码,每一步都环环相扣。

Modbus RTU 知识体系 Modbus RTU 协议 报文结构 地址+功能码+数据+CRC 功能码详解 01/03/05/06/15/16 CRC校验计算 多项式0x8005 常见错误码分析 01/02/03/04 主从通讯 → 请求/响应 → 异常处理

这张图把咱们今天讲的内容都串起来了。报文结构是基础,功能码是操作指令,CRC保证数据完整,错误码帮你定位问题。四者缺一不可。


好了,关于Modbus RTU的深度解析就到这里。记住,协议本身不复杂,但细节决定成败。下次遇到通讯问题,先查报文结构,再看功能码,然后算CRC,最后分析错误码。按这个顺序来,大部分问题都能解决。

公众号:蓝海资料掘金营,微信deep3321