3、通信协议入门:Modbus RTU/TCP 协议详解、寄存器地址映射、数据帧结构

好,咱们进入实战前的第一道硬菜——通信协议。

做设备联网,说白了就是让设备跟你的系统「说上话」。而Modbus,就是工业界最通用的「普通话」。我做了这么多年能源项目,十套系统里至少有七八套在用Modbus。你想想看,一个1980年代诞生的协议,到现在还活得好好的,一定有它的道理。

3.1 Modbus 是个啥?为什么选它?

Modbus是一种主从通信协议。简单说,就是一台主机(比如你的网关或上位机)问,多台从机(比如电表、温控器)答。一问一答,规矩清楚。

它有两个主流变种:Modbus RTUModbus TCP

  • Modbus RTU:走串口(RS-232/RS-485),数据是二进制编码,效率高。适合长距离、多设备挂载。我在工厂里见过一条485总线上挂了32块电表,跑得稳稳的。
  • Modbus TCP:走以太网,基于TCP/IP协议栈。说白了就是把RTU的报文封装到TCP包里。适合局域网或跨网段通信。

核心区别一句话:RTU是串口物理层,TCP是网络物理层。上层应用逻辑几乎一样。

3.2 寄存器地址映射——你得知道「问谁要数据」

Modbus把设备里的数据分成了四个区域,每个区域有固定的功能码和地址范围。我刚开始接触时,最头疼的就是记这些地址。后来发现,其实就四种类型:

区域类型 功能码 地址范围(PLC习惯) 数据宽度 典型用途
线圈(Coil) 01(读)/ 05(写) 00001 - 09999 1 bit 开关状态、继电器控制
离散输入(Discrete Input) 02(只读) 10001 - 19999 1 bit 按钮、限位开关状态
输入寄存器(Input Register) 04(只读) 30001 - 39999 16 bit 模拟量输入、传感器值
保持寄存器(Holding Register) 03(读)/ 06或16(写) 40001 - 49999 16 bit 参数设置、累计量、配置

嗯,这里要注意:实际通信时,地址是从0开始的。比如保持寄存器40001,在报文里地址是0x0000。很多新手在这里栽跟头——明明文档写40001,你发地址1,设备根本不搭理你。

我的习惯:拿到设备手册后,先把所有寄存器地址整理成一张Excel表,标注好「协议地址」和「实际报文地址」。省得现场调试时手忙脚乱。

3.3 数据帧结构——报文长什么样?

咱们先看Modbus RTU的帧结构。它没有帧头帧尾的固定标识,而是靠时间间隔来区分一帧数据。

3.3.1 Modbus RTU 帧

| 从机地址 (1字节) | 功能码 (1字节) | 数据区 (N字节) | CRC校验 (2字节) |

举个例子,读取1号电表(从机地址0x01)的电压值,电压存在保持寄存器40001里:

主机发送:01 03 00 00 00 01 84 0A
从机回复:01 03 02 02 1C B9 7A

拆解一下:

  • 01:从机地址,告诉1号表「我在叫你」
  • 03:功能码,读保持寄存器
  • 00 00:起始地址,对应40001
  • 00 01:读取数量,读1个寄存器
  • 84 0A:CRC校验,低字节在前

回复报文:

  • 01:从机地址回显
  • 03:功能码回显
  • 02:数据字节数,2个字节
  • 02 1C:电压值,0x021C = 540,单位0.1V,即54.0V
  • B9 7A:CRC校验

我曾经踩过的坑:CRC校验算错了,设备死活不回包。后来发现是CRC的字节序问题——有些设备要求低字节在前,有些要求高字节在前。建议用现成的CRC计算工具验证一下。

3.3.2 Modbus TCP 帧

TCP帧就是在RTU的基础上,去掉了CRC(因为TCP/IP自带校验),加了一个MBAP报文头。

| MBAP头 (7字节) | 从机地址 (1字节) | 功能码 (1字节) | 数据区 (N字节) |

MBAP头包含:

  • 事务处理标识(2字节):用于匹配请求和响应
  • 协议标识(2字节):固定为0x0000
  • 长度(2字节):后续字节数
  • 单元标识(1字节):相当于RTU的从机地址

同样的读取电压操作,TCP报文长这样:

主机发送:00 01 00 00 00 06 01 03 00 00 00 01
从机回复:00 01 00 00 00 07 01 03 02 02 1C

你看,是不是比RTU多了前面7个字节?但核心的数据部分(01 03 00 00 00 01)跟RTU一模一样。

3.4 实战中的几个要点

  1. 波特率与校验位:RTU通信时,主从机的波特率、数据位、停止位、校验位必须一致。我一般用9600波特率、8数据位、1停止位、无校验。兼容性最好。
  2. 超时设置:串口通信没有TCP那样的重传机制。建议超时设为500ms~1s。太短容易误判,太长影响采集效率。
  3. 多寄存器读取:尽量一次读取多个连续寄存器,而不是一个一个读。比如读三相电压,一次读3个寄存器,比发3次请求快得多。
  4. 字节序问题:16位以上的数据(如32位浮点数),不同设备可能有不同的字节序。有的高字节在前(Big-Endian),有的低字节在前(Little-Endian)。拿到数据后先验证一下。

一句话总结:Modbus不难,难的是设备手册写得不清楚。我建议你在实验室先搭一个最小系统——一台网关、一块支持Modbus的电表,用串口助手把报文调通,再上代码。这样到了现场,心里才有底。

好了,通信协议这块咱们就聊到这儿。下一节我会带你手写一个Modbus RTU的采集程序,把今天讲的这些帧结构变成真正的代码。