运动控制卡初学(1)
ZMotion运动卡 学习笔记(一)
第一步 使用Zmotion软件判断当前设备是否运行良好
-
打开ZDevelop软件
-
单击连接
3.全新的轴卡设备默认Ip地址是192.168.0.11,需要手动修改下,然后单击连接。
-
成功以后会看到如下信息
5.使用手动控制的方式进行控制
我这里目前是单个轴的。轴类型一般写1(1 脉冲方向方式的步进或者伺服).
脉冲当量(基本上可以理解为1mm对于的脉冲个数) 计算如下:
细分:步进电机通过细分驱动器的驱动,其步距角变小。如驱动器工作在10细分状态时,其步距角只为“固定步距角”的十分之一,也就是:当驱动器工作在不细分的整步状态时,控制系统每发一个步进脉冲,电机转动1.8;而用细分驱动器工作在10细分状态时,电机只转动0.18度。细分功能完全是由驱动器靠精度控制电机的相电流所产生的,于电机无关。
导程:是螺纹上任意一点沿同一条螺旋线转一周所移动的轴向距离。简单说就是。电机转动一圈以后,这个机器横向移动的距离。
-
我手上的这款电机的步距角是1.8°,转动到360°需要200个脉冲。然后又因为驱动器的细分是8.所以转动一周实际需要200*8=1600个脉冲。已知旋转一周的导程是4mm,消耗1600个脉冲。那么求解1mm的对应脉冲数就是=1600/4=400个。所以当前脉冲当量设置为400.即让步进电机前进1mm。
- 测试办法一般就是使用下左和右运动即可。
第二步 开始软件功能设计
主要思考方向:(先单轴控制)
- 把界面控制与功能函数隔离
- 功能函数基本覆盖如下的功能:
- 板卡的基本操作:通断、连续运动、点动、绝对运动与相对运动,插补运动(相对位置、圆弧)与回零动作等
- 板卡的消息的处理:板卡的错误信息处理,获取板卡的实时运动与位置信息。
大致需求如上:那么编写Zmotion.cs文件的功能基本如下:
-
OperationResult 存放操作结果:返回成功还是失败的结果
-
Zmotion类存放直接与轴卡交互的功能函数:轴卡初始化与轴卡的运动控制。当需要向上层传递操作结果的时候使用OperationResult 的实例对象。
-
UI层界面可以获取到轴卡的运动状态,通过定时器读取Zmotion类中的方法,更新到界面控件上。
-
在设计的时候,如果考虑后续的复合型,比如操作其他厂家的轴卡设备。那么最好将功能操作提取成父类,实际需要使用的动作解释作为延伸类,UI层选择需要调用设备的协议,那么解释层通过协议来改变需要使用的厂家设备。但是考虑到时间成本的问题,目前就不进行如下的代码重构了。
OperationResult 代码如下(这里考虑到不能返回结果都生成或者绑定新的对象,所以使用了一个静态方法返回错误失败信息):
public class OperationResult { /// <summary> /// 是否成功 /// </summary> public bool IsSuccess { get; set; } /// <summary> /// 错误信息 /// </summary> public string ErrorMsg { get; set; } public static OperationResult CreateSuccessResult() { return new OperationResult() { IsSuccess = true, ErrorMsg = "Success" }; } public static OperationResult CreateFailResult() { return new OperationResult() { IsSuccess = false, ErrorMsg = "Fail" }; } }
zMotion代码中的轴卡的通断
/// <summary> /// 初始化板卡 /// </summary> /// <returns></returns> public OperationResult InitCard() { int error = zmcaux.ZAux_OpenEth(this.IPAddress, out Handle); InitedOK = true; if (error == 0 && Handle != IntPtr.Zero) { InitedOK = true; return OperationResult.CreateSuccessResult(); } else { InitedOK = false; return OperationResult.CreateFailResult(); } } /// <summary> /// 关闭板卡 /// </summary> /// <returns></returns> public OperationResult CloseCard() { if (zmcaux.ZAux_Close(Handle) == 0) { Handle = IntPtr.Zero; InitedOK = false; return OperationResult.CreateSuccessResult(); } return OperationResult.CreateFailResult(); }
在 进行运动操作之前先判断当前轴卡是否处于连接状态,如果指定轴没有处于运动状态,那么抛出错误:
/// <summary> /// 通用运动验证 /// </summary> /// <param name="axis">轴号</param> /// <returns>操作结果</returns> private OperationResult CommonMotionValidate(short axis) { OperationResult result = CommonInitedValidate(); //判断是否已经初始化 if (!result.IsSuccess) return result; //判断是否正在运行 if (IsMoving(axis)) { result.IsSuccess = false; result.ErrorMsg = "轴正在运行"; return result; } return OperationResult.CreateSuccessResult(); } /// <summary> /// 通用初始化验证 /// </summary> /// <returns></returns> private OperationResult CommonInitedValidate() { OperationResult result = new OperationResult(); //判断是否已经初始化 if (!InitedOK) { result.IsSuccess = false; result.ErrorMsg = "控制器未初始化"; return result; } return OperationResult.CreateSuccessResult(); } /// <summary> /// 判断某个轴是否正在运行 /// </summary> /// <param name="axis"></param> /// <returns></returns> public bool IsMoving(short axis) { OperationResult result = CommonInitedValidate(); //判断是否已经初始化 if (!result.IsSuccess) return false; //定义运行状态 int runstate = -1; //定义错误码 int error = 0; try { //获取轴状态 error = zmcaux.ZAux_Direct_GetIfIdle(Handle, axis, ref runstate); //错误码验证 ErrorHandler("ZAux_Direct_GetIfIdle", error); return runstate == 0; } catch (Exception) { return true; } }
操作后返回错误码信息处理,让程序直接抛出异常,这个目前基本够用,但是不够好:
/// <summary> /// 错误处理 /// </summary> /// <param name="command">执行命令</param> /// <param name="error">错误码</param> private void ErrorHandler(string command, int error) { string result = string.Empty; switch (error) { case 0: break; default: result = string.Format("{0}" + "指令执行错误,错误码为{1}", command, error); break; } if (result.Length > 0) { throw new Exception(result); } }
连续运动函数的编写(思路上:首先确定使用运动验证程序,判断轴的状态。然后若是异常,调用错误处理程序;正常,则写入当前轴号等参数信息并执行运动。如果没有触发异常,那么返回执行成功的信息)
/// <summary>
/// 连续运动
/// </summary>
/// <param name="axis">轴号</param>
/// <param name="vel">运行速度</param>
/// <param name="dir">方向</param>
/// <param name="velMin">最小速度</param>
/// <param name="acc">加速度</param>
/// <param name="dec">减速度</param>
/// <param name="sramp">S曲线时间</param>
/// <returns>操作结果</returns>
public OperationResult VMove(short axis, float vel, bool dir, float velMin, float acc, float dec, float sramp)
{
// 判断是否满足运动条件
var result = CommonMotionValidate(axis);
if (!result.IsSuccess) return result;
//创建错误码
int error = 0;
try
{
//设置轴类型
/*
Atype类型 描述
0 虚拟轴。
1 脉冲方向方式的步进或伺服 。
2 模拟信号控制方式的伺服 。
3 正交编码器 。
4 步进+编码器 。
6 脉冲方向方式的编码器,可用于手轮输入。
7 脉冲方向方式步进或伺服+EZ信号输入。
8 ZCAN扩展脉冲方向方式步进或伺服 。
9 ZCAN扩展正交编码器。
10 ZCAN扩展脉冲方向方式的编码器。
*/
error = zmcaux.ZAux_Direct_SetAtype(Handle, axis, 1);
ErrorHandler("ZAux_Direct_SetAtype", error);
//设置脉冲当量
switch (axis)
{
case 0:
error = zmcaux.ZAux_Direct_SetUnits(Handle, axis, unit0);
ErrorHandler("ZAux_Direct_SetUnits", error);
break;
case 1:
error = zmcaux.ZAux_Direct_SetUnits(Handle, axis, unit1);
ErrorHandler("ZAux_Direct_SetUnits", error);
break;
case 2:
error = zmcaux.ZAux_Direct_SetUnits(Handle, axis, unit2);
ErrorHandler("ZAux_Direct_SetUnits", error);
break;
case 3:
error = zmcaux.ZAux_Direct_SetUnits(Handle, axis, unit3);
ErrorHandler("ZAux_Direct_SetUnits", error);
break;
default:
break;
}
//设置最小速度
error = zmcaux.ZAux_Direct_SetLspeed(Handle, axis, velMin);
ErrorHandler("ZAux_Direct_SetLspeed", error);
//设置运行速度
error = zmcaux.ZAux_Direct_SetSpeed(Handle, axis, vel);
ErrorHandler("ZAux_Direct_SetSpeed", error);
//设置加速度
error = zmcaux.ZAux_Direct_SetAccel(Handle, axis, acc);
ErrorHandler("ZAux_Direct_SetAccel", error);
//设置减速度
error = zmcaux.ZAux_Direct_SetDecel(Handle, axis, dec);
ErrorHandler("ZAux_Direct_SetDecel", error);
//设置S曲线
error = zmcaux.ZAux_Direct_SetSramp(Handle, axis, sramp);
ErrorHandler("ZAux_Direct_SetSramp", error);
//设置方向并运动
error = zmcaux.ZAux_Direct_Single_Vmove(Handle, axis, dir ? 1 : -1);
ErrorHandler("ZAux_Direct_Single_Vmove", error);
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMsg = ex.Message;
return result;
}
return OperationResult.CreateSuccessResult();
}
相同的思路,编写 相对运动
/// <summary>
/// 相对运动
/// </summary>
/// <param name="axis"></param>
/// <param name="vel"></param>
/// <param name="distance"></param>
/// <param name="velMin"></param>
/// <param name="acc"></param>
/// <param name="dec"></param>
/// <param name="sramp"></param>
/// <returns></returns>
public OperationResult MoveRelative(short axis, float vel, float distance, float velMin, float acc, float dec, float sramp)
{
// 判断是否满足运动条件
var result = CommonMotionValidate(axis);
if (!result.IsSuccess) return result;
//创建错误码
int error = 0;
try
{
//设置轴类型
/*
Atype类型 描述
0 虚拟轴。
1 脉冲方向方式的步进或伺服 。
2 模拟信号控制方式的伺服 。
3 正交编码器 。
4 步进+编码器 。
6 脉冲方向方式的编码器,可用于手轮输入。
7 脉冲方向方式步进或伺服+EZ信号输入。
8 ZCAN扩展脉冲方向方式步进或伺服 。
9 ZCAN扩展正交编码器。
10 ZCAN扩展脉冲方向方式的编码器。
*/
error = zmcaux.ZAux_Direct_SetAtype(Handle, axis, 1);
ErrorHandler("ZAux_Direct_SetAtype", error);
//设置脉冲当量
switch (axis)
{
case 0:
error = zmcaux.ZAux_Direct_SetUnits(Handle, axis, unit0);
ErrorHandler("ZAux_Direct_SetUnits", error);
break;
case 1:
error = zmcaux.ZAux_Direct_SetUnits(Handle, axis, unit1);
ErrorHandler("ZAux_Direct_SetUnits", error);
break;
case 2:
error = zmcaux.ZAux_Direct_SetUnits(Handle, axis, unit2);
ErrorHandler("ZAux_Direct_SetUnits", error);
break;
case 3:
error = zmcaux.ZAux_Direct_SetUnits(Handle, axis, unit3);
ErrorHandler("ZAux_Direct_SetUnits", error);
break;
default:
break;
}
//设置最小速度
error = zmcaux.ZAux_Direct_SetLspeed(Handle, axis, velMin);
ErrorHandler("ZAux_Direct_SetLspeed", error);
//设置运行速度
error = zmcaux.ZAux_Direct_SetSpeed(Handle, axis, vel);
ErrorHandler("ZAux_Direct_SetSpeed", error);
//设置加速度
error = zmcaux.ZAux_Direct_SetAccel(Handle, axis, acc);
ErrorHandler("ZAux_Direct_SetAccel", error);
//设置减速度
error = zmcaux.ZAux_Direct_SetDecel(Handle, axis, dec);
ErrorHandler("ZAux_Direct_SetDecel", error);
//设置S曲线
error = zmcaux.ZAux_Direct_SetSramp(Handle, axis, sramp);
ErrorHandler("ZAux_Direct_SetSramp", error);
//设置方向并运动
error = zmcaux.ZAux_Direct_Single_Move(Handle, axis, distance);
ErrorHandler("ZAux_Direct_Single_Move", error);
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMsg = ex.Message;
return result;
}
return OperationResult.CreateSuccessResult();
}
绝对运动,编写思路。
我自己就是查询手册,自己改写。看到函数手册。文末会给到百度云连接。
第一步:在手册中全文搜索绝对运动
第二步:找到绝对运动函数。
第三步:用搜索到的相关函数进行全文搜索
找到一个带有代码示例的程序
第四步:分析示例程序里面下达的配置命令。
发现所有代码中就只有最后的执行代码不同,那么就按照相对运动的代码进行改写。所以代码如下:
/// <summary>
/// 绝对运动
/// </summary>
/// <param name="axis"></param>
/// <param name="vel"></param>
/// <param name="pos"></param>
/// <param name="velMin"></param>
/// <param name="acc"></param>
/// <param name="dec"></param>
/// <param name="sramp"></param>
/// <returns></returns>
public OperationResult MoveAbs(short axis, float vel, float pos, float velMin, float acc, float dec, float sramp)
{
// 判断是否满足运动条件
var result = CommonMotionValidate(axis);
if (!result.IsSuccess) return result;
//创建错误码
int error = 0;
try
{
//设置轴类型
/*
Atype类型 描述
0 虚拟轴。
1 脉冲方向方式的步进或伺服 。
2 模拟信号控制方式的伺服 。
3 正交编码器 。
4 步进+编码器 。
6 脉冲方向方式的编码器,可用于手轮输入。
7 脉冲方向方式步进或伺服+EZ信号输入。
8 ZCAN扩展脉冲方向方式步进或伺服 。
9 ZCAN扩展正交编码器。
10 ZCAN扩展脉冲方向方式的编码器。
*/
error = zmcaux.ZAux_Direct_SetAtype(Handle, axis, 1);
ErrorHandler("ZAux_Direct_SetAtype", error);
//设置脉冲当量
switch (axis)
{
case 0:
error = zmcaux.ZAux_Direct_SetUnits(Handle, axis, unit0);
ErrorHandler("ZAux_Direct_SetUnits", error);
break;
case 1:
error = zmcaux.ZAux_Direct_SetUnits(Handle, axis, unit1);
ErrorHandler("ZAux_Direct_SetUnits", error);
break;
case 2:
error = zmcaux.ZAux_Direct_SetUnits(Handle, axis, unit2);
ErrorHandler("ZAux_Direct_SetUnits", error);
break;
case 3:
error = zmcaux.ZAux_Direct_SetUnits(Handle, axis, unit3);
ErrorHandler("ZAux_Direct_SetUnits", error);
break;
default:
break;
}
//设置最小速度
error = zmcaux.ZAux_Direct_SetLspeed(Handle, axis, velMin);
ErrorHandler("ZAux_Direct_SetLspeed", error);
//设置运行速度
error = zmcaux.ZAux_Direct_SetSpeed(Handle, axis, vel);
ErrorHandler("ZAux_Direct_SetSpeed", error);
//设置加速度
error = zmcaux.ZAux_Direct_SetAccel(Handle, axis, acc);
ErrorHandler("ZAux_Direct_SetAccel", error);
//设置减速度
error = zmcaux.ZAux_Direct_SetDecel(Handle, axis, dec);
ErrorHandler("ZAux_Direct_SetDecel", error);
//设置S曲线
error = zmcaux.ZAux_Direct_SetSramp(Handle, axis, sramp);
ErrorHandler("ZAux_Direct_SetSramp", error);
//设置方向并运动
error = zmcaux.ZAux_Direct_Single_MoveAbs(Handle, axis, pos);
ErrorHandler("ZAux_Direct_Single_MoveAbs", error);
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMsg = ex.Message;
return result;
}
return OperationResult.CreateSuccessResult();
相同写法的简单代码:
-
获取轴速度、轴实时位置
public float GetVel(short axis) { //判断是否满足初始化条件 var result = CommonInitedValidate(); if (!result.IsSuccess) return 0.0f; //定义速度 float vel = 0.0f; //定义错误码 int error = 0; try { error = zmcaux.ZAux_Direct_GetVpSpeed(Handle, axis, ref vel); ErrorHandler("ZAux_Direct_GetVpSpeed", error); return vel; } catch (Exception) { return 0.0f; } }
/// <summary> /// 获取实时位置 /// </summary> /// <param name="axis"></param> /// <returns></returns> public float GetPos(short axis) { //判断是否满足初始化条件 var result = CommonInitedValidate(); if (!result.IsSuccess) return 0.0f; //定义位置 float pos = 0.0f; //定义错误码 int error = 0; try { error = zmcaux.ZAux_Direct_GetMpos(Handle, axis, ref pos); ErrorHandler("ZAux_Direct_GetMpos", error); return pos; } catch (Exception) { return 0.0f; } }
2.所有轴停止和轴归零
/// <summary> /// 停止所有轴 /// </summary> /// <returns></returns> public OperationResult StopAllAxis() { var result = CommonInitedValidate(); if (!result.IsSuccess) return result; //错误码 int error = 0; try { /* 0取消当前运动 。 1取消缓冲的运动 。 2取消当前运动和缓冲运动 。 3 立即停止。 */ error = zmcaux.ZAux_Direct_CancelAxisList(Handle, 4, new int[] { 0, 1, 2, 3 }, 3); ErrorHandler("ZAux_Direct_CancelAxisList", error); } catch (Exception ex) { result.IsSuccess = false; result.ErrorMsg = ex.Message; return result; } return OperationResult.CreateSuccessResult(); }
回零模式在书中的案例讲解如下:
再看书中的代码案例:
先说一下,这种模式需要运行前获取当前滑块在原点的左右位置,然后反向靠近原点(这就需要做出一定的判断)。所以我们换一种思路:
-
强制向着负相位运行距离(大于正负限位的距离),那么滑块必然运行到负限位的地方,然后停止下来。
-
(剩下是模式4的方法)到达负限位的地方以后,向着正限位的距离移动(稍微大于一点负限位距离原点的距离)。
-
然后向着负相位反向运行找原点,遇到原点以后停下。
个人感觉这种方法有点不对,后续会再重新思考
-
/// <summary>
/// 通用回零操作
/// </summary>
/// <param name="axis">轴号</param>
/// <param name="vel">速度</param>
/// <param name="creep">爬行速度</param>
/// <param name="homeio">HomeIO</param>
/// <param name="distance">原限距离</param>
/// <param name="velMin">最小速度</param>
/// <param name="acc">加速度</param>
/// <param name="dec">减速度</param>
/// <param name="sramp">s曲线时间</param>
/// <param name="homemode">回零模式</param>
/// <returns>操作结果</returns>
public OperationResult ZeroAxis(short axis, float vel, float creep, int homeio, float distance, float velMin, float acc, float dec, float sramp, int homemode = 4)
{
// 判断是否满足运动条件
var result = CommonMotionValidate(axis);
if (!result.IsSuccess) return result;
//先往原点方向一直走,直到到达限位
result = MoveRelative(axis, vel, -500, velMin, acc, dec, sramp);
if (!result.IsSuccess) return result;
//等待停止
result = WaitStop(axis);
if (!result.IsSuccess) return result;
//位置
result = ZeroPos(axis);
if (!result.IsSuccess) return result;
//往反向走一段距离,超过原点和限位的距离
result = MoveRelative(axis, vel, distance, velMin, acc, dec, sramp);
if (!result.IsSuccess) return result;
//等待停止
result = WaitStop(axis);
if (!result.IsSuccess) return result;
//直接回零
result = DirectZeroAxis(axis, vel, creep, homeio, velMin, acc, dec, homemode);
if (!result.IsSuccess) return result;
//等待回零停止
result = WaitHomeStop(axis);
if (!result.IsSuccess) return result;
//返回成功
return OperationResult.CreateSuccessResult();
}