第4章 Modbus TCP协议详解
好,咱们今天聊聊Modbus TCP。说实话,这是目前工业现场用得最多的协议之一。我刚开始接触时也觉得它跟RTU差不多,后来踩了几个坑才明白——两者差别其实挺大的。
4.1 TCP帧结构:MBAP报文头
Modbus TCP的帧结构,说白了就是在标准TCP数据包外面套了个7字节的MBAP头。这个头是啥?我习惯叫它“报文头”,全称是Modbus Application Protocol Header。
MBAP报文头一共7个字节,结构如下:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 事务处理标识符 | 2 | 用于匹配请求和响应,一般由客户端生成 |
| 协议标识符 | 2 | 固定为0x0000,表示Modbus协议 |
| 长度 | 2 | 后续数据的字节数(从单元标识符开始算) |
| 单元标识符 | 1 | 相当于RTU里的从站地址 |
这里有个细节我特别想强调:事务处理标识符。我在项目中遇到过一台PLC同时发多个请求,结果从机返回时标识符搞混了,整个系统直接乱套。所以我的建议是——客户端每次发请求时,事务ID必须递增,从机响应时原样返回。这样两边才能对上号。
重点记忆:MBAP头7字节,事务ID + 协议ID + 长度 + 单元ID。协议ID永远是0x0000,别问为什么,这是标准。
4.2 功能码与数据区
MBAP头后面紧跟着的就是功能码和数据区。这部分跟RTU完全一样,没有变化。
功能码还是那老三样:
- 0x01:读线圈状态
- 0x03:读保持寄存器
- 0x06:写单个寄存器
- 0x10:写多个寄存器
数据区的格式也一模一样。比如读保持寄存器,请求帧是:功能码 + 起始地址 + 寄存器数量。响应帧是:功能码 + 字节数 + 寄存器数据。
举个例子,读从站1的保持寄存器地址0x0000开始,读2个寄存器:
请求帧(十六进制):
00 01 00 00 00 06 01 03 00 00 00 02
|--事务ID--|协议ID|长度|单元ID|功能码|起始地址|数量|
响应帧:
00 01 00 00 00 07 01 03 04 12 34 56 78
|--事务ID--|协议ID|长度|单元ID|功能码|字节数|数据...|
你看,长度字段是0x0006,表示从单元标识符开始往后数6个字节。响应里长度是0x0007,因为多了1个字节的字节数。
我的小技巧:调试时先看长度字段对不对。如果长度不对,后面解析全是错的。我曾经花了一下午查一个bug,最后发现是长度算错了1个字节。
4.3 与RTU/ASCII的异同
这个问题面试经常问。我简单列个表,你一看就明白:
| 对比项 | Modbus TCP | Modbus RTU | Modbus ASCII |
|---|---|---|---|
| 物理层 | 以太网 | RS-232/485 | RS-232/485 |
| 帧头 | MBAP头(7字节) | 地址码(1字节) | 起始符":" + 地址码 |
| 校验 | 无(依赖TCP校验) | CRC16 | LRC |
| 结束符 | 无 | 3.5字符静默时间 | CR/LF |
| 最大数据量 | 无限制(受TCP限制) | 253字节 | 253字节 |
说白了,TCP版本去掉了地址码和CRC校验,因为TCP/IP协议栈已经帮你做了这些事。你想想看,TCP本身就有序列号、确认机制、校验和,Modbus再搞一套CRC不是多此一举吗?
不过有个坑要注意:单元标识符。虽然TCP里没有从站地址了,但单元标识符还是保留着。为什么?我遇到过一种情况:一台网关设备后面挂了多个串口从站,网关通过单元标识符来区分它们。所以别以为TCP里这个字段没用,它其实是给网关用的。
注意:有些设备把单元标识符固定为0xFF或0x01,这没问题。但如果你做网关开发,这个字段必须认真处理。
4.4 端口502的约定
Modbus TCP默认使用端口502。这是IANA分配的知名端口,就像HTTP用80、HTTPS用443一样。
为什么是502?说实话我也不知道,可能是当年设计者随手选的。但既然成了标准,大家就都遵守了。
实际项目中,我见过两种用法:
- 标准用法:客户端连接从机的502端口
- 非标准用法:有些设备为了安全,把端口改成别的,比如1502、2502等
我个人建议:能用502就用502。因为很多现成的Modbus库和工具都默认502端口,你改了反而麻烦。除非有安全要求,比如不允许外部直接访问502端口,那可以映射到其他端口。
嗯,这里还要注意一点:一个端口只能被一个进程占用。如果你在同一台机器上跑多个Modbus从机,它们不能都用502端口。解决办法是每个从机用不同的端口,或者用虚拟IP地址。
避坑指南:我曾经在项目里同时跑两个从机程序,结果第二个启动时报错“端口被占用”。后来改成端口1502和2502才解决。所以多从机场景下,端口规划要提前想好。
4.5 小结
Modbus TCP其实就是把RTU的帧结构套了个TCP的壳。核心变化就三点:
- 多了7字节的MBAP头
- 去掉了CRC校验
- 用单元标识符代替从站地址
你只要记住这三点,TCP和RTU之间的转换就手到擒来了。下一章我会讲怎么在STM32上实现一个Modbus TCP从机,到时候咱们再细聊。