学习如何基于Qt框架、用Zmotion运动控制器开发库编写上位机以实现进阶的多轴插补运动功能。
本文是系列前文的续集,前文链接:
Zmotion运控器+Hiwin伺服驱动的Qt上位机开发(一):EtherCAT通讯、基本单轴控制、回零功能的实现_Zaiton的博客-CSDN博客
1 功能总览
正运动技术提供的Zmotion Tools V1.0中提供了多轴插补运动界面:
该界面实现了轴选择、轴运动状态展示、插补模式选择、插补运动示教、数据显示等功能,方便易用。
于是博主基于Qt框架,调用ZMC的VC编程库实现了其中大部分关键功能:
2 多轴运动状态更新的实现
为了同步更新多轴运动状态,在本系列第一章文章中定时器关联的槽函数updateGlobalDate()
的基础上继续添加多轴状态功能,此处仅考虑常用的X、Y、Z、U四轴,这也是ZMC函数库提供的插补函数所适用的轴类型。
void FrmMotionControl::slot_updateGlobalDate()
{
//.......
//多轴状态更新
//反馈位置
float m_fAxisesMPos[4];
ZAux_Direct_GetAllAxisPara( g_handle,"MPOS",5,m_fAxisesMPos); //获取 0-4 轴的轴类型
ui->edt_XPos->setText(QString ("%2").arg (m_fAxisesMPos[g_iXIndex]));//显示轴反馈位置
ui->edt_YPos->setText(QString ("%2").arg (m_fAxisesMPos[g_iYIndex]));
ui->edt_ZPos->setText(QString ("%2").arg (m_fAxisesMPos[g_iZIndex]));
ui->edt_UPos->setText(QString ("%2").arg (m_fAxisesMPos[g_iUIndex]));
//反馈状态
int status = 0;
ZAux_Direct_GetIfIdle(g_handle,g_iXIndex,&status); //判断轴X状态
if (status == -1)
{
ui->edt_XStatus->setText("停止中");
}else
{
ui->edt_XStatus->setText("运动中");
}
ZAux_Direct_GetIfIdle(g_handle,g_iYIndex,&status); //判断轴Y状态
if (status == -1)
{
ui->edt_YStatus->setText("停止中");
}else
{
ui->edt_YStatus->setText("运动中");
}
ZAux_Direct_GetIfIdle(g_handle,g_iZIndex,&status); //判断轴Z状态
if (status == -1)
{
ui->edt_ZStatus->setText("停止中");
}else
{
ui->edt_ZStatus->setText("运动中");
}
ZAux_Direct_GetIfIdle(g_handle,g_iUIndex,&status); //判断轴U状态
if (status == -1)
{
ui->edt_UStatus->setText("停止中");
}else
{
ui->edt_UStatus->setText("运动中");
}
//多轴连续插补运动状态更新
int m_iRembuff = 0;
int m_iCurmark = 0;
//判断主轴状态(即Ethercat的第一个轴)
ZAux_Direct_GetIfIdle(g_handle,1,&status);
//判断存放直线和圆弧的剩余缓冲 ,ZAux_Direct_GetRemain_Buffer判断的空间圆弧的缓冲,也是占缓冲最大的运动
// ZAux_Direct_GetRemain_LineBuffer(g_handle,0,&m_iRembuff);
ZAux_Direct_GetRemain_Buffer(g_handle,0,&m_iRembuff);
//判断当前命令执行到第几,
ZAux_Direct_GetMoveCurmark(g_handle,0,&m_iCurmark);
ui->edt_RemainBufferRemainBuffer->setText(QString ("%2").arg(m_iRembuff));
ui->edt_CurrExecut->setText(QString ("%2").arg(m_iCurmark));
}
3 多轴选择序号切换的实现
设置QComboBox控件用于切换选择轴号,切换后发出信号函数&QComboBox::currentIndexChanged
,触发槽函数checkAxisIndexChanged()
更新X、Y、Z、U对应轴的下标。此处考虑到传参的方便,采用Lambda表达式传递槽函数,写在主窗口Widget类的同名构造函数中:
connect(ui->cmb_ChooseAxis1, &QComboBox::currentIndexChanged, this, [=](){checkAxisIndexChanged(1);});
connect(ui->cmb_ChooseAxis2, &QComboBox::currentIndexChanged, this, [=](){checkAxisIndexChanged(2);});
connect(ui->cmb_ChooseAxis3, &QComboBox::currentIndexChanged, this, [=](){checkAxisIndexChanged(3);});
connect(ui->cmb_ChooseAxis4, &QComboBox::currentIndexChanged, this, [=](){checkAxisIndexChanged(4);
槽函数checkAxisIndexChanged()
,更新下标:
void FrmMotionControl::checkAxisIndexChanged(int index)
{
switch(index)
{
case 1://X轴
g_iXIndex=ui->cmb_ChooseAxis1->currentText().toInt();
break;
case 2://Y轴
g_iYIndex=ui->cmb_ChooseAxis2->currentText().toInt();
break;
case 3://Z轴
g_iZIndex=ui->cmb_ChooseAxis3->currentText().toInt();
break;
case 4://U轴
g_iUIndex=ui->cmb_ChooseAxis4->currentText().toInt();
break;
default:
break;
}
}
4 多轴单次插补运动的实现
在设定、选择好图中红框所示参数后,点击右下角“单词插补”按钮即可触发多轴单次插补运动,触发槽函数on_btn_StartMultiAxisMotion_clicked()
。
void FrmMotionControl::on_btn_StartMultiAxisMotion_clicked()
{
if(NULL == g_handle)//检测链接是否连通
{
QMessageBox::warning(this,"提示","链接断开状态");
return ;
}
int m_Axislist[4] = {g_iXIndex,g_iYIndex,g_iZIndex,g_iUIndex};//运动EtherCAT轴列表
float m_Poslist[4] = {ui->edt_DXPos->text().toFloat(),ui->edt_DYPos->text().toFloat(),ui->edt_DZPos->text().toFloat(),ui->edt_DUPos->text().toFloat()}; //运动列表
float m_Midlist[4] = {ui->edt_DXAngPos->text().toFloat(),ui->edt_DYAngPos->text().toFloat(),ui->edt_DZAngPos->text().toFloat(),ui->edt_DUAngPos->text().toFloat()}; //圆弧中间点
float m_Endmove[4] = {0}; //用于相对绝对转换
bool m_Absmode;//是否绝对运动模式
if(ui->rbtn_AbsMove->isChecked()) m_Absmode=0;//绝对运动
if(ui->rbtn_Move->isChecked()) m_Absmode=1;//相对运动
int m_Movemode=ui->cmb_MoveType->currentIndex();//获取直线圆弧插补类型下标
//选择参与运动的轴,第一个轴为主轴,插补参数全用主轴参数
ZAux_Direct_SetSpeed(g_handle,m_Axislist[0],ui->edt_Speed->text().toFloat()); //速度 UNITS / S
ZAux_Direct_SetAccel(g_handle,m_Axislist[0],ui->edt_Accel->text().toFloat()); //加速度
ZAux_Direct_SetDecel(g_handle,m_Axislist[0],ui->edt_Decel->text().toFloat()); //减速度
//单次插补运动
if(m_Absmode == 0) //绝对运动
{
switch(m_Movemode)
{
case 0://XY直线插补
ZAux_Direct_MoveAbs(g_handle,2,m_Axislist,m_Poslist);
break;
case 1://XY 3点圆弧
ZAux_Direct_MoveCirc2Abs(g_handle,2,m_Axislist,m_Midlist[0],m_Midlist[1],m_Poslist[0],m_Poslist[1]);
break;
case 2://XYZU直线插补
ZAux_Direct_MoveAbs(g_handle,4,m_Axislist,m_Poslist);
break;
case 3://XYZ空间圆弧插补U螺旋 没有绝对指令,需要用ENDMOVE_BUFF转相对
for(int i=0;i<4;i++)
{
ZAux_Direct_GetEndMoveBuffer(g_handle,i,&m_Endmove[i]); //计算相对位移
m_Poslist[i] = m_Poslist[i] - m_Endmove[i];
m_Midlist[i] = m_Midlist[i] - m_Endmove[i];
}
ZAux_Direct_MSpherical(g_handle,4,m_Axislist,m_Poslist[0],m_Poslist[1],m_Poslist[2],m_Midlist[0],m_Midlist[1],m_Midlist[2],0,m_Poslist[3],0);
break;
default:
break;
}
}
else //相对运动
{
switch(m_Movemode)
{
case 0:
//XY直线插补
ZAux_Direct_Move(g_handle,2,m_Axislist,m_Poslist);
break;
case 1:
//XY 3点圆弧
ZAux_Direct_MoveCirc2(g_handle,2,m_Axislist,m_Midlist[0],m_Midlist[1],m_Poslist[0],m_Poslist[1]);
break;
case 2:
//XYZU直线插补
ZAux_Direct_Move(g_handle,4,m_Axislist,m_Poslist);
break;
case 3:
//XYZ空间圆弧插补U螺旋 第5轴距离为0
ZAux_Direct_MSpherical(g_handle,2,m_Axislist,m_Poslist[0],m_Poslist[1],m_Poslist[2],m_Midlist[0],m_Midlist[1],m_Midlist[2],0,m_Poslist[3],0);
break;
default:
break;
}
}
}
5 多轴连续插补指令管理的实现
多轴连续插补指令管理包含指令添加、指令清空、指令载入、指令显示等功能。
5.1 指令添加与显示
设定好指令参数后,点击按钮“添加一条”,触发槽函数on_btn_AddMoveData_clicked()
,然后将命令存入容器g_fDestdis
,并在图中右下角的文本框中输出命令的参数。
void FrmMotionControl::on_btn_AddMoveData_clicked()
{
int m_Movemode=ui->cmb_MoveType->currentIndex();//获取直线圆弧插补类型下标
float m_Poslist[4] = {ui->edt_DXPos->text().toFloat(),ui->edt_DYPos->text().toFloat(),ui->edt_DZPos->text().toFloat(),ui->edt_DUPos->text().toFloat()}; //运动列表
float m_Midlist[4] = {ui->edt_DXAngPos->text().toFloat(),ui->edt_DYAngPos->text().toFloat(),ui->edt_DZAngPos->text().toFloat(),ui->edt_DUAngPos->text().toFloat()}; //圆弧中间点
float m_Speed=ui->edt_Speed->text().toFloat();//sp速度
int m_Index;//当前命令g_fDestdis的下标
switch(m_Movemode)//确认运动指令的类型、数据来源,并存入命令容器g_fDestdis,显示在一个命令文本框中
{
case 0://XY直线插补
g_fDestdis.push_back({m_Poslist[0],m_Poslist[1],m_Poslist[2],m_Poslist[3],m_Speed,(float)m_Movemode});//可能有问题?
m_Index = distance(g_fDestdis.begin(), g_fDestdis.end());
ui->edt_MoveCmd->append(QString::number(m_Index)+" 方法:XY直线插补 x:"+QString::number(m_Poslist[0],'f',3)+" y:"+QString::number(m_Poslist[1],'f',3)+" sp:"+QString::number(m_Speed,'f',3));
break;
case 1://XY 3点圆弧
g_fDestdis.push_back({m_Midlist[0],m_Midlist[1],m_Poslist[0],m_Poslist[1],m_Speed,(float)m_Movemode});
m_Index = distance(g_fDestdis.begin(), g_fDestdis.end());
ui->edt_MoveCmd->append(QString::number(m_Index)+" 方法:XY圆弧插补 中x:"+QString::number(m_Midlist[0],'f',3)+" 中y:"+QString::number(m_Midlist[1],'f',3)+" 末x:"+QString::number(m_Poslist[0],'f',3)+" 末y:"+QString::number(m_Poslist[1],'f',3)+" sp:"+QString::number(m_Speed,'f',3));
break;
case 2://XYZU直线插补
g_fDestdis.push_back({m_Poslist[0],m_Poslist[1],m_Poslist[2],m_Poslist[3],m_Speed,(float)m_Movemode});
m_Index = distance(g_fDestdis.begin(), g_fDestdis.end());
ui->edt_MoveCmd->append(QString::number(m_Index)+" 方法:XYZU直线插补 x:"+QString::number(m_Poslist[0],'f',3)+" y:"+QString::number(m_Poslist[1],'f',3)+" z:"+QString::number(m_Poslist[2],'f',3)+" u:"+QString::number(m_Poslist[3],'f',3)+" sp:"+QString::number(m_Speed,'f',3));
break;
case 3://XYZ空间圆弧插补U螺旋 没有绝对指令,需要用ENDMOVE_BUFF转相对
g_fDestdis.push_back({m_Poslist[0],m_Poslist[1],m_Poslist[2],m_Midlist[0],m_Speed,(float)m_Movemode,m_Midlist[1],m_Midlist[2],m_Poslist[3]});
m_Index = distance(g_fDestdis.begin(), g_fDestdis.end());
ui->edt_MoveCmd->append(QString::number(m_Index)+" 方法:XYZ空间圆弧插补U螺旋 x1:"+QString::number(m_Poslist[0],'f',3)+" y1:"+QString::number(m_Poslist[1],'f',3)
+" z1:"+QString::number(m_Poslist[2],'f',3)+" x2:"+QString::number(m_Midlist[0],'f',3)+" y2:"+QString::number(m_Midlist[1],'f',3)+" z2:"+QString::number(m_Midlist[2],'f',3)
+" u:"+QString::number(m_Poslist[3],'f',3)+" sp:"+QString::number(m_Speed,'f',3));
break;
default:
break;
}
}
5.2 指令清空
清空当前指令容器中的所有指令并清空指令文本框中的内容:
void FrmMotionControl::on_btn_ClearMoveData_clicked()
{
vector<vector<float>> tmp;
g_fDestdis.swap(tmp);//清空元素并释放内存
ui->edt_MoveCmd->clear();
}
5.3 指令载入与显示
下图是博主自定义的命令存储格式示例,一般直线插补、圆弧插补坐标参数只需要前4位,但是更复杂的XYZ空间圆弧+U螺旋插补至少需要7位坐标参数,因此在第6位开始额外设置了3位拓展坐标参数:
参照上图格式拟定好存储命令的外部.txt文件,点击“载入数据”按钮,触发槽函数on_btn_LoadMoveData_clicked()
,选择并载入命令:
void FrmMotionControl::on_btn_LoadMoveData_clicked()
{
QString file_path = QFileDialog::getOpenFileName(NULL,QStringLiteral("数据另存为"),"",QObject::tr("Text Files(*.txt)"));
QFile f(file_path);
if(! f.open(QIODevice::ReadOnly|QIODevice::Text))
qDebug()<<f.errorString();
else
qDebug()<<"openok";
f.seek(0);
QTextStream txtInput(&f);
QString lineStr;
int flag=0;//命令容器g_fDestdis下标
if(!g_fDestdis.empty())//预先清空命令容器并释放内存
{
vector<vector<float>> tmp;
g_fDestdis.swap(tmp);
}
while(!txtInput.atEnd())//读取每个子块
{
g_fDestdis.push_back({});
QString line=txtInput.readLine();//读取一行
line = line.trimmed();//去掉首尾空格和多余空格
QStringList strlist=line.split(" ");//以“ ”分割字符串,得到坐标点对
for(int i=0;i<strlist.size();i++)//将条目存入命令容器
{
g_fDestdis[flag].push_back(strlist[i].toFloat());
}
flag++;//更新当前下标
//更新命令文本框内容。为了下标从1开始,故意放在flag++之后。
if(strlist.count()>=5)
{
int m_Movemode=strlist[5].toInt();
switch(m_Movemode)//确认运动指令的类型、数据来源,并存入命令容器g_fDestdis,显示在一个命令文本框中
{
case 0://XY直线插补
ui->edt_MoveCmd->append(QString::number(flag)+" 方法:XY直线插补 x:"+strlist[0]+" y:"+strlist[1]+" sp:"+strlist[4]);
break;
case 1://XY 3点圆弧
ui->edt_MoveCmd->append(QString::number(flag)+" 方法:XY圆弧插补 中x:"+strlist[0]+" 中y:"+strlist[1]+" 末x:"+strlist[2]+" 末y:"+strlist[3]+" sp:"+strlist[4]);
break;
case 2://XYZU直线插补
ui->edt_MoveCmd->append(QString::number(flag)+" 方法:XYZU直线插补 x:"+strlist[0]+" y:"+strlist[1]+" z:"+strlist[2]+" u:"+strlist[3]+" sp:"+strlist[4]);
break;
case 3://XYZ空间圆弧插补U螺旋 没有绝对指令,需要用ENDMOVE_BUFF转相对
ui->edt_MoveCmd->append(QString::number(flag)+" 方法:XYZ空间圆弧插补U螺旋 x1:"+strlist[0]+" y1:"+strlist[1]
+" z1:"+strlist[2]+" x2:"+strlist[3]+" y2:"+strlist[6]+" z2:"+strlist[7]
+" u:"+strlist[8]+" sp:"+strlist[4]);
break;
default:
break;
}
}
}
}
6 多轴连续插补运动的实现
6.1 指令运行
本例的多轴连续插补运动指令运行通过按钮槽函数on_btn_StartMultiAxisContMotion_clicked()
启动,在该槽函数中分段循环启动(每次仅读入一定数量的命令,有个缓冲)定时器timer_MultiAxisInterpolateDataUpdate
,每次启动都触发执行分段命令的槽函数slot_updateMultiAxisInterpolate()
。
//多轴连续插补运动调用计时器
connect(timer_MultiAxisInterpolateDataUpdate, SIGNAL(timeout()), this, SLOT(slot_updateMultiAxisInterpolate()));
//按钮槽函数:多轴开始连续插补运动
void FrmMotionControl::on_btn_StartMultiAxisContMotion_clicked()
{
if(NULL == g_handle)//检测链接是否连通
{
QMessageBox::warning(this,"提示","链接断开状态");
return ;
}
int m_Axislist[4] = {g_iXIndex,g_iYIndex,g_iZIndex,g_iUIndex};//运动EtherCAT轴列表
int m_Cornermode = 0;//选择轨迹前瞻的转角模式,0为未选择
if(ui->chk_CornerDecelerate->isChecked())
{
m_Cornermode = m_Cornermode + 2;
}
if(ui->chk_SmallCircleSpeedLimit->isChecked())
{
m_Cornermode = m_Cornermode + 8;
}
if(ui->chk_CirChamfer->isChecked())
{
m_Cornermode = m_Cornermode + 32;
}
if(ui->chk_ContinuousInterpolation->isChecked())
{
ZAux_Direct_SetMerge(g_handle,m_Axislist[0],1); //连续插补开关
//选择参与运动的轴,第一个轴为主轴,插补参数全用主轴参数
ZAux_Direct_SetSpeed(g_handle,m_Axislist[0],ui->edt_Speed->text().toFloat()); //速度 UNITS / S
ZAux_Direct_SetAccel(g_handle,m_Axislist[0],ui->edt_Accel->text().toFloat()); //加速度
ZAux_Direct_SetDecel(g_handle,m_Axislist[0],ui->edt_Decel->text().toFloat()); //减速度
//拐角模式参数设置
int m_Startang=ui->edt_StartDecAng->text().toFloat();
int m_Stopang=ui->edt_StopDecAng->text().toFloat();
int m_Fullradius=ui->edt_FullSpRad->text().toFloat();
int m_Zsmooth=ui->edt_Zsmooth->text().toFloat();
ZAux_Direct_SetLspeed(g_handle,m_Axislist[0],ui->edt_StartSpeed->text().toFloat()); //起始速度 ,拐角减速由 运动速度-起始速度 线性减速的
ZAux_Direct_SetCornerMode(g_handle,m_Axislist[0],m_Cornermode); //拐角模式
ZAux_Direct_SetDecelAngle(g_handle,m_Axislist[0],m_Startang*3.14/180); //开始减速角度,转换为弧度
ZAux_Direct_SetStopAngle(g_handle,m_Axislist[0],m_Stopang*3.14/180); //停止减速角度
ZAux_Direct_SetFullSpRadius(g_handle,m_Axislist[0],m_Fullradius); //小圆减速半径
ZAux_Direct_SetZsmooth(g_handle,m_Axislist[0],m_Zsmooth); //倒角半径
//拐角减速时带SP的运动指令生效,不使用时设置一个大于sp的startmovespeed与endmovespeed。
ZAux_Direct_SetStartMoveSpeed(g_handle,m_Axislist[0], 10000000);
ZAux_Direct_SetEndMoveSpeed(g_handle, m_Axislist[0], 10000000);
//调用运动 通过检查是否还有剩余缓冲来确定是否发运动
ZAux_Direct_SetMovemark(g_handle,m_Axislist[0],0 ); //设置MARK = 0 ,来通过读取CURMARK实现判断当前执行到哪里
g_iCurseges = 0;//开始执行命令前,重置 当前执行命令的下标为0
//触发定时器,开始运行分段命令
timer_MultiAxisInterpolateDataUpdate->start(1000);//每1秒运行1次分段命令,每1个分段内包含固定数量的命令
}
}
//定时器:用于多轴连续插补运动的分段调用
void FrmMotionControl::slot_updateMultiAxisInterpolate()
{
int m_Axislist[4] = {g_iXIndex,g_iYIndex,g_iZIndex,g_iUIndex};//运动EtherCAT轴列表
int iresult = 0;
int iremain = 0;
// #define LEGS_MAX (sizeof(g_fDestdis) / sizeof(float)/5) //总分段数
bool m_Absmode;//是否绝对运动模式
if(ui->rbtn_AbsMove->isChecked()) m_Absmode=0;//绝对运动
if(ui->rbtn_Move->isChecked()) m_Absmode=1;//相对运动
for (int i = 0 ; i < 5; i++) //每次定时器中断填入几个运动缓冲,可修改
{
if(g_iCurseges >= g_fDestdis.size()) //是否填完,如果填完则停止定时器,停止运动序列
{
//循环运动,暂时没遇到相关需求所以没写,此处介绍相关思路,含两种循环状况:1.倒退运动,需要置反速度,倒序执行命令;2.循环运动,再正序执行一遍命令。
if(ui->chk_RepMove->isChecked())
{
return;//break;
}
else//不循环,停止运动
{
timer_MultiAxisInterpolateDataUpdate->stop();
return;
}
}
iresult = ZAux_Direct_GetRemain_Buffer(g_handle, 0, &iremain);//不同类型插补函数不同、直线插补缓冲判断用ZAux_Direct_GetRemain_LineBuffer
if(iremain > 0)
{
//加入一段,每段可以有自己的ForceSpeed
iresult = ZAux_Direct_SetForceSpeed(g_handle, m_Axislist[0], g_fDestdis[g_iCurseges][4]);
float m_Endmove[4] = {0}; //用于相对绝对转换
if(m_Absmode == 0) //绝对运动
{
int m_Movetype=(int)g_fDestdis[g_iCurseges][5];//运动指令的类型
float m_Poslist[4], m_Midlist[4];
if(m_Movetype==3)//声明要在switch-case结构前面,否则会报错。因为switch-case的c++逻辑控制用label来标记跳转,所以声明参数的作用域是在整个switch的{}括号内
{
m_Poslist[0]=g_fDestdis[g_iCurseges][0];
m_Poslist[1]=g_fDestdis[g_iCurseges][1];
m_Poslist[2]=g_fDestdis[g_iCurseges][2];
m_Poslist[3]=g_fDestdis[g_iCurseges][8]; //运动列表
m_Midlist[0]=g_fDestdis[g_iCurseges][3];
m_Midlist[1]=g_fDestdis[g_iCurseges][6];
m_Midlist[2]=g_fDestdis[g_iCurseges][7];
m_Midlist[3]=0; //圆弧中间点,第四位无用
}
switch(m_Movetype)//确认运动指令的类型
{
float destdis[5];//模式0和2运动参数只输入4位,此处从命令容器中抽出前4位
case 0://XY直线插补
copy(g_fDestdis[g_iCurseges].begin(),g_fDestdis[g_iCurseges].begin()+3,destdis);
iresult = ZAux_Direct_MoveAbsSp(g_handle, 4,m_Axislist, destdis);
break;
case 1://XY 3点圆弧
iresult = ZAux_Direct_MoveCirc2AbsSp(g_handle,2,m_Axislist,g_fDestdis[g_iCurseges][0],g_fDestdis[g_iCurseges][1],g_fDestdis[g_iCurseges][2],g_fDestdis[g_iCurseges][3]);
break;
case 2://XYZU直线插补
copy(g_fDestdis[g_iCurseges].begin(),g_fDestdis[g_iCurseges].begin()+3,destdis);
iresult = ZAux_Direct_MoveAbs(g_handle,4,m_Axislist,destdis);
break;
case 3://XYZ空间圆弧插补U螺旋 没有绝对指令,需要用ENDMOVE_BUFF转相对
for(int i=0;i<4;i++)
{
ZAux_Direct_GetEndMoveBuffer(g_handle,i,&m_Endmove[i]); //计算相对位移
m_Poslist[i] = m_Poslist[i] - m_Endmove[i];
m_Midlist[i] = m_Midlist[i] - m_Endmove[i];//第4位计算无用,因为此处不存在第5轴
}
iresult = ZAux_Direct_MSpherical(g_handle,4,m_Axislist,m_Poslist[0],m_Poslist[1],m_Poslist[2],m_Midlist[0],m_Midlist[1],m_Midlist[2],0,m_Poslist[3],0);
break;
default:
break;
}
}
else //相对运动
{
switch((int)g_fDestdis[g_iCurseges][5])
{
float destdis[5];
case 0://XY直线插补
copy(g_fDestdis[g_iCurseges].begin(),g_fDestdis[g_iCurseges].begin()+3,destdis);
iresult = ZAux_Direct_MoveSp(g_handle, 4,m_Axislist, destdis);
break;
case 1://XY 3点圆弧
iresult = ZAux_Direct_MoveCirc2Sp(g_handle,2,m_Axislist,g_fDestdis[g_iCurseges][0],g_fDestdis[g_iCurseges][1],g_fDestdis[g_iCurseges][2],g_fDestdis[g_iCurseges][3]);
break;
case 2://XYZU直线插补
copy(g_fDestdis[g_iCurseges].begin(),g_fDestdis[g_iCurseges].begin()+3,destdis);
iresult = ZAux_Direct_MoveSp(g_handle,4,m_Axislist,destdis);
break;
case 3://XYZ空间圆弧插补U螺旋 第5轴距离为0
iresult = ZAux_Direct_MSphericalSp(g_handle,2,m_Axislist,g_fDestdis[g_iCurseges][0],g_fDestdis[g_iCurseges][1],g_fDestdis[g_iCurseges][2],g_fDestdis[g_iCurseges][3],g_fDestdis[g_iCurseges][6],g_fDestdis[g_iCurseges][7],0,g_fDestdis[g_iCurseges][8],0);
break;
default:
break;
}
}
if(ERR_OK != iresult)
{
QMessageBox::warning(this,"提示","call function return error.");
}
g_iCurseges++;
}
}
}
6.2 指令暂停/继续
点击“暂停”按钮后,暂停运动指令,且按钮文本转换为“继续”;点击“继续”按钮后,继续执行中断的运动指令,且按钮文本转换为“暂停”。
void FrmMotionControl::on_btn_StopStartMultiAxisMotion_clicked()
{
int m_iMoveStatue;
ZAux_Direct_GetMtype(g_handle,g_iXIndex,&m_iMoveStatue);//获取当前运动状态
if(m_iMoveStatue)
{
ZAux_Direct_MovePause(g_handle,g_iXIndex,0);//暂停轴运动
ZAux_Execute(g_handle, "PAUSETASK 1", NULL, 0);//暂停线程1
ui->btn_StopStartMultiAxisMotion->setText("继续");
}
else
{
ZAux_Direct_MoveResume(g_handle,g_iXIndex);//恢复轴运动
ZAux_Execute(g_handle, "RESUMETASK 1", NULL, 0);//恢复线程1
ui->btn_StopStartMultiAxisMotion->setText("停止");
}
}
展望
除了本文实现的上述基本功能外,还可以设想一些不难实现的小功能:
-
示教跟随功能:人工示教,在轴禁用时手动将平台移动到目标位置,直接在坐标输入的文本框里截取示教坐标。这样节省了手动输入数据的步骤,方便存储。
-
循环运行功能:含两种循环状况:1.倒退运动,需要置反速度
g_fDestdis[g_iCurseges][4]
,倒序执行命令g_fDestdis
;2.循环运动,直接再正序执行一遍命令g_fDestdis
。 -
存储命令功能:存储当前输入的命令。
参考资料
-
《Zmotion PC函数库编程手册 V2.1.1》 基本功能篇 第六章、高级功能篇 第章
-
《ZMC408SCAN 总线振镜运动控制用户手册 V1.5》
-
EtherCAT运动控制卡开发教程之Qt(中):小线段连续轨迹加工、暂停与继续-正运动技术 (zmotion.com.cn)