第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的壳。核心变化就三点:

  1. 多了7字节的MBAP头
  2. 去掉了CRC校验
  3. 用单元标识符代替从站地址

你只要记住这三点,TCP和RTU之间的转换就手到擒来了。下一章我会讲怎么在STM32上实现一个Modbus TCP从机,到时候咱们再细聊。