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 知识体系 帧结构 • 从站地址 (1B) • 功能码 (1B) • 数据域 (N B) • CRC校验 (2B) 帧间隔 ≥ 3.5字符时间 功能码分类 • 位操作:01/02/05/0F • 字操作:03/04/06/10 • 诊断:08/11等 常用:03读、06写 地址映射规则 • 0区:线圈 (DO) • 1区:离散输入 (DI) • 3区:输入寄存器 • 4区:保持寄存器 报文地址 = 手册地址 - 基址 异常处理 • 异常码:01/02/03/04 • 响应格式:功能码|0x80 先看异常码再改代码 实战要点 • 大端字节序 • 地址偏移计算 • 帧间隔控制 图:Modbus RTU 知识体系结构图

嗯,这一章的内容就到这儿。Modbus RTU看起来简单,但每个细节都可能成为项目中的绊脚石。我个人建议,初学者先把帧结构和地址映射搞透,功能码先记住03和06,其他的遇到再查。下一章咱们接着聊Modbus TCP,看看它和RTU有什么不同。