Zmotion运控器+Hiwin伺服驱动的Qt上位机开发(一):EtherCAT通讯、基本单轴控制、回零功能的实现
学习如何基于Qt框架、用Zmotion运动控制器开发库编写上位机以实现一些基础功能。
1 概况
1.1 控制器架构和接线
Zmotion运动控制器支持 PC 直接在线控制,提供 DLL 函数库和 VC、VB、C#、PYTHON、LABVIEW 等例程。本例采用EtherCAT总线连接2台Hiwin伺服驱动器,分别控制XY运动平台的2个伺服电机。
主要包含三类端口之间的连线:
- 在对控制器和PC端进行连线时,要通过网线从控制器ETHERNET口引出,连到主机上,连通后可以用Zdevelop软件测试效果,具体操作过程请参考说明书。
- 在对伺服驱动器到控制器的连接时,通过网线从第一个驱动器(Node 1)的In口引出,连接到控制器的EtherCAT口即可。
- 在伺服驱动器之间进行连接时,确定好驱动器之间的上下位,通过网线从上位驱动器的Out口连到下位驱动器的In口,如下图。
1.2 建立工程
Zmotion资料包中提供了函数库及一些例程,VC++函数库具体位置在:“8.PC编程相关”——“64位编程”——“VS2013 VC”——“函数库”中,一共有以下4个文件。
然后在Qt的.pro文件里添加路径引用上图中的2个dll库,如果“添加库”一直失败的话,不妨把这上述文件放入工程的路径文件夹里,然后引用相对路径:
win32: LIBS += -L$$PWD/./ -lzmotion -lzauxdll
INCLUDEPATH += $$PWD/.
DEPENDPATH += $$PWD/.
在需要调用库的.cpp/.h文件中,记得引用头文件:
#include "zauxdll2.h"
#include "zmotion.h"
然后就可以正常开发了,我根据资料包中的“例程8-总线控制运动”界面(用MFC做的)做了一个Qt框架下的简单复现:
1.3 EtherCAT伺服驱动器参数设置
一般情况下Zmotion控制器的函数库可以适应伺服驱动器厂家配置,但如果需要进行一些特殊开发的话,要调整伺服驱动器的通讯周期、PDO等参数,具体请参考下文第三节:快速入门 | 篇十六:正运动控制器EtherCAT总线快速入门-正运动技术 (zmotion.com.cn)。
2 EtherCAT通讯的实现
接下来开始介绍上位机库函数的调用和编写,首先是EtherCAT通讯的实现。
EtherCAT通讯的实现主要有控制器连断、总线初始化、全局变量反馈几个部分。
2.1 控制器连断
此处采用最常用的IP连接方式,IP连接主要通过ZAux_OpenEth函数实现,解释如下:
设定一个按钮,将ZAux_OpenEth函数写入其clicked()槽函数:
void FrmMotionControl::on_btn_OnInit_clicked()
{
char address[]="192.168.0.11";//更智能的IP地址输入方式:用ZAux_SearchEthlist函数搜索IP,然后设计一个Combox列举查找到的IP,然后直接选择
int ret =ZAux_OpenEth(address,&g_handle);
if (ERR_SUCCESS != ret)
{
QMessageBox::warning(this,"提示","控制器连接失败");
g_handle = NULL;
QTest::qSleep(2000);
return;
}
qDebug() << "控制器连接成功!\n";
}
通讯的断开通过ZAux_Close函数实现,设定一个按钮,将ZAux_Close函数写入其clicked()槽函数:
void FrmMotionControl::on_btn_OnClose_clicked()
{
ZAux_Close(g_handle);
g_handle = NULL;
qDebug() << "控制器已断开!\n";
timer_GlobalDataUpdate->stop();//状态刷新计时器停止
}
2.2 总线初始化
总线初始化主要通过在bas文件中提前写好EtherCAT初始化函数实现。对 EtherCAT 总线进行初始化配置的方法有两种:
- 编写一个对 EtherCAT 总线进行初始化配置的 bas 文件,再通过 ZAux_BasDown 函数把 Bas 文件下载到控制器中。在Zmotion资料包的“ …\8.PC编程相关\64位例程\VS2013 VC\例程-新\例程8-总线控制运动\总线初始化BASIC程序\”路径中提供了总线初始化的bas文件,基本不用修改即可使用。Zdevelop实现参考:EtherCAT运动控制系统的开发,视觉控制器-EtherCAT运动控制卡-运动控制PLC-正运动技术 (zmotion.com.cn)。
- 通过使用Zmotion免费提供的 Zmotion Tools小工具,对总线初始化配置,下载地址:运动控制系统-运动控制卡-机器人控制器-正运动技术 (zmotion.com.cn)。
方法1:在软件上下载bas文件,通过ZAux_BasDown函数实现:
设定一个按钮,将ZAux_BasDown函数写入其clicked()槽函数:
void FrmMotionControl::on_btn_BasDown_clicked()
{
if(NULL == g_handle)
{
QMessageBox::warning(this,"提示","链接断开状态");
return;
}
//程序路径下找到bas包
const char * filename= "E:\\QT_example\\LaerMarkingVision\\EcatInit.bas"; //!!!!!!
//下载程序到 ROM 中 0-RAM 1-ROM !!!!!
int ret = ZAux_BasDown(g_handle,filename,0);//把初始化 bas 文件下载到控制器里面进行初始化
if(ret !=0)
{
QMessageBox::warning(this,"提示","下载文件失败");
qDebug("初始化bas错误:%d",ret);
return ;
}
g_iInitStatus=-1;//初始化状态标志,-1表示初始化成功
g_basflag = true;//bas下载状态标志,true表示下载成功
}
方法2:借助现成工具初始化,更为直观方便,初始化后软件中不需要下载bas文件,可以直接初始化,具体步骤如下。
- 连接地址
- 点击“文件操作”——3. 点击“打开文件”——选取要下载的bas文件——4. 点击“下载到ROM/RAM”
5.命令框显示上图所示的文字。便表示初始化成功了。
若bas文件中没有对节点进行分配(例如Zmotion Tools中自带的例程“EcatInit.bas”就没有提前分配轴),那么此时需要对节点进行分配,步骤如下:
- 点击“总线配置”——7. 点击“自动分配”——8. 根据需要修改参数——9. 点击“保存配置”。
该功能在实现节点分配的同时,还能直接对轴进行使能。
2.3 全局变量反馈
全局变量包括总线状态参数、轴状态参数、回零状态参数。函数库中包含全局变量ZAux_Direct_GetUserVar、节点数量ZAux_BusCmd_GetNodeNum等实时反馈的函数:
我们可以通过一个定时器实现这些参数的实时反馈更新。
//按钮——开启全局参数刷新定时器
void FrmMotionControl::on_btn_BasInit_clicked()
{
if(NULL == g_handle)
{
QMessageBox::warning(this,"提示","链接断开状态");
return;
}
if(g_basflag && (g_iInitStatus==-1) )
{
timer_GlobalDataUpdate->start(1000);//定时器,每1秒更新全局参数
qDebug() << "控制器连接成功!\n";
}
else
{
int ret = ZAux_Execute(g_handle,"RUNTASK 1,Ecat_Init",NULL,0);//任务1重新运行BAS中的初始化函数
if(ret !=0)
{
ui->edt_BusInitStatus->setText("初始化失败");
return ;
}
g_iInitStatus=-1;
//开始刷新数据
timer_GlobalDataUpdate->start(1000);//每1秒更新
}
}
//全局数据刷新———单轴
void FrmMotionControl::updateGlobalDate()
{
//总线状态更新
QString tempstr;
if(g_basflag && g_iInitStatus == -1) //已经加载文件并且正在初始化 读取状态
{
float m_tempstatus = -1;
char BUS_TYPE[]="BUS_TYPE",Bus_InitStatus[]="Bus_InitStatus",Bus_TotalAxisnum[]="Bus_TotalAxisnum";
int ret = ZAux_Direct_GetUserVar(g_handle,BUS_TYPE,&g_fBusType); //读取BAS文件中的变量判断总线类型
ret += ZAux_Direct_GetUserVar(g_handle,Bus_InitStatus,&m_tempstatus); //读取BAS文件中的变量判断总线初始化完成状态
ret += ZAux_BusCmd_GetNodeNum(g_handle,0,&g_fBusNodeNum); //读取槽位0上节点个数。
ret += ZAux_Direct_GetUserVar(g_handle,Bus_TotalAxisnum,&g_fBusAxisNum); //读取BAS文件中的变量判断扫描的总轴数
g_iInitStatus = (int)m_tempstatus;
if(ret !=0 && g_iInitStatus ==-1) //初始化完成刷新状态
{
tempstr =g_iInitStatus?"初始化成功":"初始化失败";//显示初始化状态
ui->edt_BusInitStatus->setText(tempstr);
ui->edt_BusNodeNums->setText(QString::number(g_fBusNodeNum, 'f', 1));//显示总线上节点数量
ui->edt_BusAxisNums->setText(QString::number(g_fBusAxisNum, 'f', 1));//显示总线上轴数量
}
}
//轴状态更新
ZAux_Direct_GetMpos(g_handle,g_nAxis,&g_fMPos);//轴编码器反馈位置
ZAux_Direct_GetDpos(g_handle,g_nAxis,&g_fDPos);//轴指令位置
ZAux_Direct_GetAxisStatus(g_handle,g_nAxis,&g_iAxisStatus);//轴状态
ZAux_Direct_GetIfIdle(g_handle,g_nAxis,&g_iIdle);//轴是否在运动
ui->edt_DirectAxisPos->setText(QString ("%2").arg (g_fDPos));//显示轴指令位置
ui->edt_CurrentAxisPos->setText(QString ("%2").arg (g_fMPos));//显示轴反馈位置
ui->edt_AxisStatus->setText(QString ("%2").arg(g_iAxisStatus));//显示轴状态
ui->edt_IfIdle->setText(QString ("%2").arg(g_iIdle));//显示轴运动状态
//回零状态更新
if(g_iAxisStatus & 64) //第6位是否被置1
{
ui->edt_GetHomeStatus->setText("回零中");
}
else
{
ZAux_BusCmd_GetHomeStatus(g_handle,g_nAxis,&g_iHomeStatus);
tempstr =g_iHomeStatus?"回零完成":"回零未完成";
ui->edt_GetHomeStatus->setText(tempstr);
}
}
其中总线初始化、轴控全局变量实时反馈状态如下图中的红框部分:
3 单轴运动控制的实现
本例中单轴运动控制窗口如下图。
3.1 轴使能
轴使能要在总线初始化成功的状态下进行,通过ZAux_Direct_SetAxisEnable函数对目标轴进行使能,通过ZAux_Direct_GetAxisEnable函数获取目标轴使能状态,通过ZAux_Direct_GetAtype函数获取轴类型。
这里提前提示一下,在后面设置轴运动参数函数ZAux_Direct_SetAtype的第3个参数轴类型时,要根据控制卡反馈的轴类型,选择轴在EtherCAT通讯下使用的类型,如果模式选择错误,控制器会解除轴使能并报错。
选择轴号后,通过一个按钮点击使能轴,将上述函数写入其clicked()槽函数:
//使能轴
void FrmMotionControl::on_btn_EnableAxis_clicked()
{
int m_iGetValue,ret;
ret = ZAux_BusCmd_GetInitStatus(g_handle, &m_iGetValue); //获取总线初始化状态 0:失败 1:成功(只针对 Zmotion tools 工具软件配置过总线参数控制器使用有效)
if(!ret)//总线初始化成功
{
int m_bAxisEnable,m_atype;
ZAux_Direct_GetAxisEnable(g_handle, g_nAxis, &m_bAxisEnable);// 获取轴使能状态 0 表示关闭 1 表示打开
if(m_bAxisEnable>=1)//若处于打开状态
{
ZAux_Direct_SetAxisEnable(g_handle,g_nAxis, 0);// 设置轴使能 0 表示关闭 1 表示打开
ZAux_Direct_GetAxisEnable(g_handle, g_nAxis, &m_bAxisEnable);// 再次获取轴使能状态 0 表示关闭 1 表示打开
if(m_bAxisEnable<1)
{
ui->btn_EnableAxis->setText("打开");
ui->edt_EnableAxis->setText("off");
}
}
else//若处于关闭状态
{
ZAux_Direct_SetAxisEnable(g_handle,g_nAxis, 1);
ZAux_Direct_GetAxisEnable(g_handle, 1, &m_bAxisEnable);// 再次获取轴使能状态 0 表示关闭 1 表示打开
if(m_bAxisEnable>=1)
{
ui->btn_EnableAxis->setText("关闭");
ui->edt_EnableAxis->setText("on");
}
}
ZAux_Direct_GetAtype(g_handle,g_nAxis,&m_atype);//获取轴类型
ui->edt_AxisType->setText(QString ("%2").arg (m_atype));
}
else
{
QMessageBox::warning(this,"提示","总线未初始化成功,错误码:"+QString::number(m_iGetValue,10));
return ;
}
}
3.2 轴刷新
如果要更换当前轴,需要刷新一下目标轴及其轴状态。
//修改轴号,刷新轴参数
void FrmMotionControl::on_btn_UpdateAxisNum_clicked()
{
g_nAxis=ui->edt_AxisNum->text().toInt();//更新当前轴号,关键
if(g_handle==NULL)
{
QMessageBox::warning(this,"提示","控制器未连接");
return;
}
else
{
//刷新轴号,获取更新轴当前实时反馈的运动参数
int m_atype;
float m_units,m_speed,m_accel;
ZAux_Direct_GetAtype(g_handle,g_nAxis,&m_atype);
ZAux_Direct_GetUnits(g_handle,g_nAxis,&m_units);
ZAux_Direct_GetSpeed(g_handle,g_nAxis,&m_speed);
ZAux_Direct_GetAccel(g_handle,g_nAxis,&m_accel);
ui->edt_AxisType->setText(QString ("%2").arg (m_atype));
ui->edt_PulseEquivalent->setText(QString ("%2").arg (m_units));
ui->edt_Speed->setText(QString ("%2").arg (m_speed));
ui->edt_Accel->setText(QString ("%2").arg (m_accel));
}
int m_bAxisEnable;
ZAux_Direct_GetAxisEnable(g_handle, g_nAxis, &m_bAxisEnable);// 获取轴使能状态 0 表示关闭 1 表示打开
if(!m_bAxisEnable)//若轴未使能
{
ui->btn_EnableAxis->setText("打开");
ui->edt_EnableAxis->setText("off");
}
else//若轴已使能
{
ui->btn_EnableAxis->setText("关闭");
ui->edt_EnableAxis->setText("on");
}
}
3.3 轴运动
在设定好轴运动的相关参数后,即可驱动轴进行运动,此处有点动、连动两种运动方式,点动需要提前输入点动距离。注意检查相关参数,例如:
-
前面提到的轴运动参数函数ZAux_Direct_SetAtype的第3个参数轴类型时,要选择轴在EtherCAT通讯下使用的类型,否则轴会使能失败报错;
-
轴状态是否为0,如果有错误需要先清除错误,一部分错误可以软清除(通过函数),一部分错误要检查硬件连接状态等,轴状态报错码如下图。
-
还有一个易忽略的极危险错误在于单位的设置。在设置速度、距离时要注意单位,主要关注①伺服驱动器设置的初始单位units(cm、mm、counts等)、②设置脉冲当量后的单位换算,否则容易造成撞车危险。
//单轴开始运动
void FrmMotionControl::on_btn_OnStart_clicked()
{
if (NULL == g_handle)
{
QMessageBox::warning(this,"提示","链接断开状态");
return;
}
//判断当前轴运动状态
g_nAxis=ui->edt_AxisNum->text().toInt();
int status = 0;
ZAux_Direct_GetIfIdle(g_handle, g_nAxis, &status);
if (status == 0) //已经在运动中
return;
//设定轴类型 1-脉冲轴类型
ZAux_Direct_SetAtype(g_handle, g_nAxis, 65);
//设定脉冲模式及逻辑方向(脉冲+方向)
ZAux_Direct_SetInvertStep(g_handle, g_nAxis, 0);
//设置脉冲当量 1表示以一个脉冲为单位 ,设置为1MM的脉冲个数,这度量单位为MM
ZAux_Direct_SetUnits(g_handle, g_nAxis, ui->edt_PulseEquivalent->text().toInt());
//设定速度,加减速
ZAux_Direct_SetLspeed(g_handle, g_nAxis, 0);//起始速度为0
ZAux_Direct_SetSpeed(g_handle, g_nAxis, ui->edt_Speed->text().toInt());
ZAux_Direct_SetAccel(g_handle, g_nAxis, ui->edt_Accel->text().toInt());
ZAux_Direct_SetDecel(g_handle, g_nAxis, ui->edt_Decel->text().toInt());
//设定S曲线时间 设置为0表示梯形加减速
ZAux_Direct_SetSramp(g_handle, g_nAxis, m_sramp);
//启动
int m_mode=0,m_step=ui->edt_JoggleDis->text().toInt();
bool m_bLogic;
if(ui->cbx_Forward->isChecked()) m_bLogic=1;
if(ui->cbx_Reverse->isChecked()) m_bLogic=0;
if(ui->cbx_Joggle->isChecked()) m_mode=1;
if(ui->cbx_Successive->isChecked()) m_mode=2;
if (m_mode == 2)
{//持续驱动(速度模式)
ZAux_Direct_Single_Vmove(g_handle, g_nAxis, m_bLogic ? 1 : -1);
}
else if(m_mode ==1)
{//寸动(位置模式)
ZAux_Direct_Single_Move(g_handle, g_nAxis, m_step*(m_bLogic ? 1 : -1));
}
}
3.4 轴停止
设置一个按钮即可停止轴的运动
void FrmMotionControl::on_btn_OnStop1_clicked()
{
if (NULL == g_handle)
{
QMessageBox::warning(this,"提示","链接断开状态");
return;
}
ZAux_Direct_Single_Cancel(g_handle,g_nAxis,2);
}
3.5 解除警告
有时候轴状态栏会报错,此时轴被锁死无法使能,需要清除故障和警报后重新使能,通过ZAux_BusCmd_DriveClear函数实现。
void FrmMotionControl::on_btn_ClearAlm_clicked()
{
if(NULL == g_handle)
{
QMessageBox::warning(this,"提示","控制器未连接");
return;
}
ZAux_BusCmd_DriveClear(g_handle,g_nAxis,0);
}
4 限位接线与回零功能的实现
安装限位器、设置限位器IO口是实现回零功能的前提。
4.1 限位
限位包括硬限位、软限位两种。硬限位是用实体的限位开关、限位传感器设置限位点,软限位由用户设置,行程在硬限位行程中间,一般通过记录编码器位置实现,较为不可靠,如果硬限位行程与总行程之间的余量足够、或停车加速度较大时,软限位作用较小。
两种限位行程的关系如下图。
安装限位开关/传感器时要根据型号进行接线,查阅相关信号,如限位开关实物接线图_限位开关接线方法 - 电子发烧友网 (elecfans.com)。
本例采用的是Omron光栅限位传感器,除了蓝色的线接负极、棕色的线接正极,还有一根黑色线做输出端(常闭),一根白色线做公共端(接在正极后常开)。
其中黑色线要接入控制器的IO口,各IO口具体类型和功能要查阅控制器的用户手册。
这里要注意设置电平取反,否则传感器会处于常开状态,与实际需求相悖。此处可采用ZAux_Direct_SetInvertIn函数进行电平置反。
设置好IO口、电平反转后可以通过Zdevelop或Zmotion tools软件预先查看IO口状态,以确定是否设置成功。
4.2 回零
可以通过ZAux_Direct_SetDpos函数设置软零点
void FrmMotionControl::on_btn_OnZero_clicked()
{
if (NULL == g_handle)
{
QMessageBox::warning(this,"提示","链接断开状态");
return;
}
g_nAxis=ui->edt_AxisNum->text().toInt();
ZAux_Direct_SetDpos(g_handle, g_nAxis, 0); //设置零点
}
ZMC在EtherCAT通讯下提供的回零方式有两种:控制器回零、伺服参数回零。
控制器回零是把零点位置传感器连接到运动控制器上,控制器通过搜索零点传感器位置回零点。运动控制器轴回零的配置与实现_正运动 回零_正运动技术的博客-CSDN博客
伺服参数回零是将零点传感器连接到伺服驱动器上,控制器通过发送命令给伺服驱动器,伺服驱动器进行回零的操作。EtherCAT与RTEX驱动器轴回零的配置与实现_禾川ethercat读正反限位_正运动技术的博客-CSDN博客
本例采用控制器回零。
Zmotion提供了多种回零模式,需查阅手册进行设置。设置时要注意加10 表示碰到限位后调头反找,不会碰到限位停止。
void FrmMotionControl::on_btn_OnHome_clicked()
{
if (NULL == g_handle)
{
QMessageBox::warning(this,"提示","控制器未连接");
return;
}
//判断当前轴运动状态
g_nAxis=ui->edt_AxisNum->text().toInt();
int status = 0;
ZAux_Direct_GetIfIdle(g_handle, g_nAxis, &status);
if (status == 0) //已经在运动中
return;
//设定轴类型 1-脉冲轴类型
ZAux_Direct_SetAtype(g_handle, g_nAxis, 65);
//设定脉冲模式及逻辑方向(脉冲+方向)
ZAux_Direct_SetInvertStep(g_handle, g_nAxis, 0);
//设置脉冲当量 1表示以一个脉冲为单位 ,设置为1MM的脉冲个数,这度量单位为MM
ZAux_Direct_SetUnits(g_handle, g_nAxis, ui->edt_PulseEquivalent->text().toInt());
//设定速度,加减速
ZAux_Direct_SetLspeed(g_handle, g_nAxis, 0);//起始速度为0
ZAux_Direct_SetSpeed(g_handle, g_nAxis, ui->edt_Speed->text().toInt());
ZAux_Direct_SetAccel(g_handle, g_nAxis, ui->edt_Accel->text().toInt());
ZAux_Direct_SetDecel(g_handle, g_nAxis, ui->edt_Decel->text().toInt());
ZAux_Direct_SetCreep(g_handle, g_nAxis, ui->edt_Creep->text().toInt());//设置反向回零爬行速度为 10units
//设定对应轴的原点输入口信号
ZAux_Direct_SetDatumIn(g_handle, g_nAxis, ui->edt_DatumMin->text().toInt());
ZAux_Direct_SetInvertIn(g_handle, ui->edt_DatumMin->text().toInt(), 1); //ZMC系列认为OFF时碰到了原点信号(常闭),如果是常开传感器则需要反转输入口
//设定对应轴的正向限位开关输入口信号
ZAux_Direct_SetFwdIn(g_handle, g_nAxis, ui->edt_FwdIn->text().toInt());
ZAux_Direct_SetInvertIn(g_handle, ui->edt_FwdIn->text().toInt(), 1);
//设定对应轴的负向限位开关输入口信号
ZAux_Direct_SetRevIn(g_handle, g_nAxis, ui->edt_RevIn->text().toInt());
ZAux_Direct_SetInvertIn(g_handle, ui->edt_RevIn->text().toInt(), 1);
//回零:运动控制卡控制
int m_datummode=ui->cbx_DatumMode->currentText().toInt();
ZAux_Direct_Single_Datum(g_handle, g_nAxis, m_datummode);
}
最后实现的窗口效果如图:
参考资料
-
《Zmotion PC函数库编程手册 V2.1.1》
-
《ZMC408SCAN 总线振镜运动控制用户手册 V1.5》