ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

三菱CNC数据采集C#源码解析:从协议到落库的完整实践

三菱CNC数据采集C#源码解析:从协议到落库的完整实践 简介工业设备数据采集是智能制造的基础环节其中CNC数控机床的数据获取尤为关键。三菱CNC系统通信协议复杂如MC Protocol、EZSocket让许多工程师在报文结构上遭遇瓶颈。基于TCP Socket的3E帧二进制格式通过构造软元件地址读取请求可实现寄存器读写与设备状态监控。一份封装完善的C#源码能够将连接握手、报文拼装、断线重连等链路标准化大幅降低开发门槛帮助快速实现机床联网与OEE计算。此类方案适用于车间设备监控、产量统计、数据上云等场景尤其适合上位机工程师与系统集成商参考。本文从协议选型到代码实现结合故障排查与进阶应用系统拆解了三菱CNC数据采集的完整技术路径为自研数采系统提供可落地的工程实践参考。1. 三菱 CNC 数据采集 Demo一份能少走三个月弯路的 C# 源码做工厂数字化改造的朋友大概率都经历过这种场面设备科催着要产量数据老板盯着看板要 OEE结果你抱着三菱 700 系列手册翻了一整天连 PLC 的软元件地址都还没理清。三菱 CNC 系统不像发那科那样有现成的 FOCAS 库可以快速上手它的以太网通信协议EZSocket、MC Protocol资料分散在几本手册里新手很容易在 TCP 报文格式上卡死——尤其是 A 系列 1E 帧、Q 系列 3E 帧、二进制和 ASCII 码格式的排列组合这已经是著名的三菱数采劝退点。我拆过的这份三菱 CNC 数据采集 Demo附带 C# 源码最值钱的地方不是采集逻辑本身而是作者把「连接握手 → 读寄存器 → 解析地址 → 断线重连」这条链路完整跑通并封装好了。它解决的是「机器摆在那数据却拿不出来」的最后一公里问题适合三类人正在做设备数据采集的上位机工程师、需要快速验证数控车间联网方案的集成商、以及想搞懂三菱以太网协议报文结构的 C# 开发者。这份 Demo 的完整度超出了我的预期不只是能跑的样例而是一套可以往项目里搬的骨架。2. 三菱数采协议选型为什么你该直接走以太网而不是串口2.1 三菱 CNC 数据采集的三种主流路径在实际车间里三菱 CNC 的数据往外搬主要有三条路串口RS-232C/RS-422、以太网口内置以太网或加装 Ethernet 模块、以及存储卡/CF 卡轮询。三条路我都走过先说结论新项目里能用以太网就绝不要用串口。串口方案的优点是老设备基本都带口子不需要额外硬件成本但缺点非常致命——三菱串口协议A 系列 G4 报文在长距离传输时抗干扰能力差波特率到了 9600 以上就容易丢字节而且串口天然是半双工采集 PLC 数据时要严格排队如果设备多一台一台轮询的时间延迟会直接让实时数据变成历史数据。以太网方案走的是三菱的 MC ProtocolTCP 端口号一般是 5000 到 5007。这份 Demo 用的就是 MC Protocol 里的 3E 帧Q 系列兼容格式也就是我在项目里最常用的一种。3E 帧是 TCP 无头帧采用子头请求数据体的结构可以批量读取 D 寄存器、M 继电器、R 文件寄存器等一次读个上百个地址没问题吞吐量比串口高一个数量级。选型上建议大家优先确认设备有没有内置以太网口老的 700 系列如果没有可以加装三菱的 LJ71E71 或 IEUC 模块走网络成本远低于重新布线。2.2 按报文格式选二进制格式比 ASCII 格式快在哪儿MC Protocol 的报文格式又分 ASCII 和二进制两种。ASCII 格式每字节都是一个可打印字符调试方便——用网络调试助手就能直接看到内容——但同样的数据要多传一倍的字节数。二进制格式则是把数据按字节直接填进报文长度短、解析快但用抓包工具看是一堆乱码排查时眼睛容易瞎。这两者的选型逻辑其实很实在如果你是在机器边上用网线直接连笔记本调试ASCII 格式会舒服得多如果走车间局域网、设备多、数据量大二进制格式能有效降低网络负载。这份 Demo 里提供了两种格式的拼接方法默认走的是二进制我在下面的实现部分会把两种报文结构都拆开讲便于你们在项目里灵活切。2.3 这份 Demo 的代码骨架到底帮你封装了什么源码里最有含金量的其实是两个类一个是 PLC 连接管理类处理 TCP Socket 的连接、心跳保持、断线重连另一个是报文构造/解析类把 MC Protocol 的子头、请求数据长度、监视定时器、指令代码这些字段全部封装成了方法调用方只需要传入软元件地址和读取长度。这意味着你用它做二次开发时不需要把三菱通信手册的上册和下册各翻三遍只要抓住ReadD、ReadM、ReadR这几个公开方法就能上手。我用这份代码改造过一个老车间的三台加工中心数据采集项目从拿到源码到跑通第一块看板数据只花了一个下午。它最大的价值是把「协议层」和「业务层」隔离得很干净你在业务层写代码时根本不需要反复去看字节偏移量。3. 用 C# 打开三菱数控系统的口袋核心源码与逐段解析3.1 连接握手TCP Socket 连接三菱 CNC 的关键细节先把最底层的连接代码拉出来看。三菱的 MC Protocol 走 TCP 时和串口不一样不需要发送 SEND 报文来激活连接只要 TCP 三次握手成功PLC 就会把连接建立起来像这样的逻辑——但有个前提你要先确认 PLC 侧已经配置了对应的端口并且在连接前不能忘记设置超时否则设备断电或者网线松动时你的程序会被阻塞在Connect调用上界面卡死这在上位机程序里是不可接受的。/// summary /// 建立与三菱 CNC 的 TCP 连接 /// /summary public bool Connect(string ipAddress, int port 5007, int timeout 3000) { try { _socket new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _socket.ReceiveTimeout timeout; _socket.SendTimeout timeout; // 注意三菱 PLC 的端口范围一般是 5000~5007具体要查设备参数 IAsyncResult result _socket.BeginConnect(IPAddress.Parse(ipAddress), port, null, null); // 阻塞等待连接完成超时时间是 3 秒 bool success result.AsyncWaitHandle.WaitOne(timeout, true); if (success _socket.Connected) { _socket.EndConnect(result); return true; } else { _socket.Close(); return false; } } catch (Exception ex) { // 连接异常后一定要释放 Socket不然下次连接会报地址占用 _socket?.Close(); Console.WriteLine($[连接失败] {ipAddress}:{port} - {ex.Message}); return false; } }这段代码里有几个值得注意的坑。第一BeginConnect的WaitOne超时等待是必须的因为三菱 PLC 侧的连接响应时间其实在局域网内通常低于 50ms但一旦 IP 配置错误或者设备关机TCP 握手会因为网络层重试机制一直挂在那儿第二EndConnect必须在连接成功后调用否则 Socket 的异步状态没有闭合第三连接失败后我会显式调用Close是因为 Windows 的 Socket 在未释放状态下会持有本地端口资源连续失败重连几次后端口会被占满程序就再也连不上了——这个是很多新手会忽略的玄学问题。3.2 报文构造三菱 3E 帧二进制格式详解连接建立后核心就是按照 3E 帧格式构造请求报文。二进制格式的 3E 帧结构从前往后依次是子头固定为 0x50 0x00、请求数据长度2 字节表示后续内容的字节数、PLC 监视定时器2 字节设为 0 表示使用 PLC 侧默认时间、指令代码2 字节读是 0x01 0x04、子指令2 字节固定 0x00 0x00、软元件地址块。地址块里的信息包括软元件代码2 字节、起始地址3 字节、软元件点数2 字节。这里最大的坑是三菱的软元件地址是十六进制编码后的三字节大端存储比如 D100 要转成三字节另外三菱部分软元件的地址基数不是按十进制从 0 开始的比如 R 文件寄存器是按 0x0000 开始算的但 D 寄存器是从 0 开始算没问题M 继电器是按位寻址代码是 0x0090。下面给出一段把地址和点数封装成报文的代码我在搭建通讯模块时通常把这个方法放到一个PlcMessageBuilder静态类里方便多处调用。/// summary /// 构造 Qna 兼容 3E 帧二进制格式的读请求报文 /// /summary /// param namedeviceCode软元件代码如 D0xA8, R0xAF/param /// param namestartAddress起始地址十进制/param /// param namepoints读取点数/param /// returns完整的请求字节数组不含 TCP 头/returns public static byte[] BuildReadRequest(ushort deviceCode, int startAddress, ushort points) { byte[] buffer new byte[26]; // 3E帧二进制请求固定长度 int index 0; // 子头固定为 50 00表示是二进制格式 buffer[index] 0x50; buffer[index] 0x00; // 请求数据长度从监视定时器开始到报文末尾 // 监视定时器(2) 指令(2) 子指令(2) 软元件代码(2) 起始地址(3) 点数(2) 13字节 int dataLength 13; buffer[index] (byte)(dataLength 0xFF); buffer[index] (byte)((dataLength 8) 0xFF); // 监视定时器0 表示用 PLC 默认时间通常 10 秒 buffer[index] 0x00; buffer[index] 0x00; // 指令代码0401 表示按字读取注意小端排列后是 01 04 buffer[index] 0x01; buffer[index] 0x04; // 子指令0000 表示以 2 字节为单位访问字访问 buffer[index] 0x00; buffer[index] 0x00; // 软元件代码D 寄存器是 0xA8 buffer[index] (byte)(deviceCode 0xFF); buffer[index] (byte)((deviceCode 8) 0xFF); // 起始地址三字节大端低字节在前中字节次之高字节在后 // 注意这里的地址是十六进制编码后的字节需要按位拆开 buffer[index] (byte)(startAddress 0xFF); buffer[index] (byte)((startAddress 8) 0xFF); buffer[index] (byte)((startAddress 16) 0xFF); // 软元件点数读取字数1 到 960超过 960 要分帧读 buffer[index] (byte)(points 0xFF); buffer[index] (byte)((points 8) 0xFF); return buffer; }务必注意三菱的地址存储格式跟大部分 PLC 厂商不一样这里起始地址不是直接换算成三个字节补码塞进去的而是按照「各字节的十六进制表示」来填的。什么意思呢比如你想读 D100100 的十六进制是 0x0064那么这三个字节是 64 00 00——也就是先放低字节、再放中字节、最后放高字节而不是把 int 类型的内存布局直接复制过来。如果你要读的地址是 D10001000 的十六进制是 0x03E8那么三字节依次是 E8 03 00。这个规则是这份源码里最容易看错的地方。再强调一下点数最大 960 字如果你要读连续 2000 个字必须拆成三帧请求否则 PLC 侧会直接回错误状态码。3.3 响应解析与状态位判断别拿到了数据就万事大吉请求发出去后响应报文的结构是子头、子命令、结束代码、响应数据长度、数据。其中结束代码 0x0000 表示正常非 0 则代表 PLC 那边出了状况——常见的有 0xC051地址越界、0xC052点数超过上限、0xC056软元件不存在、0xC058指令代码错误。拿到错误码后不要傻眼去查对应的错误码表我在集成时遇到过几次 0xC056排查到最后都是因为我把软元件的 deviceCode 给填错了比如把 R 文件寄存器0xAF写成了 D 寄存器0xA8。解析代码如下/// summary /// 解析 PLC 响应返回读取到的字数据或错误信息 /// /summary public static ushort[] ParseReadResponse(byte[] response) { if (response null || response.Length 11) { throw new Exception(响应长度不足报文不完整); } // 子头校验二进制格式响应以 50 00 开头 if (response[0] ! 0x50 || response[1] ! 0x00) throw new Exception(响应子头错误不是二进制格式); // 结束代码第 9~10 字节小端存储0x0000 为正常 ushort endCode (ushort)(response[9] | (response[10] 8)); if (endCode ! 0) { // 把错误码转成十六进制方便定位 throw new Exception($PLC 返回错误码 0x{endCode:X4}); } // 响应数据长度第 11~12 字节表示后面数据块的字节数 int dataLength response[11] | (response[12] 8); int dataStartIndex 13; // 数据区起始位置 // 每 2 个字节组成一个 word 数据小端排列 int wordCount dataLength / 2; ushort[] words new ushort[wordCount]; for (int i 0; i wordCount; i) { int offset dataStartIndex i * 2; words[i] (ushort)(response[offset] | (response[offset 1] 8)); } return words; }这里有个很微妙的细节响应数据区的前两个字节是「响应数据长度」但这个长度指的是后面实际数据的字节数不包含这两个字节本身。我第一次写解析器时多留了个 bug回传的数据有个头错位结果读出来的 D 寄存器值全都向后偏了一个字节。另外三菱总线里数据统一是低位在前如果你下端设备是 Cortex-M 这类小端单片机直接拷贝还行但如果对接的是某些把 Modbus 大端惯坏了的网关必须做字节序反转不然数值会完全错乱。3.4 断线重连与轮询框架把采集做成可持续运转的循环单次请求/响应搞定之后剩下的是把它转成一个稳定运行的轮询循环。车间里网线接头松了、交换机断电、CNC 控制面板重启都可能导致 Socket 连接断开所以断线重连机制的健壮性直接决定系统能不能长期挂着跑。我一般把采集循环写成一个后台线程用ManualResetEventSlim控制暂停和停止。核心流程定时读一批寄存器 → 解析数据 → 存入共享队列 → 检查 Socket 是否断开 → 断开则等待几秒重连。下面给出关键代码段/// summary /// 后台采集线程每隔 interval 毫秒读取一次地址列表 /// /summary private void PollingLoop(object? state) { while (!_cancellation.Token.IsCancellationRequested) { try { // 检查连接状态断开则尝试重连 if (_socket null || !_socket.Connected) { Console.WriteLine(连接断开5秒后重试...); Thread.Sleep(5000); if (!Connect(_ip, _port)) continue; // 重连失败跳过本轮采集 } // 按预先配置的地址表逐组读取 foreach (var addrGroup in _readGroups) { byte[] request PlcMessageBuilder.BuildReadRequest( addrGroup.DeviceCode, addrGroup.StartAddress, addrGroup.Points); SendRequest(request); byte[] response ReceiveResponse(addrGroup.Points * 2 13); ushort[] values PlcMessageBuilder.ParseReadResponse(response); // 把数据和地址绑定后写入业务层队列 _dataSink?.Invoke(addrGroup.StartAddress, values); } // 轮询间隔注意别设得太小否则三菱 PLC 会拒绝过密请求 Thread.Sleep(interval); } catch (Exception ex) { // 任何异常都不能让线程退出记录并继续 Console.WriteLine($[采集异常] {ex.Message}); Thread.Sleep(1000); } } }轮询间隔我默认设的是 500ms 到 1000ms。有些工程师急着追求实时性把间隔压到 50ms结果发现三菱 PLC 偶尔回一个 0xC052 或者在 PLC 侧看到通讯错误率上升——三菱的连接机制里有一个监视定时器请求的发送频率太快时PLC 侧的处理队列会溢出我一般推荐的稳妥范围是 200ms 以上。如果车间里同时有十几台设备接入一起轮询建议把间隔提到 1 秒以上同时可以开多个 Socket 并发连不同设备别单线程逐个跑——那样最慢的机器会拖累所有设备的数据时间戳。4. 把 Demo 改造成车间级采集服务地址规划与数据上云4.1 三菱数控系统的软元件地址地图D / R / M / X / Y 各管什么在三菱数控系统里软元件的地址规划决定了你采集什么、漏掉什么。D 寄存器DeviceCode 0xA8是数据寄存器按 16 位字存主要用于存系统参数和用户自定义数据R 寄存器0xAF是文件寄存器容量比 D 大很多多用于断电保持的工艺参数M 继电器0x90是位单元一个 bit 代表一个开关状态比如主轴运转中、切削液开、报警信号等。X/Y 则是输入输出继电器直接映射物理 IO 点在数控系统里较少开放给上位机。实操中采集每台 CNC 时我建议你把地址规划分成三类状态类M 继电器、数值类D 寄存器、计数类R 寄存器。比如主轴负载显示在 D 寄存器里当前刀具号在 D 里程序号也在 D 寄存器而加工件数有时候在 R 寄存器里做断电保持报警信息一般通过 M 继电器的多个位组合表示。这份 Demo 源码里默认只给了 D 寄存器的读取示例但你只要修改 DeviceCode 就能读 R 和 M。唯一的坑是 M 继电器是按位访问的虽然请求报文形式上仍按字发但返回的 16 位里每一位都代表不同的继电器状态解析时要做一个按位掩码转换否则整数值只是个加总没有业务意义。4.2 地址偏移与 PLC 侧配置的一致性检查开发中一个比较隐蔽的坑是三菱 CNC 的软元件地址并不一定与 PLC 梯形图里看到的一致。很多三菱数控系统是由内藏的 PLC 或者 PMC就是可编程机床控制器承担逻辑控制的PMC 与外部设备的软元件映射表通常由机床厂在 PMC 参数里定义这意味着你要读的 D100 也许根本不是 PLC 里的 D100而是某个映射后的系统内部地址。解决这个问题唯一可靠的办法是拿机床厂提供的 PMC 地址分配表对照一遍。如果你手头没有这份文档可以在三菱 CNC 的维护菜单里找到 I/O 诊断界面手动置位某个地址观察机床反应用排除法来确定映射关系。我自己就吃过亏有一台车削中心的件数地址我以为是 D200实际是 R500接错后连续采集了三天垃圾数据直到复盘时才发现地址表给错了。4.3 数据上云与存储SQLite 落库和 MQTT 推送的轻量方案采集出来的数据不能只停在内存里。车间级应用最少得有一个本地落库我在小项目里习惯用 SQLite——无服务、单文件、备份直接拷走配合Microsoft.Data.Sqlite这个包代码量很少。核心思路是维护一张cnc_realtime表和一个cnc_history表实时表只存最新状态历史表按时间戳追加。SQLite 的写入锁性能弱点没关系每秒写几条完全扛得住。如果是集团级系统要把数据往工业物联网平台推走 MQTT 协议会更通用因为 MQTT 天然支持 Qos、遗嘱消息正好可以利用遗嘱消息检测设备离线——三菱 CNC 突然断电或者网线断开时MQTT 的遗愿能在 30 秒内把离线状态推给平台设备管理页面上立刻就能看到。以下是采集数据入 SQLite 的关键代码// 注意正式项目里建议改用 Dapper 或 EF Core这里直接写 SQL 便于理解 public void SaveRealtimeData(DateTime ts, string machineNo, int startAddr, ushort[] values) { using var conn new SqliteConnection($Data Source{_dbPath}); conn.Open(); // 先删除同地址的旧数据保证起始地址对应的最新状态只保留一条 using var deleteCmd conn.CreateCommand(); deleteCmd.CommandText DELETE FROM cnc_realtime WHERE MachineNo $mach AND StartAddr $addr; deleteCmd.Parameters.AddWithValue($mach, machineNo); deleteCmd.Parameters.AddWithValue($addr, startAddr); deleteCmd.ExecuteNonQuery(); // 批量插入最新值 using var tran conn.BeginTransaction(); for (int i 0; i values.Length; i) { using var insertCmd conn.CreateCommand(); insertCmd.CommandText INSERT INTO cnc_realtime (Timestamp, MachineNo, StartAddr, Offset, Value) VALUES ($ts, $mach, $addr, $off, $val); insertCmd.Parameters.AddWithValue($ts, ts); insertCmd.Parameters.AddWithValue($mach, machineNo); insertCmd.Parameters.AddWithValue($addr, startAddr); insertCmd.Parameters.AddWithValue($off, i); insertCmd.Parameters.AddWithValue($val, values[i]); insertCmd.ExecuteNonQuery(); } tran.Commit(); }如果采集频率是每秒一次一台设备 30 个寄存器一天的数据量大概在 250 万条量级SQLite 会开始感觉到压力。我一般在这种情况下引入一个轻量级的聚合步骤——把原始数据按分钟粒度聚合成均值、最大值、最小值原始明细保留三天聚合结果保留三个月这样既保住了 OEE 计算的精度也不会让数据库文件膨胀到几百兆。这个思路在 Demo 源码里没有现成实现但是改造起来很直接你只需要在PollingLoop里加一个统计窗口即可。4.4 多设备并发采集线程池与 Socket 资源管理车间里的设备很少只有一台。当你要同时采集 10 台三菱 CNC 时一定不要在一个线程里依次轮询否则每台设备的刷新周期会被拉长到 10 倍。正确的做法是每台设备一个独立线程 独立 Socket 连接互不阻塞。这方面有一个细节要注意每台设备的 Socket 对象最好随线程生命周期一起创建和释放不要用共享的 Socket 数组然后在多线程里去访问同一个元素容易踩到ObjectDisposedException。我用ConcurrentDictionarystring, Socket来管理每台设备的连接句柄线程只通过设备号拿自己的 Socket这种模式在 50 台设备以内都很稳。再往上走就要考虑用异步 Socket 加上连接池了不过那份复杂度一般只有在集团级 SCADA 系统里才有必要。5. 三菱数采常见问题排查从连不上到数据错乱的五类典型故障5.1 TCP 能 ping 通但程序报连接超时现象三菱 CNC 用电脑 ping 能通但 C# 程序里Connect一直超时。原因三菱自带的以太网模块端口号和 FCN 参数没配置对。部分老设备默认端口是 5000新版是 5007还有一点坑是 PLC 侧开启了「仅从特定 IP 段访问」的功能你的电脑 IP 不在白名单里。解决先在 CNC 维护界面检查以太网参数里的 TCP 端口号然后看是否启用了 IP 过滤如果有把自己的 IP 添加进去。最后再确认程序里的端口号和 PLC 设置一致不要想当然用默认值。5.2 请求发出去了收到 0xC051 错误码现象读取点数超过 PLC 允许上限。原因三菱 MC Protocol 的单次读取点数限制是 960 个字超过这个数字 PLC 直接拒绝。有些设备的实际限制是 480 字更严格。解决把超过上限的读取请求拆成多次读每次读点数小于等于 480 字比较保险。在代码里对points参数做一次强制校验超过 480 就直接抛出异常避免 PLC 侧返回错误。5.3 读到的 D 寄存器的值全部向左偏移了一个字节现象数据能返回但每个寄存器值和实际值对照不上整体像是错位。原因我在前面就挖过这个坑——响应报文的前 13 个字节里包含了子头、结束代码、响应数据长度如果你从第 12 字节开始取数据而正确起点是第 13 字节就会把长度字节当成数据。解决打印出原始响应报文的 Hex 字符串逐个字节对照报文结构文档确认数据区的起点。一般核对 11 字节的结束代码和 12 字节的响应数据长度就能发现偏移。5.4 数据采集正常但重连后 PLC 侧出现通讯错误记录现象程序重连后活干得好好的但一段时间后在 PLC 的诊断历史中看到了通讯错误。原因重连时旧的 Socket 没有完全释放TCP 半开连接占着 PLC 侧的资源。三菱 PLC 侧对每个通信通道的 TCP 连接数量有限制你的程序反复重连每次留下一些 TIME_WAIT 状态的连接最后把通道占满。解决在重连之前确保旧 Socket 显式ShutdownClose并设置Linger选项为 0 秒彻底释放端口_socket.LingerState new LingerOption(true, 0); _socket.Shutdown(SocketShutdown.Both); _socket.Close();5.5 读到的报警状态和机床面板不一致现象M 继电器读回来的 bit 状态与 CNC 屏幕上看到的报警灯不一样。原因报警信号往往是多个地址的逻辑组合一个 M 继电器可能经过 PMC 梯形图的取反或者延时处理才映射到面板上。你读的地址是原始 PMC 地址而屏幕上显示的是经过程序加工后的状态。解决逐个对照 PMC 地址分配表确认你要采集的是「物理输入」还是「经过逻辑运算后的输出」。如果是后者你可能要在上位机里复制一遍梯形图的逻辑——通常就是个 NOT 和延时几分钟就能搞定。6. 进阶玩法把采集数据换算成 OEE 指标并反向写回 CNC 坐标系6.1 从寄存器数值推导设备状态机数据采上来之后最常用的业务就是算 OEE。OEE 时间开动率 × 性能开动率 × 合格品率其中时间开动率需要你区分设备的三种状态运行、空闲、故障。三菱的寄存器里通常有主轴运转信号M 继电器的一个 bit和自动/手动模式信号另一个 bit。我用这两个 bit 组合出一个简单的状态机自动 主轴运行 加工状态自动 主轴停 待料状态手动 报警 故障状态。这个映射关系在 Demo 源码里没有直接给但读出来原始 bit 后做状态判定非常简单——关键在于状态迁移的迟滞处理比如主轴从运行到停止的瞬间有一个短暂的假报警我一般会加上 2 秒的确认窗口再切换状态否则 OEE 曲线会在零和满值之间抖得没法看。6.2 反向写寄存器修改刀补和工件计数的代价与风险这份 Demo 不仅支持读反向写寄存器的代码也有。但我要给你一个明确的警告向三菱 CNC 写寄存器是高风险操作尤其是写 D 寄存器可能会覆盖机床本身的 PMC 工作区导致逻辑错乱。我在一个演示项目里用十行代码往 R 寄存器里写了一个工件计数清零结果那台设备第二天就被机床电气工程师拦下了——原因是我写寄存器的时机不对撞上了 PMC 自身刷新窗口导致计数被重置了两次。如果你确实有写需求建议遵守这几条只写机床厂明确指定的用户自定义保持寄存器写完之后延时 200ms 再读回校验写操作只允许在设备停机状态下进行禁止在自动加工循环中写任何地址。6.3 可视化看板用 Blazor 或 WinForm 做实时监控界面数据通路打通后只要把 UI 加上去就完活了。如果是一个车间级的看板我个人推荐用 Blazor Server 模式——好处是浏览器打开就能看不用每台电脑装运行时你要是有现成的 WinForm 客户端需求直接用绑定的方式更新数值也可以。注意一个问题UI 线程不能直接和采集线程共享 ListBox 这类控件要经过SynchronizationContext或者Dispatcher.Invoke否则 WinForm 刷得快时会抛出跨线程操作异常。换成 Blazor 反而省事一点靠 SignalR 去订阅采集线程的事件就能刷新页面不需要关心消息泵的调度。核心逻辑就是采集线程每完成一轮读取就触发一个事件事件里把数据打包成 DTO交给 SignalR Hub 推给前端页面。6.4 我还想留给你一个习惯三菱数采这种系统最怕的不是写不出代码而是设备状态千奇百怪每个现场都有每个现场的脾气。从那以后我每次落地一个三菱采集项目都强制自己先在记事本里写一份「地址映射 端口配置 重连策略」的检查清单到了现场先跑通最小路径连上一台设备、读一个 D 寄存器、建立一个 SQLite 文件再批量铺开设备。很多排障的线索其实都藏在启动时那几分钟的日志里——日志写不清楚排查的时候你就只能靠猜了。希望这份源码和拆解能帮你把三菱数采这道坎迈过去少走我当年走过的弯路做出稳定、能被车间师傅认可的数据采集系统来。本文还有配套的精品资源点击获取
返回列表