4、通信协议解析(上):Modbus RTU/TCP协议原理、数据帧结构、地址映射

各位同学,咱们今天聊聊Modbus。说实话,这玩意儿在工业现场太常见了。我做了十几年SCADA集成,几乎每个项目都绕不开它。你想想看,从PLC到变频器,从温控表到智能电表,Modbus就像工业界的“普通话”。

为什么先讲Modbus?因为它是工业物联网网关最基础、最通用的协议。你搞懂了Modbus,其他协议上手会快很多。今天咱们重点讲RTU和TCP两种模式,把原理、帧结构、地址映射这些核心东西掰开揉碎了说清楚。

4.1 Modbus协议的前世今生

Modbus是1979年Modicon公司(就是现在的施耐德电气)发明的。最初就是为了让PLC和智能设备之间能通信。说白了,它就是个主从架构的协议——一个主站(Master)问,多个从站(Slave)答。

我个人习惯把Modbus分成三类:

  • Modbus RTU:串行通信,二进制编码,效率高。现场最常见。
  • Modbus ASCII:串行通信,ASCII编码,可读性好但效率低。现在用得少了。
  • Modbus TCP:基于以太网,封装在TCP/IP包里。现代工厂越来越普及。

嗯,这里要注意:RTU和TCP虽然名字不同,但核心的数据模型和功能码是完全一样的。区别只在于传输层和封装方式。我在项目中遇到过不少新手,以为RTU和TCP是两套完全不同的协议,其实不然。

4.2 Modbus RTU协议原理与数据帧结构

先讲RTU。它跑在RS-232或RS-485物理层上。RS-485用得最多,因为支持多站、抗干扰能力强。我建议你在现场布线时,尽量用双绞屏蔽线,接地要做好。有一次我在一个钢厂项目里,就因为485线没屏蔽,数据乱得一塌糊涂。

4.2.1 RTU数据帧结构

RTU的帧结构非常紧凑。一个完整的RTU报文长这样:

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

咱们拆开来看:

  • 地址码:从站地址,范围1-247。0是广播地址,248-255保留。
  • 功能码:告诉从站要干什么。比如03是读保持寄存器,06是写单个寄存器。
  • 数据区:具体参数,比如起始地址、寄存器数量、要写入的数据等。
  • CRC校验:循环冗余校验,确保数据在传输过程中没被干扰。

关键点:RTU帧里没有帧头和帧尾标记。它靠“静默时间”来区分报文。也就是说,如果总线上超过3.5个字符时间没有数据传输,就认为上一帧结束了。这个时间参数在网关配置时一定要设对,否则容易粘包。

4.2.2 常用功能码

功能码是Modbus的灵魂。我整理了一张表,这些是你在项目中几乎天天要用的:

功能码 名称 作用 数据模型
01 读线圈 读取DO(数字量输出) 线圈
02 读离散输入 读取DI(数字量输入) 离散输入
03 读保持寄存器 读取AO(模拟量输出)或参数 保持寄存器
04 读输入寄存器 读取AI(模拟量输入) 输入寄存器
05 写单个线圈 控制单个DO 线圈
06 写单个寄存器 设置单个AO或参数 保持寄存器
15 写多个线圈 批量控制DO 线圈
16 写多个寄存器 批量设置参数 保持寄存器

你想想看,03和04有什么区别?03读的是保持寄存器,可以读写;04读的是输入寄存器,只能读不能写。我在调试一个水处理项目时,就遇到过有人用04去读PLC里设的参数,结果死活读不到——因为参数存在保持寄存器里。

4.2.3 RTU报文示例

咱们看个实际例子。假设你要读取地址为01的从站,从寄存器地址100开始读2个寄存器:

主站发送:01 03 00 64 00 02 C4 0B
从站回复:01 03 04 00 0A 00 14 7A 3B

拆解一下:

  • 发送帧:01(地址) 03(功能码) 00 64(起始地址100) 00 02(读2个) C4 0B(CRC)
  • 回复帧:01(地址) 03(功能码) 04(数据长度4字节) 00 0A(寄存器1的值=10) 00 14(寄存器2的值=20) 7A 3B(CRC)

注意,Modbus寄存器是16位的,所以每个寄存器占2字节。数据采用大端模式,高位在前。这个细节容易搞错,我曾经在对接一个进口设备时,因为字节序没搞对,读出来的数据全是乱的。

4.3 Modbus TCP协议原理与数据帧结构

Modbus TCP就是把Modbus报文装进TCP/IP包里。它去掉了CRC校验(因为TCP本身有校验),加了一个MBAP报文头。说白了,就是给Modbus报文穿了个“网络外套”。

4.3.1 TCP数据帧结构

Modbus TCP的帧结构:

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

MBAP头包含:

  • 事务处理标识符(2字节):用于匹配请求和响应。我习惯从0开始递增。
  • 协议标识符(2字节):固定为0x0000,表示Modbus协议。
  • 长度(2字节):后续字节数(包括功能码和数据区)。
  • 单元标识符(1字节):相当于RTU里的从站地址。在TCP里,这个字段用于区分网关后面的设备。

实战技巧:在工业物联网网关里,Modbus TCP的单元标识符特别有用。你可以用一个网关IP,通过不同的单元标识符去访问后面挂载的多个RTU设备。我做过一个项目,一个网关接了32个串口设备,就是靠单元标识符来区分的。

4.3.2 TCP报文示例

同样的需求——读地址01的从站,寄存器100开始读2个:

主站发送:00 01 00 00 00 06 01 03 00 64 00 02
从站回复:00 01 00 00 00 07 01 03 04 00 0A 00 14

拆解:

  • 发送帧:00 01(事务ID) 00 00(协议ID) 00 06(长度=6) 01(单元ID) 03(功能码) 00 64(地址) 00 02(数量)
  • 回复帧:00 01(事务ID) 00 00(协议ID) 00 07(长度=7) 01(单元ID) 03(功能码) 04(数据长度) 00 0A(值1) 00 14(值2)

看到没?TCP帧里没有CRC,但多了MBAP头。事务ID必须对应,否则从站不会认。我调试时经常用Wireshark抓包,一看事务ID不对,就知道是哪里出问题了。

4.4 地址映射——网关的核心工作

好了,协议原理讲完了。现在说说地址映射。这是工业物联网网关最核心的功能之一。说白了,就是把Modbus的寄存器地址,映射到SCADA系统能理解的标签(Tag)上。

4.4.1 地址映射的三种方式

我总结了一下,常见的映射方式有三种:

  1. 直接映射:Modbus地址直接对应SCADA标签。比如保持寄存器40001对应“温度1”。简单粗暴,适合小系统。
  2. 偏移映射:在Modbus地址上加一个偏移量。比如PLC里地址从0开始,SCADA里从40001开始。很多PLC就是这么干的。
  3. 计算映射:对原始数据进行运算后再映射。比如把两个16位寄存器拼成一个32位浮点数,或者把原始值乘以系数得到工程值。

注意:地址映射最容易出问题的地方是“地址偏移”。不同厂家的设备,地址起始可能不一样。有的从0开始,有的从1开始,有的从40001开始。我曾经在一个项目里,就因为地址偏移搞错了1,导致所有数据都错位了。排查了整整一天才找到原因。

4.4.2 实际映射案例

咱们看个实际案例。一个水处理项目,PLC里存了这些数据:

PLC内部地址 数据类型 含义 Modbus地址 SCADA标签
40001 16位无符号 进水流量 0x0000 FLOW_IN
40002 16位无符号 出水流量 0x0001 FLOW_OUT
40003-40004 32位浮点数 pH值 0x0002 PH_VALUE
40005 16位无符号 水泵状态(位映射) 0x0004 PUMP_STATUS

在网关里配置时,你需要告诉它:

  • 从站地址是多少(比如01)
  • 功能码是多少(03读保持寄存器)
  • 起始地址是多少(0x0000)
  • 读取多少个寄存器(比如5个)
  • 每个寄存器怎么解析(16位、32位、浮点数、位映射)

嗯,这里有个坑:32位浮点数在Modbus里怎么存?不同厂家可能不一样。有的用ABCD字节序,有的用CDAB。我建议你在配置网关时,先读一个已知值验证一下字节序,免得后面数据全错。

4.4.3 地址映射的优化策略

做SCADA集成时,地址映射不是简单的一对一。我分享几个优化策略:

  • 批量读取:尽量用功能码03一次读多个连续的寄存器,而不是一个一个读。这样效率高很多。我见过有人读100个点,发了100次请求,其实一次就能搞定。
  • 缓存机制:网关内部可以缓存最近读到的数据。如果SCADA系统频繁请求同一个点,直接从缓存返回,不用再去问PLC。这个在实时性要求不高的场景下特别有用。
  • 异常处理:如果某个从站没响应,网关要有超时重试机制。我一般设3次重试,间隔500ms。如果3次都失败,就报错并记录日志。

避坑指南:我曾经在一个项目中,把网关的轮询周期设得太快(10ms),结果PLC的CPU负载飙到90%,差点死机。后来改成100ms轮询,一切正常。记住,不是越快越好,要留有余量。

4.5 小结

今天咱们把Modbus RTU和TCP的核心原理、帧结构、地址映射都过了一遍。说白了,Modbus就是个“问-答”协议,主站问什么,从站答什么。RTU适合串口通信,TCP适合网络通信。地址映射是网关的翻译工作,把设备侧的寄存器地址转成SCADA系统能理解的标签。

下一节咱们继续讲Modbus的高级话题——功能码的深入应用、异常码处理、以及多主站冲突的解决方案。到时候我会分享一些实际项目中的调试技巧,保证让你少走弯路。

好,今天就到这里。有什么问题,咱们课后交流。