写在前面:为什么我建议你从FOCAS入手做机床数据采集
我在设备联网这条路上摸爬滚打了不少年,前后接触过发那科、西门子、三菱、兄弟、马扎克等主流数控系统的数据采集。要说哪家最容易上手、坑最少,三菱的FOCAS协议库绝对排得进前三。它的文档全、API设计规整、回调机制清晰,基本上照着官方例子改一改就能跑通。更重要的是,三菱CNC在国内二手机床市场和中小型机加车间里的占有率相当高,很多做设备联网、MES落地的朋友迟早都会撞上它。
这篇内容聚焦三菱CNC数据采集的完整落地路径:从FOCAS通信库的选型与安装、机床侧以太网参数配置,到Windows环境下用C语言编写第一个采集程序,再到坐标读取、程序号读取、宏变量读写这几个核心功能的代码实现,最后是我自己踩过的一些坑和排查思路。适合刚从自动化控制转设备数据、或者已经在做MES实施但卡在机床通信环节的工程师参考。
1. 三菱CNC数据采集的整体思路与方案选型
1.1 FOCAS协议到底是什么
FOCAS(Factory Open Communication Architecture System,实际官方叫法是FA Open Communication API Standard,但大家习惯直接叫FOCAS)是三菱电机为其CNC数控系统提供的一套通信协议库。它封装了从外部设备访问CNC内部数据的全部底层逻辑,对外提供C/C++接口。
这套库有两种常见的通信物理层形式:一种走以太网,通过CNC的以太网接口直连或组网;另一种走串口,通过RS-232C连接。现在的项目里以太网是绝对主流,原因很简单:速度快、能同时被多台设备访问、不用跟机床自带的功能抢串口。
FOCAS库本身不是一个独立运行的软件,而是一套动态链接库和头文件的集合。你在程序里调用它的API,底层会自动处理握手、帧格式、校验、超时重传等细节。这个"细节屏蔽"是FOCAS最大的价值,因为你完全不需要关心TCP/IP数据包里每一字节的含义,只需要学会几十个API的用法,就能覆盖绝大部分采集需求。
1.2 数据采集的架构选型:直连采集还是走中间层
做CNC数据采集,架构上通常有两种选择。第一种是采集程序直接跟每台机床建立连接,一对一的通信,写完一个采集任务就是单点的逻辑。第二种是在采集程序和CNC之间加一层采集网关或转发服务,程序只管跟网关通信,网关负责管理多台机床的连接拓扑。
如果你只是临时采集一两台机床,直连方案最省事,程序逻辑简单,调试方便。但如果目标是车间级甚至工厂级的设备联网,强烈建议你做一个"连接管理器"层,把每台机床的IP、端口、协议版本、采集频率统一管理起来,采集业务逻辑跟通信连接解耦。后面不管是增加设备还是更换通信协议,改动范围都能控制住。
FOCAS库的另一个特点是线程安全性。官方文档对多线程调用的支持没有做太强的保证,实际操作中我习惯用一个独立的采集线程去跑所有的FOCAS调用,避免在多个线程里同时操作同一个连接句柄,否则很容易出现偶发的读取超时或数据错乱。如果你确实需要在多个任务之间共享一个连接,建议加锁,或者干脆按"每任务一连接"的方式来处理,代价是机床侧的连接数会多一些,但稳定性更可控。
2. 环境搭建:从零开始准备FOCAS开发环境
2.1 获取FOCAS库与安装注意事项
FOCAS库不是开源的,需要到三菱电机自动化官网或者通过设备供应商的技术支持渠道获取。通常你会拿到一个安装压缩包,里面包含以下内容:
- FOCAS1/Ethernet库的安装程序,包含32位和64位两个版本
- cnc.h、focas.h等头文件
- 官方示例代码,包括C和C++版本
- 库使用手册(PDF格式)
安装过程没有太多花哨的地方,跟着向导点击下一步即可。但有一点需要特别注意:安装时一定要选对位数。你的采集程序如果编译成32位的,就装32位的FOCAS库;如果编译成64位的,就装64位的。混用的话,链接阶段会直接报错。
开发环境我推荐Visual Studio社区版,免费而且调试体验好,对FOCAS这种原生C库的兼容性也没问题。
2.2 工程配置的关键步骤
新建一个空的C++控制台项目后,按下面的路径做配置:
项目属性 -> VC++目录 -> 包含目录,添加FOCAS库的头文件路径。库目录添加lib文件夹的路径。
链接器 -> 输入 -> 附加依赖项,添加fwlib32.lib(32位环境)或fwlib64.lib(64位环境)。
如果编译的时候提示找不到头文件或库文件,优先检查是不是路径写错了,其次检查是不是位数选错了。我见过不少人卡在这一步很久,最后发现是装了32位库却用64位编译器去连,或者反过来。
配置好之后,在代码文件顶部加上:
#include "fwlib32.h"这个头文件里面包含了所有FOCAS API的声明,也包含了连接句柄结构体定义、各类常量定义。如果这一行能编译通过,说明你的环境基本就绪了。
2.3 机床侧的以太网参数设置
开发环境准备好只是其中一半,机床侧也得做相应的参数配置。三菱CNC在系统界面上需要设置的参数主要有几个:
- IP地址:为CNC设置一个和设备网段一致的IP,比如你的车间设备网是192.168.10.x,那CNC就要配一个该网段的地址
- 子网掩码:按你的网络规划来填
- 端口号:FOCAS默认使用8193端口,这个端口在大多数情况下不用改
- 通信协议:如果机床支持多协议,要确认选择FOCAS支持的协议
提示:参数设置完成后,一定在机床端做一次重启或者通信模块的复位,再测试网络连通性。很多时候"连不上"的锅,其实是参数写进去了但没生效。
配置完成后,用开发机ping一下CNC的IP地址。能ping通,说明网络层通了,后面的通信调试才有基础;ping不通,排查方向先放在线缆、交换机端口、IP配置上。
2.4 验证连接:用官方示例程序跑通第一次握手
FOCAS库里自带的示例程序里有连接测试的片段,我建议先把这段代码跑通再做业务开发。连接的核心流程其实就三步:创建以太网句柄、调用连接函数、检查返回值。
官方示例里的连接代码大致是这样的:
#include "fwlib32.h" #include <stdio.h> #include <string.h> int main() { unsigned short handle; short ret; // 创建以太网连接句柄 handle = cnc_allclibhndl3("192.168.10.50", 8193, 3, 10, &handle); // 检查连接结果 if (ret == EW_OK) { printf("连接成功!\n"); } else { printf("连接失败,错误码: %d\n", ret); } // 稍后关闭连接 cnc_freelibhndl(handle); return 0; }这里面的cnc_allclibhndl3函数参数含义是:第一个参数是CNC的IP地址字符串,第二个是端口号,第三和第四个参数分别对应超时时间和协议版本等。不同场景下最后一个参数的具体含义略有差异,但基本逻辑不变。
跑通这段示例之后,你的环境搭建就算完成了。接下来要做的,就是把连接过程封装成自己的模块,然后开始写具体的采集逻辑。
3. 核心数据采集功能代码实现
3.1 坐标位置读取
机床坐标是数据采集里最常用的数据之一。三菱FOCAS库读取绝对坐标、相对坐标和机械坐标对应的函数是cnc_getpos。
这个API的签名比较有迷惑性,它需要你预先定义一个特定类型的结构体来接收数据。结构体里包含了各个轴的坐标值、坐标系统状态、是否正在运动等信息。
使用示例:
ODBPOS posData; short ret = cnc_getpos(handle, &posData); if (ret == EW_OK) { printf("X axis absolute: %lf\n", posData.abs.data[0]); printf("Y axis absolute: %lf\n", posData.abs.data[1]); printf("Z axis absolute: %lf\n", posData.abs.data[2]); }这里需要关注的是ODBPOS结构体里的abs字段,它代表绝对坐标。相对坐标在rel字段里,机械坐标在mach字段里。针对不同的坐标类型,按需取用即可。
data数组的下标对应轴号,规则是0对应X轴,1对应Y轴,2对应Z轴,以此类推。如果你的机床是四轴或五轴,后面的下标依次对应第四轴、第五轴。
坐标值虽然返回的是double类型,但实际精度取决于机床的设定。如果你发现坐标值跟操作面板上显示的不一致,大概率是单位换算的问题。FOCAS库默认返回的是毫米单位,如果你的系统设置为英寸,那需要做换算。
3.2 程序号与运行状态读取
MES系统经常要展示"当前正在跑哪个程序号""机床当前处于自动运行还是手动模式""主轴转速多少"这类状态信息。FOCAS里有专门的API来获取这些数据。
程序号读取:
ODBSYS sysData; short ret = cnc_rdmcinfo(handle, &sysData); if (ret == EW_OK) { printf("当前程序号: %d\n", sysData.program_no); }机床模式读取,用cnc_rdopmode获取当前运行模式,返回的是枚举值,比如手动模式、自动模式、编辑模式、MDI模式等。这个值在做状态统计的时候非常有用,可以准确判断这台机床当前是不是在"生产"。
主轴转速和进给速度,可以用cnc_rdspdl和cnc_rdfeed来读。这两个数据在设备效率统计、饱和度分析里都是关键输入。
读取这些数据通常会放到一个循环里,以固定的扫描周期去刷新。比如说每500毫秒采集一次坐标和运行状态,写进本地数据库或者上报到MES平台。
3.3 宏变量读写
宏变量是三菱CNC数据采集里一个很灵活的功能。它本质上是CNC内部的一组变量,编号从#100开始,有公用变量、局部变量、系统变量等不同类型。你可以在加工程序里往宏变量赋值,也可以在外部通过FOCAS去读,甚至去写。
宏变量读取:
#undef 不存在的宏 #include "fwlib32.h" short readMacro(unsigned short handle, int macroNo, double* value) { long data = macroNo; // 宏变量号 long dec; long sign; double val; short ret = cnc_rdmacro(handle, &data, 1, &dec, &sign, &val); if (ret == EW_OK) { *value = val; } return ret; }宏变量写入与之对应的函数是cnc_wrmacro,参数结构大体类似。
宏变量的一个典型应用场景是这样:机床操作员在开始加工时,通过程序把工件编号、操作员编号写进#500、 #501等公共变量;上位机系统每隔几秒钟扫描一遍这些变量,就能自动得知当前这台机床正在加工的是哪个工件。这种"软信号"的方式比加装传感器做识别便宜得多,实施也快。
3.4 报警信息采集
报警采集在设备监控里同样重要。FOCAS库读取报警信息使用cnc_rdalm函数。
报警分为当前报警和历史报警,读取方式略有差异。当前报警用cnc_rdalm就能拿到,包括报警号、报警级别、报警文本等信息。历史报警需要额外开启报警历史记录功能,并且读取的逻辑稍微麻烦一些,需要先获取历史记录的总条数,再逐条读取。
在实际项目里,我通常的做法是:每1到2秒钟循环扫描一次当前报警,如果检测到有新报警出现,立刻上报给上层系统,同时记录报警发生时间。这样能够满足大多数MES系统对设备报警的监控要求。
报警文本需要在程序里做编码转换,因为三菱返回的报警信息通常是日文或中文的Shift-JIS编码,而Windows控制台默认是GBK或者UTF-8。不做转换的话,打印出来的报警内容是乱码。
4. 完整实战:封装一个稳定的数据采集模块
4.1 采集模块的整体设计
简单跑通FOCAS API是一回事,但要应用到实际项目中,必须把它封装成一个结构清晰、异常处理完善的模块。
我的做法是把采集模块分成三层:
最底层是连接管理层,负责建立连接、断开连接、断线重连。对上层暴露的接口只有一个GetConnection()。
中间层是数据读取层,针对坐标、程序号、状态、报警等不同类型的采集需求,封装出对应的读取函数。读取函数内部统一处理FOCAS返回的错误码,如果连接断开了,标记连接状态异常,由连接管理层负责重连。
最上层是业务调度层,根据配置的采集周期,定时调用中间层的函数,然后将数据按照约定的格式上报给MES、数据库或者消息中间件。
这种分层设计的好处很明显:后续如果新增一个采集指标,只需要在中间层增加一个函数,不影响其他层次的代码。
4.2 断线重连机制怎么写
FOCAS的连接本质上是一个TCP连接,网络抖动、CNC重启,都会导致连接断开。程序里如果不对这种情况做处理,采集任务很快就会死掉。
我的断线重连逻辑是这样的:
bool EnsureConnection() { if (IsConnected()) return true; for (int retry = 0; retry < 5; retry++) { if (TryConnect()) return true; Sleep(3000); } return false; }连接成功之后,每次采集数据之前都先检查句柄状态,如果发现上一次读取返回的是连接类错误,就主动把句柄置为无效状态,触发下一轮重连。
另外要特别提醒一下,cnc_freelibhndl这个函数在连接已经断开的情况下调用,有可能导致程序卡住,所以重连前要把超时时间设置得短一点,别让它一直死等。
4.3 数据采集的完整代码清单
这里给出一份整合后的采集模块核心代码,包含连接建立、坐标采集、状态采集和清理资源四个部分。
#include "fwlib32.h" #include <stdio.h> #include <string.h> class CNCSampler { public: CNCSampler(const char* ip, unsigned short port, int timeoutMs) : m_ip(ip), m_port(port), m_timeout(timeoutMs), m_handle(0) {} bool Connect() { short ret = cnc_allclibhndl3( (char*)m_ip.c_str(), m_port, 3, m_timeout, &m_handle ); if (ret == EW_OK) { m_connected = true; return true; } m_connected = false; return false; } void Disconnect() { if (m_connected) { cnc_freelibhndl(m_handle); m_connected = false; } } bool GetPositions(double* x, double* y, double* z) { if (!m_connected) return false; ODBPOS posData; short ret = cnc_getpos(m_handle, &posData); if (ret != EW_OK) { m_connected = false; return false; } *x = posData.abs.data[0]; *y = posData.abs.data[1]; *z = posData.abs.data[2]; // 单位换算,如果你的坐标用毫米,这一步可以跳过 // 有的系统会返回英寸,这时候需要乘25.4 return true; } bool GetProgramNo(int* programNo) { if (!m_connected) return false; ODBSYS sysData; short ret = cnc_rdmcinfo(m_handle, &sysData); if (ret != EW_OK) { m_connected = false; return false; } *programNo = sysData.program_no; return true; } bool IsConnected() const { return m_connected; } private: std::string m_ip; unsigned short m_port; int m_timeout; unsigned short m_handle; bool m_connected = false; };这份代码里我刻意把类和FOCAS函数耦合得比较紧,便于理解。实际生产环境里,你可以把上面的逻辑进一步解耦,连接管理、数据读取分别拆分到独立的类中,但核心逻辑不变。
4.4 运行效果的验证方法
程序写好之后,怎么判断数据采得对不对?我的调试方法是先跑起来打印所有采集到的数据,然后去机床操作面板上人工对照。
坐标值可以和面板的"绝对坐标"显示页面比对,程序号可以和当前运行的程序名比对,宏变量可以在MDI模式下手动输#500=123这种指令,再在上位机上读取,看看是不是能读到123。
这种人工比对的验证方法虽然原始,但是最可靠。只有把每条数据的真实含义都确认无误,后续做数据分析和看板展示才不会出现"采集了一大堆数据,最后发现口径不对"的尴尬情况。
5. 高频故障排查与避坑经验
5.1 连接失败的排查全路径
连接不上,是FOCAS采集里最让人头疼的问题之一。按照下面的路径排查,可以最大限度缩小范围:
| 现象 | 排查位置 | 常见原因 | 解决办法 |
|---|---|---|---|
| ping不通 | 网络层 | IP配置错误、网线松动、交换机端口问题 | 检查IP、换网线、换交换机端口 |
| ping通但连接失败 | FOCAS配置 | 端口不对、协议版本不匹配、机床侧参数未生效 | 检查端口号,正确设置机床以太网参数并重启 |
| 连接成功但时报超时 | 通信稳定性 | 交换机缓存不足、多设备抢占、采集频率太高 | 降低采集频率,增加超时时间,优化交换机配置 |
| 返回错误码110 | 连接句柄 | 连接已经断开,还在使用旧句柄 | 重新建立连接,更新句柄 |
错误码110是我在实际项目里遇到频率最高的一个,它代表连接已经断开。排查思路很简单:看看是不是有人手动在三菱面板上关掉了通信端口,或者交换机那边做了端口隔离。
5.2 编码问题:乱码的解决方案
三菱FOCAS返回的字符串,比如报警文字、程序名、系统信息等,编码格式五花八门。在新版本的FOCAS库中,有的接口已经能返回UTF-8字符串,但很多老版本接口返回的依然是Shift-JIS编码。
处理方案是统一做一个编码转换层。Windows平台上用MultiByteToWideChar和WideCharToMultiByte配合,实现在Shift-JIS、GBK、UTF-8之间互相转换。
注意:转换之前一定要明确源编码。最稳妥的方式是先查询一下机床的系统语言设置,然后在代码里做对应的编码假设,别一股脑当成UTF-8处理。
我见过有人为了图省事,直接用printf输出FOCAS接口返回的字符串,结果中文乱码,调了半天才发现是编码问题。这种事情浪费的时间完全可以通过预判规避。
5.3 采集频率与系统负载的平衡
FOCAS采集不是越快越好。我见过有人把采集周期调到100毫秒,结果机床正常干活没影响,但交换机负载上去了,其他设备的通信开始抖动。
实际操作中,坐标位置这类快速变化的数据,500毫秒到1秒的采集周期足够;程序号、宏变量这类变化频率低的数据,3到5秒采集一次都没问题。报警信息需要及时性,可以1秒扫一次,但没必要更快,因为CNC本身的报警响应时间就在百毫秒量级,扫得再快,意义也不大。
5.4 历史报警读取的额外工作
如果你需要读历史报警,要注意三菱CNC的报警历史默认不一定开启,有些型号需要在机床参数里手动打开报警历史记录功能。开启之后,历史上曾经出现过的报警才会被保存到CNC内部寄存器里。
读取历史报警的逻辑比当前报警复杂,因为要先查询总条数和起始位置,再分批读取。官方文档里的示例代码逻辑可以参考,但要注意数组越界的处理,避免读取时把之前已经读过的记录反复覆盖。
6. 更进一步的扩展:从采集到应用
6.1 采集数据如何与MES系统对接
采集程序只负责取数,但取回来的数要真正发挥作用,必须跟MES、ERP或者自研的看板系统对接。
对接方式一般有两种。一种是程序直接把数据写入数据库,上层系统从数据库里读;另一种是程序通过消息队列或者HTTP接口主动推送数据。
如果车间里有现成的消息中间件,推荐走消息队列的方式,上游下游解耦,扩展性强。如果只是几个人看的简单看板,直接写数据库最快,甚至Excel导出都够用。
6.2 多机床采集的管理思路
当采集的机床数量从一两台变成几十台时,每个采集任务创建一个独立线程的模型就会变得很笨重,而且线程管理、资源回收都是麻烦事。
我建议做一个简单的"设备列表驱动采集"的模式:一个管理线程维护设备列表,每台设备有独立的连接对象和采集配置;用一个线程池来执行采集任务,任务执行结果统一写入公共缓冲区。
这种模式下,新增一台机床只需要在设备列表里加一条配置记录,代码层面几乎不用改动。
6.3 数据安全与稳定性建议
采集程序的稳定运行时间往往是以月为单位的,所以要提前考虑异常退出和自动恢复。
几个实操建议:程序做Windows服务,开机自启动;服务挂了由看门狗守护进程拉起;数据库写入失败不要直接崩溃,先缓存到本地,网络恢复后再补传;采集数据带上时间戳,并且以机床IP加轴号作为唯一标识,方便后续溯源。
这些细节单个看起来不起眼,但真正影响了采集程序能不能在车间里长时间稳定运行。
7. 我的一些心得和补充建议
做FOCAS采集这些年,有个体会特别深:通信层面的代码只是整个数据采集项目的冰山一角。真正花时间的地方,在于理解现场的网络环境、跟车间人员沟通参数配置、处理各种"信号烂到你以为程序写错了"的实际问题。
如果你刚开始接触这块,建议先不要急着做复杂的功能,把连接跑通、坐标读出来,形成正反馈之后再逐步增加功能。每增加一个功能前,先思考好这个数据的业务价值是什么,否则采集了一大堆用不上的数据,后期维护起来会很痛苦。
另外,三菱FOCAS库是商业化产品,授权和使用的合规性需要在项目启动前确认清楚。如果你所在的公司有法务流程,别忽略这一步,免得后面产生不必要的麻烦。
最后再分享一个小技巧:如果你管理着多台不同型号的三菱CNC,最好在代码里记录下每台设备支持的FOCAS库版本和固件版本。不同版本的库在某些API的行为上有细微差别,提前记录会帮你减少不少排查时间。