今天,正运动小助手给大家分享一下EtherCAT运动控制卡的自定义运动曲线,主要介绍一下如何通过在线命令封装自己想使用的Basic指令到上位机接口中供上位机调用。
一、ECI2828运动控制卡的硬件介绍
ECI2828系列控制卡支持最多达16轴直线插补、任意圆弧插补、空间圆弧、螺旋插补、电子凸轮、电子齿轮、同步跟随、虚拟轴、机械手指令等。采用优化的网络通讯协议可以实现实时的运动控制。
ECI2828系列运动控制卡支持以太网,232通讯接口和电脑相连,接收电脑的指令运行,可以通过EtherCAT总线和CAN总线去连接各个扩展模块,从而扩展输入输出点数或运动轴。
ECI2828系列运动控制卡的应用程序可以使用 VC、VB、VS、C++、C#等多种高级语言来开发,程序运行时需要动态库zmotion.dll。调试时可以把ZDevelop软件同时连接到控制器,从而方便调试、方便观察。

ECI2828控制卡本身的硬件接口也十分丰富。具有8个本地脉冲轴,每个轴带独立编码器,最多16个虚拟轴。板上自带24个通用输入口(其中4路是高速输入口可以作为高速锁存使用),16个通用输出口(其中4路是高速输出口可以实现4路的PSO),2路AD和DA。
自带1个 RS232串口,1个以太网接口。带一个CAN总线接口,支持通过ZCAN协议来连接扩展模块。带一个CANOPEN接口(功能需要软件版本支持)。带一个EtherCAT总线接口可扩展数字模拟IO以及脉冲定位模块等,带一个手轮接口。
二、运动控制卡的Qt开发流程 1.新建Qt 项目
图 1-1 新建Qt项目

图 1-2 选择项目路径
图 1-3 选择Qt编译套件(kits)

图1-4 选择基类
(1)将函数库相关的文件复制到新建的项目中。

图1-5 库文件复制
(2)向新建的项目里面添加函数库的静态库。(zmotion.lib)

图1-6 添加函数库1

图1-7 添加函数库2

图 1-8 添加函数库3
(3)添加函数库相关的头文件到项目中(zmcaux.cpp、zmcaux.h、Zmotion.h)。

图 1-9 添加头文件
(4)声明相关头文件,并定义连接句柄。

图1-10 声明头文件
2.PC函数介绍
(1)PC函数手册也在光盘资料里面,具体路径如下:“光盘资料8.PC函数函数库2.1Motion函数库编程手册V2.1.pdf”。

(2)PC编程一般需要建立上位机和控制器之间的链接。和控制卡的连接一般习惯使用网口方式进行链接,具体接口说明如下。
| 指令7 | ZAux_OpenEth |
| 指令原型 | int32 __stdcall ZAux_OpenEth(char *ipaddr, ZMC_HANDLE * phandle) |
| 指令说明 | 以太网链接控制器。 |
| 输入参数 | 输入参数1个,详细见下面说明。 |
| ipaddr | 链接的IP地址。 |
| 输出参数 | 输出参数1个,详细见下面说明。 |
| Phandle | 返回的链接句柄。 |
| 返回值 | 详细见错误码说明。 |
(3)如果想将Basic指令封装成上位机可以直接调用的接口就必须使用在线命令这个接口进行函数封装,在线命令接口说明如下。
| 指令 | 说明 |
| ZAux_Execute | 直接发送指令(当控制器没有缓冲时自动阻塞) |
| ZAux_DirectCommand | 直接发送指令(用于调试, 只支持少数命令 暂时不支持) |
| 指令1 | ZAux_Execute |
| 指令原型 | int32 __stdcall ZAux_Execute(ZMC_HANDLE handle, const char *pszCommand, char *psResponse, uint32 uiResponseLength) |
| 指令说明 | 发送字符串命令到控制器(当控制器没有缓冲时自动阻赛)。 |
| 输入参数 | 共有3个输入参数,见下方说明。 |
| handle | 链接句柄。 |
| pszCommand | 发送的命令字符串。 |
| uiResponseLength | 返回的字符长度。 |
| 输出参数 | 共有1个输出参数,见下方说明。 |
| psResponse | 返回的字符串。 |
| 返回值 | 见错误码详细说明。 |
| 指令2 | ZAux_DirectCommand |
| 指令原型 | int32 __stdcall ZAux_DirectCommand(ZMC_HANDLE handle, const char *pszCommand,char *psResponse, uint32 uiResponseLength) |
| 指令说明 | 发送字符串命令到控制器(用于调试, 只支持少数命令,暂时不支持)。 |
| 输入参数 | 共有3个输入参数,见下方说明。 |
| handle | 链接句柄。 |
| pszCommand | 发送的命令字符串。 |
| uiResponseLength | 返回的字符长度。 |
| 输出参数 | 共有1个输出参数,见下方说明。 |
| uiResponseLength | 返回的字符串。 |
| 返回值 | 见错误码详细说明。 |
(4)Basic指令SPEED的封装示例。

3.Qt进行Move_Pt指令的封装,实现自定义曲线的运动。
(1)自定义曲线运动例程Qt界面如下。

(2)通过Qt的connect将【连接按钮】的单击事件绑定一个槽函数进行链接控制器的操作。
//定义一个定时器
QTimer *UpData = new QTimer(this);
connect(UpData,&QTimer::timeout,this,[=](){
if(g_handle!=0)
{
//获取轴位置信息
ZAux_Direct_GetAllAxisPara(g_handle,"DPOS",AxisNum,Dpos);
ui->DposX->setText(QString("%1").arg(Dpos[0]));
ui->DposY->setText(QString("%1").arg(Dpos[1]));
ui->DposZ->setText(QString("%1").arg(Dpos[2]));
ui->DposU->setText(QString("%1").arg(Dpos[3]));
//获取轴速度信息
ZAux_Direct_GetAllAxisPara(g_handle,"MSPEED",AxisNum,Mspeed);
ui->MspeedX->setText(QString("%1").arg(Mspeed[0]));
ui->MspeedY->setText(QString("%1").arg(Mspeed[1]));
ui->MspeedZ->setText(QString("%1").arg(Mspeed[2]));
ui->MspeedU->setText(QString("%1").arg(Mspeed[3]));
//获取各个轴的运动情况
float idle[4]={0};
ZAux_Direct_GetAllAxisPara(g_handle,"idle",AxisNum,idle);
idle[0]=idle[0]+idle[1]+idle[2]+idle[3];
if(idle[0]>(-4))
{
MotionStatus=1;//运动中
}
else
{
MotionStatus=0;
}
}
}
(3)通过定时器更新控制器各个轴的位置和速度信息。
//定义一个定时器
QTimer*UpData =newQTimer(this);
connect(UpData,&QTimer::timeout,this,[=](){
if(g_handle!=0)
{
//获取轴位置信息
ZAux_Direct_GetAllAxisPara(g_handle,"DPOS",AxisNum,Dpos);
ui->DposX->setText(QString("%1").arg(Dpos[0]));
ui->DposY->setText(QString("%1").arg(Dpos[1]));
ui->DposZ->setText(QString("%1").arg(Dpos[2]));
ui->DposU->setText(QString("%1").arg(Dpos[3]));
//获取轴速度信息
ZAux_Direct_GetAllAxisPara(g_handle,"MSPEED",AxisNum,Mspeed);
ui->MspeedX->setText(QString("%1").arg(Mspeed[0]));
ui->MspeedY->setText(QString("%1").arg(Mspeed[1]));
ui->MspeedZ->setText(QString("%1").arg(Mspeed[2]));
ui->MspeedU->setText(QString("%1").arg(Mspeed[3]));
//获取各个轴的运动情况
floatidle[4]={0};
ZAux_Direct_GetAllAxisPara(g_handle,"idle",AxisNum,idle);
idle[0]=idle[0]+idle[1]+idle[2]+idle[3];
if(idle[0]>(-4))
{
MotionStatus=1;//运动中
}
else
{
MotionStatus=0;
}
}
}
(4)Basic指令Move_Pt的介绍。

(5)Move_Pt的接口封装。
A.通过在线命令ZAux_DirectCommand()进行接口封装。
/*************************************************************
Description: 单位时间距离
Input: 卡链接handle
运动轴数、轴列表
运动时间 ticks单位, 1ticks≈1ms
运动距离 units单位
Output: 无
Return: 错误码
*************************************************************/
int32 MyApi::ZAux_Direct_MovePt(ZMC_HANDLE handle, int iAxisNum, int *piAxisList, int iTime, float *pfDisList)
{
char cmdbuff[2048],tempbuff[2048];
char cmdbuffAck[2048];
//输入参数判断
if((0 > iAxisNum || iAxisNum > MAX_AXIS_AUX)) return ERR_AUX_PARAERR;
if(NULL == piAxisList) return ERR_AUX_PARAERR;
if(iTime<=0) return ERR_AUX_PARAERR;
if(NULL == pfDisList) return ERR_AUX_PARAERR;
//生成指令选择运动的轴......Basic指令BASE(0,1,2,3)表示选择轴0、轴1、轴2、轴3
//通过字符串拼接指令封装Basic选择轴指令BASE(piAxisList[0],piAxisList[1],.....piAxisList[i])
strcpy(cmdbuff, "BASE(");
for(int i = 0; i< iAxisNum-1; i++)
{
sprintf(tempbuff, "%d,",piAxisList[i]);
strcat(cmdbuff, tempbuff);
}
sprintf(tempbuff, "%d)",piAxisList[iAxisNum-1]);
strcat(cmdbuff, tempbuff);
//换行继续封装Basic指令
strcat(cmdbuff, "
");
//生成单位时间运动距离指令,......Basic指令通过Move_PT(ticks, dis1,dis2…)实现
sprintf(tempbuff, "Move_PT(%d,",iTime);
strcat(cmdbuff, tempbuff);
//封装各个轴的运动距离
for(int i = 0; i< iAxisNum-1; i++)
{
sprintf(tempbuff, "%f,",pfDisList[i]);
strcat(cmdbuff, tempbuff);
}
sprintf(tempbuff, "%f)",pfDisList[iAxisNum-1]);
strcat(cmdbuff, tempbuff);
//调用命令执行函数
//printf("%s",cmdbuff);
return ZAux_DirectCommand(handle, cmdbuff, cmdbuffAck, 2048);
}
B.Qt例程调用刚刚封装的接口MyApi::ZAux_Direct_MovePt()。
//启动Move_Pt运动
voidWidget::on_PtStartButton_clicked()
{
if(0== MotionStatus)
{
intbuffNum=0;
//获取0轴剩余缓冲区数目,缓冲区数目足够才下发指令
ZAux_Direct_GetRemain_LineBuffer(g_handle,0,&buffNum);
if(buffNum>3)
{
intAxisList[4]={0,1,2,3};
floatDisList[3][5];
for(inti=0;i<3;i++)
{
for(intj=0;j<5;j++)
{
DisList[i][j]=LineData[i][j]->text().toFloat();
}
//调用自己封装的函数接口进行MOVE_PT运动
myapi->ZAux_Direct_MovePt(g_handle,AxisNum,AxisList,(int)(DisList[i][0]),&DisList[i][1]);
}
}
else
{
QMessageBox::warning(this,"warning","轴缓冲区剩余不足");
}
}
else
{
QMessageBox::warning(this,"warning","系统在运行中......");
}
}
C.示波器波形抓取。

(6)封装一个API可以下发多个move_ptabs指令进行加工。 A.一次下发多个move_ptabs指令的封装。
/*************************************************************
Description: 一次发送多个单位时间距离指令
Input: 卡链接handle
运动轴数、轴列表
运动时间 ticks单位, 1ticks≈1ms
运动距离 units单位
Output: 无
Return: 错误码
*************************************************************/
int32 MyApi::ZAux_Direct_MovePtAbsS(ZMC_HANDLE handle, int iAxisNum, int *piAxisList, int ApiNum,int *iTime, float *pfDisList)
{
char cmdbuff[2048*128],tempbuff[2048];
char cmdbuffAck[2048];
//输入参数判断
if((0 > iAxisNum || iAxisNum > MAX_AXIS_AUX)) return ERR_AUX_PARAERR;//轴数不正确
if(NULL == piAxisList) return ERR_AUX_PARAERR;//轴列表空
if(iTime== 0) return ERR_AUX_PARAERR;//时间列表空
if(NULL == pfDisList) return ERR_AUX_PARAERR;//运动距离列表空
if((ApiNum<0)||(ApiNum>50)) return ERR_AUX_PARAERR;//Api数目不对
//生成指令选择运动的轴......Basic指令BASE(0,1,2,3)表示选择轴0、轴1、轴2、轴3
//通过字符串拼接指令封装Basic选择轴指令BASE(piAxisList[0],piAxisList[1],.....piAxisList[i])
strcpy(cmdbuff, "BASE(");
for(int i = 0; i< iAxisNum-1; i++)
{
sprintf(tempbuff, "%d,",piAxisList[i]);
strcat(cmdbuff, tempbuff);
}
sprintf(tempbuff, "%d)",piAxisList[iAxisNum-1]);
strcat(cmdbuff, tempbuff);
//换行继续封装Basic指令
strcat(cmdbuff, "
");
for(int j=0;j0)
{
//生成单位时间运动距离指令,......Basic指令通过Move_PT(ticks, dis1,dis2…)实现
sprintf(tempbuff, "Move_PtAbs(%d,",iTime[j]);
strcat(cmdbuff, tempbuff);
//封装各个轴的运动距离
for(int i = 0; i< iAxisNum-1; i++)
{
sprintf(tempbuff, "%.5f,",pfDisList[i+j*iAxisNum]);
strcat(cmdbuff, tempbuff);
}
sprintf(tempbuff, "%.5f)",pfDisList[iAxisNum-1+j*iAxisNum]);
strcat(cmdbuff, tempbuff);
strcat(cmdbuff, "
");
}
}
//调用命令执行函数
return ZAux_DirectCommand(handle, cmdbuff, cmdbuffAck, 2048);
}
B.Qt例程调用刚封装的接口MyApi::ZAux_Direct_MovePtAbsS()。
//获取轴运动缓冲剩余情况
ZAux_Direct_GetRemain_LineBuffer(g_handle,0,&buffNum);
if((buffNum>ApiNum*2)&&(SendNum*1<(4*ui->HorizoScale->text().toFloat())))
{
Num = (float) 4*ui->HorizoScale->text().toFloat();
switch (RunType) {
//((-sin(PI*2*i/T)/(PI*2))+i/T)*500
case 0:
for(int i=0;iCanvas->StartPoint.setX(0);
ui->Canvas->StartPoint.setY(-DisList[0]*EquivalentY);
ui->Canvas->StopPoint.setX(0);
ui->Canvas->StopPoint.setY(-DisList[0]*EquivalentY);
TimerWaveform->start(10);
QString Str;
char buff[1204];
//发送在线命令启动示波器
Str=QString("%1%2%3%4%5").arg("SCOPE(ON,1,").arg(0).arg(",").arg(3500).arg(",DPOS(0))");
ZAux_Execute(g_handle,Str.toLatin1().data(),buff,1024);
ZAux_Trigger(g_handle);
QThread::msleep(1);
}
myapi->ZAux_Direct_MovePtAbsS(g_handle,1, &AxisList,ApiNum, TimeList, DisList);
}
C.Qt抓取轴运动的位置数据,产生位置波形图。
oidDraw::paintEvent(QPaintEvent*event)
{
if(DrawFlag!=0)
{
//实例化一个画家this指绘图设备
QPainterPainter(WaveformFigure);
Painter.translate(0,(int)(ImgH/2));
//设置画笔
QPenPen(QColor(255,0,0));
Pen.setWidth(4);
Pen.setStyle(Qt::SolidLine);
Painter.setPen(Pen);
//发送触发示波器抓取的命令
if((CurTriggerNum>=SingTriggerNum))
{
qDebug()<<"触发示波器"<::iteratoriet;
for(inti=0;i0))
{
Painter.drawLine(StartPoint,StopPoint);
}
}
}
}
D.抓波形图查看效果。

b.Qt抓取Y=((-sin(PI*2*i/T)/(PI*2))+i/T)*500的位置曲线。

本次,正运动技术EtherCAT运动控制卡的自定义运动曲线,就分享到这里。
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !