运动控制卡周期上报实时数据IO状态之C++篇

电子说

1.3w人已加入

描述

本文导读

今天,正运动小助手为大家分享一下运动控制卡周期上报,通过提前设置经常读取的参数主动周期上报,可以减少PC主动轮询的时间。此次介绍将以ECI2A18B为例,主要讲解如何使用C++编程语言来进行周期上报函数的编写和功能的开发。

01ECI2A18B控制卡硬件介绍

ECI2A18B经济型多轴运动控制卡是一款脉冲型、模块化的网络型运动控制卡。控制卡本身最多支持10轴,用以实现直线插补、任意圆弧插补、空间圆弧、螺旋插补、电子凸轮、电子齿轮、同步跟随、虚拟轴、机械手指令等简单的轨迹控制需求;采用优化的网络通讯协议可以实现实时的运动控制。

数据

ECI2A18B控制卡功能特点:

(1)本身支持6差分脉冲轴+4单端脉冲轴运动控制,最多可扩展至12轴运动控制。 

(2)脉冲输出模式:脉冲/方向或双脉冲。

(3)AXIS接口支持编码器位置测量,可以配置为手轮输入模式。

(4)专用的手轮输入接口。

(5)每轴最大输出脉冲频率10MHz。

(6)通过CAN总线,最多可扩展到256个隔离输入口和256个隔离输出口。

(7)轴正负限位信号口/原点信号口可以随意配置到任何输入口。

(8)通用数字输出口最大输出电流可达500mA,可直接驱动部分电磁阀。

(9)RS232接口、以太网接口、CAN接口。

(10)支持最多达12轴直线插补、任意圆弧插补、螺旋插补。

(11)支持点位运动、电子凸轮、直线插补、圆弧插补、连续插补运动、机械手指令。

(12)支持Basic多文件多任务编程。

(13)多种程序加密手段,保护客户的知识产权。

数据

接口定义:

数据

ECI2000系列经济型多轴运动控制卡可用于电子半导体设备(检测类设备、组装类设备、锁附类设备、焊锡机)、点胶设备和流水线等12轴以内脉冲应用场合。

控制器支持windows、linux、Mac、Android、wince各种操作系统下的开发,提供vc、c#、vb.net、labview等各种环境的dll库,如下图。上位机软件编程参考《ZMotion PC函数库编程手册》。

数据

02 为什么要进行周期上报,作用是什么?

1、当PC主动轮询的次数过多时,可能会导致以下问题:

(1)消耗系统资源

轮询会使系统资源消耗增加,无论是任务轮询或定时器轮询,都会消耗系统的部分资源。在多用户或者资源受限的环境里,这极有可能致使系统性能下滑。

(2)浪费CPU资源

只要是轮询都会造成CPU资源的浪费。这是因为轮询会会在系统内不间断的运行,不论当前设备的状态是否有改变。实际上,设备的诸多状态并不经常改变,轮询空转只会无端消耗CPU的时间。

(3)影响电源管理

向PC报告外围设备次数增多会使功耗提高,这可能缩短电池寿命或增加能源消耗,从而影响电源管理。

(4)降低响应速度

如果轮询频率过高,系统响应其他任务的速度或许会变慢。造成原因是由于CPU会不间断的轮询当前状态,从而响应处理其他计算或与用户交互任务会减慢。

(5)网络负载增加

若轮询涉及网络通信,轮询请求过多就可能加大网络负载,造成网络拥堵或者延迟加剧。

(6)服务器压力增大

在客户端服务器架构里,要是频繁进行轮询请求,就可能给服务器带来压力。主要集中在在服务器资源不足时,也许会使服务质量降低或者出现请求超时的情况。

2、多种获取方式对于程序运行占比的区别:

在探讨单条获取、多条获取以及周期性获取对程序运行产生的影响时,我们需要考量这些操作的特性以及它们对程序整体性能可能存在的潜在影响。

(1)单条获取

单条获取即程序每次仅处理一个单独的数据项。此方式简单直接,然而处理大量数据时效率不高,因为每次操作都会有上下文切换与资源管理方面的开销。此时,程序运行时间主要耗费在数据处理上。

(2)多条获取

多条获取意味着同时处理多个数据项。在现代计算机系统里,常借助多线程或者并发技术达成这一操作,这样做能大幅提升数据处理的吞吐量。不过,多线程虽有好处,却可能被锁争、内存竞争和上下文切换等问题所抵消。所以,多条获取也许会缩短单个数据项处理的相对运行时间,但总体运行时间能否减少取决于多线程优化的成效。

(3)周期性获取

周期获取即按照固定的时间间隔重复开展数据获取操作。在诸如实时监控系统、定时任务这类需要定期更新数据状态的应用场景中较为常见。其运行时间占比由任务的周期性和每个周期内实际工作量决定。若周期性任务负载较轻,则对程序整体运行时间影响不大。

应用场合:

在实际应用里,具体的应用场景、数据特性以及性能要求决定了选择何种数据获取策略。比如,若程序要对单个事件快速响应,单条获取或许更合适;要是旨在使数据处理速度最大化,多条获取可能更有利;而对于那些需要定期保持数据新鲜度的应用而言,周期性获取则不可或缺。

03 新建MFC项目并添加函数库

1、首先打开Visual Studio 2022,点击创建新项目。

数据

2、选择开发语言为“Visual C++”和程序类型“MFC应用程序”。

数据

3、点击下一步即可。

数据

4、选择类型为“基于对话框”,下一步或者完成。

数据

5、前往正运动官网下载PC函数库,路径如下(本文采用64位函数库为例)。

(1)进入官网,选择支持与服务,打开下载中心选择库文件,就能找到所有的PC函数库。

数据

(2)点击下载Windows C++(64位),可按需求另存为想要保存的路径下。

数据

(3)函数库另存为具体路径如下。

数据

6、将厂商提供的C++库文件和相关头文件复制到新建的项目里。

数据

7、在项目中添加静态库和相关头文件。

(1)先右击项目文件,接着依次选择:“添加”→“现有项”。

数据

(2)在弹出的窗口中依次添加静态库和相关头文件。

数据

8、声明用到的头文件和定义控制器连接句柄。

数据

至此项目新建完成,可进行MFC项目开发。

04 查看PC函数手册,熟悉相关函数接口

1、PC函数手册也可以在正运动官网“支持与服务”→“下载中心”→“编程手册”中找到。

数据

2、链接控制器,获取链接句柄。

数据

3、控制器自动上报相关指令。

数据

4、读取数字输入输出相关指令。

数据

5、读取Modbus寄存器相关指令。

数据

05 MFC实现轴的周期上报

1、例程界面如下。

数据

2、通过下拉控件选择连接控制器/控制卡连接方式。

BOOL CTest_CycleUpDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 将“关于...”菜单项添加到系统菜单中。 // IDM_ABOUTBOX 必须在系统命令范围内。 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if(pSysMenu != NULL) { BOOL bNameValid; CString strAboutMenu; bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); ASSERT(bNameValid); if(!strAboutMenu.IsEmpty()) { pSysMenu- >AppendMenu(MF_SEPARATOR); pSysMenu- >AppendMenu(MF_STRING, IDM_ABOUTBOX,strAboutMenu); } } // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动 // 执行此操作 SetIcon(m_hIcon, TRUE); // 设置大图标 SetIcon(m_hIcon, FALSE); // 设置小图标 // TODO: 在此添加额外的初始化代码 GetDlgItem(IDC_COMBO2)- >SetWindowTextA("网口n"); CComboBox *connetList; connetList = (CComboBox *)GetDlgItem(IDC_COMBO2); connetList- >AddString(_T("网口n")); connetList- >AddString(_T("LOCALn")); connetList- >AddString(_T("PCIn")); connetList- >AddString(_T("串口n")); return TRUE; // 除非将焦点设置到控件,否则返回 TRUE }

3、自动搜索IP。

void CTest_CycleUpDlg::OnCbnDropdownCombo1() { char Buffer[256]; CTest_CycleUpDlg* pDlg = (CTest_CycleUpDlg*)AfxGetMainWnd(); GetDlgItemText(IDC_COMBO2,Buffer,256); Buffer[255] = ''; if(0==strcmp(Buffer,"串口n")) { Com_SCAN(pDlg); } else if(0==strcmp(Buffer,"网口n")) { IP_SCAN(pDlg); } else if(0==strcmp(Buffer,"PCIn")) { PCI_SCAN(pDlg); } else if(0==strcmp(Buffer,"LOCALn")) { CComboBox *m_pEthList; m_pEthList = (CComboBox *)GetDlgItem(IDC_COMBO1); m_pEthList- >ResetContent(); m_pEthList- >AddString(_T("LOCAL1n")); }else { CString str; MessageBox("请选择正确的链接类型!"); return; } return; }

数据

4、开启上报。

数据

//开启关闭上报 void CTest_CycleUpDlg::OnBnClickedCheckStart() { if(NULL == G_ZmcHandle) { MessageBox(_T("控制器未连接")); return; } CString tempstr; UpdateData(true); int iret = 0; if(m_If_StartUp) //开启上报 { GetCycleStr(); iret = ZAux_CycleUpEnable(G_ZmcHandle,m_CyclePort,m_CycleTime,Str_CycleCmd); if(ERR_SUCCESS != iret) { tempstr.Format("周期上报打开失败!错误码:%d 命令:%srn",iret,Str_CycleCmd); AppendTextOut(tempstr); return; } tempstr.Format("周期上报开始!命令:%srn",Str_CycleCmd); AppendTextOut(tempstr); ifirsttimeus = GetTickCount(); SetTimer(1,1,NULL); } else { m_CycleCount = ZAux_CycleUpGetRecvTimes(G_ZmcHandle,m_CyclePort); iret = ZAux_CycleUpDisable(G_ZmcHandle,m_CyclePort); if(ERR_SUCCESS != iret) { tempstr.Format("周期上报关闭失败!错误码:%d rn",iret); AppendTextOut(tempstr); return; } tempstr.Format("周期上报关闭-上报用时:%dms, 上报次数:%d ,平均时间:%.3fmsrn",(GetTickCount() - ifirsttimeus),m_CycleCount,(float)(GetTickCount() - ifirsttimeus)/m_CycleCount); AppendTextOut(tempstr); KillTimer(1); } }

5、选择获取参数类型和起始地址及数量。

数据

//获取上报参数 void CTest_CycleUpDlg::GetCycleStr() { memset(Str_CycleCmd,0,sizeof(Str_CycleCmd)); CString TempString = ""; int ilen = 0; if(m_CycleParaEnAble[0]) { switch(m_CyclePara[0]) { case 0: //AXISSTATUS TempString.Format("AXISSTATUS(%d,%d),",m_CycleParaStart[0],m_CycleParaNum[0]); break; case 1: //DPOS TempString.Format("DPOS(%d,%d),",m_CycleParaStart[0],m_CycleParaNum[0]); break; case 2: //IDLE TempString.Format("IDLE(%d,%d),",m_CycleParaStart[0],m_CycleParaNum[0]); break; case 3: //IN TempString.Format("IN(%d,%d),",m_CycleParaStart[0],m_CycleParaNum[0]); break; case 4: //MODBUS_REG TempString.Format("MODBUS_REG(%d,%d),",m_CycleParaStart[0],m_CycleParaNum[0]); break; case 5: //MPOS TempString.Format("MPOS(%d,%d),",m_CycleParaStart[0],m_CycleParaNum[0]); break; case 6: //OP TempString.Format("OP(%d,%d),",m_CycleParaStart[0],m_CycleParaNum[0]); break; case 7: //TABLE TempString.Format("TABLE(%d,%d),",m_CycleParaStart[0],m_CycleParaNum[0]); break; default: break; } } ilen += TempString.GetLength(); memcpy(Str_CycleCmd,TempString.GetBuffer(0),TempString.GetLength()*sizeof(TCHAR)); if(m_CycleParaEnAble[1]) { switch(m_CyclePara[1]) { case 0: //AXISSTATUS TempString.Format("AXISSTATUS(%d,%d),",m_CycleParaStart[1],m_CycleParaNum[1]); break; case 1: //DPOS TempString.Format("DPOS(%d,%d),",m_CycleParaStart[1],m_CycleParaNum[1]); break; case 2: //IDLE TempString.Format("IDLE(%d,%d),",m_CycleParaStart[1],m_CycleParaNum[1]); break; case 3: //IN TempString.Format("IN(%d,%d),",m_CycleParaStart[1],m_CycleParaNum[1]); break; case 4: //MODBUS_REG TempString.Format("MODBUS_REG(%d,%d),",m_CycleParaStart[1],m_CycleParaNum[1]); break; case 5: //MPOS TempString.Format("MPOS(%d,%d),",m_CycleParaStart[1],m_CycleParaNum[1]); break; case 6: //OP TempString.Format("OP(%d,%d),",m_CycleParaStart[1],m_CycleParaNum[1]); break; case 7: //TABLE TempString.Format("TABLE(%d,%d),",m_CycleParaStart[1],m_CycleParaNum[1]); break; default: break; } } if((ilen + TempString.GetLength()) < 1000) { memcpy(&Str_CycleCmd[ilen],TempString.GetBuffer(0),TempString.GetLength()*sizeof(TCHAR)); ilen += TempString.GetLength(); } if(m_CycleParaEnAble[2]) { switch(m_CyclePara[2]) { case 0: //AXISSTATUS TempString.Format("AXISSTATUS(%d,%d)",m_CycleParaStart[2],m_CycleParaNum[2]); break; case 1: //DPOS TempString.Format("DPOS(%d,%d)",m_CycleParaStart[2],m_CycleParaNum[2]); break; case 2: //IDLE TempString.Format("IDLE(%d,%d)",m_CycleParaStart[2],m_CycleParaNum[2]); break; case 3: //IN TempString.Format("IN(%d,%d)",m_CycleParaStart[2],m_CycleParaNum[2]); break; case 4: //MODBUS_REG TempString.Format("MODBUS_REG(%d,%d)",m_CycleParaStart[2],m_CycleParaNum[2]); break; case 5: //MPOS TempString.Format("MPOS(%d,%d)",m_CycleParaStart[2],m_CycleParaNum[2]); break; case 6: //OP TempString.Format("OP(%d,%d)",m_CycleParaStart[2],m_CycleParaNum[2]); break; case 7: //TABLE TempString.Format("TABLE(%d,%d)",m_CycleParaStart[2],m_CycleParaNum[2]); break; default: break; } } if((ilen + TempString.GetLength()) < 1000) { memcpy(&Str_CycleCmd[ilen],TempString.GetBuffer(0),TempString.GetLength()*sizeof(TCHAR)); ilen += TempString.GetLength(); } }

6、获取上报结果并输出。

数据

//获取上报结果 void CTest_CycleUpDlg::GetCycleInfo() { CString ParaString = ""; CString TempString = ""; CString ShowString = ""; int iret = 0; int ival = 0; for(int inum=0;inum< 3;inum++) { ShowString =""; if(m_CycleParaEnAble[inum]) { switch(m_CyclePara[inum]) { case 0: //AXISSTATUS ParaString = "AXISSTATUS"; break; case 1: //DPOS ParaString = "DPOS"; break; case 2: //IDLE ParaString = "IDLE"; break; case 3: //IN ParaString = "IN"; break; case 4: //MODBUS_REG ParaString = "MODBUS_REG"; break; case 5: //MPOS ParaString = "MPOS"; break; case 6: //OP ParaString = "OP"; break; case 7: //TABLE ParaString = "TABLE"; break; default: break; } ShowString += ParaString; for(int i =0;i< m_CycleParaNum[inum];i++ ) { iret = ZAux_CycleUpReadBuffInt(G_ZmcHandle,m_CyclePort,ParaString,m_CycleParaStart[inum] +i,&ival); //获取周期上报信息 if(ERR_SUCCESS != iret) { MessageBox(_T("周期上报读取失败!")); return; } TempString.Format(" %d",ival); ShowString +=TempString; } ShowString +="rn"; AppendTextOut(ShowString); } } }

7、强制上报一次。

//强制上报一次 void CTest_CycleUpDlg::OnBnClickedBtnCycleup() { if(NULL == G_ZmcHandle) { MessageBox(_T("控制器未连接")); return; } UpdateData(true); int iret = ZAux_CycleUpForceOnce(G_ZmcHandle, m_CyclePort); if(ERR_SUCCESS != iret) { MessageBox(_T("周期上报刷新失败!")); return; } }

8、使用正运动RTSys软件输出窗口和轴参数窗口方便直接的观察到我们周期上报的数值。

数据

9、上位机读取周期上报的值并输入在文本框。

数据

10、可以点击下拉框选择其他参数或更改起始地址及数量读取不同区域数据。

数据

11、周期上报获取信息和单次获取信息的比较。

数据

12、视频教程。

教学视频可点击→“教学视频:运动控制卡周期上报实时数据IO状态之C++篇”查看。

本次,正运动运动控制卡周期上报实时数据IO状态之C++篇就分享到这里。

更多精彩内容请关注“正运动小助手”公众号,需要相关开发环境与例程代码,请咨询正运动技术销售工程师:400-089-8936。

本文由正运动技术原创,欢迎大家转载,共同学习,一起提高中国智能制造水平。文章版权归正运动技术所有,如有转载请注明文章来源。

审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分