3. Modbus协议详解:RTU与TCP模式、寄存器映射、功能码实战
各位好,我是老张。在储能监控这个行当里摸爬滚打了十来年,要说哪个协议最绕不开,那绝对是Modbus。你想想看,从几块钱的温湿度传感器,到几十万的PCS(储能变流器),几乎都支持它。今天咱们就把这个老伙计彻底聊透。
3.1 两种传输模式:RTU vs TCP
Modbus协议本身是应用层的规范,但底层怎么传数据,分成了两派:RTU和TCP。我刚开始接触时也犯过迷糊,后来被现场坑过一次就彻底记住了。
3.1.1 Modbus RTU
RTU模式,说白了就是串口通信。它用RS-232或RS-485物理层,数据以二进制形式传输。我个人习惯在储能BMS(电池管理系统)的本地通信中用RTU,因为成本低、布线简单。
它的报文结构长这样:
| 地址域(1字节) | 功能码(1字节) | 数据域(N字节) | CRC校验(2字节) |
这里有个关键点——CRC校验。我曾经在某个储能电站调试时,发现数据偶尔跳变,查了两天才发现是RTU的CRC计算代码写错了。嗯,这里要注意,CRC是循环冗余校验,不是简单的累加和。
3.1.2 Modbus TCP
TCP模式就简单多了。它基于以太网,用502端口。报文结构里没有CRC,因为TCP/IP协议栈自己会做校验。我建议在储能站控层(SCADA系统)用TCP,毕竟现在交换机比串口线好找多了。
TCP的报文结构:
| MBAP头(7字节) | 功能码(1字节) | 数据域(N字节) |
MBAP头里有个关键字段——单元标识符。它用来区分同一个网关下挂的多个RTU设备。说白了,就是TCP转RTU时的“路由信息”。
3.2 寄存器映射:看懂设备手册的关键
做数据采集,最头疼的就是看设备手册里的寄存器表。我见过太多新手对着手册发呆,其实它是有规律的。
Modbus定义了四种数据对象:
| 对象类型 | 访问方式 | 地址范围 | 典型用途 |
|---|---|---|---|
| 线圈(Coil) | 读写位 | 00001-09999 | 开关控制 |
| 离散输入(Discrete Input) | 只读位 | 10001-19999 | 状态读取 |
| 输入寄存器(Input Register) | 只读字 | 30001-39999 | 模拟量采集 |
| 保持寄存器(Holding Register) | 读写字 | 40001-49999 | 参数设置 |
你想想看,储能系统里最常用的是哪个?对,保持寄存器。因为我们要读SOC(荷电状态)、电压、电流,还要写充放电功率指令。
3.3 功能码实战:读与写
功能码就是Modbus的“指令集”。常用的就几个,我列出来:
- 0x01:读线圈
- 0x02:读离散输入
- 0x03:读保持寄存器
- 0x04:读输入寄存器
- 0x05:写单个线圈
- 0x06:写单个寄存器
- 0x0F:写多个线圈
- 0x10:写多个寄存器
在储能监控里,0x03和0x10是绝对的主力。我举个例子,读一个BMS的SOC值:
// 请求报文(读保持寄存器,起始地址0x3100,读1个寄存器)
// 从站地址: 0x01
// 功能码: 0x03
// 起始地址高字节: 0x31
// 起始地址低字节: 0x00
// 寄存器数量高字节: 0x00
// 寄存器数量低字节: 0x01
// CRC校验: 0x84 0x0A
// 请求帧: 01 03 31 00 00 01 84 0A
// 响应报文
// 从站地址: 0x01
// 功能码: 0x03
// 数据字节数: 0x02
// 数据高字节: 0x00
// 数据低字节: 0x3C (SOC=60%)
// CRC校验: 0xB8 0x44
// 响应帧: 01 03 02 00 3C B8 44
写操作也类似,比如设置PCS的功率指令:
// 请求报文(写单个寄存器,地址0x2000,写入值5000)
// 从站地址: 0x02
// 功能码: 0x06
// 寄存器地址高字节: 0x20
// 寄存器地址低字节: 0x00
// 数据高字节: 0x13
// 数据低字节: 0x88 (5000 = 0x1388)
// CRC校验: 0x85 0xE2
// 请求帧: 02 06 20 00 13 88 85 E2
3.4 知识体系图
下面这张图是我自己整理的Modbus知识框架,你可以把它当作学习路线图:
这张图把Modbus的核心脉络理清了。你从传输模式入手,然后理解数据对象,最后掌握功能码,整个协议就算拿下了。
3.5 实战中的几个坑
最后分享几个我踩过的坑,希望能帮你省点时间:
- 字节序问题:Modbus默认是大端模式(高字节在前),但有些国产设备用的小端。我建议你在代码里加一个字节序配置项,方便现场切换。
- 超时设置:RTU通信的响应超时不能太短。我曾经设了100ms,结果在长距离通信时频繁超时。后来改成500ms就稳了。
- 广播地址:地址0是广播地址,所有从站都会响应。但注意,广播时从站不能回复报文,否则总线会冲突。