3、设备接口协议详解(一):Modbus RTU/TCP协议原理、数据模型(线圈、寄存器)、功能码、报文结构解析。

各位工程师朋友,咱们今天来啃一块硬骨头——Modbus协议。说实话,这玩意儿在工业现场太常见了。我做了十几年MES集成,跟设备打交道,十台里有七八台都支持Modbus。你想想看,一个1999年就发布的协议,到现在还能活跃在一线,说明它确实有两把刷子。

Modbus本质上是个“主从”通信协议。什么意思呢?就是一台主设备(比如你的上位机、PLC或者MES接口机)发起请求,从设备(比如传感器、变频器、智能仪表)响应。主设备问,从设备答,就这么简单。但简单归简单,里面的门道可不少。

核心要点:Modbus协议只定义了应用层的数据格式,底层走串口还是走以太网,它不管。这就衍生出了两个最常用的变种:Modbus RTU(串口)和Modbus TCP(以太网)。

3.1 数据模型:线圈、离散输入、输入寄存器、保持寄存器

我第一次接触Modbus时,最晕的就是这四个数据对象。说白了,Modbus把设备里的数据分成了四类,每一类都有不同的读写权限和用途。

数据模型 对象类型 读写属性 数据大小 典型用途
线圈 (Coil) 可读可写 1 bit 开关量输出(继电器、指示灯)
离散输入 (Discrete Input) 只读 1 bit 开关量输入(按钮、限位开关)
输入寄存器 (Input Register) 只读 16 bit 模拟量输入(温度、压力传感器)
保持寄存器 (Holding Register) 可读可写 16 bit 参数设置、控制指令、累计值

嗯,这里要注意。很多设备手册里写的“寄存器地址”,其实指的是保持寄存器。比如你看到“地址40001”,这个4开头就代表保持寄存器区。我个人习惯,在MES对接时,最常用的就是保持寄存器和线圈。因为你要读数据、写参数,基本都落在这两个区。

我的经验:曾经对接一台温控仪表,手册上写温度值在“地址40003”。我一开始直接用功能码03去读,结果死活读不到。后来才发现,它用的是输入寄存器,得用功能码04。所以,拿到设备手册第一件事,先确认数据模型是哪一类。

3.2 功能码:Modbus的“指令集”

功能码就是主设备告诉从设备“你要干什么”。常用的功能码其实没几个,我列个表,你一看就明白。

功能码 名称 作用 操作对象
01 (0x01) 读线圈 读取一组线圈的状态 线圈
02 (0x02) 读离散输入 读取一组离散输入的状态 离散输入
03 (0x03) 读保持寄存器 读取一个或多个保持寄存器的值 保持寄存器
04 (0x04) 读输入寄存器 读取一个或多个输入寄存器的值 输入寄存器
05 (0x05) 写单个线圈 强制一个线圈的通断 线圈
06 (0x06) 写单个寄存器 写入一个保持寄存器的值 保持寄存器
15 (0x0F) 写多个线圈 连续写入多个线圈 线圈
16 (0x10) 写多个寄存器 连续写入多个保持寄存器 保持寄存器

你看,是不是很清晰?读用01-04,写用05、06,批量读写用15、16。我在项目中遇到过最坑的事,就是有人用功能码03去读线圈,结果从设备返回异常码。说白了,功能码和操作对象是绑定的,不能乱用。

3.3 报文结构解析:RTU vs TCP

报文结构是Modbus的核心。咱们先看RTU,再看TCP。两者最大的区别,就是RTU多了一个CRC校验,而TCP没有,因为TCP/IP协议栈自己会做校验。

3.3.1 Modbus RTU 报文

RTU报文是二进制格式,没有起始位和停止位,靠时间间隔来区分帧。结构如下:

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

举个例子,我要读取从站地址为1的设备,从保持寄存器地址0开始,读2个寄存器。请求报文就是:

01 03 00 00 00 02 C4 0B

拆解一下:

  • 01:从站地址,表示我要找1号设备
  • 03:功能码,读保持寄存器
  • 00 00:起始地址,从0号寄存器开始
  • 00 02:读取数量,读2个寄存器
  • C4 0B:CRC校验,低字节在前

如果设备正常响应,会返回:

01 03 04 00 0A 00 14 7A 5B
  • 01:从站地址
  • 03:功能码
  • 04:数据字节数,表示后面有4个字节
  • 00 0A:第一个寄存器的值,十进制10
  • 00 14:第二个寄存器的值,十进制20
  • 7A 5B:CRC校验

避坑指南:我曾经调试一个RTU设备,报文发出去总是没响应。查了半天,发现是CRC算错了。很多初学者会忽略CRC的字节序——Modbus RTU的CRC是低字节在前,高字节在后。比如CRC值0x0BC4,在报文里要写成C4 0B。

3.3.2 Modbus TCP 报文

TCP报文去掉了CRC,加了一个MBAP报文头。结构如下:

| 事务处理标识 (2字节) | 协议标识 (2字节) | 长度 (2字节) | 单元标识 (1字节) | 功能码 (1字节) | 数据区 (N字节) |

同样读保持寄存器的例子,TCP请求报文:

00 01 00 00 00 06 01 03 00 00 00 02

拆解:

  • 00 01:事务处理标识,用于匹配请求和响应
  • 00 00:协议标识,固定为0
  • 00 06:长度,表示从单元标识开始到结束共有6个字节
  • 01:单元标识,相当于RTU里的从站地址
  • 03:功能码
  • 00 00:起始地址
  • 00 02:读取数量

响应报文:

00 01 00 00 00 07 01 03 04 00 0A 00 14

注意看,TCP响应里没有CRC,但多了一个字节的长度字段。嗯,这里要记住,TCP报文的最大长度是260字节,超过这个数就要分包了。

3.4 知识体系图:Modbus协议全景

下面这张图,是我自己总结的Modbus协议知识体系。你可以把它当成一张地图,以后遇到Modbus相关的问题,先看这张图,心里就有数了。

Modbus协议知识体系 Modbus RTU (串口) Modbus TCP (以太网) 数据模型 (4类) • 线圈 (Coil) - 1bit, 可读写 • 离散输入 (DI) - 1bit, 只读 • 输入寄存器 (IR) - 16bit, 只读 • 保持寄存器 (HR) - 16bit, 可读写 功能码 (常用8个) • 01/02: 读线圈/离散输入 • 03/04: 读保持/输入寄存器 • 05/06: 写单个线圈/寄存器 • 15/16: 写多个线圈/寄存器 报文结构 RTU: [从站地址] [功能码] [数据区] [CRC校验] TCP: [MBAP头(7字节)] [功能码] [数据区] 应用场景:PLC、变频器、仪表、传感器、MES对接

3.5 实战要点:MES对接时的注意事项

最后,我结合自己的项目经验,给你几个实实在在的建议:

  1. 先看设备手册:确定设备支持RTU还是TCP,支持哪些功能码,数据模型是哪种。别上来就写代码,八成要返工。
  2. 注意字节序:Modbus默认是大端模式(高字节在前),但有些设备厂商会搞小端。我遇到过一台国产仪表,手册上写大端,实际是小端,折腾了我一整天。
  3. 超时和重试:工业现场干扰多,报文丢包很正常。我建议超时时间设500ms,重试3次。别设太短,也别无限重试。
  4. 地址偏移:很多设备手册里的地址是PLC地址(如40001),实际Modbus报文里要减1。比如40001对应报文地址0x0000。这个坑,我踩过不止一次。
  5. 用工具先测试:推荐ModScan(主站模拟)和ModSim(从站模拟)。先拿工具把报文调通,再写代码,效率高得多。

我的习惯:每次对接新设备,我都会先用ModScan发一条功能码03的报文,读几个寄存器看看。如果返回的数据能看懂,说明协议没问题。如果返回异常码或乱码,那就得回头查手册了。

好了,Modbus RTU和TCP的核心内容就这些。数据模型、功能码、报文结构,这三样你吃透了,Modbus就算入门了。下一节咱们接着聊Modbus的异常处理和多设备轮询策略,那才是真正考验工程能力的地方。


公众号:蓝海资料掘金营,微信deep3321