三菱CNC数据采集Demo:C#源码实现以太网通信与实时监控

发布时间:2026/10/15 10:12:01
三菱CNC数据采集Demo:C#源码实现以太网通信与实时监控
简介这份资源是三菱CNC数据采集Demo及其配套C#源码面向从事数控机床通讯开发、自动化产线数据采集的工程师与C#开发者帮助解决三菱CNC机台加工参数、设备状态与错误日志等数据的实时获取与处理问题。压缩包为zip格式整体约3.52MB内含参考手册与SimCNC模拟环境等资料可用于理解通讯协议、数据格式并搭建无实机调试环境。资源标签涉及C#软件与插件开发、三菱CNC数采源码既可独立运行也可作为插件集成到其他系统。目前已有624人学习下载适合希望快速上手三菱CNC通讯、对照手册与仿真环境进行程序开发与排错的技术人员参考。1. 从一台老机床的“哑巴”状态说起这个 Demo 到底能采到什么车间里那台三菱 CNC 加工中心跑了快八年屏幕上该显示的都显示可数据就是出不来——产量、报警、主轴负载全靠操作工拿本子抄。这种“哑巴机床”在中小工厂里太常见了不是设备不行是缺一个能把控制器里的数据读出来的中间层。这份三菱 CNC 数据采集 Demo 附带 C# 源码解决的就是这个事用 C# 写一个上位机程序通过以太网跟三菱 CNC 控制器通信把加工状态、报警信息、坐标数据实时抓出来。它适合两类人一是工厂里负责设备联网的自动化工程师想快速验证采集可行性二是做 MES 或工业看板的 C# 开发者需要一个能直接改的通信底座。Demo 本身不复杂但通信链路是完整的拿过去改改就能接自己的业务系统。2. 通信底座拆解C# 怎么跟三菱 CNC 控制器对上话2.1 选型逻辑为什么是 C# 加以太网直连三菱 CNC 控制器对外提供数据接口的方式有好几种常见的是通过以太网口走专用协议也有用串口或现场总线的。这个 Demo 选的是以太网直连方案上位机用 C# 写直接跟控制器建立 TCP 连接。为什么这么选因为产线上大部分三菱 CNC 都带以太网口不用额外加硬件网关一根网线从机床电柜拉到工控机就能跑。C# 在 Windows 工控机上部署最省事开发效率也高做界面、做后台服务、对接数据库都顺手。通信协议层面三菱 CNC 的以太网接口通常走的是基于 TCP/IP 的自定义报文格式不是标准的 Modbus TCP。报文结构一般是“请求头 命令码 数据区 校验”上位机发一条读取指令控制器回一条包含当前值的数据帧。这个 Demo 里把报文封装成了 C# 的类发送前序列化成字节数组接收后按偏移量解析。我一般会把协议常量单独放一个静态类命令码、数据区长度、超时时间都做成可配置的换一台不同型号的控制器时只改配置不改逻辑。注意不同系列的三菱 CNC 控制器报文格式和命令码可能有差异Demo 里的协议实现是针对某一类接口做的实际项目里要拿对应型号的通信手册核对一遍。2.2 连接管理TCP 长连接与心跳保活工业现场的网络不像办公室那么稳交换机重启、网线松动、控制器断电都会导致 TCP 连接断掉。如果每次采集都重新建连接延迟高不说控制器那边也可能因为频繁连接而拒绝服务。所以 Demo 里用的是长连接加心跳的机制程序启动时建立一条 TCP 连接之后所有采集请求复用这条连接同时每隔几秒发一条心跳报文确认链路还活着。// 连接管理核心逻辑长连接 心跳保活 public class CncConnection { private TcpClient _client; private NetworkStream _stream; private Timer _heartbeatTimer; private readonly string _ip; private readonly int _port; public CncConnection(string ip, int port) { _ip ip; _port port; } // 建立连接并启动心跳 public bool Connect() { try { _client new TcpClient(); // 设置连接超时避免网络不通时卡死 var result _client.BeginConnect(_ip, _port, null, null); if (!result.AsyncWaitHandle.WaitOne(3000)) { _client.Close(); return false; } _client.EndConnect(result); _stream _client.GetStream(); _stream.ReadTimeout 2000; // 读超时 2 秒 _stream.WriteTimeout 2000; // 写超时 2 秒 // 每 5 秒发一次心跳 _heartbeatTimer new Timer(Heartbeat, null, 5000, 5000); return true; } catch (Exception ex) { // 记录日志方便排查是网络问题还是控制器拒绝 Console.WriteLine($连接失败: {ex.Message}); return false; } } // 心跳发一条空请求看控制器是否正常回应 private void Heartbeat(object state) { try { var response SendCommand(CncCommand.Heartbeat, null); if (response null || !response.IsValid) { // 心跳失败触发重连 Reconnect(); } } catch { Reconnect(); } } // 断线重连先关旧连接再重新建立 private void Reconnect() { _heartbeatTimer?.Dispose(); _stream?.Close(); _client?.Close(); Thread.Sleep(1000); // 等一秒再重连避免频繁冲击控制器 Connect(); } }这段代码里几个关键点BeginConnect加WaitOne是为了控制连接超时不然网络不通时程序会卡住ReadTimeout和WriteTimeout设成 2 秒防止读写阻塞心跳定时器每 5 秒跑一次失败就触发重连。参数方面IP 和端口从配置文件读心跳间隔和超时时间我一般做成可调的现场网络差就放宽一点。2.3 数据解析从字节流到可读的加工状态控制器返回的数据是一串字节上位机要按协议定义把它拆成有意义的字段。Demo 里把解析逻辑单独放在一个DataParser类里每个字段对应一个偏移量和数据类型。比如主轴转速通常是 4 字节的整数报警号是 2 字节的短整型坐标值是 4 字节浮点数。解析的时候要注意字节序三菱控制器一般用大端序C# 默认是小端需要做转换。// 数据解析按协议偏移量提取字段 public class DataParser { // 解析主轴转速偏移量 84 字节大端整数 public static int ParseSpindleSpeed(byte[] data) { if (data.Length 12) return -1; // 大端转小端 var bytes new byte[] { data[11], data[10], data[9], data[8] }; return BitConverter.ToInt32(bytes, 0); } // 解析当前报警号偏移量 122 字节大端短整型 public static short ParseAlarmCode(byte[] data) { if (data.Length 14) return -1; var bytes new byte[] { data[13], data[12] }; return BitConverter.ToInt16(bytes, 0); } // 解析 X 轴坐标偏移量 144 字节大端浮点数 public static float ParseAxisX(byte[] data) { if (data.Length 18) return 0f; var bytes new byte[] { data[17], data[16], data[15], data[14] }; return BitConverter.ToSingle(bytes, 0); } }解析这块最容易翻车的地方是偏移量对不上。不同控制器的数据区布局不一样有的把报警号放在前面有的放在后面。我的习惯是先用调试工具把原始字节流打出来对着通信手册一个字段一个字段标标完再写解析代码。Demo 里的偏移量是参考值实际用的时候要按你手头控制器的手册改。3. 从采集到展示一个完整请求的链路与工程结构3.1 目录结构源码里有什么拿到这份 Demo 源码目录结构大致是这样的CncDataCollector/ ├── CncDataCollector.sln # VS 解决方案文件 ├── src/ │ ├── CncDataCollector.Core/ # 核心通信库 │ │ ├── CncConnection.cs # TCP 连接管理 │ │ ├── CncCommand.cs # 命令码常量定义 │ │ ├── DataParser.cs # 数据解析 │ │ └── Models/ # 数据模型 │ │ └── CncStatus.cs # 加工状态实体 │ ├── CncDataCollector.Service/ # 后台采集服务 │ │ ├── CollectorService.cs # 采集主循环 │ │ └── appsettings.json # 配置文件 │ └── CncDataCollector.UI/ # WinForms 界面 │ ├── MainForm.cs # 主窗体 │ └── MainForm.Designer.cs # 界面布局 └── README.md # 说明文档Core 库是核心通信和解析都在里面不依赖界面可以单独引用到其他项目。Service 是后台采集服务负责定时轮询控制器、把数据写数据库或发消息队列。UI 是一个简单的 WinForms 界面用来实时显示采集到的数据方便调试。这种分层的好处是你如果只想做后台采集直接拿 Core 和 Service 就行UI 可以不要。3.2 采集主循环定时轮询与数据落地采集服务的主循环逻辑不复杂启动后先连控制器连上了就按固定间隔发采集命令拿到数据后解析、存库、推给界面。间隔时间一般设 200 毫秒到 1 秒看你对实时性的要求。太短了控制器压力大太长了数据曲线不连贯。// 采集主循环定时轮询 数据分发 public class CollectorService { private CncConnection _connection; private Timer _collectTimer; private readonly int _intervalMs 500; // 采集间隔 500ms public void Start() { _connection new CncConnection(192.168.1.10, 5000); if (!_connection.Connect()) { Console.WriteLine(控制器连接失败请检查网络和 IP 配置); return; } // 启动定时采集 _collectTimer new Timer(CollectOnce, null, 0, _intervalMs); } private void CollectOnce(object state) { try { // 发采集命令拿原始字节 var rawData _connection.SendCommand(CncCommand.ReadStatus, null); if (rawData null) return; // 解析成业务对象 var status new CncStatus { SpindleSpeed DataParser.ParseSpindleSpeed(rawData), AlarmCode DataParser.ParseAlarmCode(rawData), AxisX DataParser.ParseAxisX(rawData), CollectTime DateTime.Now }; // 存数据库这里用控制台输出代替 SaveToDatabase(status); // 推给界面事件或委托 OnDataCollected(status); } catch (Exception ex) { Console.WriteLine($采集异常: {ex.Message}); } } // 数据落地实际项目里换成数据库或消息队列 private void SaveToDatabase(CncStatus status) { // 示例写 SQLite 或 SQL Server Console.WriteLine($[{status.CollectTime}] 转速:{status.SpindleSpeed} 报警:{status.AlarmCode} X:{status.AxisX}); } // 事件界面订阅这个事件来刷新显示 public event ActionCncStatus DataCollected; private void OnDataCollected(CncStatus status) { DataCollected?.Invoke(status); } }这段代码里_intervalMs控制采集频率SendCommand是同步阻塞的实际项目里如果采集点多可以考虑异步或批量读取。SaveToDatabase这里只是打印真实场景要换成写数据库或者发 MQ。事件机制让界面和数据采集解耦界面订阅DataCollected事件就能实时刷新不用轮询。3.3 界面调试WinForms 实时显示采集结果Demo 的 UI 很简单一个窗体几个 Label 显示转速、报警、坐标一个按钮控制启停。界面订阅采集服务的DataCollected事件收到数据就更新 Label。WinForms 里跨线程更新控件要用Invoke不然会抛异常。// 界面订阅采集事件跨线程更新 UI public partial class MainForm : Form { private CollectorService _service; public MainForm() { InitializeComponent(); } private void btnStart_Click(object sender, EventArgs e) { _service new CollectorService(); _service.DataCollected OnDataReceived; _service.Start(); btnStart.Enabled false; } // 采集回调注意跨线程 private void OnDataReceived(CncStatus status) { if (this.InvokeRequired) { this.Invoke(new ActionCncStatus(OnDataReceived), status); return; } lblSpindle.Text $转速: {status.SpindleSpeed}; lblAlarm.Text $报警: {status.AlarmCode}; lblAxisX.Text $X轴: {status.AxisX:F3}; } }界面这块没什么玄学就是记得InvokeRequired判断加Invoke调用。调试的时候先把采集服务跑起来看控制台输出对不对再接界面。如果界面不刷新先查事件有没有订阅上再查是不是跨线程没处理。4. 避坑与排查现场调试最容易翻车的几个点4.1 连不上控制器先查网络再查端口现象是程序启动后一直提示连接失败。原因通常有三个一是 IP 地址填错了控制器实际 IP 跟配置不一致二是端口不对三菱 CNC 的以太网通信端口不一定是 5000有的型号是 5001 或 8000三是工控机和控制器不在同一网段或者中间有防火墙拦了。解决办法先用 ping 确认网络通再用 telnet 试端口最后拿控制器的手册核对 IP 和端口配置。我一般会在程序里加一个“测试连接”按钮把连接结果和错误码打出来省得猜。4.2 数据全是零或乱码偏移量和字节序对不上现象是连接成功了但解析出来的转速、坐标全是 0 或者负数。原因基本是偏移量写错了或者字节序搞反了。三菱控制器用大端序C# 默认小端解析的时候必须手动翻转。解决办法先用抓包工具或者程序里的调试模式把原始字节流打出来对着通信手册一个字段一个字段核对偏移量和数据类型。Demo 里的偏移量是参考值换控制器一定要重新对。4.3 采集一段时间后断连心跳没做或者超时太短现象是程序跑了几分钟或几小时后就收不到数据了重启又正常。原因是 TCP 长连接被中间网络设备断掉了而程序没有心跳保活机制或者心跳间隔太长、超时太短。解决办法加上心跳定时器每隔几秒发一条空请求同时把读写超时设成合理值2 到 5 秒。如果现场网络特别差心跳间隔可以放宽到 10 秒但重连逻辑一定要有。4.4 界面卡死采集在主线程里跑现象是点了开始采集后界面就卡住不动了。原因是采集循环跑在 UI 线程里同步阻塞了消息循环。解决办法把采集服务放到后台线程或 Task 里跑界面通过事件接收数据。WinForms 里跨线程更新控件记得用Invoke。如果采集频率高还可以考虑用BackgroundWorker或者async/await模式。4.5 多台机床采集时数据串了连接对象没隔离现象是同时采集多台 CNC 时A 机床的数据显示到了 B 机床的界面上。原因是所有采集共用一个连接对象或一个数据缓存。解决办法每台机床对应一个独立的CncConnection实例和独立的采集任务数据模型里带上机床编号界面按编号分发。如果采集点很多可以考虑用连接池或者异步批量读取。5. 二次开发把这套 Demo 改成能上产线的采集服务5.1 从 Demo 到产线需要补哪些东西Demo 能跑通通信链路但离产线部署还有距离。第一配置要外置IP、端口、采集间隔、数据库连接串都放appsettings.json里别硬编码。第二日志要完善用 NLog 或 Serilog 把连接状态、采集异常、重连记录都写下来出问题能查。第三数据落地要换成真正的数据库或消息队列SQLite 适合单机SQL Server 或 MySQL 适合集中部署如果对接 MES 就发 MQTT 或 Kafka。第四异常处理要健壮网络断了要自动重连控制器返回异常要记录并跳过不能让整个服务崩掉。// appsettings.json 配置示例 { CncDevices: [ { Name: CNC-01, Ip: 192.168.1.10, Port: 5000, CollectIntervalMs: 500, Enabled: true }, { Name: CNC-02, Ip: 192.168.1.11, Port: 5000, CollectIntervalMs: 500, Enabled: true } ], Database: { Type: SqlServer, ConnectionString: Server.;DatabaseCncData;Trusted_ConnectionTrue; }, Logging: { Level: Info, FilePath: logs/cnc-.log } }配置化之后加一台机床只需要在 JSON 里加一个节点不用改代码。采集服务启动时遍历CncDevices给每台设备创建一个采集任务。数据库写入可以用批量插入攒一批再写减少 IO 压力。5.2 扩展采集点从状态数据到刀具寿命和加工计数Demo 只采了转速、报警、坐标这几个基础字段实际产线还需要刀具寿命、加工计数、程序号、进给倍率等。扩展的方法是在CncCommand里加新的命令码在DataParser里加对应的解析方法在CncStatus模型里加字段。刀具寿命一般存在控制器的特定寄存器里读出来是剩余寿命百分比或者已用次数。加工计数通常是累加值读的时候要注意溢出处理。// 扩展采集读取刀具寿命和加工计数 public static int ParseToolLife(byte[] data) { // 假设刀具寿命在偏移量 202 字节大端短整型 if (data.Length 22) return -1; var bytes new byte[] { data[21], data[20] }; return BitConverter.ToInt16(bytes, 0); } public static int ParseMachiningCount(byte[] data) { // 加工计数在偏移量 224 字节大端整数 if (data.Length 26) return -1; var bytes new byte[] { data[25], data[24], data[23], data[22] }; return BitConverter.ToInt32(bytes, 0); }扩展的时候注意不同控制器的寄存器地址和数据类型可能不一样一定要拿手册核对。我一般会先写一个“寄存器扫描”的小工具把控制器里所有可读的寄存器都读一遍看看哪些有值、值的变化规律是什么然后再决定采哪些点。5.3 对接 MES 和看板数据推送的几种方式采集到的数据最终要送到 MES 或者车间看板上。常见的方式有三种一是直接写数据库MES 那边定时查二是发 MQTT实时性高适合看板三是调 REST API适合跟 Web 系统集成。Demo 里可以先把数据写 SQLite然后加一个 MQTT 客户端把数据转发出去。MQTT 的 Topic 按机床编号分比如cnc/CNC-01/status看板订阅所有机床的 Topic 就能实时刷新。// MQTT 推送示例 var mqttClient new MqttFactory().CreateMqttClient(); var options new MqttClientOptionsBuilder() .WithTcpServer(192.168.1.100, 1883) .WithClientId(CncCollector) .Build(); await mqttClient.ConnectAsync(options); // 采集回调里发消息 var payload JsonSerializer.Serialize(status); var message new MqttApplicationMessageBuilder() .WithTopic($cnc/{deviceName}/status) .WithPayload(payload) .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) .Build(); await mqttClient.PublishAsync(message);MQTT 的好处是解耦采集端只管发看板端只管收中间加个 Broker 就行。如果车间网络不稳定QoS 设成 AtLeastOnce 能保证消息不丢。数据库方案适合做历史追溯MQTT 适合做实时看板两个可以同时用。5.4 一个调试习惯先抓包再写解析这个项目里我踩过最大的坑就是解析偏移量对不上折腾了大半天。从那以后我每次对接新的控制器型号都强制走一遍“抓包—标偏移—写解析”的流程先用 Wireshark 或者程序里的调试模式把原始字节流抓出来存成十六进制文本然后对着通信手册一个字段一个字段标标完再写解析代码。标的时候把每个字段的偏移量、长度、数据类型、字节序都写在一张表里解析代码直接照着表写基本不会错。这个习惯帮我省了很多返工的时间也推荐你试试。希望帮到你。本文还有配套的精品资源点击获取