3. 常见通信协议(上):Modbus RTU/TCP协议原理、寄存器映射、功能码详解、报文解析实战
3.1 为什么能源系统离不开Modbus?
做能源管理系统这些年,我接触过的设备没有一千也有八百。电表、逆变器、温控器、PLC……你猜它们共同支持什么协议?
答案就是Modbus。
为什么?说白了,Modbus太实用了。它诞生于1979年,比我还大几岁。但你别看它老,在工业现场,老家伙往往最可靠。Modbus协议简单、开放、不收费,几乎所有的工业设备都支持它。
我个人习惯把Modbus比作「工业界的普通话」。不管设备是哪国造的,只要会说Modbus,就能跟你的EMS系统对话。
核心要点:Modbus是主从架构。一个主站(你的EMS服务器)可以带最多247个从站(现场设备)。主站问,从站答,规矩得很。
3.2 Modbus的两副面孔:RTU vs TCP
Modbus主要有两种变体:RTU和TCP。我刚开始做项目时也搞混过,后来发现记住一句话就行——RTU走串口,TCP走网口。
| 特性 | Modbus RTU | Modbus TCP |
|---|---|---|
| 物理层 | RS-232 / RS-485 | 以太网 |
| 传输距离 | RS-485可达1200米 | 100米(交换机可扩展) |
| 从站数量 | 最多247个 | 理论上无限制 |
| 报文格式 | 带CRC校验 | 带MBAP头,无CRC |
| 典型场景 | 工厂、配电房 | 楼宇、数据中心 |
嗯,这里要注意:RTU报文末尾有2字节的CRC校验,TCP没有。为什么?因为TCP/IP协议栈自己会做校验,Modbus就不重复劳动了。
3.3 寄存器映射——设备的数据字典
你想想看,Modbus协议本身只定义了「怎么传」,没定义「传什么」。那设备的数据怎么组织?靠寄存器映射表。
Modbus定义了四种数据对象:
- 线圈(Coil):1位,可读写。比如开关状态。
- 离散输入(Discrete Input):1位,只读。比如报警信号。
- 输入寄存器(Input Register):16位,只读。比如电压、电流。
- 保持寄存器(Holding Register):16位,可读写。比如设定值、参数。
我在项目中遇到过最坑的事:某厂家把电压值放在保持寄存器里,但文档写的是输入寄存器。我查了三天才找到原因。所以拿到设备第一件事——先看寄存器映射表,别信经验主义。
我的习惯:拿到新设备,先读一遍所有保持寄存器的值,再读一遍输入寄存器。对比两份数据,基本能发现映射关系。
3.4 功能码详解——Modbus的指令集
Modbus功能码就是主站发给从站的「命令」。常用的其实就几个:
| 功能码 | 名称 | 操作对象 | 说明 |
|---|---|---|---|
| 0x01 | 读线圈 | 线圈 | 读一组开关量 |
| 0x02 | 读离散输入 | 离散输入 | 读一组只读开关量 |
| 0x03 | 读保持寄存器 | 保持寄存器 | 最常用,读参数 |
| 0x04 | 读输入寄存器 | 输入寄存器 | 读测量值 |
| 0x05 | 写单个线圈 | 线圈 | 控制单个开关 |
| 0x06 | 写单个寄存器 | 保持寄存器 | 设置单个参数 |
| 0x0F | 写多个线圈 | 线圈 | 批量控制 |
| 0x10 | 写多个寄存器 | 保持寄存器 | 批量设置参数 |
你看,是不是很简单?实际项目中,80%的通信只用0x03和0x04这两个功能码。读数据嘛,够用了。
3.5 报文解析实战——手撕Modbus报文
光说不练假把式。我们来拆一个真实的Modbus RTU报文。
假设主站发送:01 03 00 00 00 02 C4 0B
我来逐字节拆解:
- 01:从站地址,1号设备
- 03:功能码,读保持寄存器
- 00 00:起始寄存器地址,从0号开始
- 00 02:读取数量,读2个寄存器
- C4 0B:CRC校验,低字节在前
从站回复:01 03 04 00 0A 00 14 7A 1E
- 01:从站地址
- 03:功能码回显
- 04:数据字节数,4字节
- 00 0A:寄存器0的值,10(十进制)
- 00 14:寄存器1的值,20(十进制)
- 7A 1E:CRC校验
我曾经用Wireshark抓一个TCP报文,发现设备回复的数据总是少两个字节。折腾半天,原来是设备固件有bug,把CRC当数据发了。嗯,现场调试就是这样,什么怪事都能遇到。
避坑指南:我曾经在调试时发现,有些国产设备对功能码0x03和0x04的实现有差异。明明文档写的是输入寄存器,但用0x04读不到数据,换0x03反而能读到。遇到这种情况,别纠结,哪个能用用哪个。
3.6 知识体系总览
下面这张图是我自己整理的Modbus知识框架,帮你快速建立全局认知:
3.7 实战小贴士
最后分享几个我踩过的坑:
- 字节序问题:Modbus默认大端序(高字节在前),但有些设备用的小端序。我遇到过读出来的电压值变成65535,其实就是字节序搞反了。
- 浮点数处理:32位浮点数占2个寄存器。IEEE754标准,但有些厂家自己定义格式。建议先读一个已知值验证。
- 超时设置:RTU模式下,报文间隔不能超过3.5个字符时间。TCP模式下,建议超时设500ms以上,网络抖动时不会误判。
我的调试三板斧:第一,用Modbus Poll模拟主站;第二,用串口助手看原始报文;第三,对照寄存器映射表逐字节核对。这三招下来,90%的问题都能定位。
好了,Modbus RTU和TCP的核心内容就这些。下一节我们会聊Modbus的进阶话题,包括批量读取策略、异常处理、以及如何用Python写一个Modbus采集程序。到时候我会分享一个我实际项目中用过的代码框架。
公众号:蓝海资料掘金营,微信deep3321