拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

三菱CNC数据采集实战:FOCAS库与C#完整指南

三菱CNC数据采集实战:FOCAS库与C#完整指南

车间里那批三菱加工中心要接上位机,让我去弄数据采集,当时第一反应是这事应该不复杂,结果一查资料才发现到处是坑。网上的案例要么是卖方案的不讲原理,要么就扔给你一句"用FOCAS库",剩下全部自己摸索。这篇文章我把自己从环境搭建到代码实现、再到数据落地的完整过程整理出来,包括三菱FOCAS2通信库的选型逻辑、数控系统侧的参数设置、C#调用DLL的完整Demo,以及报警、宏变量、运行状态这些真正对车间管理有用的数据怎么采。不管你是刚接触CNC采集的工程师,还是想给老设备做数据化改造的维护人员,照着这条链路走一遍,基本能跑通。

1. 先把FOCAS这条链路看清楚:协议、接口和数据从哪来

做三菱CNC数据采集,第一个绕不开的东西就是FOCAS。FOCAS是Factory Open Computer Aided System的缩写,是三菱电机为数控系统开放以太网通信而设计的一套应用程序接口库。简单说,三菱已经把通信协议封装好了,我们不需要自己写TCP/IP报文去解析,更不需要动机床的梯形图,只需要在PC上引入FOCAS的动态链接库,按API文档调用函数,就能读到坐标、程序、报警、宏变量、主轴信息等一系列数据。

这条数据链路从物理上看是这样的:CNC控制器自带的以太网口,通过网线连到交换机,再连到PC或工控机。三菱CNC侧相当于一个TCP Server,PC作为Client主动发起连接。FOCAS库则工作在应用层,完成数据请求和应答的封装。整个链路上最关键的节点就是那个以太网口,所有数据都从这进进出出。

用FOCAS之前先确认机床型号。三菱M700、M70、M80、M800这些系列都支持FOCAS2,通信协议是标准的以太网TCP连接,默认端口8193。如果是特别老的M64、M65系列,要用的不是FOCAS2,而是更早的EZSocket库,接口完全不同,参数设置也不一样,需要单独处理。我做项目的车间里主要是M70和M80,所以下面所有内容都按FOCAS2来展开。

FOCAS能读的数据种类很多,我列一下实际项目里最常用的函数,方便后面代码部分对照:

API函数功能说明典型用途
cnc_allclibhndl3通过IP和端口建立连接,返回句柄采集程序启动时建立连接
cnc_freehndl释放连接句柄程序退出或断线时释放资源
cnc_rdactual读取实际坐标(绝对、机械、相对、剩余移动量)实时位置监控
cnc_rdmacro读取宏变量读刀具寿命、工件计数等
cnc_rdalarm2读取报警信息故障预警与停机原因分析
cnc_rdspdl读取主轴实际转速加工状态判断
cnc_rdtimer读取运行时间设备稼动统计
cnc_rdstat读取系统状态判断设备是否处于自动运行

这里要特别纠正一个常见误区:有人以为FOCAS采集和MODBUS一样,需要去配置寄存器地址,其实完全不是一回事。FOCAS走的是"函数调用"模式,你要读什么,就调用对应的API函数,数据以结构体的形式返回,省去了自己拼报文的麻烦。

另外有个选择问题值得说。三菱机床做数据采集还有另外几条路:一是通过PLC的IO点或Ladder程序把数据写到某个寄存器,再走MC协议或MODBUS转发出来;二是外接传感器取物理信号;三是在机床侧加装硬件采集模块。这些方案不是不行,但各自的限制都很明显——PLC转发需要改梯形图,加传感器成本高且只能采集部分数据,硬件模块往往无法读取系统内部的坐标、报警、宏变量。相比之下FOCAS是官方协议支持,数据最全、不改机床逻辑,而且是免费的,只需要去申请库文件。

2. 环境搭建里最容易卡住的三个环节:库申请、IP设置、端口放行

2.1 FOCAS库的获取与安装

FOCAS库不是挂在官网公开页面上让人随便下载的,需要到三菱电机自动化的官网或者渠道平台申请,填写产品信息和使用用途,隔一两天审核通过后给你一个下载链接,里面包含FOCAS Library的安装包和完整的通信手册。如果机床采购时有配套的光盘,里面也经常带着。我们是联系机床代理商的售后工程师要到的,说明是要做数据采集对接,一般都会给,厂商也乐见客户用官方接口。

安装过程本身不复杂,一路Next。安装完成后需要注意:安装目录下会生成Fwlib32.dll和Fwlib64.dll两个动态库,文件名带32或64不代表只能给32位或64位程序用,而是对应CPU架构。写代码之前,先确认你的程序是编译成x86还是x64,然后加载对应位数的DLL。这一点我在后面踩坑章节还会细说,因为很多人在这里栽过跟头。

2.2 数控系统侧的以太网参数配置

这一步极其容易忽略,但做不对,PC怎么连都是白搭。三菱CNC出厂时以太网口不一定处于开启状态,IP地址默认值也可能和你的生产网段冲突。M70和M80上操作路径大致如下:在机床操作面板上进入维护界面,找到"以太网参数"或者"Ethernet Setup"菜单,配置IP地址、子网掩码,确认端口号是8193。有的系统要求修改后重启系统才能生效,改完别急着连,先重启一下。

IP地址规划要严格和PC侧在同一个网段。比如机床设192.168.1.10,PC就设192.168.1.20,子网掩码255.255.255.0,不要跨网段。车间里如果已有MES网络,最好提前申请一个固定的IP,不要用DHCP动态分配——CNC系统里一般也不支持自动获取,或者获取了之后重启会变,采集端会断线。

这里有个小细节:一台CNC在同一时间能接受的FOCAS连接数量有限制,一般是8个。如果你同时开了两三个调试工具连同一台机床,后面再连就会提示连接失败。调试时务必养成用完就释放的好习惯。

2.3 PC侧连通性检查清单

PC侧需要确认的事就三件:库文件位数匹配、防火墙放行、网络连通性。

网络连通性是最先要验证的。在PC上ping机床的IP,看能不能通。ping不通,后面所有事都不用做,先查网线、查交换机、查IP。ping通了之后,再用telnet测试TCP 8193端口是否可达:

telnet 192.168.1.10 8193

如果端口通,屏幕通常会出现一个空白命令窗口或者直接清空,说明TCP链路没问题。如果连接失败或超时,大概率是Windows防火墙拦住了——FOCAS的连接是PC主动发起,但同时也有可能出现系统侧防火墙或杀毒软件拦截的情况。解决办法是把对应程序加入防火墙入站规则白名单,或者采集程序第一次运行时弹窗允许访问网络,点允许即可。

我把环境准备阶段需要检查的项目整理成了清单,调试时按顺序过一遍,能省掉很多无谓的排查时间:

检查项验证方式预期结果
CNC以太网口已开启维护界面查看以太网状态显示已连接或链路激活
IP在同一网段PC命令行ping机床IP返回对答(TTL响应)
8193端口可访问telnet 机床IP 8193连接不报错
FOCAS库已安装查看安装目录下DLL存在Fwlib32/Fwlib64.dll
DLL位数与程序匹配确认编译目标平台x86程序用32位库,x64程序用64位库
防火墙放行查看Windows入站规则对应程序已允许
连接数未占满用调试工具逐个测试机床最多支持8个会话

3. 用C#跑通第一个三菱CNC采集程序:连接、读坐标、释放句柄

3.1 为什么选择C#

工业上位机制造领域,C#的普及度非常高。MES系统、数据看板、设备监控软件,很大比例都是.NET技术栈。C#调用FOCAS这种非托管DLL非常方便,用DllImport特性声明外部函数,加上结构体封送,几十行代码就能跑通最小Demo。相比C++,C#的托管内存管理和调试体验对大多数工厂信息化工程师来说更友好;相比Python,C#在Windows服务部署、性能稳定性、与数据库和Web框架集成方面都更成熟。所以我的建议很直接:做三菱CNC采集,首选C# + .NET。当然你有惯用的别的语言比如Go或者Java,也可以通过CGO/JNA来调用,但那是进阶玩法,不是首推路径。

3.2 最小可运行示例:连接、读坐标、释放

下面这个示例,是能跑通的最小完整程序,功能是连接机床、读取一次绝对坐标、然后释放句柄。代码基于.NET 6,任意控制台项目都适用。

先定义DLL导入声明和数据结构:

using System.Runtime.InteropServices; namespace CncFocasDemo { public class FocasApi { // 建立连接:IP地址、端口、超时时间(秒),返回句柄 [DllImport("Fwlib32.dll", EntryPoint = "cnc_allclibhndl3")] public static extern short cnc_allclibhndl3( string ip, ushort port, short timeout, out ushort handle); // 断开连接 [DllImport("Fwlib32.dll", EntryPoint = "cnc_freehndl")] public static extern short cnc_freehndl(ushort handle); // 读取实际坐标 [DllImport("Fwlib32.dll", EntryPoint = "cnc_rdactual")] public static extern short cnc_rdactual(ushort handle, ref ODBACT position); } // ODBACT 结构对应 FOCAS 手册中的实际位置数据定义 [StructLayout(LayoutKind.Sequential)] public struct ODBACT { public short dummy; public short alarm; // 报警状态 public short prgnum; // 程序号 public short mfgrn; // 程序段号 public int datal; // DNC运行时剩余数据长度 public int datas; // DNC运行时数据大小 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public double[] absolute; // 绝对坐标 X, Y, Z [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public double[] machine; // 机械坐标 X, Y, Z [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public double[] relative; // 相对坐标 X, Y, Z [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public double[] distance; // 剩余移动量 X, Y, Z } }

然后写主程序:

class Program { static void Main() { // 填入你机床的实际IP string cncIp = "192.168.1.10"; ushort cncPort = 8193; ushort handle = 0; // 1. 建立连接 short ret = FocasApi.cnc_allclibhndl3(cncIp, cncPort, 5, out handle); if (ret != 0) { Console.WriteLine($"连接失败,错误码:{ret}"); return; } Console.WriteLine($"连接成功,句柄:{handle}"); try { // 2. 读取实际坐标 ODBACT pos = new ODBACT(); ret = FocasApi.cnc_rdactual(handle, ref pos); if (ret == 0) { Console.WriteLine( $"绝对坐标 X={pos.absolute[0]:F3} Y={pos.absolute[1]:F3} Z={pos.absolute[2]:F3}"); Console.WriteLine( $"机械坐标 X={pos.machine[0]:F3} Y={pos.machine[1]:F3} Z={pos.machine[2]:F3}"); } else { Console.WriteLine($"读取坐标失败,错误码:{ret}"); } } finally { // 3. 释放句柄,这步无论如何都要执行 FocasApi.cnc_freehndl(handle); Console.WriteLine("连接已释放"); } } }

运行后输出大致是:

连接成功,句柄:1 绝对坐标 X=-123.456 Y=88.321 Z=0.000 机械坐标 X=-120.111 Y=90.333 Z=-0.002 连接已释放

这段代码里面有三个核心点需要理解:

第一个是cnc_allclibhndl3的第三个参数timeout,单位是秒,也就是PC等待CNC应答的最长时间。设太短,CNC系统忙时可能超时返回错误;设太长,调试时等半天才报错。我习惯设5秒,对这个场景足够。

第二个是结构体的MarshalAs。ODBACT中absolute、machine、relative、distance都是固定大小为3的double数组,对应XYZ三轴。C#里必须用UnmanagedType.ByValArray明确标注,否则拿不到数据。不同型号的坐标轴数可能不一样,4轴5轴设备用这个结构体会只读到前3轴。

第三个是句柄必须在finally里释放。CNC侧连接数是有限的,如果程序每次都连不释放,几次之后机床就"拒绝连接"了,而且这个状态会持续到CNC那边超时清理,比较麻烦。一定要把cnc_freehndl写在finally里,不管中间有没有异常,都要释放。

3.3 连接失败时快速排查顺序

看到错误码非0,先别急着改代码。我的排查顺序是:

第一步,确认返回值是不是"端口不通"类错误,用telnet再测一次8193端口。端口不通查防火墙、网线、IP,大概率不是代码问题。

第二步,确认是不是"参数错误",检查IP地址字符串有没有拼错,端口是不是8193,holdTimeout是不是在合理范围。

第三步,确认机床侧是不是已经有其他人占用了连接数。可以问车间操作工,也可以把之前调试开的程序全部关掉再试。

第四步,如果是M70系列老系统,确认是不是开启了"仅允许特定IP连接"之类的安全限制,需要在机床侧以太网参数里把PC的IP加进白名单。

4. 从Demo到能落地的采集服务:轮询策略、解耦存储、异常自愈

跑通了单个读取函数,离真正能用的采集系统还差一大截。车间需要的是每隔几秒自动采一次数据、断电重连、数据落库、上层能通过接口查询历史的完整服务。这一章讲的就是把Demo改造为可部署的采集服务时,需要想清楚的几个设计问题。

4.1 轮询频率:不同类型数据要分频采集

很多初做采集的人会陷入一个误区:为了实时性,把轮询间隔调到100毫秒,结果把CNC系统搞得很吃力,甚至出现系统响应变慢或者报警的情况。原因在于,FOCAS库的每次数据请求都需要CNC系统分配资源来处理,频率太高对老系统负担很大,而且多数应用场景根本不需要那么高的频率。

我实际项目里用的是分频采集策略:

  • 坐标位置:1秒采样一次,用于实时位置显示和轨迹回放,1秒精度足够。
  • 运行状态和程序号:1秒采样一次,与坐标同批次读取。
  • 报警信息:200~500毫秒扫描一次,报警必须及时发现,但太快也没意义,人不可能在毫秒级做出反应。
  • 宏变量:2~5秒读一次,宏变量大多表达的是刀具寿命、工件计数这类变化频率低的数据。

在同一个轮询周期内,把坐标、状态、程序号、主轴数据一次性读完,然后分别推送出去。这样对CNC的压力最小,也不会丢失关键数据。

4.2 采集服务的整体结构

不要把所有逻辑都塞进一个while循环里。我推荐的架构是:采集线程 + 内存队列 + 存储线程 + 查询接口。

采集线程负责按固定频率调用FOCAS读取数据,把结果打包成结构化对象,写入一个内存队列(推荐.NET的Channel,底层是线程安全的并发队列)。存储线程从队列里取数据,批量写入数据库。查询接口负责对外提供数据访问。

这样做的好处是:采集线程不等待数据库写入,即使数据库临时不可用,数据也能暂存在内存队列里排队,不会丢数据也不会卡住采集循环。下面是一个完整的采集循环代码骨架,直接写在一个控制台服务里就可以跑:

using System.Threading.Channels; public class FocasCollector { private readonly string _ip; private readonly ushort _port; private readonly Channel<MachineSnapshot> _channel; public FocasCollector(string ip, ushort port) { _ip = ip; _port = port; _channel = Channel.CreateUnbounded<MachineSnapshot>(); } public async Task RunAsync(CancellationToken token) { while (!token.IsCancellationRequested) { ushort handle = 0; short ret = FocasApi.cnc_allclibhndl3(_ip, _port, 5, out handle); if (ret != 0) { Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 连接失败,5秒后重试"); await Task.Delay(5000, token); continue; } try { while (!token.IsCancellationRequested) { try { ODBACT pos = new ODBACT(); ret = FocasApi.cnc_rdactual(handle, ref pos); if (ret == 0) { var snapshot = new MachineSnapshot { Time = DateTime.Now, X = pos.absolute[0], Y = pos.absolute[1], Z = pos.absolute[2], MachineX = pos.machine[0], MachineY = pos.machine[1], MachineZ = pos.machine[2] }; await _channel.Writer.WriteAsync(snapshot, token); } else if (ret == -17) // 连接断开类错误 { break; } await Task.Delay(1000, token); // 1秒轮询 } catch (Exception ex) { Console.WriteLine($"采集异常:{ex.Message}"); break; } } } finally { FocasApi.cnc_freehndl(handle); } } } }

这里要说明一下:当连接断开或者返回特定错误码时,内层循环直接跳出,然后让外层循环重新建立连接,这就实现了简单的断线重连。重试间隔建议3~5秒,太频繁会导致日志刷屏,也会给CNC侧造成不必要的连接风暴。

4.3 数据存哪里:从SQLite到时序数据库

采集服务跑起来后,数据要落库。针对车间常用的三种存储方案,我给个实际的选型建议:

  • 单机监控、数据量小:用SQLite,零配置,文件型数据库,部署最简单,存几百万条完全无压力。
  • 车间多台设备、需要Web查询和报表:用MySQL或PostgreSQL,配合采集程序直接写库。
  • 长时间高频采集、需要做趋势分析和稼动率统计:推荐时序数据库,比如InfluxDB或TDengine,数据压缩率高,聚合查询方便,而且按时间维度建索引对这类场景效率非常高。

无论选哪个,表结构设计都要注意:必须包含设备标识、时间戳、坐标值、状态字段。设备标识很重要,因为将来同一套采集服务可能会管多台机床,没有设备标识数据就全混了。

建表的SQL大概是这样的:

CREATE TABLE cnc_snapshot ( id BIGINT AUTO_INCREMENT PRIMARY KEY, device_ip VARCHAR(32) NOT NULL, device_no VARCHAR(32), capture_time DATETIME NOT NULL, x DOUBLE, y DOUBLE, z DOUBLE, machine_x DOUBLE, machine_y DOUBLE, machine_z DOUBLE, spindle_speed INT, feed_rate INT, program_no VARCHAR(16), status VARCHAR(16), INDEX idx_device_time (device_ip, capture_time) );

存储线程从Channel里批量取数据(一次取几十条),用一个事务批量插入。相比一条条插入,性能提升非常明显。

4.4 采集中断恢复与进程保护

工业现场最怕的不是功能做不出来,而是采集服务连跑几周之后莫名挂掉,还没有人注意到。所以采集服务一定要做这几件事:

  1. 日志落地:程序的控制台输出同时写文件日志,排障时能看到停在哪一步。
  2. 进程守护:用Windows服务或NSSM把采集程序注册成系统服务,设置服务失败自动重启。
  3. 数据完整性检查:启动时和每天定时各查一次数据库最近记录时间,如果发现数据中断超过设定阈值,发告警通知。最简单的告警方式是发微信或短信,或者写入一个状态表让外部监控系统巡检。
  4. 内存队列溢出防护:如果数据库持续不可用超过一定时间,队列会越积越长,最终内存爆掉。此时要控制队列上限,达到上限时丢弃旧数据保留新数据,并标记一个"数据丢失"日志。

5. 报警、宏变量和运行状态:让数据真正在车间用起来

坐标采集只是把设备"动起来"的状态上传了,但车间管理真正关心的是停机原因、产量统计、刀具寿命这些东西。这一章讲怎么把这些业务数据用FOCAS读出来。

5.1 实时报警监听与停机原因分析

报警数据是车间设备管理中优先级最高的数据之一。读报警的API是cnc_rdalarm2,返回结构体包含报警类型、报警编号、所在轴等信息。采集逻辑上,不是每次都把报警文本拉下来,而是先看报警编号有没有变化,如果同一报警编号持续存在,就只在变化时记录一条报警事件,避免数据库被重复报警刷爆。

一次典型的报警处理流程是:

  1. 轮询周期内调用cnc_rdalarm2。
  2. 判断返回的报警编号,0表示无报警,非0表示有报警。
  3. 如果有报警,和最近一条报警记录比较:是新报警就插入一条报警记录,记录设备号、报警号、报警文本、开始时间;还是同一个报警就只更新该报警的最后确认时间,不重复写入。
  4. 报警解除后,更新该报警记录的结束时间,计算报警持续时长。

报警文本的翻译需要参考三菱报警手册。M70系列常见报警比如"Emergency Stop"是急停触发、"Spindle Alarm"是主轴异常,把这些编号和中文含义维护成一张码表,采集程序查表转换后写入数据库。码表维护虽然琐碎,但价值非常大,现场无一例外都要做这一步。

5.2 宏变量:产量统计的利器

宏变量是三菱CNC中一个非常灵活的功能。机床侧的宏程序可以把刀具使用寿命、工件加工数量、加工循环时间等业务数据写入公共变量(保持型变量范围一般是#500到#999),然后PC端通过cnc_rdmacro函数把这些变量读出来。

举个例子:机床侧的加工程序末尾执行了#501 = #501 + 1,意思是一刀活做完了,累计加工数量加1。PC每隔几秒读一次#501,就能实时知道这台机床今天做了多少个活。再比如刀具管理,操作工换刀后在宏程序里给#610赋一个2000的值,表示新刀片寿命是2000个工件,之后每加工一个件宏程序自动减1,PC读到这个值就可以实时展示刀具剩余寿命,提前预告换刀。

读取宏变量的C#代码和读坐标类似:

[StructLayout(LayoutKind.Sequential)] public struct ODBMRD { public short dummy; public short datano; // 宏变量编号 public short type; // 值类型 public int data; // 实际数值 } [DllImport("Fwlib32.dll", EntryPoint = "cnc_rdmacro")] public static extern short cnc_rdmacro(ushort handle, ref ODBMRD macro); // 调用示例:读取宏变量#501 ODBMRD macro = new ODBMRD(); macro.datano = 501; short ret = FocasApi.cnc_rdmacro(handle, ref macro); if (ret == 0) { Console.WriteLine($"宏变量#501 = {macro.data}"); }

宏变量这个功能用好之后,产量统计比任何外部传感器方案都准,而且数据直接来自CNC内部,完全可靠。

5.3 主轴转速与进给速度:判断真实加工状态

只看坐标数据判断不了一台设备到底是在切屑加工还是空转,但结合主轴转速、进给速度、运行模式这几个状态就能准确推断。主轴转速大于0并且进给速度大于0,基本可以判定设备在加工中;主轴在转但进给为0,可能在快速定位或暂停;两者都停,设备处于待机或程序结束状态。

把这些状态组合出炉后,就能做非常实用的稼动率分析:一天的统计周期里,设备真正在加工的时间占比是多少。这部分数据不需要什么高级算法,但需要采集端保证数据连续、时间戳准确,所以前文说的断线重连和数据库可靠性就变得非常关键。

5.4 不做FOCAS的几条备选方案

不是所有三菱机床都适合走FOCAS。遇到个别老古董系统或者以太网接口损坏的机床,可以考虑备选方案:通过PLC输出点位状态,把关键信号(运行中、报警、暂停)通过硬接线或MODBUS转出来;或者对于注塑机等非CNC设备,直接用IO采集模块监测运行信号。如果目标是采集注塑机数据,它的控制器和CNC不一样,很多注塑机本身支持MODBUS或OPC UA,走通用协议比FOCAS更合适。所以遇到不同设备,先看清控制器支持哪种协议,不要拿着锤子看什么都像钉子。

6. 避坑实录:这些坑我踩过,希望你绕开

最后这一章,把我实际项目中踩过且最有代表性的几个坑整理出来。有些坑在文档里根本不会写,只有现场被坑过才知道有多难受。

6.1 DLL位数不匹配导致各种莫名其妙错误

FOCAS的DLL分32位和64位。如果程序编译成x64,却加载了x86版本的Fwlib32.dll,调用时会直接抛BadImageFormatException,或者程序启动就崩。反过来也一样。这个问题看着简单,但在现场调试时特别容易忽略,因为有的安装包里Fwlib32.dll是32位的,Fwlib64.dll是64位的;而另一台电脑上装的是老版本安装包,里面两个文件位数完全反了,极其误导。

对策:正式开发前,在代码里加一段初始化检查,加载DLL后打印架构信息,或者干脆在部署文档中写明必须使用哪种版本的DLL。用.NET的话,可以在.csproj里指定RuntimeIdentifier,编译时强制x86或x64,别选AnyCPU。

6.2 句柄泄漏:连接满了之后新程序永远连不上

这是最坑的一个问题。如果你在调试过程中频繁开关程序,连接句柄没有正确释放,一段时间后CNC侧的可连接会话数会被占满。这个时候你再去连接,返回一个连接失败的错误码,而且无论怎么重启你的PC程序都没用,因为占用会话的是CNC系统侧的资源,它不会因为你这边程序退出就立刻释放。

解决办法有两个层次。程序层面,务必把cnc_freehndl放在finally里,异常路径也要释放;同时给程序加一个退出事件钩子,保证Ctrl+C或杀进程时也能走释放逻辑。如果已经出现了会话被占满的问题,一般是等CNC系统内部超时自动回收,时间不定,有的系统十几分钟后自动清了。也可以尝试在机床侧断电重启,这是最后手段。

6.3 FOCAS库不是线程安全的

FOCAS dll内部没有做线程保护。如果开多个线程同时去调同一个句柄的接口,会出现返回值错乱、数据不对、甚至程序崩溃。我见过有同事用并行Task去同时读坐标和报警线程,结果读回来的坐标一会儿对一会儿不对,排查了很久才发现是并发导致的。

对策就一条:一个进程内只用一个轮询线程访问FOCAS接口。所有其他线程如果需要数据,通过消息队列或共享内存方式从轮询线程拿,不要自己直接调DLL。如果确实要管理多台机床,每台机床独立一个采集线程,各自持有一个句柄,互不干扰,这样是安全的,因为不同句柄之间的DLL内部数据不会共享。

6.4 中文乱码:程序名、报警文本编码要转换

FOCAS接口返回的字符串类数据(程序名、NC程序内容等),本身是不做编码转换的,直接给原始字节数组。日系系统内部常用Shift-JIS编码,而国内Windows系统默认代码页是GBK或UTF-8。如果你直接按ASCII或UTF-8解码,出来的程序名是一堆乱码。

解决方法是在解析字符串时明确指定编码方式,把原始字节数组按Shift-JIS解码后再转成GBK/UTF-8:

string programName = Encoding.GetEncoding("shift-jis") .GetString(rawBytes).Replace("\0", "").Trim();

读NC程序内容时同样要这样处理。这个问题不遇到确实想不到,遇到了很容易卡半天。

6.5 轮询频率过高:老系统扛不住的

前文提到过分频采集策略,这里再加一个现场实例。曾经在M70老系统上做测试,同事为了追求数据平滑把轮询间隔设成了20毫秒,结果机床正常加工时没异常,一到快速移动就出现系统报警。后来把间隔调整到500毫秒以上,报警消失。其实坐标位置的平滑度完全可以通过前端曲线插值来做,没必要用高频轮询去怼系统。三菱的以太网处理模块在繁忙时会降级响应,严重情况下甚至影响插补。这个红线不要碰,宁可丢一点数据实时性,也不能影响机床正常运行。

另外还有一个小坑:Timeout参数如果设得太短(比如1秒),在CNC系统忙碌时容易出现偶发超时。如果你发现采集程序隔一会儿就会报一次超时错误,但网络又是通的,优先把这个超时时间调成5秒或更大,往往能解决问题。

6.6 常见错误码与调试经验速查表

现象可能原因排查手段
连接超时机床IP不通、防火墙拦截、端口错误ping测试、telnet测试、检查防火墙
连接拒绝会话数已满、机床侧限制IP关闭无用会话、检查机床白名单
调用崩溃(进程挂掉)DLL位数不匹配、结构体封送错误检查x86/x64、核对结构体定义与手册
数据乱码编码未转换按Shift-JIS解码后转GBK
偶发超时轮询过快、CNC系统繁忙调低轮询频率、增加timeout
读不到部分轴坐标结构体SizeConst与实际轴数不符按实际轴数调整数组长度

最后说一个我自己的体会:做CNC数据采集,代码能跑通只是第一步,真正有价值的是把采集系统做成一个稳定、可持续运行的基础设施,让它和车间业务流程结合起来。上面这套链路——FOCAS库申请、数控系统参数配置、C#采集、断线重连、数据落库、报警监控、宏变量产量统计——每一环都不算难,但串起来就是一个完整的设备联网方案。后续如果还想扩展,可以在同样的采集服务上增加多设备管理、web端实时看板、历史报表统计这些功能,架构不用改,加上去就行。

做这个项目的过程中我最大的感受是,很多所谓的坑其实都是文档不熟悉造成的。三菱FOCAS手册写得很详细,但全是英文或日文,指标又埋在各种章节细节里。希望这篇文章能把前路的坑先帮你填平,让你少走几个月的弯路。

返回列表