ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

汇川AM402与H3U基于ModbusTCP的C#上位机通讯实战解析

汇川AM402与H3U基于ModbusTCP的C#上位机通讯实战解析 做工业上位机这些年ModbusTCP 一直是我绕不开的话题。标题里“ModebusTcp”这个写法我在各种搜索记录里见过不少次先说明一下规范写法是 ModbusTCP下文统一用这个。今天想聊的这套组合——汇川 AM402、H3U 和 C# 上位机走 ModbusTCP 通讯是我在一条小型自动化产线上实际用过的方案。它不算高深但踩坑点不少尤其是 AM402 这种走 EtherCAT 的总线型 PLC很多人以为它只能用专用协议其实它的以太网口天然支持 ModbusTCP 从站配合 C# 写一个上位机采集和下发参数非常顺手。这篇文章适合刚接触汇川 PLC 和上位机通讯的工程师也适合那些已经有了一定 PLC 基础、但第一次动手写 C# 上位机的人。我会把 PLC 侧的参数规划、C# 侧的代码实现、地址映射方式、字节序问题以及我在调试现场遇到过的各种怪毛病全部写出来。都是实际跑过的办法可以直接抄作业的那种。1. 先想清楚为什么用 ModbusTCP 连这两台 PLC很多新手拿到 AM402 和 H3U 之后第一反应是“这俩不都是汇川的 PLC 吗能不能直接走总线互通”。能但场合不对。AM402 是 AM 系列的高性能运动控制器走 EtherCAT 总线带伺服是它的主场H3U 则是中小型逻辑控制 PLC适合做 IO 控制、气缸电磁阀、简单工艺逻辑。设备和设备之间需要交互数据可以用 EtherCAT 的从站通讯也可以走 ModbusTCP但这个方案里真正要处理的是“上位机”和“这两台 PLC”之间的关系不是 PLC 和 PLC 之间的关系。1.1 设备定位AM402 管运动H3U 管逻辑生产线上的分工通常是这样的AM402 负责多轴插补、电子凸轮、点位运动这类对时序要求高的任务它作为 EtherCAT 主站带着一堆伺服跑H3U 则守在设备旁边处理传感器信号、气缸动作、气动阀岛还有各种手动/自动逻辑。站在上位机的角度看我根本不关心你内部怎么跑运动轨迹我只知道设备状态在哪个寄存器里产量数在哪个寄存器里配方参数该往哪个寄存器里写。所以通讯架构就清楚了C# 上位机是 ModbusTCP 客户端AM402 和 H3U 分别是两个独立的 ModbusTCP 服务端。上位机按需去轮询两台 PLC把数据读到界面上或者把用户的设置项写到 PLC 里去。这套架构最大的好处是解耦。两台 PLC 的固件版本、内部程序随便改只要把 Modbus 寄存器映射表维护好上位机这边基本不用动。我之前接过一个项目现场临时把 H3U 换成 H5U上位机代码就只改了一个 IP 地址其余全部保留这就是标准化通讯协议带来的收益。1.2 通讯协议取舍ModbusTCP 比 OPC 和 EtherCAT 更适合谁AM402 和上位机通讯还有别的选择。比如 AM 系列支持 OPC UAH3U 也能通过网关走 OPC再比如 AM402 的 EtherCAT 本身是实时总线想读数据似乎更“直接”。但我最终选了 ModbusTCP原因很现实OPC UA 功能强大但配置繁琐。你要在 PLC 侧建信息模型上位机侧装 OPC 客户端还要解决 DCOM、证书、用户认证这一堆东西。产线维护工程师听到“OPC”两个字就开始头大。EtherCAT 是主站和从站之间的实时总线上位机如果要直接读 EtherCAT 从站状态通常还得借助 AM402 的 ADS 或者第三方网关方案复杂度上去了实时性却没有质变因为上位机所在的网络和运动控制网络最好还是物理隔离。ModbusTCP 是基于 TCP/IP 的明文协议报文结构极其简单调试工具也多任何一个网络抓包工具都能直接看到数据。C# 里用现成库十几行代码就能跑通。如果你的系统里有几十台设备、几百个变量对数据刷新率和点位管理要求特别高那值得考虑 OPC UA。但对大多数单机设备、中小产线ModbusTCP 是性价比最高的方式——简单、稳定、跨语言、跨平台PLC 侧和上位机侧都不需要额外授权。1.3 主从拓扑上位机做客户端两台 PLC 做服务器既然定了 ModbusTCP主从关系就得说死。ModbusTCP 协议里主动发起请求的一方叫客户端被动等待请求并响应的一方叫服务端。在这个方案里C# 上位机 客户端Master主动连接、主动读、主动写AM402 服务端Slave监听 502 端口响应请求H3U 服务端Slave监听 502 端口响应请求所以 PLC 侧不用写“主动往上位机发送数据”的程序只要把 ModbusTCP 服务端功能打开把要暴露的数据整理到指定寄存器区静等上位机来读就行。这个思路一定要先建立起来否则你会在 PLC 里写一堆“发送数据”的逻辑完全走偏方向。2. PLC 端准备先把从站地址和数据区规划好通讯就像两个人打电话光有协议还不行两边得知道对方的号码。PLC 侧如果配置不对C# 写多少行代码都白搭。我习惯先做 PLC 侧的准备把 IP、端口、从站参数、寄存器映射表全部定下来然后再动手写上位机。2.1 IP 规划与接线从源头减少网络雷ModbusTCP 走的是标准以太网所以 IP 规划是第一件要命的事。我的建议是单独拉一张工业以太网不要让 PLC 和办公网混在一起。AM402 默认 IP 一般是 192.168.0.10具体看固件和上次保存的配置H3U 的以太网口默认 IP 是 192.168.0.11 这类地址也取决于程序上位机电脑的网卡 IP 要固定不要用 DHCP建议设为 192.168.0.100 或同网段其他地址接线方面如果只是两台 PLC 和一台电脑一个小交换机就能搞定。注意网线质量工业现场一定要用带屏蔽的六类网线水晶头压接可靠别用那种几块钱一根的办公网线。我遇到过反复通讯超时最后发现是网线内部有一根线接触不良换了网线之后问题瞬间消失。这里有一个重要建议把每台 PLC 的 IP、子网掩码、MAC 地址记录到项目调试记录表里。别看这个动作小后期维护的时候能省你半天时间。很多现场故障排查到最后都变成了“确认设备 IP 到底是什么”。2.2 H3U 的 ModbusTCP 从站参数配置H3U 的以太网口支持 ModbusTCP 从站功能。我通常是在汇川的编程软件InoProShop 或者 AutoShop看具体型号里找到以太网配置参数把 ModbusTCP 从站功能使能。需要重点关注下面几个参数从站使能开启本地端口默认 502没有特殊原因不要改从站地址也就是单元标识符Unit ID一般填 1但这个值在 ModbusTCP 里很多从站会忽略最好保持一致避免某些库做校验时出错保持寄存器映射区H3U 内部默认把 D 寄存器映射到 Modbus 4x 区H3U 的 D 寄存器是 16 位保持寄存器范围很大足够我们用来传递数据。假设我们采集设备状态、产量、报警代码就直接把 PLC 程序里对应的变量传到 D 区。严谨的做法是在 PLC 程序里单独划分一段 D 区作为“通讯映射区”比如 D1000 到 D1100 专门给上位机读写程序里所有需要和上位机交互的数据都汇总到这里。这样上位机只需要读写这一段连续地址不需要关心 PLC 内部逻辑有多复杂。2.3 AM402 的 ModbusTCP 服务器配置和 %MW 区规划AM402 这边的配置稍有不同。AM 系列内置的内存区是 %MW保持寄存器、%MX位区、%MD双字区等支持 ModbusTCP 从站。在 AM402 的组态软件里同样把 ModbusTCP 服务端打开端口默认 502。这里要注意AM402 毕竟是运动控制器它同时还要跑 EtherCAT 总线。如果你把 EtherCAT 网络和上位机网络接到同一个交换机上虽然理论上能通但我建议尽量把“运动控制网”和“上位机通讯网”分开。AM402 一般不止一个网口一个网口走 EtherCAT 总线到伺服另一个网口走 ModbusTCP 给上位机物理隔离互不干扰。AM402 的数据区规划推荐用 %MW 区因为 Modbus 的 4x 区正好映射到 %MW。%MW0 对应第一个保持寄存器%MW1 对应第二个以此类推。如果要在 C# 里读浮点数、双字就涉及连续两个 %MW 的组合后面我会详细讲。2.4 寄存器地址映射D 和 %MW 怎么对应到 4x 区通讯协议说到底就是“寄存器地图”这张表必须在一开始就定好并且要发给所有相关的人。下面是这个项目里我用过的映射表模板设备PLC 内部软元件Modbus 地址4x 区数据长度说明H3UD10004000116bit设备状态字H3UD10014000216bit当前产量H3UD1002-D100340003-4000432bit运行时间秒数H3UD1010-D101940011-4002032bit x5温度参数浮点AM402%MW04000116bit轴状态字AM402%MW14000216bit报警代码AM402%MW2-%MW340003-4000432bit当前位置脉冲AM402%MW10-%MW1940011-4002032bit x5速度/加速度参数浮点注意H3U 和 AM402 虽然都支持 ModbusTCP但地址映射逻辑要从各自的用户手册里确认。比如 H3U 里 D0 具体对应 Modbus 的哪个地址、AM402 里 %MW 的双字排列是高字在前还是低字在前这些细节不同固件版本可能有差异我的习惯是开工前先做一次简单的读写测试确认映射关系正确再大规模写代码。3. C# 上位机核心实现连接、读写、数据转换PLC 侧准备好了接下来写 C# 上位机。这是很多电气工程师觉得头疼的部分但实际上用对工具之后代码量并不多。核心就是三块建立连接、按地址读写、把原始寄存器数据转成我们需要的类型。3.1 开发环境选型与 VS 版本兼容性说明C# 上位机的开发工具基本就是 Visual Studio。我用的版本是 VS2019这个版本成熟稳定网上资料也多。有人会问 VS2019 开发的 C# 上位机程序能不能用 VS2015 打开这个我也遇到过——答案是看项目格式。如果项目是传统 .NET Framework 格式也就是项目文件里能看到大量的Reference和DefaultNamespace之类节点VS2019 打开保存后 VS2015 通常还能打开前提是 .NET Framework 版本没有超。如果项目是 SDK 风格的新格式 csproj比如net472或netcoreapp这类简化配置VS2015 是打不开的必须用 VS2017 及以上。所以如果现场环境比较老旧只有 VS2015你在建项目的时候就要故意选“.NET Framework”模板不要选“.NET Core”或“.NET 5”。另外 NuGet 包也要注意有些新版本库不再支持 .NET Framework装的时候会自动降级或报错。我的建议是能上网就上网不能上网就把 HslCommunication 这类库的 DLL 直接放到项目里引用省得折腾。3.2 用 HslCommunication 快速建立 ModbusTCP 客户端做 C# 上位机通讯我主力用的库是 HslCommunication。这是一个开源通讯库对汇川、三菱、西门子、欧姆龙等主流 PLC 支持得都很好ModbusTCP 客户端封装得也很简洁。你先在 NuGet 里搜索 HslCommunication安装到项目里。然后建立一个连接对象using HslCommunication; using HslCommunication.ModBus; ModbusTcpClient am402 new ModbusTcpClient(192.168.0.10, 502); ModbusTcpClient h3u new ModbusTcpClient(192.168.0.11, 502); OperateResult connectAm am402.ConnectServer(); OperateResult connectH3u h3u.ConnectServer(); if (connectAm.IsSuccess connectH3u.IsSuccess) { // 连接成功 } else { // 弹窗提示失败 }这里有几个细节ConnectServer做的是 TCP 连接。ModbusTCP 底层是 TCP所以如果 PLC IP 不通这一步就会失败。ModbusTCP 客户端只需要一个 TCP 长连接不需要每次读写都重新建立连接。重连反而容易触发 PLC 侧连接数限制。在界面关闭的时候记得调用am402.ConnectClose()和h3u.ConnectClose()优雅释放资源。如果你不想引入第三方库自己用System.Net.Sockets.TcpClient也是可以实现的ModbusTCP 的报文格式并不复杂无非是 MBAP 报文头加功能码加数据区。但自己做一遍工作量不大只要你对协议不熟就特别容易在字节序上翻车。有现成、稳定、兼容性好的库我建议直接用把精力花在业务逻辑上。3.3 批量读写保持寄存器与数据类型转换HslCommunication 的读写方法名字非常好记。读保持寄存器用ReadInt16写保持寄存器用Write还支持直接读浮点、读双字。读取 H3U 的产量D1000 对应 40001// 读 H3U 设备状态字 OperateResultshort status h3u.ReadInt16(40001); if (status.IsSuccess) { int deviceStatus status.Content; label1.Text $设备状态{deviceStatus}; }读取 AM402 的位置脉冲%MW2-%MW3 对应 40003-40004需要读两个连续寄存器再组合成 32 位OperateResultint position am402.ReadInt32(40003); if (position.IsSuccess) { int pulse position.Content; }写参数也一样// 把配方温度写入 H3U D1010对应 40011 float targetTemp 85.5f; OperateResult writeTemp h3u.Write(40011, targetTemp);读浮点、写浮点HslCommunication 已经帮你把两个 16 位寄存器合并成 IEEE754 浮点数了省了很多事。但我要特别提醒一个点字节顺序字序问题。汇川 PLC 和 Modbus 协议通常使用大端模式也就是高字节在前。但有些 PLC 内部存储 32 位数据时是低字在前这就导致你直接读到的浮点数可能完全不对。如果遇到读出来的浮点数明显不合理优先怀疑字序反了。HslCommunication 提供了一组带 Reverse 后缀的方法比如ReadFloatReverse你可以试一下通常能解决问题。3.4 多线程定时轮询的正确姿势上位机不可能只读一个地址就完了实际项目里都是定时批量读。很多新手会把读写操作直接放在Timer事件里结果界面偶发卡死或者发送频率太高把 PLC 拖垮。正确的姿势是用一个独立的后台线程做轮询每次读取一个连续的寄存器块把结果更新到公共变量里UI 线程只负责显示。伪代码大概这样private async Task PollPlcDataAsync() { while (_isRunning) { OperateResultshort[] h3uBlock h3u.ReadInt16(40001, 20); if (h3uBlock.IsSuccess) { // 更新公共变量 DeviceStatus h3uBlock.Content[0]; ProductionCount h3uBlock.Content[1]; // ... } OperateResultshort[] amBlock am402.ReadInt16(40001, 20); if (amBlock.IsSuccess) { // 更新 AM402 相关变量 } await Task.Delay(200); // 轮询间隔 } }关于轮询间隔我建议保守一点200ms 到 500ms 就足够绝大多数设备监控界面使用了。不要用 10ms、20ms 这种频率因为一方面 ModbusTCP 本身不是实时协议另一方面 PLC 侧每秒处理几百个 Modbus 请求也容易增加负荷一旦网络抖动上位机就会收到大量超时报错。4. 调试实录我踩过的坑和排查方法调试 ModbusTCP 通讯核心思路就一句话先确认网络通再确认协议对最后确认数据合理。这句话说起来简单但现场总会冒出各种意想不到的问题。4.1 连不上 PLC 时的三步定位法我用过太多方法之后总结出一个固定流程遇到连接不上就按这个顺序排查。第一步先用 CMD 的 ping 命令试网络通不通。如果 ping 不通那就是 IP 配置、网线、交换机的问题。这里注意有些 PLC 对 ping 有响应策略不一定能 ping 通但不代表 TCP 不通所以 ping 不通只能说明网络不可达ping 通了则说明链路基本没问题。第二步用 TCP 连接工具直接测 502 端口。Windows 下可以用Test-NetConnection 192.168.0.10 -Port 502或者用一个小工具 TestTCPConnection。如果端口连不上问题大概率在 PLC 侧服务端没有启用或者防火墙上没放行 502 端口。如果是自己的电脑把防火墙给 PLC 通讯程序添加一个入站规则放行 TCP 502 出站和入站。第三步用 ModbusPoll 或者 Modbus Scan 这一类的调试软件先连一次。这个特别重要它能把“上位机程序的问题”和“PLC 配置的问题”切分开。ModbusPoll 能连上并读到数据说明 PLC 侧配置好、协议没问题剩下的就是你 C# 代码的问题如果 ModbusPoll 都读不了那就回到 PLC 侧查配置别跟自己的代码较劲。4.2 读回来的数据全是 0 或乱码有时候连接正常读回来的地址也有响应但数据要么全 0要么完全不符合预期。这种问题基本集中在两个原因地址映射不对、数据类型解析不对。先说地址映射。H3U 的 D1000 如果你用库里的 “40001” 去读到底对不对这个要查手册确认。Modbus 协议层面的地址是从 0 开始的比如功能码 03 读保持寄存器时请求报文里第一个寄存器的地址如果填 0x0000那对应的是 4x 区里的 40001。HslCommunication 里写 “40001”它内部会转换成协议地址 0写 “40002” 就转换成协议地址 1。如果你的 PLC 内部把 D0 映射到了协议地址 0那 D1000 不一定映射到 40001而是映射到 41001 这样的地址。这类坑非常常见务必先看手册里的地址映射表。再说数据类型解析。Modbus 寄存器只有一种类型就是 16 位无符号/有符号整数。浮点数、32 位整数、字符串全部要靠多个寄存器组合出来。16 位有符号直接读注意 PLC 的负数形式无符号有符号的转换要小心32 位整数/浮点数需要相邻两个 16 位寄存器注意高字低字顺序布尔量一般按位处理比如状态字 D1000 的低 8 位代表 8 个不同的 IO 状态用位运算解析我在一个项目里就因为在 AM402 上把一个%MD100当成两个独立的%MW100和%MW101结果读出来的位置数据完全对不上。后来用%MW100和%MW101分别打印发现高低字顺序和我以为的相反调整解析顺序后一切正常。这种问题没法靠猜只能实测。4.3 并发访问与连接数爆满导致掉线很多上位机程序写到最后会加入多个窗口、多个定时器结果每个窗口都去 new 一个 ModbusTcpClient 连接对象导致 PLC 侧连接数被打满。汇川 PLC 的 ModbusTCP 服务端对并发连接数通常有限制比如最多支持 4 个或者 8 个连接一旦超限新的连接就建立不上去甚至会挤掉旧连接造成“连接成功一瞬间下一秒断开”的现象。解决办法是全局共用一个连接对象所有读写操作通过一个队列或者加锁串行化。private static readonly object ModbusLock new object(); private void SafeRead() { lock (ModbusLock) { OperateResultshort[] result h3u.ReadInt16(40001, 20); // 处理结果 } }我采用的方式是在通讯类内部用一个锁对象包住所有读写方法保证同一时间只有一个 Modbus 请求在链路上发送。ModbusTCP 本身就是请求-响应式协议同一时刻不能并行发多个请求强行并行只会乱套。4.4 性能优化批量读取代替代逐条读如果一条一条 ReadInt16 去读 200 个寄存器每读一次就一个 TCP 请求-响应循环非常浪费。Modbus 协议里 03 功能码本来就可以一次读多个连续寄存器最多可以一次读 125 个。所以我的习惯绝对是用批量读。比如要读 H3U 的 D1000-D1050D1100-D1150那就不必分成 100 次直接两次ReadInt16(40001, 51)和ReadInt16(40101, 51)把数据一次性拿回来然后自己解析。批量读还有一个好处就是能保证数据的“一致性”。如果你分两次读两个寄存器中间 PLC 程序可能已经把数据改掉了你拿到的两个值根本不是同一个时刻的数据。对于设备状态、位置这类需要保持同步的信息分批读会有概率读到“新旧状态混合”的数据对业务逻辑造成隐患。5. 一个小结这套通讯方案还能怎么扩展最后说说这套方案在后期的扩展空间。很多项目第一步只是数据采集和参数下发跑通之后需求就来了。一个很常见的扩展是把 AM402 的实时位置、速度数据通过 ModbusTCP 传到上位机做曲线显示这时候你要注意批量读 20 个寄存器一次速度数据的刷新率做到 100ms 就差不多了再快就该上专业实时通讯方案ModbusTCP 不是干这个的。另一个扩展是数据上云。上位机拿到 PLC 数据之后可以直接写入 SQL Server或者通过 MQTT 协议转发到云平台。因为我用 C#所以整个链条非常顺ModbusTCP 读数据内部转成业务对象再通过其他库写到数据库或消息队列。还有一点很关键就是地址映射表的维护。我强烈建议你在 C# 工程里单独建一个静态类把所有寄存器地址定义成常量或者枚举不要把 40001 这种裸数字散落在各种业务代码里。以后 PLC 程序调整地址映射你只需要改这个静态类其余代码全部不用动。我在实际项目里做这一套时最大的体感是ModbusTCP 这种“老掉牙”的协议反而比很多新协议省心。上手快、调试工具多、资料丰富PLC 和上位机两边都能看得懂报文。只要把地址映射表整理清楚把字节序问题提前验证掉这套方案在十年内都不会过时。
返回列表