2、Modbus TCP协议详解:报文结构、功能码分类、TCP/IP封装、典型通信流程
好,咱们今天来聊聊Modbus TCP。说实话,这个协议在工业现场太常见了。我做了这么多年自动化,几乎每个项目都离不开它。你想想看,从PLC到变频器,从仪表到机器人,大家都在用Modbus TCP通信。为什么?因为它简单、可靠、容易上手。
但简单归简单,真要把它用好,还是得把底层的东西吃透。我记得刚入行那会儿,就因为在报文结构上栽了个跟头,调试了整整两天。嗯,咱们今天就把它彻底讲明白。
2.1 Modbus TCP报文结构
Modbus TCP的报文,说白了就是在标准Modbus串口协议外面包了一层TCP/IP的外衣。它的报文结构分为两部分:MBAP报文头和协议数据单元(PDU)。
咱们先看MBAP头。这个头一共7个字节,结构如下:
| 字段 | 长度(字节) | 描述 |
|---|---|---|
| 事务处理标识符 | 2 | 用于匹配请求和响应,一般由客户端生成 |
| 协议标识符 | 2 | 固定为0x0000,表示Modbus协议 |
| 长度 | 2 | 后续字节的总长度(从单元标识符开始算) |
| 单元标识符 | 1 | 相当于串口协议中的从站地址 |
这里我要特别说一下事务处理标识符。我在项目中遇到过有人把它设成固定值,结果多客户端同时访问时,响应报文全乱套了。我个人习惯是用一个递增计数器来生成这个值,这样每个请求都有唯一标识,绝对不会搞混。
关键点:MBAP头中的长度字段,只包含单元标识符+PDU的长度,不包含MBAP头本身的7个字节。这个坑我踩过,大家注意。
PDU部分就简单了,它包含:
- 功能码(1字节):告诉设备要做什么操作
- 数据(N字节):具体的操作参数或数据
2.2 功能码分类
功能码是Modbus协议的核心。我把它分成三大类,这样好记:
2.2.1 位操作类
| 功能码 | 名称 | 说明 |
|---|---|---|
| 0x01 | 读线圈 | 读取DO(数字量输出)状态 |
| 0x02 | 读离散输入 | 读取DI(数字量输入)状态 |
| 0x05 | 写单个线圈 | 控制单个DO输出 |
| 0x0F | 写多个线圈 | 批量控制DO输出 |
2.2.2 寄存器操作类
| 功能码 | 名称 | 说明 |
|---|---|---|
| 0x03 | 读保持寄存器 | 最常用,读取模拟量或参数 |
| 0x04 | 读输入寄存器 | 读取AI(模拟量输入)值 |
| 0x06 | 写单个寄存器 | 设置单个参数 |
| 0x10 | 写多个寄存器 | 批量设置参数 |
2.2.3 诊断与特殊类
- 0x07:读取异常状态
- 0x08:诊断(回环测试等)
- 0x11:读取事件计数
- 0x17:读/写多个寄存器(一次搞定读写)
我的经验:实际项目中,80%以上的通信都在用0x03和0x06这两个功能码。读保持寄存器、写单个寄存器,这两个搞明白了,基本就能应付大多数场景。
2.3 TCP/IP封装过程
Modbus TCP怎么在网络上跑?说白了就是层层打包。我画个简单的流程:
应用层: [MBAP头] + [功能码] + [数据]
传输层: TCP头 + [上述数据]
网络层: IP头 + [上述数据]
链路层: 以太网帧头 + [上述数据] + 帧尾
这里有个关键点:Modbus TCP默认使用端口502。为什么是502?嗯,历史原因,这个端口是IANA专门分配给Modbus协议的。我在项目中见过有人改成其他端口,结果防火墙策略没配好,通信死活不通。所以,除非你有特殊需求,否则别改端口。
注意:Modbus TCP没有串口协议中的CRC校验。为什么?因为TCP/IP协议栈本身已经提供了可靠的传输和校验。如果你在Modbus TCP报文里还加CRC,那就是画蛇添足了。我曾经见过一个外包团队这么干,结果两边对不上,折腾了好几天。
2.4 典型通信流程
咱们用一个实际例子来说明。假设我要读取一台变频器的当前频率,地址是40001(保持寄存器)。
步骤1:客户端发送请求
事务标识符:0x0001
协议标识符:0x0000
长度:0x0006
单元标识符:0x01
功能码:0x03
起始地址:0x0000(对应40001)
寄存器数量:0x0001
注意看,这里的起始地址是0x0000,对应的是40001。为什么?因为Modbus的地址映射规则是:40001对应地址0,40002对应地址1,以此类推。这个映射关系我刚开始也搞混过,后来养成了习惯,每次都在代码里写清楚注释。
步骤2:服务器响应
事务标识符:0x0001(和请求一致)
协议标识符:0x0000
长度:0x0005
单元标识符:0x01
功能码:0x03
数据长度:0x02
数据:0x0BB8(假设频率是30.00Hz,对应3000)
这里有个细节:响应报文的事务标识符必须和请求一致。如果对不上,客户端应该丢弃这个报文。我在调试时遇到过设备固件有bug,事务标识符总是返回0,结果客户端一直报超时。
步骤3:异常处理
如果请求出错,服务器会返回异常响应:
功能码:0x83(0x03 + 0x80,最高位置1)
异常码:0x02(非法数据地址)
常见的异常码有:
- 0x01:非法功能码
- 0x02:非法数据地址
- 0x03:非法数据值
- 0x04:从站设备故障
避坑指南:我曾经遇到一个项目,设备返回异常码0x04,我以为是设备坏了,换了三台都不行。后来才发现,是请求的寄存器数量超过了设备支持的最大值。所以,收到异常码时,先检查自己的请求参数,别急着怀疑设备。
2.5 实际部署建议
最后,给大家几个我在项目中总结的经验:
- 超时设置:Modbus TCP的默认超时一般是1秒。但如果网络环境差,或者设备响应慢,建议调到3-5秒。我一般设3秒,重试3次。
- 连接管理:不要每个请求都新建TCP连接。建立连接的开销很大,建议用长连接。我习惯用一个连接池来管理。
- 报文间隔:虽然TCP没有像串口那样的帧间隔要求,但建议请求之间留10-20ms的间隔,给设备一点处理时间。
- 字节序:Modbus默认是大端模式(Big-Endian)。如果你用的是小端模式的系统(比如x86),记得做字节序转换。这个坑我踩过不止一次。
好了,Modbus TCP的核心内容就这些。说白了,它就是一套约定好的数据交换规则。你只要把报文结构、功能码、封装流程搞清楚了,剩下的就是多练。下一章咱们聊Modbus RTU,那个和TCP有些不同,但核心思想是一样的。