Zmotion运控器+Hiwin伺服驱动的Qt上位机开发(二):多轴插补运动的实现

学习如何基于Qt框架、用Zmotion运动控制器开发库编写上位机以实现进阶的多轴插补运动功能。

本文是系列前文的续集,前文链接:

Zmotion运控器+Hiwin伺服驱动的Qt上位机开发(一):EtherCAT通讯、基本单轴控制、回零功能的实现_Zaiton的博客-CSDN博客

1 功能总览

正运动技术提供的Zmotion Tools V1.0中提供了多轴插补运动界面:

Zmotion Tools 多轴插补界面

该界面实现了轴选择、轴运动状态展示、插补模式选择、插补运动示教、数据显示等功能,方便易用。

于是博主基于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. 示教跟随功能:人工示教,在轴禁用时手动将平台移动到目标位置,直接在坐标输入的文本框里截取示教坐标。这样节省了手动输入数据的步骤,方便存储。

  2. 循环运行功能:含两种循环状况:1.倒退运动,需要置反速度g_fDestdis[g_iCurseges][4],倒序执行命令g_fDestdis;2.循环运动,直接再正序执行一遍命令g_fDestdis

  3. 存储命令功能:存储当前输入的命令。

参考资料

  1. 《Zmotion PC函数库编程手册 V2.1.1》 基本功能篇 第六章、高级功能篇 第章

  2. 《ZMC408SCAN 总线振镜运动控制用户手册 V1.5》

  3. 经济型EtherCAT运动控制器(二):ZBasic实现多轴直线插补运动-正运动技术 (zmotion.com.cn)

  4. 多轴插补运动控制 - 小淼博客 - 博客园 (cnblogs.com)

  5. 快速入门 | 篇十九:正运动技术运动控制器多轴同步与电子凸轮指令简介 - 知乎 (zhihu.com)

  6. EtherCAT运动控制卡开发教程之Qt(中):小线段连续轨迹加工、暂停与继续-正运动技术 (zmotion.com.cn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值