Zmotion运控器+Hiwin伺服驱动的Qt上位机开发(三):基础PSO、高速锁存的实现

学习如何基于Qt框架、用Zmotion运动控制器开发库编写上位机以实现进阶的基础PSO、高速锁存功能。

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

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

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

1 基础PSO

PSO(position synchronized output)即位置同步输出,本质是通过采集实时的编码器反馈位置(无编码器可使用输出的脉冲位置)与比较模式设定的位置进行比较,控制OP高速同步输出信号,PSO示意图如下。

PSO示意图

以下是飞拍功能示意图:
飞拍功能示意图

PSO功能的特点是能高速且稳定的输出信号,因为输出精度足够高,所以能够在整个运动轨迹中以固定的距离触发输出信号而不用考虑总体速度,即在直线部分以很快的速度运动,而在圆角部分减速的同时也能保证输出间距恒定。
Zmotion提供的样例界面如下:
样例

本例根据Zmotion提供的视觉飞拍例程进行修改,在Qt框架上进行实现。
PSO总界面

1.1 单轴单输出PSO

此时只考虑单个输出口,采用TABLE寄存器存储的数据表触发比较输出。

单轴单输出PSO

1.1.1 设置TABLE寄存器比较位置

提供了一个TABLE寄存器中比较位置的手动输入界面。

TABLE寄存器比较位置

首先,在运动控制主界面提供一个按钮槽函数,用于启动Table寄存器设置的子界面。主界面和子界面的传参提供了3种传参方式。

//单输出比较单轴:启动Table寄存器界面
void FrmMotionControl::on_btn_SetTable1_clicked()
{
    if(NULL == g_handle)//检测链接是否连通
    {
        QMessageBox::warning(this,"提示","链接断开状态");
        return ;
    }

    float m_Hwpara1=ui->edt_TableAddressRangeHead->text().toFloat();//参数1,第一个比较点绝对坐标所在TABLE寄存器编号
    float m_Hwpara2=ui->edt_TableAddressRangeEnd->text().toFloat();//参数2,最后一个比较点绝对坐标所在TABLE寄存器编号

    //初始化界面类
    if (m_Hwpara1<0 || m_Hwpara2<0|| m_Hwpara2<m_Hwpara1)
    {
        QMessageBox::warning(this,"提示","Table地址设置异常");
        return;
    }
    g_iListMode=1;

    //打开Table地址设置界面
    //传递构建页面Table寄存器参数
    //传参方法1:直接赋值给子窗口的全局参数
    frm_ListTable->g_fHwTableAddressRangeHead=m_Hwpara1;//参数1,第一个比较点绝对坐标所在TABLE寄存器编号
    frm_ListTable->g_fHwTableAddressRangeEnd=m_Hwpara2;//参数2,最后一个比较点绝对坐标所在TABLE寄存器编号
    frm_ListTable->g_handle=g_handle;
    //传参方法2:触发信号槽机制传参
    emit signal_sendDataToListTable(g_iListMode);//触发一个槽函数,向Table寄存器窗口发送
    //传参方法3:通过parentWidget()函数,获取父窗体对象,具体参考frmlisttable类文件
    frm_ListTable->show();
}

子界面设计如下,这里传参方式推荐方法3,通过parentWidget()函数,获取父窗体(运动控制主界面)对象。

#文件名:frmlisttable.cpp


FrmListTable::FrmListTable(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::FrmListTable)
{
    ui->setupUi(this);

    // 传参方法3:通过parentWidget()函数,获取父窗体(运动控制主界面)对象
    frm_MotionControl = (FrmMotionControl *)parentWidget();
}

FrmListTable::~FrmListTable()
{
    delete ui;
}

void FrmListTable::slot_getDataFromFMC(int g_iListMode)
{
    switch(g_iListMode)
    {
    case 1:
        this->setWindowTitle("单输出比较单轴:Table寄存器列表");
        drawTableListUniaxial();
        break;
    case 2:
//        this->setWindowTitle("多输出比较单轴:比较轴号列表");
//        break;
    case 3:
        this->setWindowTitle("多维比较:Table寄存器列表");
        drawTableListMulti();
        break;
    case 4:
//        this->setWindowTitle("多维比较:Table寄存器列表");
//        break;
    default:
        QMessageBox::warning(this,"提示","方式类型错误");
        break;
    }
}

//绘制列表:单输出比较单轴:Table寄存器列表
void FrmListTable::drawTableListUniaxial()
{
    for(int row = 0;row < ui->tb_ListTable->rowCount();row++)//删除所有行
    {
        ui->tb_ListTable->removeRow(0);
    }
    ui->tb_ListTable->setColumnCount(2);
    ui->tb_ListTable->setHorizontalHeaderLabels({"Table寄存器编号","值"});
    int ret;
    float m_GetTableTemp[1024];
    if(g_fHwTableAddressRangeHead==g_fHwTableAddressRangeEnd)
    {
        ret=ZAux_Direct_GetTable(g_handle, (int)g_fHwTableAddressRangeHead,1, m_GetTableTemp);
    }
    else
    {
        ret=ZAux_Direct_GetTable(g_handle, (int)g_fHwTableAddressRangeHead,(int)(g_fHwTableAddressRangeEnd-g_fHwTableAddressRangeHead+1), m_GetTableTemp);
    }
    if (ret!=0)
    {
        QMessageBox::warning(this,"提示","获取数据失败!");
    }

    if(g_fHwTableAddressRangeHead==g_fHwTableAddressRangeEnd)
    {
        ui->tb_ListTable->insertRow(0);
        ui->tb_ListTable->setItem(0, 0, new QTableWidgetItem(QString::number(0)));
        ui->tb_ListTable->setItem(0, 1, new QTableWidgetItem(QString::number(m_GetTableTemp[0])));
    }
    else
    {
        for(int i=0;i<(int)(g_fHwTableAddressRangeEnd-g_fHwTableAddressRangeHead+1);i++)
        {
            ui->tb_ListTable->insertRow(i);
            ui->tb_ListTable->setItem(i, 0, new QTableWidgetItem(QString::number(g_fHwTableAddressRangeHead+1)));
            ui->tb_ListTable->setItem(i, 1, new QTableWidgetItem(QString::number(m_GetTableTemp[i])));
        }
    }
}

//绘制列表:多维比较:Table寄存器列表
void FrmListTable::drawTableListMulti()
{
    ui->tb_ListTable->clearContents();
    ui->tb_ListTable->setColumnCount(3);
    int m_StartNum=frm_MotionControl->ui->edt_TableAddressHead->text().toInt();
    int m_PosNum=frm_MotionControl->ui->edt_CoordsCount->text().toInt();
    int m_Hwmode=frm_MotionControl->ui->cmb_CmpType2->currentIndex();//获取多维比较类型下标
    int m_AxisNum;
    switch(m_Hwmode)//确认PSO模式
    {
    case 0://2D比较
        m_AxisNum=2;
        ui->tb_ListTable->setHorizontalHeaderLabels({"Table寄存器编号","值","PS:每2个Table为1个点"});
        break;
    case 1://2D比较:时间
        m_AxisNum=2;
        ui->tb_ListTable->setHorizontalHeaderLabels({"Table寄存器编号","值","PS:每2个Table为1个点"});
        break;
    case 2://3D比较
        m_AxisNum=3;
        ui->tb_ListTable->setHorizontalHeaderLabels({"Table寄存器编号","值","PS:每3个Table为1个点"});
        break;
    case 3://3D比较:时间
        m_AxisNum=3;
        ui->tb_ListTable->setHorizontalHeaderLabels({"Table寄存器编号","值","PS:每3个Table为1个点"});
        break;
    default:
        m_AxisNum=0;
        ui->tb_ListTable->setHorizontalHeaderLabels({"Table寄存器编号","值","维度异常"});
        QMessageBox::warning(this,"提示","方式类型错误");
        break;
    }
    int m_TableNum=m_PosNum*m_AxisNum;
    float m_GetTableTemp[1024];
    int ret=ZAux_Direct_GetTable(g_handle, m_StartNum,m_TableNum, m_GetTableTemp);
    if (ret!=0)
    {
        QMessageBox::warning(this,"提示","获取数据失败!");
    }
    for(int i=0;i<m_TableNum;i++)
    {
        ui->tb_ListTable->insertRow(i);
        ui->tb_ListTable->setItem(i, 0, new QTableWidgetItem(QString::number(m_StartNum+1)));
        ui->tb_ListTable->setItem(i, 1, new QTableWidgetItem(QString::number(m_GetTableTemp[i])));
    }
}

//设置向窗口添加内容时触发添加1行Table量
void FrmListTable::on_btn_AddOneRow_clicked()
{
    ui->tb_ListTable->insertRow(1);
}

//更新Table寄存器内容
void FrmListTable::on_btn_UpdateTable_clicked()
{
    int m_RowCount=ui->tb_ListTable->rowCount();//行数
    float m_SaveTableValue[1024];
    for(int i=0;i<m_RowCount;i++)
    {
        QTableWidgetItem* item=ui->tb_ListTable->item(i,1);
        m_SaveTableValue[i]=item->text().toFloat();
        delete item;
    }
    ZAux_Direct_SetTable(g_handle, 0,m_RowCount, m_SaveTableValue);
}

//传参方法2:从上级窗体frmmotioncontrol得到数据
void FrmListTable::slot_getDataFromFMC(int);
1.1.2 启动比较

设置好TABLE比较位置后,点击启动输出。然后点击电机运动,即可测试PSO功能。

//单输出比较单轴:启动位置比较输出
void FrmMotionControl::on_btn_StartCmp1_clicked()
{
    if(NULL == g_handle)//检测链接是否连通
    {
        QMessageBox::warning(this,"提示","链接断开状态");
        return ;
    }

    //获取参数
    int m_Hwmode=ui->cmb_CmpType1->currentIndex(),//获取单输出比较单轴类型下标
            m_Hwaxisnum=ui->edt_CmpAxis1->text().toInt(),//获取轴号
            m_Hwopnum=ui->edt_HiSpeedOut1->text().toInt(),//获取输出口号
            m_HwopStatus=ui->cmb_FirstCmpPointStatue1->currentIndex();//第一个比较点的输出状态 0-关闭 1-打开
    float m_Hwpara1,m_Hwpara2,m_Hwpara3,m_Hwpara4,m_HwModeType,ret;//传递构建页面Table寄存器参数

    //确认PSO模式,分配参数,并启动位置比较输出
    switch(m_Hwmode)
    {
    case 0://单轴比较
        m_HwModeType=1;
        m_Hwpara1=ui->edt_TableAddressRangeHead->text().toFloat();//参数1,第一个比较点绝对坐标所在TABLE寄存器编号
        m_Hwpara2=ui->edt_TableAddressRangeEnd->text().toFloat();//参数2,最后一个比较点绝对坐标所在TABLE寄存器编号
        m_Hwpara3=ui->edt_CmpDir->text().toInt();//第一个点判断方向,0坐标负向,1坐标正向,-1不使用方向
        ret=ZAux_Direct_HwPswitch2(g_handle,m_Hwaxisnum,m_HwModeType,m_Hwopnum,m_HwopStatus,m_Hwpara1,m_Hwpara2,m_Hwpara3,0);
        break;
    case 1://矢量比较
        m_HwModeType=3;
        m_Hwpara1=ui->edt_TableAddressRangeHead->text().toFloat();//参数1,第一个比较点绝对坐标所在TABLE寄存器编号
        m_Hwpara2=ui->edt_TableAddressRangeEnd->text().toFloat();//参数2,最后一个比较点绝对坐标所在TABLE寄存器编号
        ret=ZAux_Direct_HwPswitch2(g_handle,m_Hwaxisnum,m_HwModeType,m_Hwopnum,m_HwopStatus,m_Hwpara1,m_Hwpara2,0,0);
        break;
    case 2://周期比较:距离
        m_HwModeType=5;
        m_Hwpara1=ui->edt_CmpPoint->text().toFloat();//参数1,比较点坐标
        m_Hwpara2=ui->edt_RepCycle->text().toFloat();//参数2,重复周期,一个坐标只比较一次,再输出有效状态,再输出无效状态
        m_Hwpara3=ui->edt_CycleDist->text().toFloat();//参数3,周期距离,每隔这个距离输出Opstate,输出有效状态的距离(ModePara4)后还原为无效状态
        m_Hwpara4=ui->edt_EffDist->text().toFloat();//参数4,有效距离
        ret=ZAux_Direct_HwPswitch2(g_handle,m_Hwaxisnum,m_HwModeType,m_Hwopnum,m_HwopStatus,m_Hwpara1,m_Hwpara2,m_Hwpara3,m_Hwpara4);
        break;
    case 3://周期比较:时间
        m_HwModeType=6;
        m_Hwpara1=ui->edt_CmpPoint->text().toFloat();//参数1,比较点坐标
        m_Hwpara2=ui->edt_RepCycle->text().toFloat();//参数2,重复周期,一个坐标只比较一次
        m_Hwpara3=ui->edt_CycleDist->text().toFloat();//参数3,周期距离,每隔这个距离输出一次
        m_Hwpara4=ui->edt_CycleTime->text().toFloat();//参数4,周期时间,用于HwTimer设置
        ret=ZAux_Direct_HwPswitch2(g_handle,m_Hwaxisnum,m_HwModeType,m_Hwopnum,m_HwopStatus,m_Hwpara1,m_Hwpara2,m_Hwpara3,0);
        break;
    default:
        QMessageBox::warning(this,"提示","方式类型错误");
        break;
    }
    if (ret!=0)
    {
        QMessageBox::warning(this,"提示","启动失败,错误码:"+QString::number(ret));
        return ;
    }

    if (m_HwModeType==6)//这种模式一般与HW_TIMER一起使用
    {
        int m_Time=ui->edt_EffTime->text().toFloat();//有效时间
        if (m_HwopStatus==1)
        {
            ret = ZAux_Direct_HwTimer(g_handle,2,(int )m_Hwpara4,m_Time,1,0,m_Hwopnum);//周期1S,输出变为on后500ms变为off
            if (ret!=0)
            {
                QMessageBox::warning(this,"提示","启动失败,错误码:"+QString::number(ret));
                return ;
            }
        }
        else
        {
            ret = ZAux_Direct_HwTimer(g_handle,2,(int )m_Hwpara4,m_Time,1,1,m_Hwopnum);//周期1S,输出变为on后500ms变为off
            if (ret!=0)
            {
                QMessageBox::warning(this,"提示","启动失败,错误码:"+QString::number(ret));
                return ;
            }
        }
    }else
    {
        ret = ZAux_Direct_HwTimer(g_handle,0,0,0,0,0,m_Hwopnum);//关闭定时器
        if (ret!=0)
        {
            QMessageBox::warning(this,"提示","启动失败,错误码:"+QString::number(ret));
            return ;
        }
    }
    on_btn_UpdateCmpCache_clicked();//更新比较缓冲区
}

其中要特别关注ZAux_Direct_HwPswitch2ZAux_Direct_HwTimer 两个函数,它们分别提供比较输出及定时器接口,函数解释如下。
ZAux_Direct_HwPswitch2
ZAux_Direct_HwTimer

1.1.3 清除比较
//单输出比较单轴:清除位置比较输出
void FrmMotionControl::on_btn_ClearCmp1_clicked()
{
    if(NULL == g_handle)
    {
        QMessageBox::warning(this,"提示","链接断开状态");
        return ;
    }
    int m_Hwaxisnum=ui->edt_CmpAxis1->text().toFloat();//获取轴号
    int ret=ZAux_Direct_HwPswitch2(g_handle,m_Hwaxisnum,2,0,0,0,0,0,0);
    if (ret!=0)
    {
        QMessageBox::warning(this,"提示","清除失败,错误码:"+QString::number(ret));
        return ;
    }
    on_btn_UpdateCmpCache_clicked();//更新比较缓冲区
}
1.1.4 更新PSO缓冲区

在界面上更新显示PSO缓存,可用于单轴、多轴的任意模式输出。

//更新PSO缓冲区
void FrmMotionControl::on_btn_UpdateCmpCache_clicked()
{
    if(NULL == g_handle)//检测链接是否连通
    {
        QMessageBox::warning(this,"提示","链接断开状态");
        return ;
    }

    int m_Curhwnum,m_Curaxis=ui->edt_CmpAxis1->text().toInt();//获取当前轴号
    int ret=ZAux_Direct_GetHwPswitchBuff(g_handle,m_Curaxis,&m_Curhwnum);
    if (ret!=0)
    {
        QMessageBox::warning(this,"提示","更新失败,错误码:"+QString::number(ret));
        return ;
    }
    ui->edt_RemainCmpCache->setText(QString::number(m_Curhwnum));
}

1.2 单轴多输出PSO

单轴多输出PSO

//多输出比较单轴
void FrmMotionControl::on_btn_StartCmp2_clicked()
{
    if(NULL == g_handle)
    {
        QMessageBox::warning(this,"提示","链接断开状态");
        return ;
    }

    int m_Hwstarop=ui->edt_HiSpeedOutHead->text().toInt(),//起始O口
            m_Hwendop=ui->edt_HiSpeedOutEnd->text().toInt(),//结束O口
            m_Hwcmpaxis=ui->edt_CmpAxis2->text().toInt(),//比较轴号
            m_Hwstaraxis=ui->edt_OtherBufStartAxis->text().toInt();//其它轴缓冲起始轴号

    int ret=0;
    //重新初始化Hw,其实应该根据轴数量写????
    ret+=ZAux_Direct_HwPswitch2(g_handle,m_Hwcmpaxis,2,m_Hwstarop,0,0,0,0,0);//清除比较缓冲
    ret+=ZAux_Direct_HwPswitch2(g_handle,m_Hwstaraxis,2,m_Hwstarop+1,0,0,0,0,0);//清除比较缓冲
    ret+=ZAux_Direct_HwPswitch2(g_handle,m_Hwstaraxis+1,2,m_Hwstarop+2,0,0,0,0,0);//清除比较缓冲
    ret+=ZAux_Direct_HwPswitch2(g_handle,m_Hwstaraxis+2,2,m_Hwstarop+3,0,0,0,0,0);//清除比较缓冲
    ret+=ZAux_Direct_HwTimer(g_handle,0,0,0,0,0,m_Hwstarop);//停止定时器
    ret+=ZAux_Direct_HwTimer(g_handle,0,0,0,0,0,m_Hwstarop+1);//停止定时器
    ret+=ZAux_Direct_HwTimer(g_handle,0,0,0,0,0,m_Hwstarop+2);//停止定时器
    ret+=ZAux_Direct_HwTimer(g_handle,0,0,0,0,0,m_Hwstarop+3);//停止定时器
    if (ret!=0)
    {
        QMessageBox::warning(this,"提示","HW复位失败");
        return ;
    }
    for (int i=0;i<(m_Hwendop-m_Hwstarop);i++)
    {
        ret=ZAux_Direct_SetParam(g_handle,"HW_PS2AXISNUM",m_Hwstaraxis+i,m_Hwcmpaxis);//使用其他轴的缓冲区,比较目标轴的位置
        if (ret!=0)
        {
            QMessageBox::warning(this,"提示","HW_PS2AXISNUM 失败!错误码:"+QString::number(ret));
            return ;
        }
    }
    uint32 uValue=0;
    ret=ZAux_Direct_SetOutMulti(g_handle,m_Hwstarop,m_Hwendop,&uValue);//初始化输出口状态
    if (ret!=0)
    {
        QMessageBox::warning(this,"提示","SetOutMulti 失败!错误码:"+QString::number(ret));
        return ;
    }
    ret = ZAux_Direct_HwTimer(g_handle,2,40000,20000,1,0,m_Hwstarop);周期40ms,输出20ms
    if (ret!=0)
    {
        QMessageBox::warning(this,"提示","HwTimer 失败!错误码:"+QString::number(ret));
        return ;
    }

    ret=ZAux_Direct_HwPswitch2(g_handle,m_Hwcmpaxis,6,m_Hwstarop,1,100,1,100,0);//写死只在100触发,
    if (ret!=0)
    {
        QMessageBox::warning(this,"提示","HwPswitch2 失败!错误码:"+QString::number(ret));
        return ;
    }

    for (int i=0;i<(m_Hwendop-m_Hwstarop);i++)
    {
        ret += ZAux_Direct_HwTimer(g_handle,2,1000000,20000,1,0,m_Hwstarop+1+i);//周期100ms,输出20ms
        ret +=ZAux_Direct_HwPswitch2(g_handle,m_Hwstaraxis+i,6,m_Hwstarop+1+i,1,200+i*100,1,100,0);
    }
    if (ret!=0)
    {
        QMessageBox::warning(this,"提示","Hw启动失败!错误码:"+QString::number(ret));
        return ;
    }

    on_btn_UpdateCmpCache_clicked();//更新比较缓冲区
}

1.3 多轴等距/周期输出PSO

多轴/多维输出在单轴输出的基础上,考虑了2D、3D输出两种情况。

多轴等距/周期输出PSO

//多维比较:启动Table寄存器界面
void FrmMotionControl::on_btn_SetTable2_clicked()
{
    if(NULL == g_handle)//检测链接是否连通
    {
        QMessageBox::warning(this,"提示","链接断开状态");
        return ;
    }

    //传递构建页面Table寄存器参数
    int m_ListPosNum=ui->edt_CoordsCount->text().toInt();//坐标点数
    if (m_ListPosNum<0)
    {
        QMessageBox::warning(this,"提示","位置点数异常");
        return;
    }
    int m_TableStar=ui->edt_TableAddressHead->text().toInt();//Table起始地址
    if (m_TableStar<0)
    {
        QMessageBox::warning(this,"提示","Table地址设置异常");
        return;
    }
    g_iListMode=3;

    //打开Table地址设置界面
    emit signal_sendDataToListTable(g_iListMode);
    frm_ListTable->show();
}

//多维比较:启动比较
void FrmMotionControl::on_btn_StartCmp3_clicked()
{
    if(NULL == g_handle)//检测链接是否连通
    {
        QMessageBox::warning(this,"提示","链接断开状态");
        return ;
    }

    int ret=0,m_ListAxisNum=0,m_HwModeType;
    int m_Axislist[3] = {g_iXIndex,g_iYIndex,g_iZIndex};//运动EtherCAT轴列表
    int m_Hwmode=ui->cmb_CmpType2->currentIndex();//获取多维比较类型下标
    int m_Hwopnum=ui->edt_HiSpeedOut2->text().toInt();//获取输出口号
    int m_HwopStatus=ui->cmb_FirstCmpPointStatue2->currentIndex();//第一个比较点的输出状态 0-关闭 1-打开
    float m_Hwmaxerr=ui->edt_PulseDev->text().toFloat();//获取输出口号
    int m_ListPosNum=ui->edt_CoordsCount->text().toInt();//坐标点数
    int m_TableStar=ui->edt_TableAddressHead->text().toInt();//Table起始地址
    float m_Hw2Dpara1=ui->edt_PulseTime->text().toFloat();//脉冲时间
    float m_Hw2Dpara2=ui->edt_PulseCount->text().toFloat();//脉冲个数
    float m_Hw2Dpara3=ui->edt_PulseCycle->text().toFloat();//脉冲周期

    switch(m_Hwmode)//确认PSO模式
    {
    case 0://2D比较
        m_ListAxisNum=2;
        m_HwModeType=25;
        //默认先关闭定时器
        ret += ZAux_Direct_HwTimer(g_handle,0,0,0,0,0,m_Hwopnum);
        break;
    case 1://2D比较:时间
        m_ListAxisNum=2;
        m_HwModeType=26;
        //默认先关闭定时器
        ret += ZAux_Direct_HwTimer(g_handle,0,0,0,0,0,m_Hwopnum);
        //缺省定时器状态与设置相反
        if (m_HwopStatus==1)
        {
            //启动定时器,若控制器型号为ZMC4系列以上,多维比较可以动态调整定时器参数,无需设置时间参数,启动定时器即可
            ret += ZAux_Direct_HwTimer(g_handle,2,(int )m_Hw2Dpara3,(int )m_Hw2Dpara1,(int )m_Hw2Dpara2,0,m_Hwopnum);
        }else
        {
            //启动定时器,若控制器型号为ZMC4系列以上,多维比较可以动态调整定时器参数,无需设置时间参数,启动定时器即可
            ret += ZAux_Direct_HwTimer(g_handle,2,(int )m_Hw2Dpara3,(int )m_Hw2Dpara1,(int )m_Hw2Dpara2,1,m_Hwopnum);
        }
        break;
    case 2://3D比较
        m_ListAxisNum=3;
        m_HwModeType=35;
        //默认先关闭定时器
        ret += ZAux_Direct_HwTimer(g_handle,0,0,0,0,0,m_Hwopnum);
        break;
    case 3://3D比较:时间
        m_ListAxisNum=3;
        m_HwModeType=36;
        //默认先关闭定时器
        ret += ZAux_Direct_HwTimer(g_handle,0,0,0,0,0,m_Hwopnum);
        //缺省定时器状态与设置相反
        if (m_HwopStatus==1)
        {
            //启动定时器,若控制器型号为ZMC4系列以上,多维比较可以动态调整定时器参数,无需设置时间参数,启动定时器即可
            ret += ZAux_Direct_HwTimer(g_handle,2,(int )m_Hw2Dpara3,(int )m_Hw2Dpara1,(int )m_Hw2Dpara2,0,m_Hwopnum);
        }else
        {
            //启动定时器,若控制器型号为ZMC4系列以上,多维比较可以动态调整定时器参数,无需设置时间参数,启动定时器即可
            ret += ZAux_Direct_HwTimer(g_handle,2,(int )m_Hw2Dpara3,(int )m_Hw2Dpara1,(int )m_Hw2Dpara2,1,m_Hwopnum);
        }
        break;
    default:
        QMessageBox::warning(this,"提示","方式类型错误");
        break;
    }
    if (ret!=0)
    {
        QMessageBox::warning(this,"提示","定时器启停异常失败!错误码"+QString::number(ret));
        return;
    }

    for (int i=1;i<=m_ListAxisNum;i++)//这里假设轴号从1开始!!!!
    {
        if (m_Axislist[i]==-1)
        {
            QMessageBox::warning(this,"提示","轴号未配置");
            return;
        }
    }
    ret=ZAux_Direct_HwPswitch2_2D(g_handle,m_Axislist,m_HwModeType,m_Hwopnum,m_HwopStatus,m_Hwmaxerr,m_ListPosNum,m_TableStar,m_Hw2Dpara1,m_Hw2Dpara2,m_Hw2Dpara3);
    if (ret!=0)
    {
        QMessageBox::warning(this,"提示","ZAux_Direct_HwPswitch2_2D失败!错误码"+QString::number(ret));
        return;
    }
    on_btn_UpdateCmpCache_clicked();//更新比较缓冲区
}

//多维比较:清除比较
void FrmMotionControl::on_btn_ClearCmp2_clicked()
{
    on_btn_ClearCmp1_clicked();
}

//多维比较:坐标清零
void FrmMotionControl::on_btn_ClearPos2_clicked()
{
    if(NULL == g_handle)
    {
        QMessageBox::warning(this,"提示","链接断开状态");
        return ;
    }

    int m_Hwmode=ui->cmb_CmpType2->currentIndex();//获取多维比较类型下标
    int m_ListAxisNum=0;//运动轴数
    if(m_Hwmode==0||m_Hwmode==1)
    {
        m_ListAxisNum=2;
    }
    else if(m_Hwmode==2||m_Hwmode==3)
    {
        m_ListAxisNum=3;
    }
    else{
        QMessageBox::warning(this,"提示","方式类型错误!");
        return ;
    }
    int m_Axislist[3] = {g_iXIndex,g_iYIndex,g_iZIndex};//运动EtherCAT轴列表

    for (int i=1;i<=m_ListAxisNum;i++)//这里假设轴号从1开始!!!!
    {
        if (m_Axislist[i]==-1)
        {
            QMessageBox::warning(this,"提示","轴号未配置");
            return ;
        }else
        {
            int ret=ZAux_Direct_SetMpos(g_handle,m_Axislist[i],0);
            if (ret!=0)
            {
                QMessageBox::warning(this,"提示","清除失败,错误码:"+QString::number(ret));
                return ;
            }
            ret=ZAux_Direct_SetDpos(g_handle,m_Axislist[i],0);
            if (ret!=0)
            {
                QMessageBox::warning(this,"提示","清除失败,错误码:"+QString::number(ret));
                return ;
            }
            ret=ZAux_Direct_SetVECTORMOVED(g_handle,m_Axislist[i],0);
            if (ret!=0)
            {
                QMessageBox::warning(this,"提示","清除失败,错误码:"+QString::number(ret));
                return ;
            }
        }
    }
}

根据运动模式提供设置矢量位移的函数:

//补充ZAux函数ZAux_Direct_SetVECTORMOVED:比较点VECTOR_MOVED
int32 FrmMotionControl::ZAux_Direct_SetVECTORMOVED(ZMC_HANDLE handle,int axisnum,float  fval)
{
    if(0 > axisnum || axisnum > MAX_AXIS_AUX)
    {
        return  ERR_AUX_PARAERR;
    }
    int32 iresult;
    char  cmdbuff[2048];
    char  cmdbuffAck[2048];
    //生成命令
    sprintf(cmdbuff, "VECTOR_MOVED(%d)=%f", axisnum,fval);
    //调用命令执行函数
    //iresult =  ZAux_DirectCommand(handle, cmdbuff, cmdbuffAck, 2048);
    iresult =  ZAux_Execute(handle, cmdbuff, cmdbuffAck,2048);
    if(ERR_OK != iresult)
    {
        return iresult;
    }

    return ERR_OK;
}

其中多维PSO接口的ZAux_Direct_HwPswitch2_2D函数在旧版本的ZAux函数库中并不提供,可以自己写,以下是Zmotion官方提供的例程:

//补充ZAux函数ZAux_Direct_HwPswitch2_2D
/*参数: mode 25, 26 .2D的比较模式
Axisnum:轴数组
Opnum :对应的输出口
Opstate: 第一个比较点的输出状态.
maxerr:比较位置每个轴左右的脉冲偏差, 进入偏差范围后开始比较.
num	  :TABLE 里面存储的比较点个数.
tablepos: 第一个比较点坐标所在 TABLE 编号
与 hwtimer 并用时, 可以动态调整 hwtimer 参数.
ModePara1:脉冲时间
ModePara2:脉冲个数
ModePara3:脉冲周期
*/
int32 FrmMotionControl:: ZAux_Direct_HwPswitch2_2D(ZMC_HANDLE handle, int *Axisnum,int Mode,int Opnum , int Opstate,int maxerr,int num, int tablepos, float ModePara1, float ModePara2,float ModePara3)
{
    if(0 > Axisnum[0] || Axisnum[0] > MAX_AXIS_AUX)
    {
        return ERR_AUX_PARAERR;
    }
    char cmdbuff[2048];
    char  tempbuff[2048];
    char cmdbuffAck[2048];
    //生成命令
    sprintf(cmdbuff, "BASE(%d,%d)\n", Axisnum[0], Axisnum[1]);
    switch(Mode)
    {
    case 25:
        sprintf(tempbuff, "HW_PSWITCH2(%d,%d,%d,%d,%d,%d)", Mode, Opnum, Opstate, maxerr,num,tablepos);
        strcat(cmdbuff, tempbuff);
        break;
    case 26:
        sprintf(tempbuff, "HW_PSWITCH2(%d,%d,%d,%d,%d,%d,%f,%f,%f)", Mode,Opnum, Opstate, maxerr,num,tablepos,ModePara1,ModePara2,ModePara3);
        strcat(cmdbuff, tempbuff);
        break;
    case 35:
        sprintf(tempbuff, "HW_PSWITCH2(%d,%d,%d,%d,%d,%d)", Mode, Opnum, Opstate, maxerr,num,tablepos);
        strcat(cmdbuff, tempbuff);
        break;
    case 36:
        sprintf(tempbuff, "HW_PSWITCH2(%d,%d,%d,%d,%d,%d,%f,%f,%f)", Mode,Opnum, Opstate, maxerr,num,tablepos,ModePara1,ModePara2,ModePara3);
        strcat(cmdbuff, tempbuff);
        break;
    default:
        return  ERR_AUX_PARAERR;
        break;
    }
    //调用命令执行函数
    int ret=ZAux_Execute(handle, cmdbuff, cmdbuffAck,2048);

    if (strlen(cmdbuffAck)!=0)
    return ERR_ACKERROR;

    return ret;
}

2 单轴高速锁存

在接线时,要注意提供高速锁存应用的IN口,以4系列的ZMC408SCAN控制器为例,提供高速锁存的IN口如下:
提供高速锁存的IN口

4系列运控器提供了4种输入信号R0/R1/R2/R3,它们在控制器中默认映射分别分别对应IN0/IN1/IN2/IN3。要特别注意的是,脉冲轴类型一般采用 R0、R1、Z 脉冲这三种锁存;总线轴类型采用 R2,R3 锁存。

另外提供了REG_POS/REG_POSB/REG_POSC/REG_POSD 4组寄存器用于锁存数据的存储,在函数手册的单词锁存函数ZAux_Direct_Regist和连续锁存函数**ZAux_Direct_CycleRegist处具体寄存器IN口上下沿触发的选择请关注锁存模式的选取。

本文实现一个简单的单轴单次高速锁存功能窗口如下,单次锁存函数ZAux_Direct_Regist
编码器锁存窗口——未启动
编码器锁存窗口——已启动

选择好锁存对应编码器的轴号锁存模式后,即可点击”启动锁存“按钮开启锁存功能。触发按钮信号函数on_btn_Latch_clicked,并执行第一次捕获锁存命令ZAux_Direct_Regist后,设定开启一个定时器timer_RegistDataUpdate,即可触发槽函数slot_updateMultiAxisInterpolate,在该槽函数内执行锁存数据的存储循环执行捕获锁存命令,直到点击”关闭锁存“按钮关闭锁存功能。

在开启单次锁存功能的过程中,可以控制轴进行运动,当到达IN口触发对应的编码器位置时自动锁存,锁存频率可通过定时器触发频率进行设定,以此实现连续锁存的功能。

//触发锁存的信号槽
    connect(timer_RegistDataUpdate, SIGNAL(timeout()), this, SLOT(slot_updateRegistData()));

//启动锁存
void FrmMotionControl::on_btn_Latch_clicked()
{
    if(NULL == g_handle)//检测链接是否连通
    {
        QMessageBox::warning(this,"提示","链接断开状态");
        return ;
    }

    if(g_Regist_IfOpen == false)//启动锁存
    {
        int m_RegistAxis=ui->edt_EncoderAxis->text().toInt();
        g_iRegistCount=0;
        int32 iret = ZAux_Direct_SetAtype(g_handle,m_RegistAxis,65);//如果是是编码器轴选4,如果是总线轴选65!!!对应的R0/R1/R2/R3口也要接好!!!R0,R1 输入一般对应到输入口 0 和 1,支持脉冲轴;R2,R3 输入一般对应到输入口 2 和 3,支持总线轴。
        int m_ReglistListSel= ui->edt_LatchMode->text().toInt();
        if(m_ReglistListSel >= 0 && m_ReglistListSel <=3)
        {
            g_iRegistMode =  m_ReglistListSel +1;
        }
        else if(m_ReglistListSel == 4 || m_ReglistListSel ==5)
        {
            g_iRegistMode = 10 + m_ReglistListSel;
        }
        else if(m_ReglistListSel > 5 && m_ReglistListSel < 9)
        {
            g_iRegistMode = 12 + m_ReglistListSel;
        }
        iret = ZAux_Direct_Regist(g_handle,m_RegistAxis,g_iRegistMode);//触发锁存

        if(iret==0)//成功触发
        {
            timer_RegistDataUpdate->start(1000);//开始更新锁存数据
            g_Regist_IfOpen = true;
            ui->btn_Latch->setText("停止锁存");
            clearRegistList();
        }
    }
    else//停止锁存
    {
        timer_RegistDataUpdate->stop();//停止更新锁存数据
        g_Regist_IfOpen = false;
        ui->btn_Latch->setText("启动锁存");
    }
}

//槽函数:更新锁存数据
void FrmMotionControl::slot_updateRegistData()
{
    int m_RegistAxis=ui->edt_EncoderAxis->text().toInt();
    int32 iret;
    int MarkStatus = 0;
    float m_RegistPos=0;
    if(g_iRegistMode >= 0 && g_iRegistMode < 4)//获取锁存状态
    {
        iret = ZAux_Direct_GetMark(g_handle,m_RegistAxis,&MarkStatus);
    }
    else if(g_iRegistMode >= 14 && g_iRegistMode < 16)
    {
        iret = ZAux_Direct_GetMarkB(g_handle,m_RegistAxis,&MarkStatus);
    }
    else if(g_iRegistMode >= 18 && g_iRegistMode < 20)
    {
        float tempc;
        iret = ZAux_Direct_GetParam(g_handle,"MARKC",m_RegistAxis,&tempc);
        MarkStatus = (int)tempc;
    }
    else if(g_iRegistMode >= 20 && g_iRegistMode < 22)
    {
        float tempd;
        iret = ZAux_Direct_GetParam(g_handle,"MARKD",m_RegistAxis,&tempd);
        MarkStatus = (int)tempd;
    }else iret=1;//模式选择错误

    if(MarkStatus == -1)//获取锁存坐标位置
    {
        if(g_iRegistMode >= 0 && g_iRegistMode < 4)
        {
            iret = ZAux_Direct_GetRegPos(g_handle,m_RegistAxis,&m_RegistPos);                //获取锁存位置
        }
        else if(g_iRegistMode >= 14 && g_iRegistMode < 16)
        {
            iret = ZAux_Direct_GetRegPosB(g_handle,m_RegistAxis,&m_RegistPos);
        }
        else if(g_iRegistMode >= 18 && g_iRegistMode < 20)
        {
            iret = ZAux_Direct_GetParam(g_handle,"REG_POSC",m_RegistAxis,&m_RegistPos);
        }
        else if(g_iRegistMode >= 20 && g_iRegistMode < 22)
        {
            iret = ZAux_Direct_GetParam(g_handle,"REG_POSD",m_RegistAxis,&m_RegistPos);
        }else iret=1;//模式选择错误
        ui->tb_ListLatch->insertRow(g_iRegistCount);
        ui->tb_ListLatch->setItem(g_iRegistCount, 0, new QTableWidgetItem(QString::number(g_iRegistCount+1)));
        ui->tb_ListLatch->setItem(g_iRegistCount, 1, new QTableWidgetItem(QString::number(m_RegistPos)));

        g_iRegistCount++;
        iret = ZAux_Direct_Regist(g_handle,m_RegistAxis,g_iRegistMode);    //重新触发锁存,相当于连续锁存
    }
    ui->edt_LatchStatus->setText("锁存触发状态 "+QString::number(MarkStatus)+"  次数 "+QString::number(g_iRegistCount));
}

//清空锁存表数据
void FrmMotionControl::clearRegistList()
{
    for(int row = 0;row < ui->tb_ListLatch->rowCount();row++)//删除所有行
    {
        ui->tb_ListLatch->removeRow(0);
    }
    ui->tb_ListLatch->setColumnCount(2);
    ui->tb_ListLatch->setHorizontalHeaderLabels({"编号","位置"});
}

另外,Zmotion函数库还提供了专门的连续锁存函数ZAux_Direct_CycleRegist,将锁存结果存储到 TABLE 寄存器里面,分别对两个通道进行连续锁存,可以实现上下边沿的连续锁存

使用起来大同小异,只需要多分配Table寄存器位置,以下是官方例程的应用。

ret = ZAux_Direct_CycleRegist( handle, iaxis, mode,0,100);//设置连续锁存,锁存模式:IN0 上升沿触发锁存,锁存值存放 table(0)-table(99)
ret = ZAux_Direct_MoveWait(handle, 1,"Mpos",iaxis,1,432);//轴 1 缓冲区里面加入等待轴 0 MPOS 大于 432 时才执行下一条指令的阻塞(这里占用了轴 1 的缓冲)

/*MPOS 等于 432 后,往轴 1 缓冲区里写入上升沿,本例程连接的控制器为 ZMC 系列,低电平为
有效电平,所以把 IN(0)从 1 置为 0 才可以形成上升沿(若 ECI 系列控制卡,则需要把 IN(0)从 0 置为 1 才
可以形成上升沿)*/

参考资料

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

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

  3. 运动控制器PSO视觉飞拍与精准输出的C++开发(一):单轴PSO-正运动技术 (zmotion.com.cn)

  4. 运动控制器PSO视觉飞拍与精准输出的C++开发(二):多轴PSO等距/周期输出-正运动技术 (zmotion.com.cn)

  5. 运动控制器PSO视觉飞拍与精准输出的C++开发(三):二维/三维/多轴PSO输出-正运动技术 (zmotion.com.cn)

  6. 运动控制器八通道PSO的视觉飞拍与精准输出-正运动技术 (zmotion.com.cn)

  7. 运动控制器位置锁存功能的应用-正运动技术

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值