简介:一套基于C# WPF的PLC通信测试工程,通过MxComponent组件实现与三菱PLC的数据交互,专为工控上位机开发新手及具备一定经验的工程人员设计,可帮助快速掌握现场常用的MX链接配置、数据读写与界面联动方法。压缩包共40个文件,大小约71KB,包含12个cs源文件、xaml界面布局、config运行参数、dll依赖库、pdb调试符号及exe可执行程序,同时附带sln工程文件与suo配置信息,项目结构完整清晰,便于直接运行或二次开发。目前已有220人学习,资源源自现场测试项目,实测可用,覆盖了从组件初始化、连接建立到读写寄存器、前端展示的完整实现流程。读者可借助其窗口代码、资源配置和项目目录结构,理解MxComponent的调用方式与常见参数设置,获得可直接落地的通信范例,对入门三菱PLC上位机开发很有参考价值。
1. C# 通过MX链接三菱PLC测试源代码:从找代码到跑通只差这三个门槛
接到产线设备数据采集的活,第一反应往往是搜现成的 C# 通过MX链接三菱PLC测试源代码。等你真把它下载下来,却发现连不上、读不到、一读写就报错,问题多半不在代码本身,而在你没搞清楚 MX 到底是什么、逻辑站号配在哪、DLL 位数对不对。这套方案的价值在于:让你不用去啃三菱 PLC 通讯协议的一帧一字节,也能在半小时内把“上位机读 D 寄存器、写 M 软元件”跑通。适合刚接手三菱 PLC 上位机开发、或者正在评估通信方案的 C# 工程师。
2. 先选对 MX 组件和版本:决定你后面少踩一半坑
2.1 MX 到底是什么,和直接用 MC 协议拼 Socket 有什么差别
标题里的 MX,指的是三菱 MELSEC 通信中间件 MX Component,而不是某个 Linux 发行版。它的作用是替你把复杂的 MC 协议封装成更简单的 API 调用。你在 C# 里只需要写GetDevice("D100", out value),MX 会自动把这句话翻译成请求帧,通过以太网或串口发给 PLC,再解析响应帧。
有人会问:那我直接用 Socket 连 PLC 的 5000 端口,自己拼帧不行吗?不是不行,但你要处理的细节会多很多。三菱 PLC 通讯协议详解里光帧格式就有 ASCII 帧和二进制帧两种,软元件编号要按 CPU 类型编码,监视定时器、子命令、结束代码都得自己拼。一旦 PLC 型号换一代,地址编码规则可能跟着变。而 MX Component 把这些差异全挡在中间件里,测试源代码换台 PLC,改个逻辑站号配置就能继续跑。
我一般建议项目里 C# 上位机连三菱 PLC 时,优先用 MX Component。它自己带一个 Communication Setting Utility,专门用来配置逻辑站号,把“PLC 的 IP、端口、协议类型”和“上位机侧的逻辑站号”绑定在一起。代码里只需要指定逻辑站号,不用在程序里硬编码 IP 和端口。这样换设备时,改配置工具就行,不用重新编译上位机。
2.2 引用 MX 的 DLL:注册、位数、与 C# 工程目标平台匹配
MX Component 装好之后,C# 工程一般通过 COM 组件引用 ActUtlTypeLib,对应的 DLL 是 ActUtlType.dll。不同版本还可能提供 MelfasMX.dll 这类更新接口,但 ActUtlType 这套接口最老也最通用,网上能找到的三菱 PLC 项目案例实例大多基于它。
先确认 DLL 有没有注册成功。打开命令行,用 regsvr32 注册或检查:
rem 以管理员身份运行,路径换成你本机的实际安装路径 regsvr32 "C:\Program Files (x86)\MELSEC\MX Component\ActUtlType.dll" rem 查询当前 DLL 的位数,确认是 32 位还是 64 位 corflags "C:\Program Files (x86)\MELSEC\MX Component\ActUtlType.dll"注册成功后,在 Visual Studio 里对这个工程添加引用,选 COM 选项卡,找到 MELSEC ActUtlType Lib 或类似名称的组件。这里最容易被绊倒的是目标平台位数:ActUtlType.dll 很多是 32 位组件,你却把 C# 工程设成了 AnyCPU 或 x64,一运行就报 BadImageFormatException,或者干脆“未能加载文件或程序集”。这个现象非常玄学,因为它编译能通过,运行才炸。
所以我的建议是:C# 上位机工程直接选 x86 平台。哪怕你的上位机跑在 64 位 Windows 上,32 位进程访问 32 位 DLL 也是最省心的组合。真要用 64 位,就需要确认你的 MX Component 版本提供了 64 位运行库,并且引用的是 64 位对应的 DLL,否则后面调试会花掉你好几个小时。
3. 用 C# 把 MX 测试源代码跑起来:连接、读、写、断开一个不落
3.1 最小 C# 实例化 ActUtlType 并建立 PLC 连接
写测试源代码时,我习惯把第一步做得很小:只做连接、读一个 D 寄存器、断开。这三个动作能通过,说明 MX 配置、DLL 引用、PLC 网络三层链路都是通的。看代码:
using System; using ActUtlTypeLib; class MxLinkTest { static ActUtlType plc; static int ret; static void Main() { // 1. 创建 ActUtlType 对象,逻辑站号填 0 // 0 对应 Communication Setting Utility 里配置的站号 plc = new ActUtlType(); plc.ActLogicalStationNumber = 0; // 2. Open 返回值 0 表示连接成功,负数是错误码 ret = plc.Open(); if (ret != 0) { Console.WriteLine("连接失败,错误码: " + ret); return; } Console.WriteLine("连接成功,PLC 已通信"); // 3. 读取 D100 寄存器的值,本例是 16 位整数 int d100 = 0; ret = plc.GetDevice("D100", out d100); if (ret == 0) Console.WriteLine("D100 = " + d100); else Console.WriteLine("读取 D100 失败,错误码: " + ret); // 4. 断开连接,程序退出前记得 Close plc.Close(); } }这段代码有一个关键约定:Open()返回 0 才是成功,负数错误码不是异常,而是 MX 的错误编号。常见的 -16 表示通信超时,-17 表示目标设备无响应,这类错误码在 MX Component 的帮助文档里有表可查,也可以调用ActUtlType.ActErrorCodeToString()转成文字。把错误码转换成可读信息这点,在正式上位机里尤其重要,不然现场调试时,光一个“连接失败 -16”根本看不出是 IP 不通还是 PLC 没开机。
ActLogicalStationNumber是代码里唯一和外部配置相关的参数。我见过不少测试代码把 IP 直接塞进构造方法里,但 ActUtlType 的经典做法就是只认逻辑站号。这个数字必须和 Communication Setting Utility 里某个站号一致,对应关系由工具维护。所以跑测试代码前,先打开配置工具确认站号 0 指向的是不是你手头那台 PLC 的 IP。
3.2 读写 D 寄存器、M 软元件与字符串的 C# 代码写法
连接通了之后,读写操作基本是同一套路。D 寄存器是数据寄存器,存数值;M 是内部继电器,存开关量;X/Y 是输入输出点,用十六进制编号。下面这段代码演示了这几类软元件的读写:
// 写 M0 为 ON(1),M 的位号是十进制,不要写成 M0x ret = plc.SetDevice("M0", 1); // 读 X0 输入点状态,X/Y 的编号按十六进制理解,X10 是第 17 个输入点 int x0 = 0; ret = plc.GetDevice("X0", out x0); // 读 D200 的字符串,MX 会按字数据组装字符串 string str = ""; ret = plc.GetDevice2("D200", out str); // 批量读取 D300 开始的 16 个连续寄存器 int[] buffer = new int[16]; ret = plc.ReadDeviceBlock("D300", 16, out buffer); // 批量写入 D400 开始的 4 个寄存器 int[] data = new int[4] { 100, 200, 300, 400 }; ret = plc.WriteDeviceBlock("D400", 4, data);要注意SetDevice写位的用法:M0、X0 这类位软元件传 1 就是 ON,传 0 就是 OFF。D 寄存器用GetDevice/SetDevice操作时走的是 16 位整数通道,超出 -32768 到 32767 的值要小心溢出,需要使用 32 位接口GetDevice2或分两次读写。
字符串读取用GetDevice2最容易踩坑的地方是编码。MX 默认把 D 寄存器里的数据按字拼接成字符串,PLC 侧如果存的是 ASCII 码,通常能直接读出来;如果存的是 UTF-16 或 Shift-JIS,读出来就是乱码。遇到这种场景,我一般不用GetDevice2,而是用ReadDeviceBlock把原始字数据拉回来,自己在 C# 里按约定编码转,可控性高得多。测试源代码里只验证简单 ASCII 字符串没问题,但正式项目建议直接走批量读再解析。
4. 通信设置里的参数边界:IP、端口、逻辑站号与帧格式
4.1 逻辑站号配置与 Communication Setting Utility
测试源代码跑通之前,有一步必须在代码之外完成:在 Communication Setting Utility 里新建逻辑站号,把网络参数填对。这个工具一般在开始菜单的 MELSEC 目录下,打开的界面左边是逻辑站号列表,右边是连接目标配置。我的常用配置如下:
| 配置项 | 我的常用值 | 说明 |
|---|---|---|
| 逻辑站号 | 0 | C# 代码里ActLogicalStationNumber = 0,两边必须一致 |
| 通信协议 | MC 协议(TCP) | 三菱 PLC 以太网通信的通用选择,Q/L 系列内置以太网口都支持 |
| 目标 IP 地址 | 192.168.0.10 | PLC 侧以太网模块的 IP,PLC 参数里设置 |
| 端口号 | 5000 | 三菱内置以太网口默认侦听端口,新老型号多为 5000/5001 |
| 通信超时 | 3000 毫秒 | 太短容易超时,太长会让故障响应变慢 |
步骤上,我通常先建一个站号,协议选 TCP,填 PLC 的 IP,再把超时设成 3000 测试。有个细节必须强调:端口号不一定都是 5000。老款 Q 系列内置以太网口,或者加装了以太网模块(比如 QJ71E71)的 PLC,默认端口可以在 PLC 参数里改,也可能是 5001。拿不准时,先用三菱的 GX Works 或者直接查 PLC 参数里的“内置以太网端口设置”,确认 MC 协议被启用、端口号是多少。
PLC 侧还有一个容易被忽略的点:以太网模块是否启用了 MC 协议。三菱 PLC 通讯协议详解里写的 MC 协议,对应 PLC 参数里通常是“允许 MC 协议通信”这类选项。没勾选的话,上位机无论怎么连都会被拒,MX 报超时而非“拒绝连接”。这一点在测试源代码阶段很难想到,因为网络 ping 是通的,TCP 端口也能建立连接,但一发起读写就被挂起。
4.2 关于三菱 PLC 通讯协议,测试代码里你得关心这几个字段
用 MX Component 可以不用完全手写帧,但在排查问题时,了解帧结构能帮你快速判断是地址编码错、还是点数超限。MC 协议以太网帧的二进制格式大致包含这几个字段,我按排查价值排个序:
| 字段 | 含义 | 排查作用 |
|---|---|---|
| 帧头与网络号 | 区分请求来源和网络路径 | 多 PLC 互联时容易错 |
| 监视定时器 | 请求方给 PLC 的处理时限 | 超时设置不当会误报 |
| 命令/子命令 | 读还是写、按字还是按位 | 用错命令会得到异常结束代码 |
| 软元件编号 | D100、M0 的内存编码值 | 地址写错,数据就错位 |
| 软元件点数 | 连续读多少个地址 | 超过剩余空间会报错 |
ASCII 帧则把上面的二进制转换成显式字符,比如命令读是01、写是01配合子命令区分。这些不需要背,但我推荐你在工程里保留一份帧简表,现场出问题时对照着看。MX 自带的日志功能可以输出收发帧,Communication Setting Utility 里开启“通信日志”,跑一遍测试源代码,就能看到请求帧和响应帧的原始内容。这是排查“为什么读出来全是 0”最直接的手段,比对着代码猜有用得多。
5. MX 连接三菱 PLC 的高频踩坑与排查记录
5.1 Open 返回负数错误码,代码却看不出问题
现象:C# 测试源代码编译通过,运行到plc.Open()时返回 -16 或 -17,程序却没有异常抛出。
原因:逻辑站号对应关系没建对。最常见的是 Communication Setting Utility 里站号 0 的 IP 填错、PLC 没开机、或者 PLC 侧以太网口没有启用。网络层面是通的,但 MX 发起握手后无人应答,只能靠超时返回错误码。
解决:先 ping PLC 的 IP,确认网络通;再打开 Communication Setting Utility,核对逻辑站号 0 的实际指向。把超时从默认值调大到 5000 再试一次,排除 PLC 响应慢的情况。
5.2 运行时报 BadImageFormatException 或“未能加载 COM 组件”
现象:引用 ActUtlTypeLib 后,编译没问题,一运行就在new ActUtlType()处报错,提示未能加载文件或程序集。
原因:C# 工程是 AnyCPU 或 x64,而 ActUtlType.dll 是 32 位 COM 组件,进程位数不匹配。这是 MX 通信最典型的位数翻车点。
解决:把工程目标平台改成 x86,重新生成。如果项目组强制要求 64 位进程,需要确认 MX Component 提供了 64 位替代 DLL,并用对应方式重新注册,不能拿 32 位 DLL硬顶上。
5.3 地址格式写错导致读写数据异常
现象:读 D100 返回的值不对,写 M0 报错,或者读 X 输入点地址总是不对。代码看起来没问题,但结果和现场 PLC 的值对不上。
原因:不同软元件的编号进制规则不同。X/Y 是十六进制,所以X10在程序里代表第 17 个输入点,而不是十进制的第 10 个;M 继电器是十进制编号,不要加0x前缀;W 和 R 这类字软元件也有各自的编号范围。测试源代码里如果在地址字符串上做拼接,很容易把进制搞混。
解决:写一个软元件地址规范表贴在工位旁。地址字符串统一大写,拼接索引时确认自增是按十进制还是十六进制。最好在测试源代码里封装一个地址生成函数,把进制规则收拢到一个地方,而不是散落在各处拼字符串。
5.4 轮询太快要被 PLC 拒连,CPU 占用虚高
现象:上位机用Timer每 10 毫秒读一次 PLC,运行几分钟后读写开始报错,之后 Open 也连不上。
原因:MX 和 PLC 之间的请求队列被塞满了,PLC 的以太网模块触发拒绝新连接,或者 MX 内部因响应超时累积了太多未完成请求。轮询间隔过短并不会更快,反而让通信链路变脆。
解决:把轮询间隔放宽到 100 毫秒以上,读写使用ReadDeviceBlock批量拉取,减少单次请求交互次数。现场经验值:一次读 20 个寄存器比分开读 20 次快且稳得多。
5.5 连仿真环境连不上:GX Simulator 的坑
现象:代码和配置都按以太网方式写了,连不上三菱 GX Simulator 里的虚拟 PLC,Open 超时。
原因:GX Simulator 不是网络设备,它模拟的是 PLC CPU,需要通过 MX 的“逻辑站号指向 GX Simulator”这种专用配置来访问,不能走 TCP 连接。
解决:在 Communication Setting Utility 里把逻辑站号的连接目标改成 GX Simulator,重新测试。这样做的额外好处是可以在没有实物 PLC 的情况下,把读写逻辑先调通,等现场接线后再切回以太网配置。
6. 从测试源代码到可靠上位机:补上心跳、重连和异步冲刷
6.1 用线程或 Timer 做定时轮询,跨线程访问控件要小心
测试源代码跑通后,正式上位机的路子完全不一样。我一般把通信逻辑放在后台线程里,用System.Threading的循环做轮询,而不是用 WinForms 的Timer直接读 PLC。原因很简单:Timer跑在 UI 线程,通信一卡,窗口就假死。改用线程后,PLC 读取和界面刷新解耦,体验完全不同。
// 一个简单的后台轮询示意,实际项目里要加停止标志 private void PollingLoop() { while (!stopFlag) { int[] data = new int[10]; int ret = plc.ReadDeviceBlock("D100", 10, out data); if (ret == 0) { // 刷新界面用 Invoke,避免跨线程操作控件崩溃 textBoxD100.Invoke((Action)(() => textBoxD100.Text = data[0].ToString())); } Thread.Sleep(200); } }跨线程访问控件是 C# 上位机另一个高频翻车点。Invoke是标准的 UI 控件跨线程操作方式,别为了省事把CheckForIllegalCrossThreadCalls关掉,那是自找麻烦。
6.2 断线监测与自动重连策略
PLC 断电、网线松动、设备重启,都会让通信中断。测试源代码里Close()一次就完,正式上位机必须做断线自动恢复。我的习惯是每次轮询记录连续失败次数,超过阈值就Close()再Open()重连,重连成功后立刻做一次握手读取验证通信真实性,避免虚连。
6.3 通信日志与多软元件批量读取
最后把通信日志打开,写进文件而不是控制台。现场排查时,日志能告诉你最后一条成功的请求是什么、失败的错误码是多少。从测试源代码到正式项目,我吃过最大的亏就是没留日志,现场只能反复插拔网线试。后来所有 plc 通信工程都强制带日志,调试时间至少减少一半。
另一个能感觉到明显提速的做法是:把散落的GetDevice一个一个读改成ReadDeviceBlock批量读,一条请求拿回一组数据,再在内存里按地址表分发。测试源代码可以只读单个寄存器验证链路,正式上位机这么写,通信压力小得多。
做这套 C# 通过 MX 链接三菱 PLC 的方案,我的习惯是测试阶段只验证链路,正式阶段再堆机制。你先把最小代码跑通,再去补心跳、重连、日志和批量读,这样才能分清楚到底是链路问题还是逻辑问题。希望帮到你。
本文还有配套的精品资源,点击获取