马上即将毕业了,就自己在学校做的项目中包含有几种主流数控系统需要进行数据采集,总结一下之前的一些经验和开发工作,也希望后面再做相关技术研究的时候少踩点坑,同时也算是一个阶段的总结吧,毕竟马上要从事一个与这个行业不太相关的领域了,记录一下,以后翻翻还能记起以前做过什么。
以下介绍的为带有以太网接口的Fanuc数控系统的数据采集方法,如果不带有以太网接口,需要采用串口通讯进行采集。
采集Fanuc系列的机床数据,我们需要用到FOCAS1/2开发包,FOCAS是FANUC Open CNC API Specifications version 1 or 2的缩写,FOCAS1主要应用于0i和16i/18i/21i/系列,FOCAS2则针对30i/31i/32i系列CNC。
CNC/PMC数据窗口功能可以通过HSSB或以太网(TCP/IP)接口在PC和CNC之间完成交换数据和信息共享,他的主要功能包括以下几部分:
1:CNC:NC控制的伺服轴、主轴相关数据的读写。
包括绝对坐标、相对坐标、机床坐标、剩余移动量以及实际进给速度等。
2:CNC:加工程序的相关操作。
这些操作包括NC程序的上传/下载、程序校检、查找以及删除。读取CNC程序目录以及通过电脑实现CNC加工。
3:CNC:读写CNC文件数据。
这些数据包括刀具偏置、工件坐标系偏置、参数、设定、用户宏程序变量、P-Code宏程序变量以及螺距误差补偿数值。
4:CNC:读写刀具寿命管理信息。
5:CNC:读取历史信息记录。
这些信息包括操作历史记录和报警历史记录。
6:CNC:读取伺服、主轴数据。
7:CNC:读写数据服务器、DNC1、DNC2、OSI-Ethernet(只能使用HSSB)相关信息。
8:CNC:读写波形诊断数据。
9:CNC:读写冲床CNC数据。
10:CNC:读写激光器CNC数据。
11:CNC:读写伺服学习控制相关数据(只能使用HSSB)。
12:CNC:读写其他数据。
13:PMC:读写PMC相关数据。
这些数据包括G、F、Y、X、A、R、T、C、D地址等。
14:PMC:读写PROFIBUS-DP相关内容。
FOCAS 通过以太网方式连接电脑和 CNC 系统时,使用 TCP/IP 通讯协议。在 CNC 上通常使用 FANUC 以太网板,或者 FANUC 快速以太网板。对于 16i/18i/21i 系列或者 30i/31i/32i系列,还可以使用内嵌以太网功能。
使用 FOCAS 通讯时,可以按照以下步骤进行设定:
2. 在 CNC 上设定以太网功能或者内嵌以太网功能相关参数,MDI 上 system 键 ->“向右” 扩展键 -> “ETHPRM”软键 -> “(操作)”软键 -> “板卡”软键
在这个画面设定端口号(TCP)8193,时间间隔10。
3 . 使用网线连接电脑和 CNC。一般使用交叉线,如果通过集线器、交换机等设备,则使用直通线。
VisualStudio 2017;
FOCAS说明文档(所有机床相关数据函数调用方法 )
FOCAS_Api_Help;(以XML文件形式提供一些接口上的帮助)
2、开发流程
(1)连接方式:采用以太网连接,实验采用的CNC的IP地址为192.168.0.1,端口号为8193,PC侧IP只要配在同一个局域网即可,实验用的PC机的IP为192.168.0.10。IP的配置方法这里不再赘述。
(2)PC端工程搭建操作:使用visual studio 2017新建一个C# Windows窗体应用程序,如下图:
然后进入工程文件,部署FOCAS文件,如下图所示:
如上图所示,首先将将fwlib32.cs文件添加至工程文件,该文件在FOCAS2_Libraries\Fwlib\Dot NET sample路径下。然后在你新建的项目的bin\Debug目录下将要用到的动态链接库全部考进去。如下图所示,所用到的dll在FOCAS2_Libraries下的Fwlib文件夹中,直接全部拷到工程目录下即可。
做完以上的所有准备工作后就可以正式开始开发了。
3、API函数的使用方法:API具体的函数调用方法可以参见FOCAS说明文档,如下图所示:
这里只对一些常用的API函数进行介绍。
与CNC建立连接:参考FOCAS说明文档中的Function related to library handle, node文件夹中的Function Reference related to library handle, node_cnc_allclibhndl3.doc.在测试与机床连接函数时,要首先建立机床与PC机的物理连接,保证他们之间能够ping通,具体方法这里不再赘述。参考代码如下:
private void btnConc_Click(object sender, EventArgs e)
{
string ip = txtIp.Text;
string port = txtPort.Text;
string timeout = txtTimeOut.Text;
int ret = Fanuc.cnc_allclibhndl3(ip, Convert.ToUInt16(port), Convert.ToInt32(timeout), out Fanuc.h);
if (ret == Fanuc.EW_OK)
{
//“函数方法””
}
与CNC断开连接:参考FOCAS说明文档中的Function related to library handle, node文件夹中的Function Reference related to library handle, node_cnc_freelibhndl.doc,具体代码如下:
private void btnDisConn_Click(object sender, EventArgs e)
{
int ret = Fanuc.cnc_freelibhndl(Fanuc.h);
if (ret == Fanuc.EW_OK)
{
MessageBox.Show(“与机床断开连接”);
}
else
{
MessageBox.Show(ret + "");
}
}
采集坐标信息:我们需要参考FOCAS说明文档:
FANUC related to controlled axis&spindle_cnc_absolute.doc;
Function related to controlled axis&spindle_cnc_relative.doc;
Function related to controlled axis&spindle_cnc_machine.doc;
他们分别是机床的绝对坐标,相对坐标和机床坐标。以下为本人自己写的获取位置坐标信息的函数,可供参考。
Fanuc.ODBPOS fos = new Focas1.ODBPOS();
public void get_postion()//获取位置信息
{
short num = Fanuc.MAX_AXIS;
short type = -1;
short ret = Fanuc.cnc_rdposition(Fanuc.h, type, ref num, fos);
if (ret == 0)
{
//相对坐标
label12.Text = fos.p1.rel.name.ToString();
label13.Text = fos.p2.rel.name.ToString();
//label14.Text = fos.p3.rel.name.ToString();
label15.Text = fos.p4.rel.name.ToString();
label16.Text = fos.p5.rel.name.ToString();
X_Relative.Text = Convert.ToString(fos.p1.rel.data * Math.Pow(10, -fos.p1.rel.dec));
Z_Relative.Text = Convert.ToString(fos.p2.rel.data * Math.Pow(10, -fos.p2.rel.dec));
//textBox10.Text = Convert.ToString(fos.p3.rel.data * Math.Pow(10, -fos.p3.rel.dec));
C_Relative.Text = Convert.ToString(fos.p4.rel.data * Math.Pow(10, -fos.p4.rel.dec));
V_Relative.Text = Convert.ToString(fos.p5.rel.data * Math.Pow(10, -fos.p5.rel.dec));
//绝对坐标
label21.Text = fos.p1.abs.name.ToString();
label20.Text = fos.p2.abs.name.ToString();
label18.Text = fos.p4.abs.name.ToString();
label17.Text = fos.p5.abs.name.ToString();
X_Absolute.Text = Convert.ToString(fos.p1.abs.data * Math.Pow(10, -fos.p1.abs.dec));
Z_Absolute.Text = Convert.ToString(fos.p2.abs.data * Math.Pow(10, -fos.p2.abs.dec));
C_Absolute.Text = Convert.ToString(fos.p4.abs.data * Math.Pow(10, -fos.p4.abs.dec));
V_Absolute.Text = Convert.ToString(fos.p5.abs.data * Math.Pow(10, -fos.p5.abs.dec));
//机器坐标
label26.Text = fos.p1.mach.name.ToString();
label25.Text = fos.p2.mach.name.ToString();
//label24.Text = fos.p3.mach.name.ToString();
label23.Text = fos.p4.mach.name.ToString();
label22.Text = fos.p5.mach.name.ToString();
X_Machine.Text = Convert.ToString(fos.p1.mach.data * Math.Pow(10, -fos.p1.mach.dec));
Z_Machine.Text = Convert.ToString(fos.p2.mach.data * Math.Pow(10, -fos.p2.mach.dec));
C_Machine.Text = Convert.ToString(fos.p4.mach.data * Math.Pow(10, -fos.p4.mach.dec));
V_Machine.Text = Convert.ToString(fos.p5.mach.data * Math.Pow(10, -fos.p5.mach.dec));
//剩余移动距离
label31.Text = fos.p1.dist.name.ToString();
label30.Text = fos.p2.dist.name.ToString();
label28.Text = fos.p4.dist.name.ToString();
label27.Text = fos.p5.dist.name.ToString();
X_DistanceToGo.Text = Convert.ToString(fos.p1.dist.data * Math.Pow(10, -fos.p1.dist.dec));
Z_DistanceToGo.Text = Convert.ToString(fos.p2.dist.data * Math.Pow(10, -fos.p2.dist.dec));
C_DistanceToGo.Text = Convert.ToString(fos.p4.dist.data * Math.Pow(10, -fos.p4.dist.dec));
V_DistanceToGo.Text = Convert.ToString(fos.p5.dist.data * Math.Pow(10, -fos.p5.dist.dec));
}
}
采集主轴转速:我们需要参考说明文档的Function related to controlled axis&spindle_cnc_acts.doc.以下为采集主轴转速的函数参考:
Fanuc.ODBACT pindle = new Focas1.ODBACT();
public void get_pindle()//获取主轴的转速
{
short num = 32;
short ret = Fanuc.cnc_acts(Fanuc.h, pindle);
if (ret == 0)
{
SpindleSpeed.Text = pindle.data.ToString();
}
}
采集主轴、伺服轴的负载:参考说明文档:
Function related to controlled axis&spindle_cnc_rdspmeter.doc
Function related to controlled axis&spindle_cnc_rdsvmeter.doc.参考代码如下:
Fanuc.ODBSVLOAD sv = new Focas1.ODBSVLOAD();
Fanuc.ODBSPLOAD sp = new Focas1.ODBSPLOAD();
public void get_load()//主,伺服轴的加载计//测试成功
{
short a = 6;//伺服轴的数量
short ret = Fanuc.cnc_rdsvmeter(Fanuc.h, ref a, sv);
short ret2 = Fanuc.cnc_rdspmeter(Fanuc.h, 1, ref a, sp);
if (ret == 0 && ret2 == 0)
{
SpindleLoad.Text =Convert.ToString(sp.spload1.spload.data);
sv1_Load.Text = Convert.ToString(sv.svload1.data); //伺服轴1负载
sv2_Load.Text = Convert.ToString(sv.svload2.data); //伺服轴2负载
sv3_Load.Text = Convert.ToString(sv.svload3.data); //伺服轴3负载
sv4_Load.Text = Convert.ToString(sv.svload4.data); //伺服轴4负载
}
采集主轴名称:参考说明文档的
Function related to controlled axis&spindle_cnc_rdspdlname.doc。参考代码如下:
//获取主轴名称
Fanuc.ODBEXAXISNAME spindlename = new Focas1.ODBEXAXISNAME();
public void get_spindlename()
{
short data_num = 32;
short ret = Focas1.cnc_exaxisname(Fanuc.h,1,ref data_num, spindlename);
if(ret==0)
{
Spindle_Name.Text = spindlename.axname1;
}
采集实际进给速度:参考说明文档的
Function related to controlled axis&spindle_cnc_actf.doc。参考代码如下:
Focas1.ODBACT odbact = new Focas1.ODBACT();
public void get_actfeed() {
short ret = Focas1.cnc_actf(Fanuc.h,odbact);
if (ret == 0)
{
RelFeedRate.Text = Convert.ToString(odbact.data);
}
}
获取机床的状态:参考说明文档的 Function related to others_cnc_statinfo.doc。参考代码如下:
//机床状态
Fanuc.ODBST obst = new Focas1.ODBST();
public void get_state()//获取机器的状态
{
Fanuc.cnc_statinfo(Fanuc.h, obst);
cnc_status.Text = Convert.ToString(obst.run);//0停止,1待机,开动
Alarm_Status.Text = Convert.ToString(obst.alarm);//0没有警报,1表示有警报
RunningMode.Text = Convert.ToString(obst.tmmode);
}
获取主程序号、正在运行的程序号:参考说明文档的
Function related to CNC program_cnc_rdprgnum.doc。参考代码如下:
Fanuc.ODBPRO pro = new Fanuc.ODBPRO();
public void get_program()
{
short ret = Focas1.cnc_rdprgnum(Fanuc.h, pro);
if (ret == 0)
{
CurrentRunningProgramNumber.Text = Convert.ToString(pro.data);
MainProgramNumber.Text = Convert.ToString(pro.mdata);
//}
}
获取报警类型:参考说明文档的Function related to others_cnc_alarm2.doc。参考代码如下:
////获取报警类型
public void get_alarmtype() {
int alarm = 0;
short ret = Focas1.cnc_alarm2(Fanuc.h,out alarm);
if (ret == 0)
{
switch (alarm)
{
case 0:
listBox3.Items.Add("Parameter switch on (SW)");
Global.AlarmType = "Parameter switch on (SW)";
break;
case 1:
listBox3.Items.Add("Power off parameter set (PW)");
Global.AlarmType = "Power off parameter set (PW)";
break;
case 2:
listBox3.Items.Add("I/O error (IO)");
Global.AlarmType = "I/O error (IO)";
break;
case 3:
listBox3.Items.Add("Foreground P/S (PS)");
Global.AlarmType = "Foreground P/S (PS)";
break;
case 4:
listBox3.Items.Add("Overtravel,External data");
Global.AlarmType = "Overtravel,External data";
break;
case 5:
listBox3.Items.Add("Overheat alarm");
Global.AlarmType = "Overheat alarm";
break;
case 6:
listBox3.Items.Add("Servo alarm");
Global.AlarmType = "Servo alarm";
break;
case 8:
listBox3.Items.Add("Data I/O error");
Global.AlarmType = "Data I/O error";
break;
case 9:
listBox3.Items.Add("Spindle alarm");
Global.AlarmType = "Spindle alarm";
break;
case 10:
listBox3.Items.Add("Other alarm(DS)");
Global.AlarmType = "Other alarm(DS)";
break;
case 11:
listBox3.Items.Add("Alarm concerning Malfunction prevent functions (IE)");
Global.AlarmType = "Alarm concerning Malfunction prevent functions (IE)";
break;
case 12:
listBox3.Items.Add("Background P/S (BG)");
Global.AlarmType = "Background P/S (BG)";
break;
case 13:
listBox3.Items.Add("Syncronized error (SN)");
Global.AlarmType = "Syncronized error (SN)";
break;
case 14:
listBox3.Items.Add("reserved");
Global.AlarmType = "reserved";
break;
case 15:
listBox3.Items.Add("External alarm message");
Global.AlarmType = "External alarm message";
break;
case 16:
listBox3.Items.Add("reserved");
Global.AlarmType = "reserved";
break;
case 17:
listBox3.Items.Add("reserved");
Global.AlarmType = "reserved";
break;
case 18:
listBox3.Items.Add("reserved");
Global.AlarmType = "reserved";
break;
case 19:
listBox3.Items.Add("PMC error (PC)");
Global.AlarmType = "PMC error (PC)";
break;
default:
listBox3.Items.Add("No Alarm");
Global.AlarmType = "No Alarm";
break;
}
}
}
获取报警类型:参考说明文档的Function related to others_cnc_rdalmmsg2.doc。参考代码如下:
Fanuc.ODBALMMSG2 msg = new Focas1.ODBALMMSG2();
public void get_message()
{
short b = 8;
short ret = Fanuc.cnc_rdalmmsg2(Fanuc.h, -1, ref b, msg);
string str1 = "msg";
System.Type type = msg.GetType();
if (ret == 0)
{
for (int i = 1; i < 3; i++)
{
str1 = "msg" + i;
object obj = type.GetField(str1).GetValue(msg);
System.Type type1 = obj.GetType();
AlarmNumber.Text = Convert.ToString(type1.GetField("alm_no").GetValue(obj));
listBox4.Items.Add(type1.GetField("alm_msg").GetValue(obj).ToString());
Global.AlarmMessage = type1.GetField("alm_msg").GetValue(obj).ToString();
}
}
}
获取循环时间:这个API采FOCAS说明文档中没有找到,但是在XML所提供的API Help中有介绍,这里我参考api help的介绍和对机床的一些基本了解写了一下采集循环时间的函数,可供参考,代码如下:
public void get_circleTime()
{
Fanuc.IODBPSD_1 param6757 = new Fanuc.IODBPSD_1();
Fanuc.IODBPSD_1 param6758 = new Fanuc.IODBPSD_1();
short ret = Fanuc.cnc_rdparam(Fanuc.h, 6757, 0, 8, param6757);
if (ret == 0)
{
int circlingTimeSec = param6757.ldata / 1000;
ret = Fanuc.cnc_rdparam(Fanuc.h, 6758, 0, 8, param6758);
if (ret == 0)
{
int circlingTimeMin = param6758.ldata;
int workingTime = circlingTimeMin * 60 + circlingTimeSec;
CirclingTime.Text = Convert.ToString(workingTime) + "秒";
}
}
}
这里需要注意的是Fanuc.cnc_rdparam这个函数中的第二个参数要明白其含义,它代表的是机床PARAMETER参数表的序号,如下图所示,编号6757和6758表示的是循环时间,其中6757存储的值单位是ms,6758存储的值单位是min。
获取切削时间:同样是在XML所提供的API Help中有介绍,6753和6754存储的是切削时间的数值,同样6753存储的时间单位是ms,6754存储的时间单位是min,最后全部换算成秒将其累加便是总时间,获取切削时间的函数参考代码如下:
//得到切削时间
public void get_cuttingTime()
{
Fanuc.IODBPSD_1 param6753 = new Fanuc.IODBPSD_1();
Fanuc.IODBPSD_1 param6754 = new Fanuc.IODBPSD_1();
short ret = Fanuc.cnc_rdparam(Fanuc.h, 6753, 0, 8+32, param6753);
if (ret == 0)
{
int cuttingTimeSec = param6753.ldata / 1000;
ret = Fanuc.cnc_rdparam(Fanuc.h, 6754, 0, 8+32, param6754);
if (ret == 0)
{
int cuttingTimeMin = param6754.ldata;
int workingTime = cuttingTimeMin * 60 + cuttingTimeSec;
CuttingTime.Text = Convert.ToString(workingTime) + "秒";
}
}
}
获取运行时间:同样是在XML所提供的API Help中有介绍,6751和6752存储的是运行时间的数值,同样6751存储的时间单位是ms,6752存储的时间单位是min,最后全部换算成秒将其累加便是总时间,获取切削时间的函数参考代码如下:
//得到运行时间
public void get_workingTime()
{
Fanuc.IODBPSD_1 param6751 = new Fanuc.IODBPSD_1();
Fanuc.IODBPSD_1 param6752 = new Fanuc.IODBPSD_1();
short ret= Fanuc.cnc_rdparam(Fanuc.h, 6751, 0, 8, param6751);
if (ret == 0)
{
int workingTimeSec = param6751.ldata / 1000;
ret = Fanuc.cnc_rdparam(Fanuc.h, 6752, 0, 8, param6752);
if(ret==0)
{
int workingTimeMin = param6752.ldata;
int workingTime= workingTimeMin * 60 + workingTimeSec;
OperatingTime.Text = Convert.ToString(workingTime)+"秒";
}
}
获取开机时间:同样是在XML所提供的API Help中有介绍,6750存储的是开机时间的数值,同样6750存储的时间单位是min,每隔一分钟更新一次,这个数值表示的是总共的累加值。获取开机时间的函数参考代码如下:
//得到开机时间
public void get_powerOnTime()
{
Fanuc.IODBPSD_1 param6750 = new Fanuc.IODBPSD_1();
short ret = Fanuc.cnc_rdparam(Fanuc.h, 6750, 0, 8+32, param6750);
if (ret==0)
{
int powerOnTime= param6750.ldata*60;
PowerOnTime.Text = Convert.ToString(powerOnTime)+"秒";
}
}
得到工件总数:同样是在XML所提供的API Help中有介绍,6712存储的加工工件总数的数值。获取工件总数的函数参考代码如下:
//得到加工件数
public void get_totalparts()
{
Fanuc.IODBPSD_1 param6712 = new Fanuc.IODBPSD_1();
short ret = Fanuc.cnc_rdparam(Fanuc.h, 6712, 0, 8, param6712);
if (ret == 0)
{
int totalparts = param6712.ldata;
TotalParts.Text = Convert.ToString(param6712.ldata);
}
采用VB6.0调用sopcdaauto.dll和sopcaeauto.dll来进行编程采集机床数据的,用此种方法用到的是OPC 的自动化接口,需要将编写好的OPC客户端嵌入到机床HMI中才能进行采集,也就是只能实现本地的采集,配置dcom远程采集总是会报错。所以可以将采集程序集成到HMI中,再通过Socket发送到上位机中。
此种方法需要对微软的com技术有一定的了解,因为opc是基于com技术进行通信的,而用MFC我们需要自己写这些接口函数,也就是用到了OPC的自定义接口而不是直接调用动态链接库,所以开发成本和难度相对较高。因此开发前需要先了解com原理,并懂得MFC的一些相关知识。
在开发前,首先需要配置PC机和机床PC机的COM/DCOM,具体配置步骤如下:
1.从opcfoundation.org下载opc组件并安装。目前测试所用的版本为3.0。客户端和服务器端均需要。
2.在客户端和服务器端建立相同用户名和密码的账户(或是用户组)。
3.防火墙配置。运行wf.msc,允许Windows Management Instrumentation、OPC Server Enumerator(位于c:\windows\syswow64\opcenum.exe)(也可通过设置TCP135端口来实现)、OPC客户端程序、OPC服务器程序。
4.启用网络发现服务,即在网上邻居中需要看到另一台电脑。
5.启用文件和打印机共享。
6.运行dcomcnfg。
DCOM总体配置:默认属性:连接、标识
COM安全中的
访问权限条目:编辑限制中允许下列的本地和远程操作
Everyone、System、分布式用户组、交互式用户、匿名登录
默认值为以上几个加上Self和Administration
启动和激活权限条目:编辑限制中编辑限制中允许下列的本地和远程操作
Everyone、System、Administration、分布式用户、交互式用户
默认值为以上几个加上Anonymous
DCOM程序配置:OPCEnum: 身份验证级别:无
安全:前两个使用默认值
标识:系统账户
服务器程序:身份验证级别:默认
安全:前两个使用默认值
标识:下列用户(OPC用户)
如果按照以上方式配置不成功,可以百度参考网上的教程,成功的标志是你编写的客户端调试过程中可以远程连接到机床便成功。我们在开发过程中可以用到一款叫OPC Server Demo的exe文件,它可以作为一款opc服务器运行在自己的电脑上方便进行开发测试。
开发主要流程和核心代码如下,详细开发流程参见西门子OPC西门子工业通信第2卷。:
1.注册COM
HRESULT r1;
r1 = CoInitialize(NULL);
2. 将 ProgID 转换为 CLSID
r1 = CLSIDFromProgID(L"OPC.SimaticNET", &clsid);
3. 建立与OPC服务器的连接
r1 = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER ,
IID_IOPCServer, (void**)&m_pIOPCServer);
4. 建立OPC组对象
r1 = m_pIOPCServer->AddGroup(L"grp1", TRUE, 500, 1,
&TimeBias, &PercentDeadband, LOCALE_ID,
&m_GrpSrvHandle, &RevisedUpdateRate,
IID_IOPCItemMgt,
(LPUNKNOWN*)&m_pIOPCItemMgt);
引号部分即为OPC标签的地址, OPC标签详细地址参看LIS2sl_0212_en.pdf.
5. 添加数据项到组中
r1 = pItemMgt->AddItems(1, m_Items, &m_pItemResult, &m_pErrors);
6.从OPC组对象中获取 IOPCSyncIO异步访问 接口,该接口包括Read()和Write()函数。
r1 = m_pIOPCItemMgt->QueryInterface(IID_IOPCSyncIO,
(void**)&m_pIOPCSyncIO);
使用read()和Write()函数方法便可读到opc项的值。
7. 删除对象并释放内存
在退出程序钱必须要删除OPC对象并释放内存,具体代码如下:
r1 = m_pIOPCItemMgt->RemoveItems(1, phServer, &pErrors);
CoTaskMemFree(m_pItemResult);
m_pItemResult=NULL;
完成以上几步便完成了opc远程客户端的开发。
从西门子文档中摘录的OPC标签地址(部分):
NCK系统类型:
此路径下存放NCK的一些状态信息。
手轮状态:
系统时间:
系统已开启时间:
此路径下存放一些刀具坐标数据:
路径下存放伺服轴的电流、加速度、负载、功率、进给率、状态等数
主轴相关数据:
以上只是摘录自西门子官方手册的一小部分,在西门子官方手册上,能够采集到的数据还有例如NC程序状态相关的数据,NC程序名、程序状态、当前程序行号、刀具编号、程序已运行的时间等,还包括一些工艺参数,报警信息等均可通过查阅该手册找到所处的路径,然后在程序中添加这些OPC item就可以采集到想要的数据。
西门子PLC的S7协议可以支持PLC与远程设备的数据交互,而在西门子数控系统内部,PLC与NC之间是可以进行数据通信的,在step7中通过调用FB2并设置相关的参数来实现间接的访问数控系统内部的数据。具体实现方式为通过NC-VAR Selector和Step 7来完成。具体实现步骤为:
(1)利用NC-VAR Selector工具来选择NC系统中所需要读取的系统变量,系统变量包括切削时间、机械坐标、运行模式等,然后将选择的系统变量以“*.var”格式进行保存。
(2)将第一步所保存的.var文件通过NC-VAR Selector工具生成可供Step7调用的源文件,以“*.STL”格式进行保存。
(3)再通过Step7软件将第二步的源文件导入,然后编译生成一个DB块,并给这个DB块定义一个助记符以方便后续程序中进行调用。
(4)编写PLC程序,通过调用PLC中的FB2模块并设置相应的参数来读取NC系统中的变量。远程应用程序借助PLC的以太网接口,通过读取DB块中的数据即可采集到的对应的数据。
对于近几年比较新的西门子数控系统,数控系统内部支持OPC UA方式进行数据采集。西门子数控系统内部安装有OPC UA服务器,跟机床有关的过程状态、报警信息等都会存放在OPC UA服务器中,OPC UA服务器这一部分西门子厂商已经封装好无需再另外开发。实际采集过程中只需要按照OPC UA规范开发标准的OPC UA客户端,然后订阅OPC UA服务器节点中的数据项即可。但要使用西门子数控系统OPC UA这一项功能需要去西门子官方开通该授权。
OPC UA客户端的开发遵循以下几个步骤:
OPC UA服务器的开发使用open62541官方开源开发包进行开发,遵循以下几个步骤:
建立OPC UA工业统一接口来实现对异构数控系统机床的数据采集,其实现方式主要包括以下几点:
(1)建立异构数控机床信息源模型。根据数控机床所存在的共同属性进行建模,缕清各个属性之间的从属关系。
(2)将建立的信息源模型映射到OPC UA信息模型上。建立的异构数控机床信息源模型到OPC UA信息模型的映射方法有两种。一种是利用OPC UA官方提供的建模工具来实现映射,此方法的优势是方便快捷,操作迅速,但是缺乏灵活性;另一种方式是手动添加各个异构机床信息源模型中的数据项到OPC UA信息模型当中,该方法的优势是准确,能够实现一一对应的关系,缺点是工作量太大。
(3)建立OPC UA客户端来实现地址空间的访问。OPC UA服务器建立完成后,按照OPC UA的标准建立OPC UA客户端即可实现对OPC UA服务器中建立的各个节点信息进行访问。
OPC UA服务器的整体开发流程如图所示。将XML文档所描述的异构数控机床信息源加载到服务器中形成数控机床信息模型的OPC UA实例,从而实现对异构数控机床接口的统一,为外部OPC Client提供定义好的节点路径实现数据采集。将所建立的异构数控机床信息源的静态属性,例如设备名称、设备编号等,此类数据直接通过XML文档可以直接映射到OPC UA地址空间的节点当中;但是对于过程数据,例如坐标、转速等,此类数据利用OPC UA模型有一定的缺陷。可采用的解决方案为:通过机床通讯中间件实现对底层异构数控系统的封装,然后将统一的接口映射到OPC UA服务器地址空间的节点上,每次调用接口函数时加载底层的动态链接库中提供的采集函数,从而实现OPC UA服务器对实时过程数据的采集。OPC UA服务器的开发流程如图所示,使用的OPC UA开源库为open62541:
具体开发流程为:
(1) 配置config,包括安全证书、对外开放的端口等,然后调用OPC_UA_Server_new(config)
对服务器进行初始化操作。
(2)根据建立的异构机床信息源模型和OPC UA的XML文档建模规则,调用OPC UA开源库中的AddObjectNode()方法和AddVariableNode()方法来建立OPC UA地址空间中的各个节点的属性和相互关系。
(3)调用启动服务器监听方法OPC_UA_Server_run(server),在控制台界面会返回一个供OPC UA Client连接的地址,OPC UA Client根据此地址即可访问OPC UA Sever内部节点中的变量。
海德汉数控系统的数据采集需要用到Remo Tools开发包,开发包可在附件中查找。开发前首先需要确定DNC功能选项#18打开,另外需要在上位机采集软件中注册COM组件,32位的操作系统注册第一个,64位的操作系统注册第二个。下图为Remo Tools SDK的软件架构图,可以加深理解具体采用的通信原理。另外,开发过程中建议先缕清对象和接口之间的关系,然后参照示例程序进行开发。
以下将对两种通信方式的数据采集方法进行详细阐述:
(1)利用HeidenhainDNC Component组件进行采集
针对上述Remo Tools SDK,开发包中提供了的HeidenhainDNC Component组件,底层是采用RPC(Remote Procedure Call Protocol)来实现通信的。开发者根据COM组件库中所提供的API函数即可采集机床内部的数据。采用此方式进行数据采集时,必须要将机床的DNC功能选项#18打开。海德汉的DNC COM组件提供的对象接口遵循以下风格:
3.3所示。
在此模型中,Machine对象保持与NC控件的连接,连接的管理功能转发到连接组件。连接组件用来管理具有关联的连接参数的逻辑连接表。用户使用连接对话框来创建连接以及选择,编辑或删除现有连接。连接的设置存储在Windows注册表中的HKEY_LOCAL_MACHINE区域中,因此可供所有用户使用这些连接。如图所示,为连接组件管理的创建的两个连接参数,连接参数包括连接名称(Connection)、连接的海德汉数控系统类型(CNC Type)、连接的主机地址(Host Address)、连接所使用的的通讯协议(Protocol)以及连接的设备在线状态(Online)。
Machine对象的GetInterface方法返回其他对象之一,例如可以返回Automatic对象、ProcessData对象等。通过这种方法,可以将Machine对象与NC控件的连接传递给其他对象。接口按其提供的功能分组。 例如,用于NC自动运行的功能位于JHAutomatic对象中,用于读取数控系统轴的配置信息位于JHConfiguration对象中。在各个对象中又包含了各种功能丰富的接口,例如JHMachine对象中包含了IJHMachine、IJHMachine2等接口,这些接口有提供了一些功能函数,例如IJHMachine接口所包含的ConnectRequest(([in] BSTR bstrConnectionName)函数,用于请求与某个CNC的连接;IJHConfiguration接口所包含的GetAxesInfo ([out, retval] IJHAxisInfoList **ppList)函数,用于读取数控系统配置的轴的信息。所有COM类的对象名称前缀为JH(例如JHMachine),所有的接口都有用于标识的IJH名称前缀。HeidenhainDNC组件中所包含的部分接口与函数方法如下表所示:
接口名称 |
函数列表 |
功能 |
IJHMachine |
Connect |
与服务器进行连接 |
|
GetState |
获取CNC启动状态 |
|
GetProgramStatus |
读取当前程序状态 |
IJHAutomatic |
GetCutterLocation GetOverrideInfo |
读取当前刀具坐标 读取当前倍率值 |
IJHProcessData |
GetMachineRunningTime GetMachineUpTime |
读取机床运行时间 读取机床开机时间 |
开发过程中可以参考开发包说明文档“HeidenhainDNC.chm”以及官方给的参照示例进行开发。
(2)利用LSV-2 ActiveX Control控件进行采集
LSV2 ActiveX控件,是海德汉RemoTools SDK的另一组件,是用于与海德汉控件进行通信的应用程序的软件开发套件。它提供了LSV-2内部自定义协议,可以实现与iTNC 530和TNC 426/430的TNC控件之间进行通信,进而可以进行Windows应用程序的编程。对于传输,可以在串行接口和以太网(TCP / IP)接口之间进行选择。LSV-2 ActiveX控件负责所有通信,应用程序开发者只能看到简单的方法,属性和事件。调用方法通常会触发一个动作,通过设置属性,可以影响动作或询问状态。每当消息异步传输到应用程序时,总是会先报告事件,然后将该事件将添加到Windows消息队列中,并且可以由父窗口处理。
LSV-2库提供了一种与控件进行通信的方式,所以在开发时无需直接处理纯LSV2报文。 库中的功能隐藏了低级通信机制,因此在开发过程中可以轻松创建所需要的功能。在使用过程中,只需要引用lsv2_all.h头文件即可,它几乎包含了所有通用的头文件,除了一些特殊的需要单独添加头文件。由于LSV-2库中使用了数据类型和回调,因此LSV-2库与
1)a将LSV2库添加到程序所包含路径;
b.将lsv2.lib和ws2_32.lib添加到链接器。
2)a.在程序中添加windows.h和lsv2_all.h头文件;
b.使用LSV2Open()函数建立与控件的连接;
c.使用LSV2Login()函数完成注册;
d. 使用LSV2ReceiveVersions()函数来实现检索版本信息的功能;
e.使用LSV2Logout()函数注销。
常用的LSV2组件库的API函数如下表所示
API通讯函数 |
功能 |
LSV2Open |
与控件建立连接 |
LSV2Login |
注册控件 |
LSV2Logout |
注销控件 |
LSV2Close |
断开连接 |
LSV2ReceiveSystemInfo |
读取系统状态信息 |
LSV2ReceiveVersion |
读取版本信息 |
LSV2ReceiveDncInfo |
读取DNC信息 |
LSV2ReceiveRunInfo
LSV2ReceiveTime LSV2GetConnectionState |
读取正在运行的状态信息(包括坐标值、运行模式、开机时间、运行时间、运行刀具号等) 读取数控系统当前时间 读取连接状态信息 |
以上就是一些所熟知的数控系统的数据采集方法,当然现在也了解到市面上有很多针对数控系统做的数据采集网关,各种各样的都有,但采集方法应该都是相通的,无非是它们把这些通讯库还有函数库移植到了arm平台上。如果嵌入式功底扎实的大佬可以尝试自己做这样的网关,里面肯定还是有很多坑要走。本来自己想尝试做一个的,但最后忙毕业没时间,也就把这个事放在一边了。在嵌入式硬件设备上确实也有它的好处,部署方便简单,价格便宜,当然便宜的是硬件设备,很多做这个数据采集网关的主要还是卖网关中集成的数据采集服务,开通一台机床多收取一定的费用。哎,为什么制造业一直发展缓慢,我在做项目的切身感受是,制造业的资源太闭塞了!很多时候难的不是开发任务,难的是找资料的过程,它不像互联网那样有各种各样开放的源代码,可以直接站在前人的肩膀上继续向前进。制造业的发展必将是信息技术与操作技术相互结合的,需要的是两者兼备的综合性人才,还是希望制造业终有一天能够发展起来吧。