
简介一份面向C#及工业自动化开发者的三菱PLC通信实例源码基于MxComponent组件实现上位机与PLC的数据交互适合新手入门及有经验的工程师参考现场工程的实现方式。资源包共40个文件约71KB以12个cs源码文件为主配合xaml界面、config配置文件、dll依赖及exe可执行程序覆盖了从界面布局、配置绑定到通信调用的完整工程结构。已有220人学习下载亲测可用的特点让读者能直接对照验证MxComponent的连接参数与读写流程减少现场调试弯路。通过这份源码可以重点理解PLC地址映射、通道建立、数据刷新等关键逻辑并借助可运行程序观察实际效果为后续二次开发提供贴近项目的范例。1. C# 通过 MX 链接三菱 PLC这份测试源代码要解决什么问题给 PLC 写上位机最烦的不是写界面而是通信层。C# 里想读写三菱 PLC 的 D 寄存器、M 位元件方案有好几种自己用 Socket 拼 MC 协议报文、走 Modbus TCP 网关、或者用三菱官方通信组件 MX。对于大多数设备调试和产线测试场景用 MX 组件是最快能跑通的路子。标题里说的“测试源代码”就是一份用 C# 通过 MX 组件完成建链、读写软元件、断链释放的最小可运行工程。它适合三类人刚接触三菱 PLC 上位机开发的 C# 程序员、需要在产线上快速验证点位映射的调试工程师、以及想评估 MX 组件能否扛住项目压力的技术选型人员。2. MX 组件的调用模型逻辑站号、软元件地址与 C# 侧的三个核心 API2.1 MX 组件是什么把 MC 协议封装成黑匣子的官方通信库三菱 PLC 的通信协议叫 MC 协议Melsec Communication Protocol走以太网时通常用二进制 3E 帧走串口时用的是专用帧格式。MC 协议本身并不复杂但自己做 Socket 报文有几个绕不开的活声明帧头、设备号、监视定时器、字节顺序、报文长度、响应解析、异常码判断。这些活不影响业务逻辑却要花掉一两天调试时间。MX 组件是三菱官方的通信组件它把这些协议细节封进了 COM 组件和配置工具。开发者在 C# 里只需要创建 ActUtlType 对象设置逻辑站号然后调用 Open、ReadDeviceBlock、WriteDeviceBlock 就行。底层是 TCP 还是串口、报文里怎么组帧MX 全包了。对项目而言MX 组件带来的最大价值不是省掉那几百行协议代码而是让你把精力放在业务逻辑上。另一个要明确的点是选型边界。方案对比通信方案优点缺点适用场景MX 组件 ActUtlType官方封装、协议细节透明、读写软元件接口简单需要安装组件和授权、COM 互操作在 .NET Core 下略麻烦快速原型、常规上位机项目Socket 直发 MC 3E 帧无依赖、可完全控制报文和超时要自己处理拆包粘包、定义帧格式、解析异常码对性能和报文控制要求高的底层开发Modbus TCP 网关通用性强、跨品牌需要 PLC 侧做映射或加网关混合品牌设备采集场景从实际项目看80% 的三菱 PLC 上位机用 MX 组件就够。如果你是在 .NET 6/8 环境下做纯托管服务需要评估 COM 组件在容器里的兼容性这个坑在后面章节单独展开。2.2 逻辑站号是怎么和 IP、端口、CPU 类型绑定在一起的MX 组件的设计里有个概念叫“逻辑站号”C# 代码里只认这个数字编号不直接写 IP 和端口。IP 和端口配置在 MX Component 自带的 Communication Settings Utility 工具里完成。你可以把逻辑站号理解成设备句柄代码里指定站号 1MX 就去读它自己的配置文件找到站号 1 对应的 PLC 地址和协议参数。逻辑站号配置项示例配置项示例值说明逻辑站号1C# 端 ActLogicalStationNumber 对应此值CPU 类型Q 系列 / R 系列 / FX 系列必须与 PLC 实际型号一致通信方式以太网 / 串口以太网走 TCP 或 UDP串口走 COM 口IP 地址192.168.1.10PLC 的 IP需在 GX Works2 里设置端口号5007三菱 MC 协议以太网常用端口PLC 侧需匹配帧类型3E 帧二进制是三菱最新推荐格式兼容性最好字节顺序小端 / 大端和 PLC 数据格式相关设错会读出乱值这组配置里最容易出错的是 CPU 类型和帧类型。曾经接手过一个项目配置里选的是 Q 系列实际用的是 FX5UOpen 返回成功但所有读写全部超时。后来在配置工具里把 CPU 类型改成 FX5U 才能通信。我一般建议把配置工具的设置通过导出功能存一份备份换电脑或者重新安装的时候直接导入省去排查这类玄学问题的时间。C# 端见不到这些参数不代表它们不存在。逻辑站号的配置文件在 MX 安装目录下通常不需要手动改但你要知道它的存在后面排错会用到。2.3 C# 侧的对象模型ActLogicalStationNumber、Open、ReadDeviceBlock、WriteDeviceBlockC# 侧用到的核心接口是 ActUtlType它提供了最常用的四组操作ActLogicalStationNumber 属性指定逻辑站号。Open()建立与 PLC 的通信链路返回 0 表示成功。ReadDeviceBlock(设备名, 长度, out 数据)连续读取软元件按 16 位为一个单位。WriteDeviceBlock(设备名, 长度, ref 数据)连续写入软元件。Close()释放通信链路。一个最小调用长这样// 创建 ActUtlType 对象指定逻辑站号 Type type Type.GetTypeFromProgID(ActUtlTypeLib.ActUtlType); dynamic plc Activator.CreateInstance(type); plc.ActLogicalStationNumber 1; int ret plc.Open(); if (ret ! 0) { Console.WriteLine(Open 失败: 0x ret.ToString(X4)); return; } // 读取 D0 开始的 1 个字 object readData; ret plc.ReadDeviceBlock(D0, 1, out readData); if (ret 0) { Console.WriteLine(D0 ((int[])readData)[0]); }这段代码里 ReadDeviceBlock 的第三个参数用的是 out object为什么不用强类型数组因为 ActUtlTypeLib 在 Visual Studio 里通过添加 COM 引用时不同版本 IDE 生成的互操作签名不完全一样有的生成 out object有的生成 ref int。直接用 dynamic 调用可以规避这些差异让代码在任何环境里都能按运行时绑定执行。返回值 ret 是通信结果0 表示成功非 0 表示错误码需要在十六进制下查看。参数上要留意两点设备名是字符串D0 代表数据寄存器 D 的编号 0长度单位是“字”不是字节也不是位。读 4 个字就是 ReadDeviceBlock(D0, 4, ...)。这个单位概念贯穿 MX 的所有读写接口后面读写 M 位元件时也得用它换算。3. 用 C# 写一个 MX 连三菱 PLC 的最小测试工程从新建项目到读写 D03.1 前置准备安装 MX Component 并确认 ActUtlTypeLib 已注册在动手敲代码前得先把 MX Component 装上。安装包从三菱官网下载注意选择与 PLC 系列匹配的版本。安装过程中会要求输入授权信息没有授权的话某些版本只能以试用模式运行通信时会附加时间限制或连接数限制。安装完成后系统中会注册一个 COM 组件ProgID 是 ActUtlTypeLib.ActUtlType。可以通过命令行验证组件是否注册成功。打开管理员权限的 CMD执行regsvr32 ActUtlTypeLib.dll如果返回“DllRegisterServer 成功”说明组件注册正常。如果提示“模块已加载但找不到入口点”或者“找不到指定的模块”说明 DLL 路径不对或者没有安装通信库需要到 MX 安装目录里找到这个 DLL 再执行。另一个验证方式是在 Visual Studio 里新建工程后通过“添加引用 - COM 库”查找 ActUtlTypeLib。如果列表里有这一项说明组件已经注册。注意这个 COM 组件是 32 位还是 64 位取决于你安装的版本后面有专门一节讲这个问题。3.2 在 Visual Studio 里新建工程为什么我推荐用 dynamic 而不是添加 COM 引用创建测试工程时我习惯用 .NET Framework 4.6.2 或 .NET 6 的控制台应用。.NET Framework 可以直接添加 COM 引用用 IntelliSense 看参数.NET 6 里虽然也能引用 COM但生成的互操作类型有时会缺失反而不如用 Type.GetTypeFromProgID 配合 dynamic 来得干净。写测试代码前把工程的平台目标设为 x86。操作路径右键工程 - 属性 - 生成 - 平台目标 - 选 x86。不设这一步的话在 64 位 Windows 上以 Any CPU 运行时会自动用 64 位进程加载 32 位 COM 组件大概率抛异常。工程文件里不需要额外 NuGet 包只引用 System 和 System.Runtime.InteropServices。完整的 Program.cs 如下using System; using System.Runtime.InteropServices; namespace MxPlcTest { internal class Program { static void Main(string[] args) { // 创建 ActUtlType 实例ProgID 来自已注册的 MX 组件 Type type Type.GetTypeFromProgID(ActUtlTypeLib.ActUtlType); if (type null) { Console.WriteLine(未找到 ActUtlType 组件请确认 MX Component 已安装); return; } dynamic plc Activator.CreateInstance(type); // 设置逻辑站号与 MX 通信设置工具中的编号一致 plc.ActLogicalStationNumber 1; // 打开通信链路 int retOpen plc.Open(); if (retOpen ! 0) { Console.WriteLine(Open 失败错误码: 0x retOpen.ToString(X4)); return; } Console.WriteLine(Open 成功); // 连续读取 D0~D3 共 4 个字 object readData; int retRead plc.ReadDeviceBlock(D0, 4, out readData); if (retRead 0) { int[] d (int[])readData; for (int i 0; i d.Length; i) { Console.WriteLine(D i d[i]); } } else { Console.WriteLine(读取失败错误码: 0x retRead.ToString(X4)); } // 向 D10、D11 写入两个测试值 object writeData new int[] { 100, 200 }; int retWrite plc.WriteDeviceBlock(D10, 2, ref writeData); if (retWrite 0) { Console.WriteLine(写入 D10~D11 成功); } else { Console.WriteLine(写入失败错误码: 0x retWrite.ToString(X4)); } // 释放连接 plc.Close(); Console.WriteLine(测试完成按任意键退出); Console.ReadKey(); } } }这段代码的执行顺序是创建 COM 对象 - 设置逻辑站号 - Open - 按业务需要读写 - Close。逻辑是通的但不同类型 PLC 对软元件范围的支持不一样D 寄存器在 FX 系列和 Q 系列里都是 16 位一个字但对连续读取长度的上限不同测试时先读 4 个字是安全范围。关于 out object 参数ReadDeviceBlock 返回的 object 本质上是 VARIANT 数组C# 里可以直接强转成 int[]。WriteDeviceBlock 里的 ref object 也是同一道理要把写入的 int[] 先装箱成 object。如果你在别的博客里看到有人用 ref int 或 out int[]那是 COM 互操作生成签名差异导致的不用纠结按自己能编译过的方式写就行。3.3 首次运行前的参数核对逻辑站号、PLC 侧端口和软元件范围代码层面最该核对的三个参数是 ActLogicalStationNumber、设备名字符串和读取长度。逻辑站号必须在 MX 通信设置工具里真实存在。如果代码里设成 1而工具里配置的是 2Open 返回 0 但后续读写全部超时因为站号根本没对上。调试时先到工具里看清楚站号列表再回代码里改 ActLogicalStationNumber。设备名字符串要按三菱软元件命名规范写。D0、D100 这种直接写M 元件写 M0、M100X/Y 输入输出元件要注意进制问题FX 系列的 X/Y 地址是八进制Q 系列是十六进制MX 组件会按你填的字符串原样解析不会自动转换进制。曾经有人在读 Q 系列 X10 到 X1F 时填成 X8、X9结果读出来全部为 0。读取长度按“字”计算。读 D0 到 D3 四个字长度填 4读 M0 到 M15 十六个位长度也要填 16。这里容易犯的错是把 M 元件的长度按字节算本来想读 16 个位结果填了 2读到的数据数量和想象中完全对不上。4. 把测试源代码扩成可复用模块位读写、错误码表和批量操作边界4.1 位软元件 M/X/Y 的读取写法与数据打包规则D 寄存器是字软元件读出来直接是 int 数组。M/X/Y 是位软元件MX 的 ReadDeviceBlock 在读取位元件时会把连续位打包成一个或多个整数返回。读 M0 到 M15 共 16 个位返回一个 intbit0 对应 M0bit1 对应 M1以此类推。// 连续读取 M0~M15 共 16 个位结果打包在低位 object bitData; int retBit plc.ReadDeviceBlock(M0, 16, out bitData); if (retBit 0) { int packed (int)bitData; for (int i 0; i 16; i) { bool value (packed (1 i)) ! 0; Console.WriteLine(M i value); } }这段代码里的打包逻辑很直观读取 M0 到 M15 后返回的 int 的低 16 位对应这 16 个位元件的状态。判断某个位时用移位和按位与就行。要注意 M 元件的编号是按十进制递增的M0、M1、M2 连续排。而 X/Y 元件的编号进制由 PLC 系列决定FX 的 X 是八进制X0~X7、X10~X17Q 系列是十六进制X0~XF、X10~X1F在 MX 里写地址时直接按这个进制写字符串即可比如 Q 系列读 X0~XF 共 16 个位代码里写 ReadDeviceBlock(X0, 16, out data)。上面代码还有个隐含约定ReadDeviceBlock 会把连续位打包成字返回。如果读 M0 到 M7返回一个 int只有低 8 位有效。读 M8 到 M15返回的 int 里也是低 8 位有效M8 对应 bit0。这个打包规则对不熟悉的人容易造成困惑记住“按起始地址对齐返回整数的低 n 位”就好。4.2 错误码表把返回值翻译成人话MX 组件所有方法都返回 int 类型的结果0 表示成功非 0 是错误码。调试时看到一串十六进制数新手往往会懵。我梳理了几个高频出现的错误码错误码含义典型处理0x0成功无需处理0x1010通信超时检查 PLC 是否在线、IP 和端口是否可达、逻辑站号配置0x1032目标站无响应检查 PLC 侧 MC 协议端口是否开启、CPU 类型是否选对0x1020报文格式错误检查设备名写法、读取长度是否超过上限0x1085软元件不存在或地址越界核对设备名和 PLC 型号的软元件范围看到 0x1010 时先别急着查代码。用 ping 测通 IP然后确认 PLC 在 GX Works2 里打开了以太网端口并且勾选了 MC 协议。很多时候是 PLC 侧没开端口和 C# 代码无关。0x1032 则更偏向逻辑站号配置问题重点看 MX 工具里 CPU 类型和站号绑定。我习惯在开发时把错误码统一写成一个工具方法static string DecodeError(int code) { switch (code) { case 0x0: return 成功; case 0x1010: return 通信超时检查网络和 PLC 状态; case 0x1032: return 目标站无响应检查逻辑站号配置; case 0x1020: return 报文格式错误检查设备名和长度; case 0x1085: return 软元件地址越界; default: return 未知错误参考 MX 手册错误码表; } }这样在控制台里输出错误时直接打印 DecodeError(ret)比干看十六进制数好得多。排错流程建议固定为先看 Open 返回值再逐条看读写返回值。Open 失败时不必继续调读写先把网络和站号搞定。4.3 批量读写与随机读写的适用边界ReadDeviceBlock 适合连续地址批量操作。读 100 个连续的 D 寄存器一次性读完性能远高于逐个读取。但连续批量读有两个边界要注意地址不能跨软元件类型不能从 D0 一直读到 M0MX 不认这种写法长度不要一次性拉满像 Q 系列虽然单个请求支持几百字但单次请求过大会拉长 PLC 的扫描周期影响设备运行稳定性。我一般控制在单次读 64 个字以内既保证效率又不给 PLC 添负担。当要读的地址不连续时用随机读取更合适。ActUtlType 提供 ReadDeviceRandom参数是设备名数组可以一次传入 D0、M100、D200 这类零散地址// 构造需要读取的设备名数组 string[] targets new string[] { D0, M100, D200 }; object result; // 随机读取多个不同地址的软元件 int retRandom plc.ReadDeviceRandom(targets, out result); if (retRandom 0) { int[] values (int[])result; Console.WriteLine(D0 values[0] , M100 values[1] , D200 values[2]); }这段代码里 targets 数组顺序就是返回结果 values 的顺序一一对应。随机读取虽然灵活但每多一个地址报文就变长一点PLC 处理时间也略增不要一个请求塞几十个地址。合理的做法是把点位按用途分组执行类点位放一个组状态类点位放另一个组每组用一次随机读。批量读取和随机读取二选一时原则很简单地址连续用 ReadDeviceBlock地址零散用 ReadDeviceRandom。两方面都做进去一个测试工具才算完备。5. 避坑MX 连三菱 PLC 的 5 个常见故障现场与排查方法5.1 现象regsvr32 注册 ActUtlTypeLib 提示“找不到模块”或“入口点找不到”在某台新电脑上部署测试程序时打开 CMD 执行 regsvr32 ActUtlTypeLib.dll系统提示模块找不到。原因基本有两个一是 MX Component 没有正确安装安装目录下根本没有 ActUtlTypeLib.dll二是装了某个绿色版或精简包DLL 被手动拷到 SysWOW64 里导致注册失败。曾经见过有人为了方便直接把 DLL 从别的电脑拷过来用最后一直报错。解决方法是重新运行 MX Component 安装程序做一次修复安装。安装完成后再到安装目录一般是 Program Files (x86) 下的 MELSOFT 文件夹里找到 ActUtlTypeLib.dll右键属性查看路径然后以管理员身份执行 regsvr32 加上全路径。注册成功后在 Visual Studio 的引用管理器里就能看到 ActUtlTypeLib代码里的 Type.GetTypeFromProgID 也能正常创建对象。5.2 现象Open 返回 0但 ReadDeviceBlock 一直超时或报 0x1010这是 MX 组件最典型的翻车现场。C# 代码运行到 Open 时正常返回 0但第一笔读操作就直接超时。原因往往是逻辑站号配置里的通信参数与 PLC 实际设置不一致最常见的是 IP 地址、端口号、CPU 类型三项中至少有一项对不上。Open 只负责建立本地会话不会验证对端 PLC 是否真的存在所以它返回成功并不代表链路真的通了。PLC 侧需要打开以太网端口并选择 MC 协议还要确认端口号与 MX 配置工具里填的一致。产线环境里 PLC 没开端口是高频原因。解决时先按这个顺序排查第一步 ping 通 PLC 的 IP第二步在 GX Works2 里查看 CPU 的以太网端口设置确认 MC 协议已勾选、端口号正确第三步打开 MX 通信设置工具检查逻辑站号里的 CPU 类型、IP、端口和帧类型三项第四步看看 Windows 防火墙有没有拦 TCP 5007 端口。大多数情况下走到第三步就能发现问题。曾经遇到过一台 FX5UGX Works2 里设置的端口是 5007MX 工具里也填了 5007但帧类型选了“四字节顺序”改成“二进制 3E 帧”后立刻正常。5.3 现象读取 M 位元件的值总是乱套串口连接 FX 系列时根本读不到一套基于 FX 系列 PLC 的设备用 USB 圆头线连 PLC 编程口C# 里通过 MX 走串口通信。Open 成功但读 M0 返回的数据时好时坏换台电脑彻底读不到。现象背后有两个坑叠在一起。第一个是串口参数和圆头针脚定义没对齐FX 系列的圆头针脚定义里SD、RD、SG 三根线必须和串口转换器一一对应接错就导致收发错位。第二个是 M 元件的读取长度问题有人把 ReadDeviceBlock(M0, 2, out data) 理解成“读 M0 和 M1 两个位”实际 MX 返回的是一个整数里打包的 16 个位长度参数指的是位数不是点数。解决方法是先到三菱官网下载 FX 系列手册找到圆头针脚定义图按图核对转换器的接线。我用过几款国产 USB 转 8 针圆头线引脚定义并不完全一致必须实测 SD 和 RD 是否交叉。然后是 M 元件的读取逻辑明确“位数长度”读 M0 到 M15 就是长度 16返回整数按位拆解。这两点都确认后串口通信才可能可靠。5.4 现象32 位编译能跑64 位编译抛出 AccessViolationException错误码 c0000005测试程序在本地能跑但换到另一台电脑上以 x64 运行时ReadDeviceBlock 直接抛 System.AccessViolationException错误信息里能看到 c0000005。还有人遇到的是“无法将类型 System.__ComObject 转换为类型 ActUtlTypeLib.ActUtlType”。原因在于 ActUtlTypeLib 是传统 COM 组件很多 MX 版本只注册了 32 位版本。64 位进程加载 32 位 COM 组件时要么失败要么内存访问违规。网上搜 c# 调用 c 出现 access violation c0000005 大概率也是这个场景。另外一个变体是工程用了 Any CPU在 64 位系统上默认以 x64 运行同样踩雷。解决方法是把工程平台目标强制设为 x86方法在 3.2 节里提过右键工程 - 属性 - 生成 - 平台目标 - x86。发布时也要选“win-x86”。如果项目组必须用 64 位进程就得确认当前 MX 版本是否提供了 64 位 COM 组件部分新版本在安装包里带 x64 注册项安装时勾选即可。没有的话就单独起一个 32 位辅助进程通过本地 IPC 与 64 位主程序通信这类做法在大型上位机里很常见但测试源代码阶段完全没必要。5.5 现象同一套代码换个 PLC 或者换台电脑后就不能用了测试代码不动换了一台 PLCOpen 成功但读写异常或者把程序部署到车间另一台工控机上报站号错误。原因是 MX 组件把逻辑站号配置存在本地代码里的 ActLogicalStationNumber 指向的是逻辑编号不是物理 PLC。换 PLC 后新设备的 IP 可能变了端口和 CPU 类型也可能不同但 MX 工具里还是旧配置。换电脑时新机器上要么没装 MX要么装了但没配置逻辑站号代码自然找不到设备。解决方法是把逻辑站号配置当成项目资产来管理。在 MX 通信设置工具里配置完一组参数后使用导出功能保存成配置文件随项目源码一起提交到版本库。部署到新电脑时先装 MX再导入配置文件然后启动程序。这个习惯能省掉大量现场排查时间尤其是产线改造场景下PLC 换了一台又一台一份导出的配置能让你几分钟内恢复通信。另外要确认新电脑上的 MX 授权已经激活授权文件没有跟着配置文件走这一点常常被人忽略。6. 验证与进阶离线模拟、心跳重传和上线检查清单6.1 没有实体 PLC 时怎么验证测试代码开发机上没有 PLC代码写完了想先跑一遍。常见做法是用三菱的 PLC 仿真功能配合 GX Works2 的仿真器MX 可以连接仿真 CPU。具体路径是先在 GX Works2 里启动仿真再在 MX 通信设置工具里把逻辑站号的 IP 指向本机回环地址端口保持默认。这样 C# 读写软元件的逻辑就能在无硬件环境下验证。还有一种做法是写一个简单的本地 TCP 服务模拟 MC 协议响应但工作量不小验证阶段没必要。6.2 给读操作加上心跳重传与断线重连测试源代码跑通后要往生产方向走第一件事是补上通信可靠性。读操作偶发超时是正常的不必每次超时都当作断线。我给读操作加一个重传机制连续失败超过阈值再执行重连static int ReadWithRetry(dynamic plc, string device, int size, out object data, int retry 3) { data null; for (int i 0; i retry; i) { int ret plc.ReadDeviceBlock(device, size, out data); if (ret 0) { return 0; } System.Threading.Thread.Sleep(50); } return -1; }这个方法的逻辑很简单单次读取失败不立刻报错而是重试三次间隔 50 毫秒三次都失败才返回 -1由调用方决定是否触发断线重连。重试间隔不能设太短否则 PLC 扫描周期还没结束又发起请求反而加重负载。重试之后仍然失败再执行 Close 加 Open重新建立会话。这套“读失败 - 重试 - 重连”的路径能扛住绝大多数产网抖动。心跳包也按同样思路做定时写一个专门的软元件像 D9999用来验证链路活性。6.3 上线前的检查清单整理一份清单每次部署新设备时对照检查PLC 侧以太网端口已开启MC 协议已勾选端口号与 MX 配置一致MX 通信设置工具里 CPU 类型与 PLC 实际型号一致逻辑站号配置文件已导出备份工程平台目标为 x86发布目录不带任何第三方运行时软元件地址和长度在 PLC 型号支持范围内D 寄存器不要连续读超过 64 字授权已激活部署机器安装的是完整版 MX 组件。我自己的习惯是把这份清单放进项目 README每次现场调试前先过一遍少跑很多冤枉路。希望帮到你。本文还有配套的精品资源点击获取