第2章:Modbus RTU协议详解
各位工程师朋友,咱们今天来啃Modbus RTU这块硬骨头。说实话,这协议看起来简单,但坑不少。我在现场调试时,十次通信故障有八次是RTU帧结构没搞对。来,咱们一步步拆解。
2.1 报文帧结构:四个核心字段
Modbus RTU的报文帧,说白了就四个部分:地址码、功能码、数据区、CRC校验。我习惯把它想象成一个快递包裹——地址码是收件人,功能码是你要干啥,数据区是货物,CRC是防拆封条。
| 字段 | 长度 | 说明 |
|---|---|---|
| 地址码 | 1字节 | 从站地址,范围1-247 |
| 功能码 | 1字节 | 操作类型,如03读保持寄存器 |
| 数据区 | N字节 | 寄存器地址、数量、数据值 |
| CRC校验 | 2字节 | 低字节在前,高字节在后 |
2.2 地址码:别小看这1个字节
地址码范围是1到247,0是广播地址。嗯,这里要注意——广播地址所有从站都会响应,但实际项目中我很少用。为什么?因为一旦广播写操作,所有设备同时动作,电流冲击能把电源干趴下。我曾经在一个工厂里,就因为广播写继电器,把24V电源模块烧了三个。
我个人习惯:每个从站分配唯一地址,从1开始顺序编号。别用跳线帽,用拨码开关,现场调试时改地址方便得多。
2.3 功能码:常用的就那几个
Modbus定义了20多个功能码,但实际项目中常用的不超过5个。我列个表,你心里有数就行:
| 功能码 | 名称 | 用途 |
|---|---|---|
| 01 | 读线圈 | 读取开关量输出 |
| 02 | 读离散输入 | 读取开关量输入 |
| 03 | 读保持寄存器 | 读取模拟量/参数(最常用) |
| 04 | 读输入寄存器 | 读取只读模拟量 |
| 05 | 写单线圈 | 控制单个开关量 |
| 06 | 写单寄存器 | 写入单个参数 |
| 15 | 写多线圈 | 批量控制开关量 |
| 16 | 写多寄存器 | 批量写入参数 |
你想想看,一个温控器项目,无非就是读温度(03功能码)、写设定值(06功能码)。搞复杂了反而容易出问题。
2.4 数据区:大端还是小端?这是个问题
数据区里最坑的就是字节序。Modbus RTU规定:多字节数据采用大端模式(Big-Endian),也就是高字节在前。但很多国产设备不按套路出牌,我遇到过用小端模式的PLC,也遇到过用中端模式的仪表。
举个例子,读取保持寄存器地址0x0000,返回数据0x1234:
// 大端模式(标准)
高字节 0x12 → 低字节 0x34
实际值 = 0x1234 = 4660
// 小端模式(非标)
高字节 0x34 → 低字节 0x12
实际值 = 0x3412 = 13330
2.5 CRC校验:最后一道防线
CRC校验是Modbus RTU的防错机制。它用CRC-16算法,多项式是0x8005。说白了,就是计算整个报文(地址码+功能码+数据区)的校验值,放在报文末尾。
我见过不少工程师自己手写CRC算法,其实没必要。标准库函数一调用就行。但有一点要注意:CRC的低字节在前,高字节在后。这个顺序搞反了,通信就废了。
2.6 主从通信机制:一问一答
Modbus RTU是典型的主从架构。主机(通常是PLC或上位机)发起请求,从机(传感器、执行器)响应。一次完整的通信流程是这样的:
- 主机发送请求帧(地址码+功能码+数据区+CRC)
- 从机接收并校验CRC
- 从机执行操作(读或写)
- 从机发送响应帧(地址码+功能码+数据区+CRC)
- 主机接收并校验CRC
这里有个关键点:帧间隔时间。RTU协议要求帧与帧之间至少有3.5个字符时间的空闲。我习惯用9600波特率时,间隔至少4ms。如果间隔太短,从机可能把两帧当成一帧处理,数据就乱了。
2.7 典型报文抓包分析
光说不练假把式。咱们来抓个包看看。假设我要读取地址为01的从机,保持寄存器起始地址0x0000,读取2个寄存器:
请求帧(主机→从机):
01 03 00 00 00 02 C4 0B
逐字节解析:
01 → 地址码,从机地址1
03 → 功能码,读保持寄存器
00 00 → 起始地址,0x0000
00 02 → 读取数量,2个寄存器
C4 0B → CRC校验
响应帧(从机→主机):
01 03 04 12 34 56 78 3A 9C
逐字节解析:
01 → 地址码,从机地址1
03 → 功能码,读保持寄存器
04 → 数据区长度,4个字节(2个寄存器×2字节)
12 34 → 第一个寄存器值,0x1234
56 78 → 第二个寄存器值,0x5678
3A 9C → CRC校验
你看,是不是很清晰?我刚开始学的时候,就是对着这种报文逐字节分析,慢慢就熟了。建议你也拿个串口助手,抓几组报文自己分析分析。
2.8 异常响应:通信出问题怎么办
如果从机无法执行请求,会返回异常响应。异常响应的功能码是请求功能码+0x80,后面跟一个异常码。常见的异常码有:
| 异常码 | 含义 | 常见原因 |
|---|---|---|
| 01 | 非法功能码 | 从机不支持该功能 |
| 02 | 非法数据地址 | 寄存器地址超出范围 |
| 03 | 非法数据值 | 写入值超出允许范围 |
| 04 | 从机设备故障 | 硬件或固件异常 |
举个例子,如果请求读地址0xFFFF(超出范围),响应可能是:
异常响应帧:
01 83 02 C0 F1
解析:
01 → 地址码
83 → 功能码(03+0x80=0x83)
02 → 异常码,非法数据地址
C0 F1 → CRC校验
嗯,这里要注意:收到异常响应时,别急着重发。先查查是不是地址或数据范围搞错了。我曾经在一个项目中,程序一直重发请求,结果从机被频繁的异常请求搞死机了。
2.9 我的调试心得
最后分享几个实用技巧:
- 先用串口助手验证:别一上来就写代码。用Modbus Poll或串口助手手动发几帧,确认通信正常再编程。
- 注意RS485的A/B线:A接A,B接B,别接反了。我见过有人把A接B,B接A,结果通信时好时坏。
- 终端电阻不能省:长距离通信时,在总线两端各加一个120Ω电阻。不加的话,信号反射会导致数据错乱。
- 波特率要统一:所有设备波特率必须一致。我习惯用9600,兼容性最好。
好了,Modbus RTU的核心内容就这些。下一章咱们讲Modbus TCP,看看它和RTU有什么不同。记住,协议是死的,人是活的。多动手抓包分析,比看十遍文档都管用。