☰
C#实现的CNC数据采集系统:从通信到数据集
2026/10/16 9:05:56 网站建设 项目流程

简介:本资源是一套面向工业自动化与智能制造方向个人学习者的CNC数据采集系统实践材料,聚焦C#在数控机床实时监控、状态记录与故障诊断中的工程应用。资源包含完整可运行的Windows Forms项目源码(含9个核心.cs文件)、Fanuc数控系统通信支持库(fwlib32.dll等)、数据集定义文件(xsd/xsc/xss)、配置文件(App.config等)及项目构建元数据(csproj/sln),共29个文件,总大小652KB,结构清晰,便于理解工业通信、数据库绑定与UI交互的集成逻辑。已有162人下载学习,适合具备基础C#和.NET框架知识的学习者,通过阅读源码掌握CNC数据采集协议对接(如Focas库调用)、实时数据可视化、加工参数持久化存储等关键技术环节,并借助配套数据集开展真实场景下的数据分析与系统调试训练。 做数据采集这个行当久了,你会发现一个很有意思的现象:很多工厂买回来的CNC设备,联网率低得惊人。哪怕是两三台加工中心的小车间,老板也想搞清楚设备到底在干什么、开了几小时、停了几小时、有没有在空转。但这个需求落到技术层面,往往就卡在“不知道怎么把机床里的数据拿出来”。我这次分享一个用C#从零搭起来的CNC数据采集系统,附带完整的源码结构设计和一套可供算法训练的数据集格式,希望能给做车间数字化、MES系统对接或者设备运维的朋友提供参考。

先说明这套系统能干什么:它能把数控系统(以发那科、西门子、三菱常见系统为主)内部的运行状态、主轴负载、坐标位置、程序号、报警信息等数据,按秒级周期采集下来,存入SQL Server数据库,同时对外提供统计查询接口。采集下来的历史数据整理成结构化数据集后,可以用于OEE计算、设备利用率分析、主轴负载异常预警,甚至后续做基于机器学习的故障预测。

这个系统适合谁看?如果你是上位机开发、MES实施工程师、工厂设备管理员,或者正在为“怎么把CNC拉进数字化系统”这件事发愁,这篇文章能给你一条能直接落地的路线。下面我按项目拆解的顺序讲,尽量把每个关键选择背后的原因也说清楚。

1. 项目背景与整体设计思路

1.1 车间数据采集的现状和痛点

数控机床和普通设备最大的区别是,它本身自带一套完整的控制系统,系统里存着大量有价值的数据。但大多数机床出厂时,这些数据只显示在屏幕上,没有对外的接口习惯。工厂里常用的数据收集方式,我总结下来无非三种:

第一种是人工登记。操作工每班结束在纸质表单上填写开机时间、加工数量、异常情况。这种方式的问题不用多说,记录不及时、容易漏填、数据可信度差,老板拿到手也只能当参考。

第二种是加装传感器。在机床外部装电流互感器、震动传感器、三色灯采集器,通过外部信号推断机床状态。这种方案不用碰机床系统,实施简单,但只能读到间接信号,拿不到程序名、主轴负载、报警代码这类内部信息,想深入分析工艺时明显不够用。

第三种就是直接跟数控系统通信。通过网口或者串口,走数控系统提供的通讯协议,直接读取系统内部变量和状态字。这才是数据采集的正路,数据最全、实时性最好、准确度最高,但技术门槛也最高。

我一开始做这套系统时,就是冲着第三种方案去的。原因很现实:只有拿到底层数据,才能谈得上后续的工艺优化和预测性维护。如果只是采集个三色灯状态,那系统的天花板太低了。

1.2 为什么用C#而不是其他语言

很多做设备采集的工程师习惯用C++、LabVIEW或者Python,我在这套系统里选了C#,主要有四个原因。

第一个原因是Windows生态成熟。车间里的上位机、数据服务器绝大多数是Windows系统,C#的WinForm/WPF做采集监控界面非常顺手,部署也简单,一台工控机装上.NET运行时就能跑。

第二个原因是通信库丰富。无论是发那科的FOCAS库、西门子的S7协议,还是走OPC UA,C#都有对应的官方库或成熟开源封装,不需要自己从零啃协议栈。对于小团队来说,这一点能省下大量开发时间。

第三个原因是处理并发和数据库操作顺手。数据采集是典型的高频写入场景,一台设备一秒一条甚至一秒多条数据,几十台设备并发写入时,C#的异步编程模型和成熟的ORM/ADO.NET生态,能比较轻松地扛住。

第四个原因也很实际,C#开发人员好找。车间级的数字化系统通常需要后期持续维护,如果用了特别小众的语言或框架,人走了项目就黄了。C#在国内工控领域的保有量大,后续接手成本低。

1.3 整体架构链路

这套系统的物理架构不复杂,从机床到数据展示分四层:

  • 设备层:CNC机床通过以太网口接入车间交换机。走网络通信的前提是机床得有网口,近十年出厂的设备基本都具备,老设备可以通过串口转以太网模块解决。
  • 采集服务层:一台工控机或虚拟机上运行C#编写的Windows服务,负责按周期轮询每一台机床的数控系统,读取设定好的数据项,解析后写入数据库。
  • 数据存储层:使用SQL Server作为主数据库,按天分表存储采集明细数据,同时建有设备基础信息表、报警记录表、产量统计表。
  • 应用展示层:一个WinForm桌面程序和一个Web API接口。WinForm用于现场查看实时状态,Web API给MES系统或报表平台提供数据。

这个架构是比较常规的工业数据采集方案,好处是各层职责清晰,采集服务只管数据采集入库,展示层只做查询,互不干扰。即使某台机床断网或者采集服务重启,也不会影响已经入库的数据。

2. 采集核心模块源码拆解

2.1 通信层封装:FOCAS与OPC UA的取舍

通信层是整个系统最核心的部分,也是最容易踩坑的地方。不同品牌的数控系统提供的通信方式完全不同,我把常用方案梳理了一下:

数控系统品牌常见通信方案C#接入方式备注
发那科FOCAS/FOCAS2P/Invoke调用Fwlib32.dll发那科官方通信库,功能最全
西门子S7协议 / OPC UAS7netplus库 / OPC UA客户端840D sl以上系统支持OPC UA
三菱iQAP / CNC通信官方ActiveX或TCP直接解析老设备需要走串口
兄弟、马扎克等各自私有协议或OPC UA按厂家SDK对接一般都有现成库或文档

我这套系统里,主力对接的是发那科系统,因为车间里发那科设备的占比最高。FOCAS库的C#调用方式很直接,就是通过DllImport导入Fwlib32.dll里的函数。比如建立连接:

[DllImport("Fwlib32.dll")] private static extern short cnc_allclibhndl3(string ip, ushort port, int timeout, out ushort handle); [DllImport("Fwlib32.dll")] private static extern short cnc_rdspdl(ushort handle, out short load, out short speed); public ushort Connect(string ip, int timeoutMs) { ushort handle; short result = cnc_allclibhndl3(ip, 8193, timeoutMs, out handle); if (result != 0) { throw new Exception($"FOCUS连接失败,错误码:{result}"); } return handle; }

这里有两个容易踩的坑。第一个是端口号,发那科默认的以太网通信端口是8193,这个值在官方文档里有明确说明,不能随便改。第二个是超时时间,如果设备IP不通或者网络有延迟,connect函数会阻塞一段时间,建议把超时设短一点(比如3秒),失败后快速重试,避免采集线程卡死。

对于支持OPC UA的数控系统,我封装了一个通用的OPC UA客户端。C#这边可以基于OPC Foundation官方库或者轻量的OpcUaHelper开源库来实现。核心逻辑是配置好EndpointUrl、连接超时和安全策略,连接成功后按节点ID批量读取数据项。OPC UA的好处是各家系统支持得越来越普遍,而且读到的数据自带标准化语义,不用像FOCAS那样自己去查各种参数含义。

实际项目里,我通常会同时保留两套通信接口,通过配置文件选择走哪条通道。这样不同品牌、不同年代的机床可以在同一套采集服务里并存,不至于因为通信方式不同而被迫部署多套系统。

2.2 数据采集轮询与入库逻辑

数据采集的核心是一个定时轮询任务。每台设备注册一个采集工作项,系统按设定好的周期(通常是1秒到5秒)依次执行读取、解析、入库操作。

轮询周期不是越短越好。周期太短,数控系统通信被频繁请求,可能影响机床本身的运行,甚至导致系统报警;周期太长,又拿不到细粒度的运行数据,主轴负载的瞬时峰值捕不到。我的经验值是:主轴负载、转速、进给速度这类快速变化的数据,用1秒或2秒周期;坐标位置、刀具号、程序名这类慢变量,5秒读一次就够。这个配置可以在采集配置表里针对每台设备单独调整。

采集入库的代码逻辑大概是这样:

public async Task CollectAsync(CancellationToken ct) { while (!ct.IsCancellationRequested) { foreach (var device in _devices) { try { var data = await _reader.ReadDeviceDataAsync(device); await _repository.InsertAsync(data); } catch (Exception ex) { _logger.LogError($"设备{device.DeviceCode}采集失败:{ex.Message}"); } } await Task.Delay(_interval, ct); } }

这段代码看起来简单,但生产环境里需要注意几个细节。首先是并发度问题,一台一台顺序采集,设备多了以后单轮耗时很长;但并行采集又会给机床通信模块造成压力。我最后是采用分组串行、组内并发的方式,比如每10台设备一组,组内并行执行,每组之间串行等待,整体采集效率提升明显。

入库时我用的批量插入,而不是逐条Insert。因为一秒一条数据看起来不多,但30台设备连续跑一个月,数据量有几百万行。如果逐条Insert,数据库事务开销太大,后期查询会越来越慢。我封装了一个简单的批量写入方法,收集一定条数或一定时间后一次性提交到数据库。

2.3 设备状态判定与产量统计逻辑

拿到原始数据之后,不能直接存库就完事,还要做一层关键加工:设备状态判定。数控系统的运行状态字能告诉我们当前是自动运行中、手动操作、暂停还是报警,但这个状态字含义比较底层,直接展示给管理层看不懂。我这边会把状态映射为四个业务状态:运行、待机、停机、报警。

判定逻辑设计如下:

  • 运行:系统状态为自动运行中,或者主轴负载超过设定阈值(比如5%)且程序正在执行。
  • 待机:系统上电但未执行程序,主轴不转,无报警。
  • 停机:通信中断或机床处于关机状态,持续读不到数据。
  • 报警:读取到报警代码,或者系统状态字表示报警。

光靠状态字还不够可靠。我曾经遇到过一种情况:机床状态字显示“运行中”,但实际操作工按了进给保持,主轴已经停转了。这时候状态字并不能准确反映加工是否真的在推进。后来我在判定逻辑里加了主轴负载条件,只有当主轴负载大于一个阈值时才认为在真实加工。这个阈值不能设死,不同加工场景差异很大,我在配置表里给每台设备单独设了参数,实施时根据实际工况标定。

产量统计也是基于状态判定延伸出来的。最靠谱的做法是在加工程序里加宏变量,每加工完一件工件就自动计数,通过FOCAS或OPC UA读取这个计数变量。但很多现场的加工程序不受控,没有统一写宏变量。作为补充,系统可以用“主轴负载曲线”来识别加工循环:当主轴负载从高负载降到空载并持续几秒后又升上去,可以认为完成了一个加工循环。这个方法识别精度不是100%,但作为参考数据够用了。

3. 数据集构建与清洗

3.1 数据字段定义与采集周期设计

系统跑起来之后,真正值钱的是积累下来的数据。这些数据整理成数据集,可以用于各种分析。我在这里把数据集的结构定义一下,方便大家直接参考。

采集明细表的核心字段如下:

字段名类型说明采集周期
timestampdatetime数据采集时间-
device_idvarchar设备编号-
status_codeint业务状态:1运行、2待机、3停机、4报警事件/每秒
spindle_loadfloat主轴负载百分比1s
spindle_speedint主轴转速rpm1s
feed_speedint进给速度mm/min1s
x_pos / y_pos / z_posfloat三轴绝对坐标mm5s
program_novarchar当前程序号事件触发
tool_noint当前刀具号事件触发
alarm_codevarchar报警代码事件触发
part_countint当前产量计数事件触发

导出的CSV样例大概是这样:

timestamp,device_id,status_code,spindle_load,spindle_speed,feed_speed,x_pos,y_pos,z_pos,program_no,tool_no,alarm_code 2025-01-18 08:00:01,MC001,1,45.2,8000,1500,123.456,45.678,89.012,O1001,12, 2025-01-18 08:00:02,MC001,1,46.1,8000,1500,123.458,45.679,89.013,O1001,12, 2025-01-18 08:00:03,MC001,1,44.8,8000,1500,123.460,45.680,89.015,O1001,12, 2025-01-18 08:00:30,MC001,4,0.2,0,0,125.300,46.120,92.000,O1001,12,ALM1023

有些读者可能奇怪,为什么报警信息不是单独一张表?实际采集时,报警代码是数控系统内部的当前报警状态,同一个时刻可能有多条报警。我为了简化处理,在明细表里只存了第一个报警代码,同时单独建了一张报警记录表,专门记录报警发生和消除的时间点。明细表用于趋势分析,报警表用于统计停机原因,两张表配合使用。

3.2 数据清洗的原则和步骤

原始采集数据直接拿来用是不行的,至少有两类脏数据必须处理。

第一类是通信抖动导致的异常值。比如网络闪断又恢复时,可能会读到0或者一个极大值。我的处理原则是:对物理量设置合理范围,主轴负载超过100%或者小于0的,直接标记为无效;坐标位置突变超过设定范围(比如一秒内移动超过100mm,明显不合理的),保留原始值但添加标记字段,分析时按需过滤。这里要特别注意,不要轻易把异常值直接删除,因为报警和异常本身就是有用的事件信号。我的做法是额外加一列quality_code,0表示正常,1表示可疑,2表示无效。

第二类是重复数据和长时间零值区间。机床长时间停机时,主轴负载会一直是0,这些数据本身有价值,但不需要一秒一条存储。我在清洗脚本里加了降采样逻辑:连续运行状态下数据按秒保留,连续停机状态下压缩为5分钟一条。这样数据集体积能缩小一半以上,分析精度基本不受影响。

补充说明一下,数据清洗不要做成一次性脚本,最好是采集服务落库之后,定期跑一个清洗流程,把清洗结果写入单独的数据集市表。这样做的好处是,原始明细数据永远保留,后续发现清洗规则有问题可以重新清洗,不至于原始数据已经被污染了。

3.3 数据集的应用场景

数据集建好了,能干什么?最基础的应用是OEE计算。

OEE是设备综合效率,公式是:OEE = 时间开动率 x 性能开动率 x 合格品率。时间开动率来自设备状态数据,用实际运行时间除以计划开机时间;性能开动率可以通过实际产量和理论节拍估算;合格品率一般要对接质检数据,如果暂时没有质检系统,可以先用良品数除以总产量。有了秒级状态数据,时间开动率和设备空闲原因可以自动统计出来,比人工记录准确太多。

再往深了走,数据集可以做设备异常预警。主轴负载是最容易做预测的信号,在正常加工循环里,主轴负载曲线是有规律的。如果连续多个循环中,负载曲线出现整体抬升或者周期性尖刺,大概率是刀具磨损或者主轴轴承开始出问题了。我跑过一个简单的方案:用历史正常数据训练一个负载基线,实时数据跟基线对比,偏差超过阈值时产生预警。这个用随机森林或者STL时序分解都能做,数据量和质量足够的话效果不错。

数据集还可以用于工艺参数分析。比如分析某一类零件加工时,切削参数和主轴负载的关系,找到负载过高或者震刀风险大的参数组合,辅助工艺工程师优化切削参数。这类分析不需要很复杂的算法,用统计工具做相关性分析和聚类就能发现不少线索。

4. 部署过程中的几个关键问题

4.1 网络隔离与网关部署

CNC机床所在的车间网络一般不能直接连接到办公网,安全上不允许,工控网和办公网通常有隔离要求。但数据采集服务往往部署在办公网一侧的服务器上,这就产生了一个网络打通的问题。

我这次部署采用的方案是加一台工业网关,网关一侧接车间设备网,另一侧接办公网,只开放特定端口的数据转发。网关上加白名单规则,除了采集服务所在服务器的IP和端口,其他流量一律拒绝。配置简单,但安全性提升明显。

如果车间设备本身没有以太网口,需要走串口转以太网模块。选模块的时候要留个心眼,有些廉价模块的透传机制不稳定,数据容易丢。我用的方案是买工业级串口服务器,工作模式设置为TCP Server,C#采集端作为TCP Client主动连接,断线自动重连。这个模式比TCP Client模式稳定得多。

4.2 时间同步与数据准确性

数据采集中一个容易被忽视的问题是机床时间不一致。数控系统的时钟可能会走偏,设备时间和服务器的采集时间对不上,会导致时序分析出错。

我的做法是采集服务不以机床时间作为数据时间戳,统一以服务器本地时间作为标准。也就是读取数据时,时间戳用服务器当前时间,而不是设备返回的时间。这样所有设备的数据在时间轴上是对齐的。另外机房服务器配置了NTP时间同步,确保服务器本身的时间是准的。

还有个细节是跨天换班时的数据归属问题。有些工厂的班次不是0点切换,比如白班8点到20点,夜班20点到次日8点。如果只按自然日统计产量,跟实际班次对不上。我这边在产量统计时支持自定义班次时段,按班次时段来切分数据和计算产量,跟车间考核体系保持一致。

4.3 并发采集与系统性能

系统上线初期只有3台设备,一切顺利。后来扩到30台之后,出现了两个明显问题:一是采集轮询一圈的时间变长,部分设备的数据延迟超过预期;二是SQL Server写入压力变大,偶尔出现死锁。

针对延迟问题,我调整了采集调度策略。原来是一台接一台串行采集,改成按设备分组并行采集。分组的原则是同一品牌的设备不要放在同一组,避免同时向同一类型的数控系统发起通信请求,减少系统侧排队。调整后,30台设备单轮采集时间从20多秒降到了5秒以内。

针对数据库死锁问题,主要原因是多条采集线程同时写入同一张表。我改成了单写入队列模式,所有线程采集到的数据统一投递到一个内存队列,由一个专用后台线程批量落库。队列消费不过来时,数据先暂存在本地文件缓存,避免内存占用过高。这个方案实施后,死锁问题再没出现过。

5. 常见问题与排查技巧

5.1 通信断断续续,很不稳定

这是CNC采集项目里最常遇到的问题,现象是采集服务日志里经常出现连接失败、读取超时,或同一台设备数据时有时无。

排查思路分几个方向。先Ping设备IP,确认网络层面通不通;再检查设备网口和交换机端口是否是百兆/千兆自适应模式,有些老机床配的是百兆网卡,如果接在千兆交换机上出现双工不匹配,就会导致通信不稳定;还要确认设备IP是否冲突,车间设备IP冲突非常常见,一冲突就时好时坏。

如果网络没问题,再看采集超时设置。FOCAS的connect超时和read超时都必须配置,而且要设得合理。超时太短容易误报,太长会拖慢轮询。经验值是连接超时3秒、读取超时2秒,单次失败后触发重连,连续3次失败才判定设备离线。

5.2 数据读出来了,但明显不对

常见的情况是主轴负载一直是0或者一直是100。最先要确认的是读的地址对不对。发那科的系统变量地址,不同版本之间可能有细微差别,要以现场系统的版本为准。

还有一种情况是系统里有数据,但C#解析出来是负数或者乱码。这通常是数据类型匹配问题。比如系统返回的是16位有符号整数,你按无符号整数解析,就会出错。调试时先写一个简单的读取程序,把原始返回值打出来,再比对系统屏上的实际显示值,确认类型和缩放系数都对上了,再去做批量解析。

5.3 数据库越来越大,查询越来越慢

采集系统跑几个月后,明细表随便就有几千万行。查询时如果没有索引,一个简单的按时间范围查询都可能要几十秒。

解决思路是分表和索引并重。我这边是按天分表,每天一张采集明细表,查询时按日期定位到具体表,单表数据量控制在几十万行以内,查询速度很快。同时建了复合索引:device_id + timestamp,这是最常用的查询条件,执行计划能稳定走索引。

分表后有一个新问题:如果需要跨天查询一个月的数据,代码层要做表名拼接和结果合并。我在数据访问层封装了一个GetTableNameByDate方法,根据查询日期自动定位表名,上层调用无感知。

最后再说一个我自己的习惯:所有采集字段在中转处理时,尽量保留原始值,不做过度加工。很多后来发现有用的分析维度,都是最初采集时多保留了一个字段才有的。宁可数据多一点,也不要等需要的时候再回去找历史记录,那时候已经晚了。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询