3、通信协议入门:Modbus RTU/TCP 协议详解、寄存器地址映射、数据帧结构
好,咱们进入实战前的第一道硬菜——通信协议。
做设备联网,说白了就是让设备跟你的系统「说上话」。而Modbus,就是工业界最通用的「普通话」。我做了这么多年能源项目,十套系统里至少有七八套在用Modbus。你想想看,一个1980年代诞生的协议,到现在还活得好好的,一定有它的道理。
3.1 Modbus 是个啥?为什么选它?
Modbus是一种主从通信协议。简单说,就是一台主机(比如你的网关或上位机)问,多台从机(比如电表、温控器)答。一问一答,规矩清楚。
它有两个主流变种:Modbus RTU 和 Modbus 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 实战中的几个要点
- 波特率与校验位:RTU通信时,主从机的波特率、数据位、停止位、校验位必须一致。我一般用9600波特率、8数据位、1停止位、无校验。兼容性最好。
- 超时设置:串口通信没有TCP那样的重传机制。建议超时设为500ms~1s。太短容易误判,太长影响采集效率。
- 多寄存器读取:尽量一次读取多个连续寄存器,而不是一个一个读。比如读三相电压,一次读3个寄存器,比发3次请求快得多。
- 字节序问题:16位以上的数据(如32位浮点数),不同设备可能有不同的字节序。有的高字节在前(Big-Endian),有的低字节在前(Little-Endian)。拿到数据后先验证一下。
一句话总结:Modbus不难,难的是设备手册写得不清楚。我建议你在实验室先搭一个最小系统——一台网关、一块支持Modbus的电表,用串口助手把报文调通,再上代码。这样到了现场,心里才有底。
好了,通信协议这块咱们就聊到这儿。下一节我会带你手写一个Modbus RTU的采集程序,把今天讲的这些帧结构变成真正的代码。