3、Modbus协议详解(上):Modbus RTU帧结构、功能码分类、寄存器地址映射规则
各位同学,今天咱们来啃Modbus协议这块硬骨头。说实话,做光伏储能通信这么多年,Modbus RTU是我打交道最多的协议之一。你想想看,从逆变器到BMS,从电表到汇流箱,几乎每个设备都挂着Modbus接口。我刚开始接触时也觉得这协议挺简单,不就是读读写写嘛?但真正做项目时才发现,坑都在细节里。
3.1 Modbus RTU帧结构——别小看这串字节
先看帧结构。Modbus RTU的报文,说白了就是一串字节按固定格式排好队。我习惯把它拆成三部分:地址域、功能码域、数据域,最后加个校验尾巴。
| 域 | 长度 | 说明 |
|---|---|---|
| 从站地址 | 1字节 | 0x01~0xF7,0x00为广播地址 |
| 功能码 | 1字节 | 指示操作类型(读/写/诊断等) |
| 数据域 | N字节 | 寄存器地址、数量、数据值等 |
| CRC校验 | 2字节 | 低字节在前,高字节在后 |
这里有个关键点——帧间空闲时间。Modbus RTU是半双工通信,报文之间必须保持至少3.5个字符时间的静默。我记得有次在现场调试,设备老是丢包,查了半天发现是主机发送间隔太短,从机还没处理完上一帧,下一帧就怼过来了。嗯,这个坑我踩过。
核心要点:Modbus RTU帧 = 地址(1B) + 功能码(1B) + 数据(NB) + CRC(2B)。帧间隔至少3.5字符时间,否则从机可能不理你。
3.2 功能码分类——读、写、诊断三把斧
功能码是Modbus的灵魂。我把它分成三大类:位操作、字操作、诊断类。实际项目中,80%的场景只用到了其中几个码。
3.2.1 位操作功能码
- 0x01 读线圈:读取DO(数字输出)状态。光伏里用来读继电器状态。
- 0x02 读离散输入:读取DI(数字输入)。比如读急停按钮状态。
- 0x05 写单个线圈:控制单个DO。我习惯用它来远程分合闸。
- 0x0F 写多个线圈:批量写DO。注意数据按位打包,字节内高位在前。
3.2.2 字操作功能码
- 0x03 读保持寄存器:最常用的功能码。读电压、电流、SOC都用它。
- 0x04 读输入寄存器:只读寄存器,一般用来读传感器值。
- 0x06 写单个寄存器:写一个16位数据。比如设置功率限值。
- 0x10 写多个寄存器:批量写。我常用它来下发充放电调度参数。
个人经验:0x03和0x06这两个码,覆盖了储能EMS 70%的通信需求。读数据用0x03,写参数用0x06。别贪多,先把这两个玩熟。
3.2.3 诊断功能码
0x08 诊断、0x11 读事件计数等。说实话,我在项目中很少用。除非是设备调试阶段,用来检查通信链路是否正常。
3.3 寄存器地址映射规则——别把地址搞错了
寄存器地址映射,是Modbus协议里最容易出bug的地方。我见过太多工程师把地址偏移搞反,导致读回来的数据全是乱的。
Modbus的寄存器地址空间分为四个区:
| 区号 | 地址范围 | 类型 | 说明 |
|---|---|---|---|
| 0区 | 00001~09999 | 线圈(DO) | 可读可写,位操作 |
| 1区 | 10001~19999 | 离散输入(DI) | 只读,位操作 |
| 3区 | 30001~39999 | 输入寄存器 | 只读,16位 |
| 4区 | 40001~49999 | 保持寄存器 | 可读可写,16位 |
注意!协议报文里传的是地址偏移量,不是绝对地址。比如你要读4区地址40001,报文里填的地址是0x0000。我曾经在项目里看到有人直接填40001,结果从机返回异常码0x02(非法地址)。
避坑指南:我曾经在调试PCS(储能变流器)时,发现读回来的功率值总是差一倍。查了半天,原来是寄存器地址映射表里写的是40001,但设备手册里实际用的是0-based偏移。记住:报文地址 = 手册地址 - 区基址(40001对应0x0000)。
3.4 实战:一个完整的Modbus RTU报文解析
咱们拿个真实例子来拆解。假设我要从地址为0x01的逆变器,读取3个保持寄存器(起始地址0x0100)。
主机发送:
01 03 01 00 00 03 05 CB
- 01:从站地址
- 03:功能码(读保持寄存器)
- 01 00:起始寄存器地址(高字节在前)
- 00 03:读取数量(3个寄存器)
- 05 CB:CRC校验(低字节在前)
从机响应:
01 03 06 02 1A 00 0A 01 2C 8A 7F
- 01:从站地址
- 03:功能码回显
- 06:数据字节数(3个寄存器×2字节=6)
- 02 1A:寄存器1的值(0x021A = 538)
- 00 0A:寄存器2的值(0x000A = 10)
- 01 2C:寄存器3的值(0x012C = 300)
- 8A 7F:CRC校验
注意:多字节数据采用大端模式(Big-Endian),即高字节在前。但有些设备厂商会搞小端,遇到数据异常时先检查字节序。
3.5 异常响应——设备说“不”的时候
不是每次请求都能成功。当从机无法处理请求时,会返回异常帧。格式是:地址 + (功能码|0x80) + 异常码 + CRC。
常见异常码:
- 0x01:非法功能码。比如对只读寄存器执行写操作。
- 0x02:非法数据地址。地址超出范围。
- 0x03:非法数据值。比如写入的值超过寄存器范围。
- 0x04:从机设备故障。硬件出问题了。
举个例子,如果你发了个写请求到只读寄存器,从机会返回:
01 86 02 C0 01
0x86 = 0x06 | 0x80,表示功能码0x06执行失败。0x02表示非法地址。
调试技巧:我习惯在代码里先打印异常码。如果收到0x02,先检查地址映射表;如果收到0x03,检查写入值是否超限。别盲目改代码,先看异常码。
3.6 本章知识体系
下面这张图,是我自己总结的Modbus RTU知识框架。你把它存下来,以后做项目时对照着看。
嗯,这一章的内容就到这儿。Modbus RTU看起来简单,但每个细节都可能成为项目中的绊脚石。我个人建议,初学者先把帧结构和地址映射搞透,功能码先记住03和06,其他的遇到再查。下一章咱们接着聊Modbus TCP,看看它和RTU有什么不同。