4、通信协议入门:Modbus RTU/TCP协议原理、寄存器地址映射、数据帧解析
各位同学,咱们今天聊聊工业通信里最基础、也最绕不开的一个协议——Modbus。
说实话,我入行那会儿,第一个接触的工业协议就是它。那时候在车间调试一台老旧的变频器,设备说明书上就写着“支持Modbus RTU”。我当时一脸懵,心想这玩意儿到底怎么跟PLC说话?后来啃了几天手册,又拿着串口调试工具一条一条地抓报文,才算真正入了门。
你想想看,Modbus为什么能活这么多年?说白了就两个字:简单。它不搞花里胡哨的握手、加密、路由,就是老老实实地“问一句,答一句”。这种朴素的哲学,在工业现场反而最可靠。
4.1 Modbus的两种“方言”:RTU vs TCP
Modbus其实分好几种变体,但咱们做能耗监测,最常打交道的就是RTU和TCP。
Modbus RTU,走的是串口(RS-232/RS-485)。它的数据是二进制编码的,紧凑、高效。我习惯在距离远、设备多、环境嘈杂的现场用RS-485 + RTU。比如一个车间里几十个电表,用一条双绞线串起来,成本低,抗干扰也不错。
Modbus TCP,走的是以太网。它的数据帧里多了个MBAP头(Modbus Application Protocol header),说白了就是把RTU的校验码去掉,换成TCP/IP的校验。为什么?因为TCP/IP底层已经帮你做了可靠传输,Modbus就不需要再重复校验了。
嗯,这里要注意:RTU和TCP的寄存器地址、功能码是完全一样的。区别只在于“怎么把数据打包发出去”。
| 特性 | Modbus RTU | Modbus TCP |
|---|---|---|
| 物理层 | RS-232 / RS-485 | 以太网(RJ45) |
| 传输方式 | 串行,半双工 | 网络,全双工 |
| 地址范围 | 1-247(从站地址) | Unit ID(通常为1或255) |
| 校验方式 | CRC-16(必须) | 无(依赖TCP校验) |
| 典型场景 | 电表、变频器、传感器 | PLC、上位机、网关 |
4.2 寄存器地址映射:读懂设备的“内存地图”
Modbus协议里,数据都存放在“寄存器”里。你可以把寄存器想象成设备的内部小格子,每个格子存一个16位的数据。
寄存器分四种类型,我用一张表给你说清楚:
| 类型 | 功能 | 地址范围(PLC习惯) | Modbus协议地址 | 读写 |
|---|---|---|---|---|
| 线圈(Coil) | 开关量输出 | 00001-09999 | 0x0000-0x270E | 可读可写 |
| 离散输入(Discrete Input) | 开关量输入 | 10001-19999 | 0x0000-0x270E | 只读 |
| 输入寄存器(Input Register) | 模拟量输入 | 30001-39999 | 0x0000-0x270E | 只读 |
| 保持寄存器(Holding Register) | 参数/数据存储 | 40001-49999 | 0x0000-0x270E | 可读可写 |
这里有个坑,我当年踩过。很多设备手册上写的地址是“40001”,但你在代码里填的地址其实是“0”。为什么?因为PLC习惯用“40001”表示第一个保持寄存器,而Modbus协议里,地址是从0开始编号的。所以40001对应协议地址0x0000,40002对应0x0001,以此类推。
4.3 数据帧解析:手撕报文
咱们来看一个实际的RTU报文。假设我要读取一台电表的电压值,保持寄存器地址是0x0000。
主站发送(请求):
01 03 00 00 00 01 84 0A
我来拆解一下:
- 01:从站地址。这台电表的地址是1。
- 03:功能码。03表示“读取保持寄存器”。
- 00 00:起始寄存器地址。高字节在前,低字节在后。这里是0x0000。
- 00 01:读取的寄存器数量。这里是1个。
- 84 0A:CRC-16校验码。低字节在前,高字节在后。
从站响应(应答):
01 03 02 0B B8 7A 3D
拆解:
- 01:从站地址,原样返回。
- 03:功能码,原样返回。
- 02:数据字节数。这里是2个字节(因为1个寄存器=2字节)。
- 0B B8:寄存器数据。0x0BB8换算成十进制是3000。如果电压量程是0-6000V对应0-4095,那实际电压就是3000 / 4095 * 6000 ≈ 4395V。嗯,这得是高压线路了。
- 7A 3D:CRC校验。
核心要点:Modbus RTU的CRC计算是多项式0x8005,初始值0xFFFF。很多现成的库都帮你算好了,但如果你要手写,记得查一下查表法,效率高很多。
再看TCP的报文,其实大同小异:
00 01 00 00 00 06 01 03 00 00 00 01
前面多了6个字节的MBAP头:
- 00 01:事务处理标识。用于匹配请求和响应。
- 00 00:协议标识。Modbus固定为0x0000。
- 00 06:后续数据长度。从Unit ID开始算起,这里是6个字节。
- 01:Unit ID。相当于RTU的从站地址。
- 后面就跟RTU一样了:03 00 00 00 01。
你看,TCP就是把RTU的CRC去掉,换成了MBAP头。因为TCP/IP协议栈已经保证了数据完整性,Modbus就不需要再算一遍CRC了。
4.4 实战中的避坑指南
讲到这里,我分享几个我在项目里遇到的坑,你以后大概率也会碰到:
- 字节序问题:Modbus默认是大端模式(高字节在前),但有些设备厂家会自作聪明地改成小端。我遇到过一台进口的流量计,手册上写的是大端,实际发出来是小端。排查了整整一天。
- 寄存器对齐:32位浮点数或整数,需要占用两个连续的保持寄存器。比如地址0x0000和0x0001。读取时一定要连续读,不能分开读,否则数据会错位。
- 响应超时:RTU模式下,从站响应时间一般在10-100ms。如果超过1秒没收到响应,我建议重试3次,然后报错。别死等,现场总线有时候就是会丢包。
- 广播地址:地址0是广播地址,所有从站都会接收,但不会响应。这个可以用来同步校时,但千万别用来读数据,否则你会收到一堆冲突的报文。
好了,这一章的内容就到这里。Modbus协议不难,但细节很多。你只要把寄存器映射、数据帧结构、字节序这三个核心点吃透,后面做能耗监测的数据采集,基本就没什么大问题了。