0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

正运动技术 来源:正运动技术 作者:正运动技术 2024-12-17 13:59 次阅读

本文导读

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

01ECI2A18B控制卡硬件介绍

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

wKgZO2dhD7aAExSYAAKMzmoxqe0256.png

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)多种程序加密手段,保护客户的知识产权。

wKgZPGdhD8WAAQbjAAMJoch5p60981.png

接口定义:

wKgZPGdhD86AO_dTAAPVRC6f_gk342.png

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

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

wKgZPGdhD9SADfCyAAEbnAlgRhI831.png

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

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

(1)消耗系统资源

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

(2)浪费CPU资源

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

(3)影响电源管理

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

(4)降低响应速度

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

(5)网络负载增加

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

(6)服务器压力增大

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

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

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

(1)单条获取

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

(2)多条获取

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

(3)周期性获取

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

应用场合:

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

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

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

wKgZPGdhENaAQhNnAACo80rt9AU803.png

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

wKgZO2dhEN2ATfGdAADtDb5XFCA712.png

3、点击下一步即可。

wKgZPGdhEOeALdZSAAAm5N5xZJM539.png

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

wKgZO2dhEO2AeGjJAAAvog10iG4162.png

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

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

wKgZPGdhEPKABE5BAAOmrDZ1S9o954.png

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

wKgZO2dhEPeAdvQZAAI5XgWucj4954.png

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

wKgZPGdhEP2AEun_AAB8eTUcS8M779.png

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

wKgZPGdhEQKAHoX2AADWPVZysZU370.png

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

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

wKgZO2dhEQiAY3SKAADxkycVkeI645.png

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

wKgZPGdhERqABlizAAEYBE10gHU189.png

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

wKgZO2dhESWAYgdyAABLR3oq8QI248.png

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

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

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

wKgZPGdhETWARnArAAPetJgXHLQ096.png

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

wKgZO2dhETqAXaJlAAAKRMIx8TI177.png

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

wKgZPGdhET-AbeNwAABuO_MbkLs659.png

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

wKgZO2dhEUSAdroGAAF970mWsbc172.png

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

wKgZPGdhEUiAZnA0AAEBGpVNqg0727.png

05 MFC实现轴的周期上报

1、例程界面如下。

wKgZO2dhEVaAX1rLAAA0zy7hH1I825.png

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; }

wKgZO2dhEaKAWkaGAAA5OW7GG-k755.png

4、开启上报。

wKgZO2dhEZ6AUAAuAAA5qEDf1iE212.png

//开启关闭上报 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、选择获取参数类型和起始地址及数量。

wKgZPGdhEcKAF9q0AAA5YAZZI-8886.png

//获取上报参数 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、获取上报结果并输出。

wKgZO2dhEfeAc2DFAAA50YHScuQ923.png

//获取上报结果 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软件输出窗口和轴参数窗口方便直接的观察到我们周期上报的数值。

wKgZO2dhEk2AXODVAABmL-p971g030.png

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

wKgZO2dhElGAHGHoAABSDGF-UCQ364.png

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

wKgZPGdhElaAOXnEAABRefiqjks078.png

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

wKgZPGdhEluARxrwAADOmuuW8sE431.png

12、视频教程

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

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

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

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

审核编辑 黄宇

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

    关注

    8

    文章

    6989

    浏览量

    88931
  • C++
    C++
    +关注

    关注

    22

    文章

    2108

    浏览量

    73607
  • 运动控制卡
    +关注

    关注

    7

    文章

    111

    浏览量

    15452
收藏 人收藏

    评论

    相关推荐

    以太网IO控制卡C#实时读写时间测试

    C#语言进行ECI IO的项目开发和快速读取多个IO状态与上位机交互速度的测试结果
    的头像 发表于 11-21 13:50 187次阅读
    以太网<b class='flag-5'>IO</b><b class='flag-5'>控制卡</b>:<b class='flag-5'>C</b>#<b class='flag-5'>实时</b>读写时间测试

    PCIe实时运动控制卡的双盘视觉筛选机上位机开发应用

    PCIe实时运动控制卡的双盘视觉筛选机上位机开发应用
    的头像 发表于 10-10 10:15 295次阅读
    PCIe<b class='flag-5'>实时运动</b><b class='flag-5'>控制卡</b>的双盘视觉筛选机上位机开发应用

    PCIe EtherCAT实时运动控制卡PCIE464的IO与编码器读写应用# 正运动技术# 运动控制卡

    编码器运动控制卡
    正运动技术
    发布于 :2024年07月23日 09:37:31

    PCIe EtherCAT实时运动控制卡PCIE464的IO与编码器读写应用

    C#进行PCIe EtherCAT运动控制卡的项目开发和快速读取多个IO和轴编码器位置的例程。
    的头像 发表于 07-17 14:47 776次阅读
    PCIe EtherCAT<b class='flag-5'>实时运动</b><b class='flag-5'>控制卡</b>PCIE464的<b class='flag-5'>IO</b>与编码器读写应用

    运动控制卡伺服电机如何控制

    运动控制卡伺服电机控制是现代工业自动化领域中一项重要的技术,它涉及到运动控制卡、伺服电机、控制
    的头像 发表于 06-12 14:13 860次阅读

    PCIE464M — 高速高精,超高速PCIe EtherCAT实时运动控制卡

    运动“高速高精运动控制卡”家族再添一员猛将!
    的头像 发表于 05-31 11:17 437次阅读
    PCIE464M — 高速高精,超高速PCIe EtherCAT<b class='flag-5'>实时运动</b><b class='flag-5'>控制卡</b>

    运动控制卡/运动控制器的ZCAN总线ZMIO310扩展模块使用

    运动控制卡/运动控制器的ZCAN总线ZMIO310扩展模块使用
    的头像 发表于 04-11 11:46 652次阅读
    <b class='flag-5'>运动</b><b class='flag-5'>控制卡</b>/<b class='flag-5'>运动</b><b class='flag-5'>控制</b>器的ZCAN总线ZMIO310扩展模块使用

    EtherCAT超高速实时运动控制卡XPCIE1032H上位机C#开发(十四)

    XPCIE1032H是一款基于PCI Express的EtherCAT总线运动控制卡,可选6-64轴运动控制,支持多路高速数字输入输出,可轻松实现多轴同步
    的头像 发表于 03-04 10:46 4.1w次阅读
    EtherCAT超高速<b class='flag-5'>实时运动</b><b class='flag-5'>控制卡</b>XPCIE1032H上位机<b class='flag-5'>C</b>#开发(十四)

    超高实时性的EtherCAT运动控制卡——PCIE464

    运动技术PCIE464运动控制卡,提供高效的工业运动控制解决方案。用户可直接将PCIE464嵌入标准PC机快速实现高性能的EtherCAT
    发表于 01-26 14:39 1136次阅读

    【正运动】高速高精,超高实时性的PCIe EtherCAT实时运动控制卡 | PCIE464

    充分利用PC的高效数据处理和开放性特性,以及控制卡高速PWM、多维位置比较输出PSO等功能,实现出色的多轴同步控制和高速点位以及复杂的轨迹运动控制
    发表于 01-24 09:48

    超高速PCle实时运动控制卡XPCIE1028 ,实现15000+pcs/分钟的IO触发检测速度!# 视觉筛选

    运动控制卡
    正运动技术
    发布于 :2024年01月18日 09:22:13

    PCIE464 — 高速高精,超高实时性的PCIe EtherCAT实时运动控制卡

    运动高速高精运动控制卡家族迎来新成员!
    的头像 发表于 01-17 16:29 672次阅读
    PCIE464 — 高速高精,超高<b class='flag-5'>实时</b>性的PCIe EtherCAT<b class='flag-5'>实时运动</b><b class='flag-5'>控制卡</b>

    EtherCAT超高速实时运动控制卡XPCIE1032H上位机C#开发(九)

    XPCIE1032H是一款基于PCI Express的EtherCAT总线运动控制卡,可选6-64轴运动控制,支持多路高速数字输入输出,可轻松实现多轴同步
    的头像 发表于 01-13 09:11 1259次阅读
    EtherCAT超高速<b class='flag-5'>实时运动</b><b class='flag-5'>控制卡</b>XPCIE1032H上位机<b class='flag-5'>C</b>#开发(九)