H3U PLC与上位机ModbusTCP通信测试全解析:从配置到排错
2026/8/31 6:33:20 网站建设 项目流程

简介:本资源是一套面向工业自动化初学者与C#上位机开发者的H3U汇川PLC ModbusTCP通信实战项目,聚焦解决PLC与上位机基于以太网的稳定数据交互问题,适用于远程监控、设备联调及产线数据采集等典型场景。压缩包共71个文件,含15个核心C#源码文件(.cs)、3个可执行程序(.exe)、4个配置文件(.cfg/.ini)、3个解决方案工程文件(.sln/.suo/.csproj)及多个PLC侧配置与编译产物(.prg/.ld/.gdt/.dat等),完整覆盖上位机客户端开发、PLC寄存器映射配置与通信调试全过程,包体仅127KB,轻量易部署。已有2331人学习下载。读者可直接运行VS工程查看ModbusTCP连接初始化、功能码读写(如Holding Register)、异常重连机制及基础UI交互逻辑,代码结构清晰、注释充分,并附带PLC端配置参考(如ModbusConfig.cfg、PortConfig.cfg),便于对照理解协议层与应用层协同要点。 搞过设备联调的工程师都见过这种压缩包:甲方工程师发来的、同事离职交接的、技术群里下载的,文件名称里写满了型号和协议,“h3u-和上位机ModbusTCP通信测试.rar”就是最典型的一例。这名字拆开其实是一句话:用汇川 H3U 系列 PLC 做从站,让上位机通过 ModbusTCP 协议去读写它的寄存器,验证整条链路能不能通。别看只是一个压缩包,它背后涉及的链路非常完整:PLC 侧以太网配置、Modbus 从站映射、上位机侧的调试工具选择、异常码排查,每一步都可能让人卡住。这篇文章就围绕这个测试包展开,把从解压文件到跑通通信、再到处理各种异常的完整过程写清楚,适合刚接触 H3U 和 ModbusTCP 的现场工程师,也适合做上位机开发的兄弟参考。

1. 拿到压缩包先别解压:先摸清这个测试包的构成与版本

1.1 测试包里的典型三层内容

我见过不少类似的测试包,也往外发过不少。这种包通常不是单独一个文件,而是把一整套联调资料压缩在一起,打开后里面基本是三层内容。

第一层是 PLC 工程文件,通常是 InoProShop 的工程目录,里面有.vsp或者一整个工程文件夹。InoProShop 是汇川自家的一体化编程软件,H3U 系列的程序、组态配置、网络配置全在里面。工程文件里一般会包含一个已经配置好的 ModbusTCP 从站设置,还有一小段测试程序,比如把 M0 做成 bool 开关、把 D0 做成计数器或手写值,方便上位机去读。

第二层是上位机示例工程。常见的有 C# WinForm 工程、Qt 工程、或者 Python 脚本,里面封装好了 ModbusTCP 客户端代码。C# 的话大概率会用 HslCommunication 或 NModbus,Qt 则直接用QModbusTcpClient,Python 则用pymodbus。如果包是从某个具体项目里截出来的,还会带上对应上位机软件的依赖库 DLL 或者 lib 文件夹,这一层是很多人打开包之后最关心的,因为可以直接抄。

第三层是说明文档。可能是一份 Word、一个 PDF,也可能是工程截图加注释。有的包里只有一张寄存器映射表截图:哪个 D 区对应 Modbus 哪个地址,哪个 M 区对应线圈的哪个位,参考价值非常高。通信类的联调项目,最终交付不一定靠口头交代,代码和截图才是硬通货。

所以拿到包的第一件事,不是急着打开工程文件,而是先整体解压,确认这三层都在不在,再确认用的软件版本跟自己机器上的能不能对上。

1.2 版本兼容性才是第一道坎

解压之后马上会碰到一个让人头大的问题:InoProShop 的工程版本升级上去了,旧版本打不开新版本。比如工程是用 InoProShop V1.6 建的,你电脑上装的是 V1.0 或者 V1.2,打开时经常提示版本不兼容或者工程损坏。就算只是小版本差异,打开的工程里某些组态页面也可能丢失或显示异常。

处理办法有几个。一是装一个跟工程匹配的版本,这是最稳妥的。二是用 InoProShop 的导入项目功能,把旧版本工程文件用文本方式导入试试,能救一部分配置但容易丢点东西。三是直接看包里的 PLC 源程序,很多测试包为了便于交流,会额外导出一份.txt格式的程序清单,用记事本就能看,便于在没有对应软件版本时先了解框架。

上位机工程的版本兼容性也不容忽视。C# 工程要看目标框架,老项目可能是 .NET Framework 4.0,新机器上装了 .NET 6 或者 7 反而可能跑不起来。Qt 工程要看 Qt 版本,5.15 的工程用 Qt 6 打开,Modbus 模块的接口变化很大,编译报错是家常便饭。Python 脚本则要看pymodbus的版本,2.x 和 3.x 的 API 完全不一样。

1.3 为什么 PLC 项目总爱打 rar

有的朋友可能疑惑,为什么不直接给 Git 仓库或者一个大文件夹,非要打 rar。做 PLC 项目跟做纯软件项目不一样,大部分现场工程师的电脑上不一定配了 Git,有些甲方现场甚至不允许随便联网,所以最原始也是最可靠的交付方式就是打包。RAR 压缩体积小、能带注释、还能加个密码,对现场交接来说很友好。

而且 H3U 的 InoProShop 工程文件包含很多二进制配置文件、缓存文件,用微信或者 QQ 传输的时候,直接传文件夹很容易丢文件或者被加密软件拦掉,打成单个 rar 就稳妥很多。这也是为什么你在网上下载的很多 PLC 资料,包括“h3u-和上位机ModbusTCP通信测试.rar”这种,都是压缩包形式。理解了这一点,也就理解了它的内容组织逻辑。

2. H3U 的 ModbusTCP 到底把数据映射到了哪

2.1 先从协议帧说起:ModbusTCP 和串口 Modbus 的差异

ModbusTCP 的核心其实很简单,就是把传统 Modbus RTU 的报文封装进 TCP 包,通过网口传输。跟串口 RTU 最大的不同有三点:一是没有从站地址的物理概念了,靠 IP 地址来定位设备,报文里的单元标识符(Unit ID)只是逻辑上的从站号;二是没有 CRC 校验,因为 TCP 协议本身保证了传输层的可靠性;三是多了一个 MBAP 报文头,7 个字节,用来标识事务、协议类型和报文长度。

在 H3U 通信测试里,最常打交道的功能码就五个:

功能码含义典型用途
0x01读线圈读 H3U 的 M 区位状态
0x03读保持寄存器读 H3U 的 D 区数据
0x05写单个线圈置位或复位单个 M 点
0x06写单个寄存器写单个 D 寄存器
0x10写多个寄存器批量写 D 区连续地址

测试包里跑的基本就是这五类操作。只要把这些功能码摸透了,ModbusTCP 通信就算掌握了八成。

2.2 H3U 寄存器映射规则:别把地址算错了

H3U 作为 ModbusTCP 从站时,对外暴露的是内部软元件区。最常见的映射关系是:M0 对应线圈区 00001 开始,D0 对应保持寄存器区 40001 开始。注意这里说的“对应”是逻辑上的,并不是说 Modbus 地址 40001 的物理地址就等于 H3U 内部的 D0 地址,中间可能还隔着偏移量。

这就要看 InoProShop 里的 Modbus 映射配置了。H3U 系列在通信配置中,有一块参数是用来设置 Modbus 地址映射的,可以指定哪些软元件区开放给外部读写、起始地址是多少、长度是多少。默认情况下可能是 0 到几百的范围,但如果你在程序里用了很大的 D 区或者 M 区,上位机请求时就要按照映射表的实际范围来。

实际测试时我踩过一个很典型的坑:包里的说明文档写着“D0 对应 40001”,但上位机读 40001 却返回异常码。结果发现是 H3U 工程里把映射起始地址改成了 400101,上位机还在按老地址读。所以在跑通信之前,先到 PLC 工程里把 Modbus 映射表截图存下来,跟包里的文档对上号,这一步能省很多后续排查时间。

2.3 大端与小端:32 位数据必须盯紧字序

Modbus 协议规定寄存器是 16 位大端模式,也就是说一个寄存器的高低字节是按高位在前传输的。但是当上位机要读一个 32 位浮点数或者 32 位整数时,会同时读两个寄存器,这时候两个寄存器的排列顺序就存在两种约定:一种是低位寄存器在前、高位寄存器在后,另一种反过来。H3U 内部对 32 位数据的存储顺序,不同型号、不同固件版本可能不一样。

很多上位机新手在这里崩溃,明明读出来的数据看着是一串乱七八糟的数,比如 PLC 里 D0 和 D1 组成一个 REAL 型数值 100.5,上位机读出来却是 1.9E-36 之类的天文数字。这就是字序没对上。C# HslCommunication 里可以直接指定数据类型,比如ReadFloat支持大小端切换;Qt 的QModbusDataUnit是 16 位为单位的,需要自己做字节拼接。包里的示例代码如果已经跑通了,千万别手痒去改里面的字节序处理逻辑,除非你确定 PLC 侧配置变了。

2.4 端口与单元标识符:看似简单却最容易配错

ModbusTCP 的默认监听端口是 502,H3U 一般也是这样。但有个容易忽略的问题:如果上位机软件跑在 Windows 上,想自己监听 502 端口做模拟从站,需要管理员权限;如果普通权限运行,端口绑定会失败。所以包里的上位机示例如果提供了“模拟从站”功能,通常会让你以管理员身份运行。

单元标识符(Unit ID)在 TCP 模式下一般填 1,少数设备填 0 或者 255。H3U 的默认从站地址通常是可以配置的,在网口通信参数里能看到。上位机填写的 Unit ID 必须和 PLC 侧配置一致,否则设备会直接丢弃报文,表现就是连接正常但读写超时。这个坑极其隐蔽,因为 TCP 连接是通的,ping 也通,就是 Modbus 请求没响应。

3. InoProShop 侧配置:从建工程到把 M 区/D 区暴露给上位机

3.1 新建工程与 CPU 选型要对号入座

打开 InoProShop,新建工程的第一步是选 CPU 型号。H3U 系列型号很多,从 H3U-1614MT 到 H3U-3232MT,不同型号的通信能力和寄存区映射略有区别。测试包里如果带着 PLC 程序,导入工程时软件一般会帮你匹配对应型号,但如果要自己动手从零建工程,选错型号会导致下载失败,而且在通信参数页签里看到的选项也会不一样。

选型时还要注意一个细节:H3U 本体是否带网口。H3U 系列部分型号自带以太网接口,部分不带,需要加扩展模块才支持 ModbusTCP。如果包里的 PLC 型号恰好是带了网口的版本,那直接就可以作为 ModbusTCP 从站用,否则还得考虑扩展模块的配置,整个通信测试的复杂度和场景就不一样了。

3.2 网口参数与 IP 规划:先通网再谈 Modbus

在 InoProShop 的工程树里找到 PLC 的本体设置,进入以太网口配置,能看到 IP 地址、子网掩码、网关这几个参数。现场联调时,我习惯把电脑的 IP 配成192.168.0.100,H3U 的 IP 配成192.168.0.10,子网掩码都是255.255.255.0,网关可以不填。确保上位机和 PLC 在同一个网段,ping 一下能通,再继续后面的配置。

如果现场有很多台设备,IP 规划要提前想好,别随手设成192.168.1.1之类跟别的设备冲突的地址。测试通信能不能通,第一判断标准永远是 ping。很多人一上来就打开上位机软件去连 PLC,连不上就开始怀疑代码,其实多半是 IP 没通。先把 IP 搞定,所有问题能少一半。

3.3 ModbusTCP 从站参数:开启服务并核对端口和站号

H3U 做 ModbusTCP 从站,需要在 PLC 通讯配置里开启对应的服务。不同固件版本的入口位置不太一样,但核心参数就三个:使能开关、端口号、单元 ID。端口默认 502,单元 ID 默认 1,使能开关打开之后,编译下载,PLC 就已经作为一个 ModbusTCP 从站服务器对外监听了。

这里有一个非常实用的小建议:在 PLC 侧把端口号和单元 ID 写死到注释里,并在程序开头做一些初始化,比如把 M0 置 1、把 D0 写一个固定值,作为上位机通信的“心跳标志”。上位机连上之后先读 M0 和 D0,如果读到预期值,说明通信链路、寄存器映射、数据格式全部正确。这个做法可以帮你在联调初期快速把问题定位到某一层。

3.4 写一个最小验证程序:让数据“动”起来

单纯让上位机读一串静止的数据,很难判断通信是否稳定。我建议在测试包的基础上,把 PLC 程序再丰富一点:写一个 100ms 的定时器,让 D0 每次加 1,或者让 M1 按固定频率翻转。上位机读 D0 的时候,如果数值一直在变,就说明不光是寄存器映射正确,而且整个通信链路是实时畅通的。

H3U 的定时器编程很简单,用 InoProShop 里的定时器指令 TON 或者加一个自加指令就行。比如:

// 每 100ms 让 D0 自加 1 IF T1.Q THEN D0 := D0 + 1; T1(IN := FALSE); END_IF; T1(IN := TRUE, PT := T#100MS);

这只是一个示意,实际写在 H3U 里要符合 InoProShop 的语法。关键在于:让数据动起来,上位机那边才能直观地看到实时性,通信测试的价值才会最大化。

3.5 不写 PLC 程序,能不能先测通信?

如果手头没有完整的 PLC 工程,甚至没有测试程序,也可以先测通信。因为 H3U 的部分寄存器区是系统区,比如特殊辅助继电器和特殊数据寄存器,上电后就有实时变化。比如某些特殊寄存器会记录当前时间、系统运行状态,上位机直接去读这些区域,如果数据有变化,一样能验证链路通不通。

但我不推荐长期这样干。因为系统区地址在不同固件版本里定义可能不一样,而且没有经过用户程序的主动控制,读回来的数据很难判断是不是你想要的。测试包里的工程通常已经写好了一套测试点位,直接用它来跑通信才是正路。

4. 上位机连接实测:Modbus Poll 打头阵,代码接线跟上

4.1 链路预检:ping 通是最基本门槛

打开上位机测试软件之前,先在命令行里跑通ping 192.168.0.10。这里我要啰嗦一句,很多工程师直接打开 Modbus Poll,填上 IP 和端口就点确定,然后连接失败,接着就开始各种怀疑,其实第一步应该先确认网络通不通。

如果 ping 不通,检查顺序是:PLC 是否上电且运行、网线是否插好、PC 和 PLC 是否同网段、PC 防火墙是否阻挡了 ICMP。如果 ping 通了但 Modbus 连不上,那问题大概率出在 PLC 侧的服务配置或者 Windows 防火墙拦截了 502 端口。

4.2 Modbus Poll 操作流程:三步读取保持寄存器

Modbus Poll 是调试 ModbusTCP 最顺手的工具。连接设置很简单:“Connection” 里选择 TCP/IP,输入 PLC 的 IP 和端口 502,Slave ID 填 1,接着在“Setup”里选择功能码 03(读保持寄存器),起始地址填 0,数量填 10,轮询周期 1000ms。这时候如果一切正常,右侧表格里就会实时刷新 D0 到 D9 的值。

如果 PLC 程序里 D0 在自加,你会在 Modbus Poll 的界面里看到数值每 100ms 变化一次。这个画面太让人安心了,基本上整个通信链路已经通了。接下来再把 0x01 功能码也测一遍,读几个 M 区的位状态,确认位访问也没问题。

4.3 Python + pymodbus:一条命令完成读写验证

Modbus Poll 能验证链路,但代码才是以后真正要用的。我平时最先用的是 Python,因为它最快。用pymodbus库写一个简单的验证脚本:

from pymodbus.client import ModbusTcpClient client = ModbusTcpClient('192.168.0.10', port=502) if client.connect(): # 读 D0-D9,共 10 个保持寄存器 rr = client.read_holding_registers(0, 10, slave=1) print("D0-D9:", rr.registers) # 写 D0 为 123 client.write_register(0, 123, slave=1) rr = client.read_holding_registers(0, 10, slave=1) print("after write:", rr.registers) client.close()

注意pymodbus2.x 和 3.x 的 API 区别很大。2.x 里用的是ModbusTcpClient('ip', port=502)这种方式,3.x 里构造参数改成了ModbusTcpClient(host='ip', port=502),里面的slave参数在 3.x 里也改成了device_id。所以包里如果是老脚本,直接跑可能报TypeError,这是版本兼容问题,不是通信问题。

4.4 C# 示例:HslCommunication 最省心

C# 做上位机在国内是主流,HslCommunication 则是目前最流行的 Modbus 库之一,简单高效。示例代码可以这样写:

using HslCommunication.ModBus; var client = new ModbusTcpClient("192.168.0.10", 502); client.ConnectServer(); // 读取 D0 的 short 值 var read = client.ReadInt16("D0", 1); if (read.IsSuccess) { Console.WriteLine("D0 = " + read.Content); } // 写 D0 为 456 client.Write("D0", (short)456); client.ConnectClose();

这个库对地址字符串的处理很友好,直接写“D0”就行,它会自动转成对应的 Modbus 地址。这个特点让很多新手避免了一上来就跟 40001 这种偏移地址较劲的麻烦。

4.5 Qt 上位机的典型写法:QModbusTcpClient 的正确打开方式

如果测试包里是 Qt 工程,用的通常是QModbusTcpClient。一个最小可跑的 Qt 客户端写法大致是:

#include <QModbusTcpClient> #include <QModbusDataUnit> QModbusClient* modbus = new QModbusTcpClient(this); modbus->setConnectionParameter(QModbusDevice::NetworkAddressParameter, "192.168.0.10"); modbus->setConnectionParameter(QModbusDevice::NetworkPortParameter, 502); modbus->connectDevice(); // 读取保持寄存器 QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, 0, 10); if (auto* reply = modbus->sendReadRequest(readUnit, 1)) { // 等待 finished 信号后可读取结果 }

Qt 的坑在于它的错误处理是异步的,很多新手写完connectDevice()之后马上发请求,结果主机还在连接过程中,请求直接超时。正确做法是在设备状态变为Connected之后再发请求,或者在请求发出后通过信号槽检查reply->isFinished()reply->error()

5. 异常码 3 与 protocolError:通信测试中最值得记录的坑

5.1 Modbus 异常码到底在说什么

Modbus 从站返回的异常响应帧里带一个异常码,常见的有四个:

异常码名称含义
0x01Illegal Function从站不支持这个功能码
0x02Illegal Data Address请求的地址超出从站范围
0x03Illegal Data Value请求的数据值不合法
0x04Slave Device Failure从站内部故障

测试包里如果出现Exception Response或者protocolError,基本都是这四个里的一个。其中 0x03(异常码 3)出现的频率特别高,值得单独拿出来说。

5.2 异常码 3 的两种典型场景

第一个场景是写寄存器时写入的值超出允许范围。H3U 的 D 区虽然是普通数据寄存器,但有些地址段被系统占用,或者是只读的,上位机去写这些区域时,PLC 会认为数据值非法而返回 0x03。比如某些特殊 D 寄存器,上位机写入 65535 这种超限值也可能被拒绝。

第二个场景是请求长度加上起始地址超过了映射区范围。比如 H3U 工程的 Modbus 映射区只开放了 0-99 这 100 个寄存器,上位机一次性请求读取 0 到 150,超出了映射尾部,有些从站实现会返回 0x02,但 H3U 某些固件版本会把它归类为 0x03。这两个码在排查时非常容易混淆,所以我建议无论如何先核对映射区范围,再核对数据值。

还有一个容易忽略的点:上位机库对异常码的处理方式不同。同一个异常,Modbus Poll 会显示Exception Response,Qt 的QModbusReply只会给你一个ModbusProtocolError,具体异常码要从reply->errorString()或者原始报文里解析。所以看到protocolError别慌,先去抓原始响应帧,确认异常码是几,再针对性地查。

5.3 一个真实的排查链路:能连上但读不到 D0

我拿一个实际案例来完整走一遍。包里的文档写的是“读 D0 就能看到电机转速”,上位机一运行,连接状态正常,但发送读请求后,返回protocolError,异常码 3。

排查第一步,用 Wireshark 或抓包工具看一下 PLC 返回的报文,确认异常码确实是 03。第二步,打开 InoProShop,检查 Modbus 映射表,发现工程配置里映射的起始地址是 100,也就是说 D0 对应的是 Modbus 地址 40101,而不是文档里写的 40001。第三步,把上位机地址改成 40101,再读,数据正常。原因就是中间交接时文档和工程配置不一致。

这个案例说明一个道理:出现异常码 3,先看映射,再看数据值,最后怀疑协议栈。顺序反了会很浪费时间。

5.4 能 ping 通但读写失败:按这个顺序检查

通信测试最让人难受的就是“网络通,请求不通”。我按实战经验排一个检查顺序,大家可以拿去直接用。

第一,检查 PLC 侧 ModbusTCP 从站服务是否真正启用,有些工程配置了但没下载,活在开发环境的组态里,PLC 里根本没生效。第二,检查单元标识符,上位机填的 Unit ID 和 PLC 配置的是否一致。第三,检查 Windows 防火墙是否放行 502 端口,现场电脑装的安全软件也可能拦,建议测试时先临时关一下防火墙。第四,检查请求地址和长度,如果映射范围只有 100 个寄存器,就别一次读 200 个。第五,检查协议类型,确认选的是 ModbusTCP 而不是 RTU Over TCP,这两者在 TCP 层看起来像,报文结构完全不同。

这个顺序按概率从高到低排,我实测下来,前两步解决了绝大多数问题。

5.5 防呆设计:让上位机主动压缩请求范围

既然映射区可能有限,最好的办法就是在上位机代码里把读范围设置为动态可配置的。比如界面里增加“起始地址”和“读取长度”两个输入框,测试时可以先从 10 个寄存器开始读,再扩展到 100 个,逐步试探出实际可用的边界。这比在代码里写死一大片地址然后祈祷 PLC 接受要可靠得多。

Qt 里还要注意一个细节:QModbusDataUnit的地址计数是从 0 开始的,但显示给用户的时候一般要加 40001 偏移。很多新手在这个地方算岔了,读出来全是异常码。在界面上直接显示“D0”或“40001”这种带修饰的地址,内部再转换成从 0 开始的基地址,能减少很多沟通成本和接错概率。

6. 收尾归档:把通信测试包整理成能复用的交接资料

6.1 测试记录要包含什么

通信测试不能“跑通了就算完”,尤其是测试包还会流转到别人手里。我建议每一次联调,都留一份测试记录,包含五个要素:测试时间、设备型号和固件版本、PLC 的 IP 和端口、测试点位和地址、测试结果截图。如果测出过异常码,把异常码和解决办法也写进去。这些记录将来就是排查问题时最重要的参照。

包里的说明文档如果只有寄存器映射表,可以再补一页“联调记录”,把上面的五个要素填好,下一次接手的人能少走很多弯路。

6.2 文档目录怎么组织才不容易乱

我见过很多压缩包,里面是一个“新建文件夹”,再套一个“新建文件夹 (2)”,打开头晕。好的通信测试包,目录结构就三层:

其实很清晰,就是“工程文件 + 代码 + 文档”三块并列,每个目录里放什么一目了然。测试包的根目录再放一个README.txt,三五行字说明 IP、端口、站号、测试步骤就够。

6.3 我在实际传输这些包时的习惯

最后分享几个我的个人习惯。打包前先删除 InoProShop 工程里的缓存目录,比如CacheBackup文件夹,不然压缩包会大得离谱,而且这些文件在别的主机上也不生效。代码工程要清理掉binobj目录,让接手的人自己编译,避免拷过去之后 DLL 版本冲突。

说明书里必须写明软件版本,比如“上位机使用 Visual Studio 2019 + .NET Framework 4.7.2 开发”“PLC 工程由 InoProShop V1.6.2 创建”。这些信息不写,过三个月再打开,你本人都未必记得用的什么版本。

关于 ModbusTCP 通信,我一直有个体会:它之所以能成为工业通信的常青树,不是因为协议多先进,而是因为够简单,简单到任何一门语言、任何一个平台都有现成的库可以用。H3U 作为从站只是它无数应用场景中的一种,但把这一种玩透了,以后再碰到其他品牌的 PLC 接上位机,思路都是一样的:先通网络,再对映射,最后查异常。这套方法论,比任何一个压缩包都值钱。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询