
简介C#数据采集Demo面向三菱CNC系统专为机床自动化工程师、上位机开发者和工业物联网从业者设计旨在解决机床实时监控、加工参数读取、设备状态追踪及故障日志获取等工业数据采集难题。压缩包约3.52MB内部包含PDF格式的参考手册和三菱CNC模拟环境SimCNC的源码目录参考手册用于查询通讯协议、寄存器地址与数据格式SimCNC则支持在无实体机床的情况下搭建仿真调试环境方便反复验证采集逻辑。Demo源码演示了配合A2.exe通讯软件与CNC建立连接通过C#实现对多种型号三菱机台的数据读取并将采集功能封装为独立软件或插件可灵活接入现有产线信息化系统。整体代码结构清晰包含连接建立、数据解析、异常处理等关键模块是学习工业通讯编程的良好范本。已有620人学习下载适合需要快速落地三菱CNC数采项目或进行技术预研的开发者参考能显著缩短环境搭建与排错时间。1. 三菱CNC数据采集Demo先想清楚要采什么别急着开VS车间里一台三菱加工中心正在跑活操作员盯着控制面板你在办公室电脑前想看到它主轴转速、当前坐标、有没有报警——这就是三菱CNC数据采集Demo附C#源码这个标题真正要解决的事。它不靠串口线不靠人抄屏而是走CNC自带的以太网口用C#把宏变量、坐标、报警号实时拉回来。反直觉的地方在于新手最想要的往往是能把数读出来的激动感但真正决定这活儿成不成的是动手之前先想清楚要采哪几个地址、用什么协议、帧怎么解析。常见做法是走三菱的EZSocket中间件DLL一引C#里P/Invoke一调十几行代码就能连上但参数不对时一个Open函数能卡你一下午。这个方向适合做设备联网、MES对接、机加工自动化的工程师也适合刚入行想拿一台真设备练手的同学。看完这篇文章你至少能把这套Demo跑通并且知道坑在哪。2. 准备工作三菱CNC数据采集Demo的网络前提与协议选型2.1 网口通信是前提串口方案到了Demo阶段就该放弃很多老工程师的第一反应是“CNC后面不是有RS232口吗直接串口读不就行了”。这个思路在二十年前没问题但现在做数据采集串口的三个短板非常明显一是传输速度慢115200波特率下刷新一次状态要几百毫秒别说做高频采集二是好多老旧串口方案只能单向输出想写宏变量回机台很难三是串口线超过15米信号就开始飘车间现场拉线很不现实。所以这个Demo的正确起点是以太网口。三菱的中高端系统比如M70、M700、M80系列标准配置里都带以太网口参数里面设好IP地址、子网掩码之后上位机直接用网线连交换机就能访问。通信链路的示意图其实很简单C#程序通过TCP协议发一个ASCII字符串命令帧给CNCCNC回一个带数据的响应帧程序把帧解析出来就完事了。这里有个关键选型点直接用TCP发MC协议帧虽然可行但工程上没人这么干因为帧头、校验、分包都得自己拼出错率太高。业界通用做法是安装三菱的EZSocket通信库它本质上是把MC协议封装成了C风格函数提供固定接口往上位机这边暴露动态链接库C#项目里声明一下就能调用。Demo阶段选它省掉的是协议拼帧的麻烦留下的是数据解析的核心逻辑。2.2 访问对象A/B/C/DDemo该用哪一个通道EZSocket里有一个概念叫“访问对象”英文叫Access Target官方分为A、B、C、D四种。A类走的是三菱PLC的MC协议用来读PLC软元件B类是Windows下和PC内部数据交换用的C类是访问CNC数值数据的通道宏变量、坐标、刀补、报警都归它管D类是访问机床侧的PLC数据比如IO信号、继电器状态。写成表格更直观访问对象面向的数据Demo里有没有用APLC软元件D、M、Y等一般不用BWindows环境内部通信不用CCNC数值数据宏变量、坐标、报警核心通道D机床侧PLC数据IO、继电器按需C#做CNC采集绝大多数需求都集中在C类上。你在MES里看到的主轴转速当前X轴坐标报警号全部通过C类读出来。有些车间还想看机床门有没有关、红灯有没有亮这些信号在D类里但那是后话Demo阶段先把C类吃透。有个细节要注意EZSocket的DLL同时支持A类访问所以如果你这台CNC的PLC里存了工件计数之类的数据也可以用同一套代码去读只需要把打开连接时的访问对象类型改一下。Demo阶段不建议一上来就通吃先把C类读通再往D类扩展。2.3 CNC侧参数核对IP、端口、系统版本差异连不上CNC的时候八成不是代码的问题而是CNC侧的网络参数没核对。三菱系统里以太网参数一般在维护菜单的以太网设定或者网络设定页面里。需要确认四项参数典型值说明IP地址192.168.1.50和上位机在同一网段子网掩码255.255.255.0不跨网段就够用端口号6000不同系统版本可能有差异通信协议TCP打开对应通道端口号是个血泪高发区。部分老系统默认用6000部分系统在参数里单独开了一个端口还有的系统要同时开UDP和TCP两个通道。我一般会在CNC旁边先用笔记本电脑ping一下IP通了再跑Demoping不通就别折腾代码先查网线和IP段。还有两个前置条件容易被忽略一是EZSocket服务器端程序要和系统匹配二是CNC侧参数里的传输协议要选成允许外部设备访问。某些系统出厂时默认关闭了外部访问需要到维护模式里打开。这些操作需要操作员权限做Demo之前最好和车间师傅打个招呼别自己偷摸改参数改完机台报警就麻烦了。3. 核心代码C#读三菱CNC宏变量、坐标与报警3.1 创建一个控制台Demo把EZSocket.dll引进来拿到三菱EZSocket安装包之后安装目录下会有一个动态链接库名字一般是EZSocket.dll。C#这边不能直接添加引用因为它是C风格导出函数不是托管DLL正确做法是用DllImport声明外部函数。先建一个控制台项目平台目标改成x86然后写一个静态类来包装接口using System; using System.Runtime.InteropServices; public static class EZSocketWrapper { // 声明三个核心函数打开、读取、关闭 [DllImport(EZSocket.dll, EntryPoint EZSocket_Open)] public static extern int Open( IntPtr[, ,] pCNC, // 连接句柄三维数组 int nType, // 访问对象类型2表示C类CNC数据 int nOpen, // 通信方式1表示TCP string pIPAddr, // CN C的IP地址 string pPort, // 端口号 int nTimeOut); // 超时时间毫秒 [DllImport(EZSocket.dll, EntryPoint EZSocket_Read)] public static extern int Read( IntPtr[, ,] pCNC, // 连接句柄 int nType, // 访问对象类型 string pReadBuf, // 读命令帧 ref string pRetBuf, // 返回数据缓冲区 string pErrMsg); // 错误信息输出 [DllImport(EZSocket.dll, EntryPoint EZSocket_Close)] public static extern int Close( IntPtr[, ,] pCNC, int nType); }这段声明里最容易栽跟头的是句柄类型。EZSocket的手册上写的是三维指针数组C#这边的对应写法就是IntPtr的三维数组初始化时每个维度给3就够了。nType传2代表C类访问nOpen传1代表走TCP。端口参数是字符串不是整数这是很多人第一次调用时报错的原因。超时时间建议给3000毫秒太短在车间网络抖动时容易误报失败太长则程序卡死在那里。注意DLL的位数必须和编译目标一致。EZSocket.dll在多数版本里是32位的所以项目平台目标一定要选x86否则运行到Open那行就抛BadImageFormatException这个在第5章避坑里展开。3.2 连接到CNCOpen和Close的写法连接这块的套路是固定的先创建句柄数组调Open判断返回值用完调Close。下面是完整的最小连接代码static void Main(string[] args) { // 连接句柄三维数组结构 IntPtr[,,] pCNC new IntPtr[3, 3, 3]; // 打开C类访问TCP方式连192.168.1.50的6000端口 int ret EZSocketWrapper.Open( pCNC, 2, 1, 192.168.1.50, 6000, 3000); if (ret 0) { Console.WriteLine(连接成功); // 在这里执行读取操作 // ... EZSocketWrapper.Close(pCNC, 2); } else { Console.WriteLine(连接失败错误码 ret); } }返回值ret为0表示成功非0为失败。这一步很多人第一次跑通后会特别兴奋但别高兴太早——连接成功只代表TCP链路通了真正读数据时的帧格式错一丁点Read照样返回失败。另外Close操作不能省有的现场出现过程序崩溃后CNC侧连接资源没释放导致后续无法再次连接的情况虽然重启机台能恢复但车间里重启设备你可得等操作员排完当前工件。3.3 读宏变量#100的实时值怎么取回来三菱系统的宏变量是机台逻辑里最常用的数据区比如把当前工件计数写到#100把主轴倍率写到#101MES要的就是这些。读取宏变量用到EZSocket_Read命令帧格式我一般按R地址长度来拼static void ReadMacro(IntPtr[,,] pCNC, int macroNum, out float value) { // 拼读命令帧读#100就填 R0,100,1 string cmd R0, macroNum ,1; string retBuf ; string errMsg ; int ret EZSocketWrapper.Read(pCNC, 2, cmd, ref retBuf, ref errMsg); if (ret 0) { // 返回数据从第6个字符开始是有效数据区 string hexData retBuf.Substring(5); // 三菱返回的是倒序十六进制字符串要反转字节再转float byte[] bytes HexToBytes(hexData); Array.Reverse(bytes); value BitConverter.ToSingle(bytes, 0); Console.WriteLine(# macroNum value); } else { Console.WriteLine(读取失败 errMsg); value 0f; } } static byte[] HexToBytes(string hex) { int length hex.Length / 2; byte[] result new byte[length]; for (int i 0; i length; i) result[i] Convert.ToByte(hex.Substring(i * 2, 2), 16); return result; }这里有个工艺问题CNC读回来的数据是十六进制字符串而且是倒序字节序。比如浮点数1.5在内存里是3F C0 00 00三菱回包里往往是0000C03F必须反转之后用BitConverter转float。这个字节序坑每一台新机型都得试一次建议把转出来的值和面板上显示的值对一下对不上就交换一下高低字节顺序。宏变量号本身也有讲究公共变量#100到#149一般默认为空变量区可以随便用#150到#199在某些系统里是模拟输入通道数据#500到#999是保持型变量断电不丢。读#500之前先确认一下机床厂有没有把这部分区域分配给专属功能避免读到无意义数据还当成宝贝存进数据库。3.4 读坐标和报警把机台状态拼出来宏变量能解决数据交换但MES上层更关心的是坐标和报警。读取绝对坐标和报警号用到的命令帧前缀分别是E和A下面是读绝对坐标和报警的核心片段static void ReadPositionAndAlarm(IntPtr[,,] pCNC) { string posBuf ; string almBuf ; string errMsg ; // 读绝对坐标 int posRet EZSocketWrapper.Read(pCNC, 2, E, ref posBuf, ref errMsg); if (posRet 0 posBuf.Length 61) { // 第1~3位是附加信息每个坐标占6个字节 string xHex posBuf.Substring(6, 12); string yHex posBuf.Substring(18, 12); string zHex posBuf.Substring(30, 12); // 转字节再反转再转float逻辑同上 float x ConvertHexToFloat(xHex); float y ConvertHexToFloat(yHex); float z ConvertHexToFloat(zHex); Console.WriteLine($X:{x:F3} Y:{y:F3} Z:{z:F3}); } // 读报警号 int almRet EZSocketWrapper.Read(pCNC, 2, A, ref almBuf, ref errMsg); if (almRet 0 almBuf.Length 9) { // 报警号也是倒序转int string alarmHex almBuf.Substring(5, 4); byte[] bytes HexToBytes(alarmHex); Array.Reverse(bytes); int alarmCode BitConverter.ToInt16(bytes, 0); if (alarmCode ! 0) Console.WriteLine(当前报警号 alarmCode); else Console.WriteLine(无报警); } }坐标读取返回的帧长度是固定的每个轴占6个字节十六进制排列顺序是X、Y、Z有些系统还带A轴、B轴长度就不一样解析前先判断返回帧长度能省不少麻烦。报警读取时要注意报警码按有符号短整型解析负号代表报警级别。读报警还分NC报警和机床报警A命令返回的是系统报警机床报警走D类访问做Demo时别把两者混在一起。一个实用技巧是把坐标和宏变量的读取放在同一个循环里每隔200到500毫秒刷一次屏。刷新太快没必要太慢又看不出实时效果车间里大部分采集需求到500毫秒级别的刷新率已经足够。4. 参数与内存映射宏变量、公共变量和回复帧的边界4.1 三菱宏变量的分区公共变量、保持型变量和刀补拿到一套Demo代码之后最先要问的问题是我该读哪些地址。三菱CNC的宏变量地址分布有规律可循但不同系统之间有一点差异常用分区是这样的变量号范围类型Demo应用场景#100 ~ #149空公共变量跨程序传递数据#150 ~ #199部分系统为模拟量输入读主轴电流等模拟量#500 ~ #999保持型变量工件计数、生产统计#2000 ~ #2199刀补变量部分系统刀具磨损管理#3000 ~ #3999系统设定相关一般不碰Demo阶段建议优先读#100区间和#500区间。#100区间断电会清零适合读当班产量这类临时数据#500区间是断电保持的适合存班长累计数。很多工厂的做法是让PLC在换班时把#500里的值搬运到另一个地址上位机只负责读不负责写。刀补的地址范围需要重点提示不同系统版本对#2000系列的映射并不一致有的系统里#2001对应1号刀的X向刀补有的系统里却跳过了部分保留段。读取刀补之前找操作员确认一下机床厂的宏变量分配表或者干脆用机台面板手动改一个已知值再读出来验证——这是最朴素的测试方法。4.2 回复帧的字节对齐和大小端坑EZSocket返回的数据区是十六进制字符串规则是所有数据都按字倒序。这里说的字倒序不是字节倒序而是两个字节一组颠倒。举例说明数据区里如果收到3412实际值应该是0x1234。浮点数则四个字节整体反转前面代码里用了Array.Reverse其实就是把大小端问题用最简单的方式抹平了。解析时的另一层坑在于命令帧和数据帧之间可能带空格或控制符。不同版本EZSocket返回的帧长度有细微差异有的带起始符有的不带。我建议在写解析逻辑之前先做一步打印裸包操作// 调试用把Read返回的原始帧原样打印出来 Console.WriteLine(RAW:[ retBuf ] len retBuf.Length);把这行加到Demo里跑一次对照三菱手册里的帧结构看一遍再写Substring的截取逻辑比对着网上抄解析代码靠谱得多。不同系统的返回帧长度不一样在M70上试好的偏移量拿到M80上可能就得加一位。还有一个小提醒全部用Substring截取十六进制字符串看起来简单但在解析多个轴坐标时容易数错位数。碰到解析结果数值怪异的情况先数一下每个字符的下标用调试器逐帧看不要靠猜。4.3 采集频率与系统负荷别把Demo写成死循环数据采集最常见的失控写法是while(true)里不加延时CPU占满不说CNC侧的EZSocket服务也会被高频请求拖累。三菱官方对采集频率没有硬性指标但业界经验是状态数据坐标、转速500毫秒刷一次报警数据可以做到200毫秒甚至事件触发产量数据按分钟或按工件触发即可。一个推荐的安全采集框架是这样的// 用定时器代替死循环避免阻塞UI线程 System.Timers.Timer timer new System.Timers.Timer(); timer.Interval 500; // 500毫秒刷一次 timer.Elapsed (sender, e) { ReadPositionAndAlarm(pCNC); ReadMacro(pCNC, 100, out float count); }; timer.Start();注意这里有个隐患Timer触发的事件如果在上一轮读取还没结束时又进来就会导致消息堆积。保险做法是加一个bool锁标志读取中则跳过本次触发。Demo阶段可以不考虑并发但往生产走这一步是必须的。另外不要同一时间开多个线程去调EZSocket_ReadDLL内部不是线程安全的。多个采集线程同时调用同一个连接句柄轻则返回数据错乱重则直接让连接断掉。需要采集多组数据时串行读别并行读。5. 三菱CNC采集Demo的常见踩坑现象、原因与解决5.1 Open返回失败但Ping得通CNC现象上位机能ping通CNC的IP但EZSocket_Open返回非0读取完全无法进行。原因TCP链路通不代表CNC侧的EZSocket服务在监听。最常见的情况有三种CNC系统参数里没打开外部访问权限端口号和实际监听端口不一致访问对象类型选错了类型比如C类通道没开但A类通道是好的。解决登录CNC维护画面检查以太网设定里的端口和访问许可。先把访问对象类型换成A类试一次如果能连上说明C类通道没开如果A类也连不上检查端口号。还有一招是看防火墙Windows自带的防火墙经常拦截TCP入站连接临时关掉再测一下就能定位。5.2 调用DLL时抛BadImageFormatException现象代码编译通过一运行到Open函数就抛出BadImageFormatException异常信息里通常写着32位还是64位不匹配。原因EZSocket.dll是32位动态库而Visual Studio里新建的.NET项目默认平台目标是AnyCPU在64位系统上运行时会用64位进程加载32位DLL直接崩。解决打开项目属性找到生成选项卡把平台目标改成x86。改完重新生成问题消失。这个坑几乎每个第一次接触EZSocket的C#开发者都会遇到网上搜相关报错一搜一大片但知道原理之后十秒钟就能解决。5.3 读到的宏变量是全零或者NaN现象Read调用返回成功但解析出的变量值要么全是0要么是NaN。原因三菱宏变量分为数值型和逻辑型而数值型里又有整型和浮点型之分。系统默认按双字节整数返回时你用float解析就会得到诡异值。另外宏变量所在的地址区间可能根本没被写入有效数据空变量区读出0是正常现象。解决先在CNC面板上手动给#100赋一个已知值比如12.34然后跑Demo读一次。读到12.34说明解析逻辑正确读到0说明地址区间为空换个变量号测试读到一堆乱码说明数据类型解析错了——尝试把BitConverter.ToSingle换成ToInt16或者反过来。用已知值校准是排查这类问题最有效的手段。5.4 程序跑一会儿后连接自己断开现象Demo运行几分钟后Read开始返回错误重试也无效只有重启程序才能恢复连接。原因EZSocket服务端对空闲连接有超时回收机制超时时间从几秒到几分钟不等。程序长时间不发命令服务端主动断开连接。还有一种情况是网络里有广播风暴TCP连接被干扰。解决在采集循环里加入周期重连机制。常见做法是每次Read失败时自动执行一次重连先Close再Open。更温和的做法是缩短Timer间隔把心跳维持在一秒一次以内避免空闲超时。注意重连时要释放旧句柄否则句柄会越积越多最终系统资源耗尽只能重启。另外排查一下网络里有没有人在跑大流量传输车间里的监控系统回传视频有时会占满交换机带宽把CNC和上位机挪到同一个交换机下或者划分独立VLAN能有效缓解。5.5 报警号读出来和面板显示不一致现象报警命令读出的编号和机台面板上显示的报警号对不上有的差个几千有的完全不在一个数量级。原因报警命令读出的是内部报警指针不等于面板显示的报警号。面板显示的是格式化后的报警文本内部编号需要对照报警表映射。另外一个常见干扰是机床厂在标准系统上叠加了自己的PMC报警这些报警由D类访问对象负责C类读不到。解决先确认读的是NC报警还是PMC报警。NC报警用A命令读内部编号再查系统报警表映射成面板报警号PMC报警要切换到D类访问对象读取。Demo阶段建议直接在CNC上触发一个已知报警比如急停或者门开关然后对比读回来的编号和面板值把对应关系记录下来写进配置表里。这个工作看起来笨但做完以后对接MES报警模块就顺畅多了。6. 让Demo往生产方向走数据落库、心跳重连与MES桥接Demo跑通了接下来要做的三件事决定了它在生产环境里能不能站住脚数据落库、断线自动重连、给上层系统留接口。数据落库最省事的方案是SQLite不需要额外装数据库服务一个文件搞定。把每次读到的坐标、宏变量、报警号连同时间戳写进一张表里粒度控制在秒级就够保留最近90天数据。注意写入操作要和采集操作解耦采集线程只负责读写库交给独立线程或后台任务避免磁盘慢的时候拖垮采集循环。断线自动重连是生产环境刚需。做法是把Open、Read、Close封装成一个类内部记录连接状态每次Read失败时触发重连重连次数达到上限后向上抛出异常由上层决定是告警还继续重试。关键细节是重连之前必须Close旧句柄很多重连几次之后彻底连不上的问题都出在句柄泄漏上。给MES桥接的方式有两种简单一点是Demo里直接提供HTTP接口MES通过网页接口拉数据更稳妥的是把采集到的数据推给消息队列由消费端去落库和展示。Demo阶段不用做太复杂把采集类写干净数据格式定义成统一的JSON结构后续扩展就顺理成章。最后说一个我在车间里淌过的教训有一回为了赶进度我把采集循环直接写进窗体Timer里刷新间隔压到50毫秒结果一台老款系统被高频请求打到以太网模块卡死操作员停机重启才恢复。后来我养成了两个习惯任何采集代码先量化频率再上线任何外挂程序都加总开关和限速。做设备采集慢一点不会出事快过头一定出事。希望帮到你。本文还有配套的精品资源点击获取