ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Modbus RTU与TCP核心区别:报文帧、实操对比与避坑指南

Modbus RTU与TCP核心区别:报文帧、实操对比与避坑指南 搞工业通讯的迟早要跟 Modbus 打交道。刚入行那会儿我总被一个问题卡住Modbus RTU 和 Modbus TCP看起来都叫 Modbus报文里都有功能码、都有寄存器地址到底差在哪为什么有人说“程序改改就能用”真改起来却又一堆坑后来调了三菱 FX3S 的 485BD、用过 ADPRW 指令读欧姆龙温控器、也在 C# 和 Python 里各写过一版 Modbus 客户端总算把这件事理顺了。这篇文章不打算堆概念直接从“同一套问答、不同的一层”这个角度拆给你看先讲清楚两者的应用层为什么是一套再把 RTU 帧和 TCP 帧逐字节摊开对比接着用三菱 FX3S 485BD 做从站、用 ADPRW 指令做主站最后把串口和 TCP 侧最常见的坑挨个点名。适合刚接触 Modbus 的电气工程师、上位机开发以及被 03 功能码搞到怀疑人生的PLC选手。1. 先搞清楚“同一套问答”指什么1.1 数据模型和功能码两边完全一致很多教程上来就列协议格式结果读者越看越乱。我建议换个角度把 Modbus 想象成一套“车间问答规则”。规则里规定了能问什么、怎么问、怎么答这套规则在 RTU 和 TCP 里是一模一样的。这套规则里有四条“问题类型”对应 Modbus 的四种数据模型线圈Coil位操作可读可写对应 PLC 的 Y 输出、DO 点。离散输入Discrete Input位操作只读对应 PLC 的 X 输入、DI 点。保持寄存器Holding Register16位可读可写对应 PLC 的 D 区、仪表参数区。输入寄存器Input Register16位只读对应模拟量采集值、温度测量值。常用的功能码也就那么几个01 读线圈、02 读离散输入、03 读保持寄存器、04 读输入寄存器、05 写单线圈、06 写单寄存器、15 写多线圈、16 写多寄存器。所以你在上位机里对 Modbus TCP 设备发一条“读保持寄存器”请求和你在串口里对 Modbus RTU 从站发一条同样的请求本质上问的是同一件事把地址 X 开始、长度为 N 的保持寄存器内容返回给我。区别只是这段问答用哪趟车运出去。1.2 真正的分岔口链路、帧结构和校验既然问答内容一样那“差在哪一层”就很好回答了差在协议栈下层的承载方式。Modbus RTU 跑在串行链路上物理层是 RS232/RS485数据链路层就是裸的串口字节流。Modbus TCP 跑在 TCP/IP 网络上物理层是以太网传输层由 TCP 保证可靠传输。你可以把 RTU 想象成在工厂内部用对讲机喊话语气、内容和回复规则都定好了但是声音得靠电波传出去TCP 想象成写挂号信内容可以完全一样但需要套信封、贴邮票、走邮局系统。内容一样但传输体系不一样。两者核心差异我整理成一张表方便对照对比项Modbus RTUModbus TCP物理层RS232 / RS485 串口以太网RJ45/光纤传输层无裸串口字节流TCP端口 502从站地址8 位站号0 为广播单元标识 Unit ID主要用于网关寻址帧头无直接站号开头MBAP 头 7 字节校验CRC16-Modbus帧尾 2 字节无交给 TCP/IP 层广播站号 0 可广播无广播只能点对点典型速率9600/19200 bps10/100/1000 Mbps所以“同一套问答差在哪一层”这句话严格说应该是应用层一样链路层、传输层、帧封装不一样。你做的轮询逻辑、寄存器映射、功能码选择在两边是通用的但你发给设备的字节序列、接线方式、调试工具参数是两边各有各的规矩。明白了这一层后面看报文就不慌了。2. 逐字节拆解同样的03报文两种帧差多少2.1 RTU 帧01 03 00 00 00 02 CRC 那串字节拿最经典的“读保持寄存器”功能码 03 来拆。假设你要读从站地址 1起始寄存器地址 0连续读 2 个寄存器。RTU 请求帧一共 8 个字节01 03 00 00 00 02 CRC_L CRC_H这串字节一路看过去01从站地址。1号从站应答时第 1 字节也必须是01地址对不上就直接丢弃。03功能码。告诉从站“我要读保持寄存器”。00 00起始寄存器地址协议层从 0 数起。00 02寄存器数量注意是“数量”不是“结束地址”。读 2 个保持寄存器就是 4 字节数据。CRC_L CRC_HCRC16-Modbus 校验值低字节在前。从站正常应答01 03 04 12 34 56 78 CRC_L CRC_H其中04是字节数12 34 56 78是两个寄存器的原始值。CRC 校验是 RTU 的灵魂好多初学者栽在这。它用的是 CRC16-Modbus多项式0xA001初值0xFFFF。核心思路是把每个字节和当前 CRC 异或然后按位右移 8 次移出的位为 1 就再异或多项式。用 C 语言写一个查表版核心逻辑大概这样unsigned short modbus_crc(unsigned char *data, unsigned int len) { unsigned short crc 0xFFFF; unsigned int i, j; for (i 0; i len; i) { crc ^ data[i]; for (j 0; j 8; j) { if (crc 0x0001) crc (crc 1) ^ 0xA001; else crc 1; } } return crc; }发送前把 CRC 低字节放前面、高字节放后面接收方对“请求帧去除 CRC 的剩余部分”重新算一次 CRC如果和收到的两个字节完全一致帧才有效。这是 RTU 和 TCP 报文最大的分水岭TCP 没有这个尾校验因为 TCP 层已经帮你做过了。还有一点RTU 协议对帧间隔有要求帧内字节间隔不能超过 1.5 个字符时间帧与帧之间要有 3.5 个字符时间的静默。9600bps 下1 个字符10位约 1.04ms3.5 个字符就是 3.64ms。这决定了你在写串口接收程序时不能用“收一个字节处理一个字节”的思路而是要用定时器做帧超时判断不然一帧数据可能被拆成好几段。2.2 TCP 帧MBAP 头多出来的 4 个字段在干嘛同样的“读保持寄存器”请求Modbus TCP 发出去是 12 字节00 01 00 00 00 06 01 03 00 00 00 02前 7 个字节叫 MBAP 头Modbus Application Protocol Header一个一个拆00 01事务标识 Transaction Identifier。客户端每发一个请求就自增 1用来配对请求和响应。TCP 是长连接允许一问一答错开没有这个编号多任务时根本分不清哪条响应对应哪条请求。00 00协议标识 Protocol Identifier。Modbus 固定为 0表明这是 Modbus 协议。00 06长度字段。指后面数据有多少字节。这里后面是01 03 00 00 00 02正好 6 字节。01单元标识 Unit ID。在纯 TCP 设备上通常填 0 或 1但如果你用网关把 Modbus TCP 转成 Modbus RTU这个字节就对应后面串口上从站地址。之后才是标准的数据段功能码03、起始地址00 00、数量00 02。应答帧也带着同样的 MBAP 头事务标识必须和请求一致长度字段变成00 06加后面数据长度。注意TCP 帧里没有 CRC也没有从站地址帧头因为 TCP/IP 的校验和和端口机制已经接管了这些事。你要做的就是监听 502 端口、解析 MBAP 头然后按应用层规则处理。2.3 寄存器地址从 0 还是从 1 的问题这个问题几乎每个人都会碰一次。协议帧里起始地址是 0 开始的比如上面例子里的00 00。但很多仪表说明书上写的地址是 40001、40002或者直接写“寄存器号 1、2、3”。原因很简单早期 Modbus 把寄存器按功能分成区比如 0 开头是线圈、1 开头是离散输入、3 开头是输入寄存器、4 开头是保持寄存器。Modbus 自己定义的地址是“数据区号 偏移量”所以说明书上的 40001对应协议地址就是 4 区偏移 1实际报文里起始地址填 0。换算口诀协议地址 说明书寄存器号 - 1。比如说明书上写“当前温度寄存器号 42001”你要读它功能码用 03起始地址就填 42001 - 40001 2000转十六进制就是07 D0。不信你抓包看01 03 07 D0 00 01后面再带 CRC和仪表手册对得上。这个坑在接温控器、变频器时特别常见。拿到手册先别急着写地址先搞清楚手册给的是“寄存器编号”还是“协议地址”。很多国产设备说明书直接给十六进制地址前面不加 4 万前缀看起来更方便但实际上还是同一套东西。3. 实操从站怎么跑起来主站怎么开口3.1 工具链三件套Modbus Slave、Modbus Poll、串口/网口调试上手最快的办法是在电脑上先把主站和从站都虚拟出来。Modbus Slave 模拟从站Modbus Poll 模拟主站两个工具配合能把 RTU 和 TCP 的报文看得明明白白。我建议按这个顺序折腾打开 Modbus Slave选 Function 03从站地址 1寄存器区手工填几个值。串口配置9600、8、无校验、1 停止位。打开 Modbus Poll同样选 Function 03从站地址 1把串口配置和 Slave 保持一致。点 ConnectPoll 里如果能刷出 1、2、3 这些值说明 RTU 通了大半。这时再开一个串口监听工具抓出来的报文就是我们上面拆的那 8 个字节。同样的操作用 Modbus Slave 开一个 TCP 从站监听 502 端口再用 Modbus Poll 换 TCP 连接方式去连。对比两次抓包你会直观感受到“RTU 帧和 TCP 帧的差别只在头尾”。这里有个经验Modbus Slave 和 Modbus Poll 是同一家公司出的试用版能用但时不时会弹窗。真需要长期调试建议买正版或者改用开源方案比如 Python 的 pymodbus 库功能完全够用还不用折腾注册事宜。3.2 三菱 FX3S 485BD 做 RTU 从站RS 指令和 CRC 子程序思路接下来说真实的 PLC 场景。三菱 FX3S 要挂 485BD 模块然后用 RS 指令做 Modbus RTU 从站。这里有个关键认知FX3S 的 485BD 本身不内置 Modbus 协议它只是把 CPU 的串口扩展出来剩下的协议解析都得靠程序自己写。所以你要做的其实是“自由协议收发 自定义解析”。第一步设通讯参数。485BD 对应的特殊数据寄存器是 D8120波特率 9600、8 数据位、无校验、1 停止位这个配置组合在 FX3S 里设成H0081具体以三菱手册为准不同固件版本个别位定义可能不同。再设 M8161把数据宽度切到 8 位模式。第二步用 RS 指令接收主站请求。RS 指令的格式是RS D0 M0 D10 M10意思是把接收到的数据存入 D0 开头的缓冲区长度放 M0要发送的数据从 D10 开头取长度放 M10。放在通讯中断里或者用 M8123 接收完成标志轮询。第三步收到请求后先做 CRC 校验再判断从站地址是不是自己然后解析功能码和寄存器地址按请求拼应答帧。从站要处理的典型请求就两种03 读保持寄存器、06 单寄存器写。03 的应答就是“站号 03 数据字节数 数据区 CRC”。CRC 子程序是重头戏。三菱梯形图写循环比较麻烦最实用的做法是把 256 个 CRC 表算好放进 D 区程序里查表计算。也可以用步进指令配合 FOR/NEXT 写按位算法只是循环次数多一些。无论如何CRC 算不对主站会一直报 CRC 错误通讯永远不通。这里要提醒一点如果用 485BD 自带的功能做从站FX3U-485ADP-MB 这种模块是支持硬件 Modbus 从站的只要设好 BFM 寄存器就能用不用自己写协议但 FX3S 的 485BD 属于“纯物理扩展”协议全靠程序两者差别很大选型时先查好手册。3.3 用 ADPRW 指令做主站读 E5CC 温控器的梯形图设计如果你手里已经没有 FX3S 做从站的需求而是想用三菱 PLC 去读别的 Modbus 从站设备最顺手的指令是 ADPRW。以 FX3U-485ADP-MB 配欧姆龙 E5CC 温控器为例整个梯形图的核心逻辑非常清晰。ADPRW 的格式是ADPRW S1 S2 S3 D MS1从站地址1 到 255填H1表示读 1 号从站。S2功能码用H3读保持寄存器、H6写单寄存器。S3寄存器的协议地址。E5CC 的当前温度寄存器号要按说明书查换算成协议地址后填入。D读取到的数据存放位置。M通讯完成标志用它触发下一步动作。梯形图程序设计可以这样走用 M0 作为读取请求触发M0 上升沿执行 ADPRW。ADPRW 参数填好从站号H1、功能码H3、S3 填温度寄存器协议地址、数据存到 D10。用 M 参数比如 M1作为完成标志M1 置 1 后把 D10 里的温度值搬去触摸屏显示区复位 M0、M1开始下一轮读取。如果你要轮询多个温控器建议在两次读操作之间加一个延时比如 100ms避免模块忙不过来。ADPRW 本身只处理一次问答轮询顺序、异常处理都得在梯形图里自己做。实际现场我见过有人把几十台设备全塞在一个 MODBUS 总线上轮询结果周期太长被吐槽“温度显示像慢动作”最后改成 4 个串口分担轮询才解决。这个例子恰好回到文章主题你用 ADPRW 写的业务逻辑跟从站是 RTU 还是 TCP 没关系因为你读的还是“保持寄存器、功能码03、从站地址1”。真正决定通讯方式的是 485ADP-MB 模块和串口不是你的梯形图。这就是“同一套问答”最直接的体现。3.4 Modbus TCP 联调速览TCP 侧的实操比 RTU 简单因为没有波特率、校验位、终端电阻这些物理参数。你只需要知道设备 IP、端口和单元标识。用 Modbus Poll 连接时协议选 Modbus TCP/IP填 IP 和 502 端口Unit ID 按设备说明书填。很多西门子、汇川、台达的 PLC 内置 Modbus TCP 服务器直接把 KeepAlive、端口、单元标识设好就能连。调试时建议开 Wireshark 抓包过滤tcp.port 502你会看到三次握手之后的应用层报文。TCP 和 RTU 最大的体验差异是TCP 允许事务标识错开也就是说可以连续发多个请求不用等上一条响应回来再发下一条吞吐量高得多。同时 TCP 自带重传和确认你不用担心线缆干扰导致 CRC 错帧但这不代表不会出问题下一章细说。4. 工程里最容易踩的坑排查与避坑实录4.1 串口侧没数据、CRC 错、偶尔抽风头号问题完全没数据。先看接线A/B 别接反485 是差分信号A 对 A、B 对 B接反了对方收不到任何数据。其次看共地485 总线建议用双绞线加一根信号地线距离长或现场干扰大时没有地线很容易随机错误。再次看终端电阻总线两端各接一个 120Ω超过 32 台设备或线长超过 100 米尤其重要。二号线问题CRC 校验失败。主站显示接收帧 CRC 错误基本是两种原因从站程序计算方式不对或者发送字节有丢位。如果只有某一个从站 CRC 错重点查这台设备的程序如果整个总线都错先降波特率比如从 19200 降到 9600 试试往往能找到问题。三号问题通讯时好时坏。大概率是地电位差或者线缆走线问题。485 是半双工共享总线设备一多某台设备掉线会把整条总线“拉死”表现为所有设备同时断联。排查时用“二分法”断开一半设备看剩下的是否恢复逐步缩小范围。4.2 数据读到了数值却不对这是最让人头大的情况因为看起来通讯是通的但数值永远是错的。大小端问题。Modbus RTU 和 TCP 的数据字都是高字节在前也就是大端。但很多中小型仪表的寄存器内部可能按小端存储你读到的12 34可能是0x1234也可能是0x3412取决于设备固件。拿到手先用一个已知值验证一下通常温控器设置一个固定温度比对着读到的值就知道要不要交换字节。数据类型的坑。16 位精度够用的场合用有符号/无符号一目了然如果是 32 位浮点或整数占用两个连续寄存器这时不仅要考虑字节序还要考虑寄存器序常见两种高寄存器在前和低寄存器在前。不同品牌 PLC 的处理方式还不一样上位机写程序时一定要做成可配置项别写死。负值问题。温度零下时读到的是补码比如-10会变成FF F6。C# 里直接把 ushort 强转 short 就能恢复负号C 里同样要做有符号转换。如果直接当成无符号整数去算零下十度会变成 65526你会在现场怀疑人生。4.3 TCP 侧连得上没应答、收不全、粘包TCP 的问题集中在三块。第一块502 端口被防火墙拦。现象是 Ping 得通Modbus Poll 却连接超时。现场第一反应一般是关防火墙或加白名单但生产环境不可能乱关所以别怀疑设备先 telnet 一下telnet 设备IP 502通不通一目了然。第二块单元标识没对上。很多人在纯 Modbus TCP 设备上随便填了个 Unit ID比如 0设备没响应就开始怀疑 IP 配置。其实纯 TCP 设备通常忽略 Unit ID 或者固定要求某一值但通过网关转 RTU 时网关会把 Unit ID 映射成串口从站地址。上位机填错了网关找不到从站自然没应答。排查方法很笨但有效先用厂商自带调试软件跑通看它填的 Unit ID 是多少。第三块粘包和半包。C 或 C# 写 TCP 客户端时不能假定“一次 recv 就是一帧完整报文”。Modbus TCP 帧很短但内核缓冲可能把两条请求并在一起发也可能一条响应被拆成两段。解决办法是按 MBAP 头里的长度字段解析先收满 7 字节头读出长度再等够剩余字节。这是基本功但网上求助里半年能见到一波因为没做这个而出 bug 的人。另外多事务标识并发的程序里响应不一定是按请求顺序返回的你不能简单地“发一条、收一条、对上位置就行”。正确做法是把请求的事务 ID 记在队列里响应到了之后用响应里的事务 ID 找匹配的请求。这在做高吞吐采集器时特别重要别问我怎么知道的。4.4 工具使用彩蛋最后说点工具层面的私货。Modbus Poll 连接成功后在底部状态栏能看到当前帧的响应时间这个数值对判断现场网络质量很有用。如果响应时间忽高忽低多半不是 Modbus 的问题而是交换机拥塞或串口被干扰。如果看到 CRC 错误计数不断上涨九成是物理层有问题。用 Modbus Slave 做从站模拟时可以勾选多个 ID 同时监听方便你测主站轮询逻辑。你甚至可以用两个 Modbus Slave 实例一个用 RTU 串口一个用 TCP 502然后用同一个 Poll 配置去连直观比较两种模式的报文差异。这个方法非常适合培训新人我每次带人入门都这么干十分钟就能让他们把“同一个问答、不同分层”记到骨子里。我个人在实际项目里更偏爱 TCP 做采集、RTU 做就地控制这样的组合控制回路必须线缆稳定、时延可预测串口 RTU 反而靠谱跨车间、跨楼栋的数据采集走 Modbus TCP 方便组网。等这两条路都跑通一遍再回头看标题那句话你会发现它其实是最好的项目注脚协议决定你问什么链路决定你多快得到答案。
返回列表