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、上位机、网关
我的小经验:如果你用RTU,记得把波特率设成9600或19200,数据位8,停止位1,无校验。这是工业现场最通用的配置。我曾经遇到一个项目,设备死活连不上,折腾了半天发现是停止位设成了2——厂家手册写错了。

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,以此类推。

注意:不同厂家对地址的偏移处理可能不一样。有的直接给协议地址,有的给PLC地址。我建议你拿到设备手册后,先读一个已知值的寄存器,验证一下地址映射关系,再批量开发。

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,最常用的工具是ModScan(模拟主站)和ModSim(模拟从站)。抓报文用Serial Port Monitor或者Wireshark(抓TCP包)。先把工具玩熟,能省你80%的排查时间。

好了,这一章的内容就到这里。Modbus协议不难,但细节很多。你只要把寄存器映射、数据帧结构、字节序这三个核心点吃透,后面做能耗监测的数据采集,基本就没什么大问题了。