今天,正运动小助手给大家分享一下EtherCAT运动控制卡ECI2828如何使用C#进行实时程序的运行和读写控制。
一 ECI2828运动控制卡硬件介绍
ECI2828系列运动控制卡支持多达 16 轴直线插补、任意圆弧插补、空间圆弧、螺旋插补、电子凸轮、电子齿轮、同步跟随、虚拟轴、机械手指令等;采用优化的网络通讯协议可以实现实时的运动控制。
ECI2828系列运动运动控制卡支持以太网,232 通讯接口和电脑相连,接收电脑的指令运行,可以通过EtherCAT总线和CAN总线去连接各个扩展模块,从而扩展输入输出点数或运动轴。
ECI2828系列运动控制卡的应用程序可以使用 VC,VB,VS,C++,C#等软件来开发,程序运行时需要动态库 zmotion.dll。调试时可以把ZDevelop软件同时连接到控制器,从而方便调试、方便观察。
二 C#语言进行运动控制开发
(一)新建MFC项目并添加函数库
1.在VS2015菜单“文件”→“新建”→ “项目”,启动创建项目向导。
2.选择开发语言为“Visual C#”和.NET Framework 4以及Windows 窗体应用程序。
3.找到厂家提供的光盘资料里面的C#函数库,路径如下(64位库为例):
A.进入厂商提供的光盘资料找到“8.PC函数”文件夹,并点击进入。
B.选择“函数库2.1”文件夹。
C.选择“Windows平台”文件夹。
D.根据需要选择对应的函数库这里选择64位库。
E.解压C#的压缩包,里面有C#对应的函数库。
F.函数库具体路径如下。
4.将厂商提供的C#的库文件以及相关文件复制到新建的项目中。
A.将zmcaux.cs文件复制到新建的项目里面中。
B.将zaux.dll和zmotion.dll文件放入bin\debug文件夹中。
5.用vs打开新建的项目文件,在右边的解决方案资源管理器中点击显示所有,然后鼠标右键点击zmcaux.cs文件,点击包括在项目中。
6.双击Form1.cs里面的Form1,出现代码编辑界面,在文件开头写入 using cszmcaux,并声明控制器句柄g_handle。
至此项目新建完成,可进行C#项目开发。
(二)查看PC函数手册
A.PC函数手册也在光盘资料里面,具体路径如下:“光盘资料\8.PC函数\函数库2.1\ZMotion函数库编程手册 V2.1.pdf”。
B.PC编程,一般如果网口对控制器和工控机进行链接。网口链接函数接口是ZAux_OpenEth();如果链接成功,该接口会返回一个链接句柄。通过操作这个链接句柄可以实现对控制器的控制。
ZAux_OpenEth()接口说明:
项目应用截图:
C.使用对下位机寄存器操作的指令操作链接句柄“g_handle”,对控制器进行寄存器内容取值,实时控制下位机相关的指令如下。
(三)C#进行实时性程序的运行和读写控制开发
1.实时性程序的运行和读写控制开发人机交互界面如下。
2.下位机Basic程序代码如下:
?"程序运行"
'轴参数初始化
base(0,1,2)
atype = 1,1,1
units = 10,10,10
speed = 100,100,100
accel = 1000,1000,1000
decel = 1000,1000,1000
merge = on
global cmd_data '全局变量 上位机可访问
cmd_data = 0
global array_para(20) '全局数组 上位机可访问
dim curcmd
curcmd = 0
'主程序,通过上位机传递的MODBUS值触发运动
while 1
if cmd_data <> 0 then
curcmd = cmd_data
cmd_data = 0
if curcmd = 1 then '执行动作1
base(0,1,2)
moveabs(array_para(0),array_para(1),array_para(2))
elseif curcmd = 2 then '执行动作2
base(0,1,2)
moveabs(array_para(5),array_para(6),array_para(7))
elseif curcmd = 3 then '执行动作3
base(0,1,2)
cancel(2)
elseif curcmd = 4 then '执行动作4
base(0,1,2)
cancel(2)
wait IDLE
dpos(0) = 0
dpos(1) = 0
dpos(2) = 0
endif
endif
wend
3.例程功能介绍
全局变量与数组交互
面板中的直线插补0、直线插补1、停止、位置清零等动作都是通过zbasic程序中的执行动作来实现的。
当直线插补0按钮被按下时全局变量cmd_data会被赋值为1、全局数组array_para会被修改。basic程序扫描到curcmd=1 时,0轴、1轴、2轴会运动一段绝对距离,运动的距离就是面板中设置的距离。
另外,PC端和下位机通讯时,操作的下位机程序变量与数组必须为全局变量。
FLASH操作
PC端可以通过ZAux_FlashReadf、ZAux_FlashWritef函数直接操作控制器FLASH进行数据的读写,在对没有进行过写操作的块进行读取操作时,函数会返回20030的错误码失败。
MODBUS寄存器操作
PC端可以通过MODBUS相关的API函数读写控制器上MODBUS寄存器。注意REG.LONG,IEEE这些字寄存器占用的是同一片存储区域。
程序这里通过设置起始读取可以读取,设置连续的4个寄存器的值。
VR掉电保持寄存器操作
PC端可以通过ZAux_Direct_GetVrf、ZAux_Direct_SetVrf读写控制器上VR寄存器。
程序这里通过设置起始读取可以读取,设置连续的4个寄存器的值。
TABLE系统数组操作
PC端可以通过ZAux_Direct_GetTable、ZAux_Direct_SetTable读写控制器上Table数组。
程序这里通过设置起始读取可以读取,设置连续的4个寄存器的值。
输入口IN监控操作
PC端可通过ZAux_Direct_GetIn进行读取控制器的输入口IN状态并将对应状态显示在界面上。
4.例程简易流程图。
5.在Form1的构造函数中调用接口ZAux_OpenEth(),使在系统初始化的时候自动链接控制器。
public Form1()
{
InitializeComponent();
//链接控制器
zmcaux.ZAux_OpenEth("192.168.0.11", out g_handle);
if (g_handle != (IntPtr)0)
{
MessageBox.Show("控制器链接成功!", "提示");
timer1.Enabled = true;
}
else
{
MessageBox.Show("控制器链接失败,请检测IP地址!", "警告");
}
}
6.通过定时器更新控制器轴状态:当前坐标、IN状态信息等等。
//定时器刷新
private void timer1_Tick(object sender, EventArgs e) //定时器
{
float[] curpos = new float[5];
for (int i = 0; i < 3; i++)
{
zmcaux.ZAux_Direct_GetDpos(g_handle, i, ref curpos[i]);
}
//获取in信号以及改变对应颜色
IoTest();
label_x.Text = "X轴:" + curpos[0];
label_y.Text = "Y轴:" + curpos[1];
label_z.Text = "Z轴:" + curpos[2];
}
7.Flash块的读取写入:
private void button_read2_Click(object sender, EventArgs e) //FLASH块读取
{
int ret = 0;
if (g_handle == (IntPtr)0)
{
MessageBox.Show("未链接到控制器!", "提示");
return;
}
ushort m_FlashStart = Convert.ToUInt16(flash_start.Text);
float[] m_FlashShow = new float[4];
uint getnum = 0;
ret = zmcaux.ZAux_FlashReadf(g_handle, m_FlashStart, 4, m_FlashShow, ref getnum);
if (ret == 0)
{
flash_get1.Text = m_FlashShow[0].ToString();
flash_get2.Text = m_FlashShow[1].ToString();
flash_get3.Text = m_FlashShow[2].ToString();
flash_get4.Text = m_FlashShow[3].ToString();
}
}
private void button_write2_Click(object sender, EventArgs e) //FLASH块写入
{
int ret = 0;
if (g_handle == (IntPtr)0)
{
MessageBox.Show("未链接到控制器!", "提示");
return;
}
ushort m_FlashStart = Convert.ToUInt16(flash_start.Text);
float[] m_FlashSet = new float[4];
m_FlashSet[0] = Convert.ToSingle(flash_set1.Text);
m_FlashSet[1] = Convert.ToSingle(flash_set2.Text);
m_FlashSet[2] = Convert.ToSingle(flash_set3.Text);
m_FlashSet[3] = Convert.ToSingle(flash_set4.Text);
ret = zmcaux.ZAux_FlashWritef(g_handle, m_FlashStart, 4, m_FlashSet);
}
8.传入全局数组坐标值到下位机,并传入全局变量值到下位机调用对应运动。
private void button2_Click(object sender, EventArgs e) //直线插补1
{
if (g_handle == (IntPtr)0)
{
MessageBox.Show("未链接到控制器!", "提示");
}
else
{
float[] array = new float[3];
array[0] = Convert.ToSingle(array_set4.Text);
array[1] = Convert.ToSingle(array_set5.Text);
array[2] = Convert.ToSingle(array_set6.Text);
zmcaux.ZAux_Direct_SetUserArray(g_handle, "array_para", 5, 3, array); //发送数据
float cmd = 2;
zmcaux.ZAux_Direct_SetUserVar(g_handle, "cmd_data", cmd); //发送命令
}
}
private void button1_Click(object sender, EventArgs e) //直线插补0
{
if (g_handle == (IntPtr)0)
{
MessageBox.Show("未链接到控制器!", "提示");
}
else
{
float[] array =new float[3] ;
array[0] = Convert.ToSingle(array_set1.Text);
array[1] = Convert.ToSingle(array_set2.Text);
array[2] = Convert.ToSingle(array_set3.Text);
zmcaux.ZAux_Direct_SetUserArray(g_handle,"array_para",0,3,array); //发送数据
float cmd = 1;
zmcaux.ZAux_Direct_SetUserVar(g_handle, "cmd_data", cmd); //发送命令
}
}
9.通过坐标清零按钮的事件处理函数来重置当前运动坐标位置。
//清零坐标
private void Button_zero_Click(object sender, EventArgs e)
{
if ((int)g_handle == 0)
{
MessageBox.Show("未链接到控制器!", "提示");
}
else
{
for (int i = 0; i < 4; i++)
{
zmcaux.ZAux_Direct_SetDpos(g_handle, i, 0);
}
}
}
10.获取全局变量、数组内容并显示。
private void button_read1_Click(object sender, EventArgs e) //读取全局数组值与变量
{
int ret = 0;
if (g_handle == (IntPtr)0)
{
MessageBox.Show("未链接到控制器!", "提示");
}
else
{
float[] m_ArrayShow = new float[10];
float cmd_data = 0;
ret = zmcaux.ZAux_Direct_GetUserVar(g_handle, "cmd_data", ref cmd_data); //读取全局变量
ret += zmcaux.ZAux_Direct_GetUserArray(g_handle, "array_para", 0, 9, m_ArrayShow); //读取全局数组,注意BASIC定义的数组最好比PC端实际数据大1,BASIC数组最后一个元素访问时会越界
if (ret == 0)
{
m_cmd.Text = cmd_data.ToString();
array_get1.Text = m_ArrayShow[0].ToString();
array_get2.Text = m_ArrayShow[1].ToString();
array_get3.Text = m_ArrayShow[2].ToString();
array_get4.Text = m_ArrayShow[5].ToString();
array_get5.Text = m_ArrayShow[6].ToString();
array_get6.Text = m_ArrayShow[7].ToString();
}
}
}
11.Modbus寄存器读取写入
private void button_read3_Click(object sender, EventArgs e) //MODBUS读取
{
int ret = 0;
if (g_handle == (IntPtr)0)
{
MessageBox.Show("未链接到控制器!", "提示");
return;
}
ushort m_ModbusStart = Convert.ToUInt16(modbus_start.Text);
if (radioButton1.Checked) //BIT
{
byte[] pdata = new byte[1];
ret = zmcaux.ZAux_Modbus_Get0x(g_handle, m_ModbusStart, 4, pdata); //pdata按位存储MODBUS_BIT
modbus_get1.Text = (pdata[0] & 0x01).ToString();
modbus_get2.Text = (pdata[0]>>1 & 0x01).ToString();
modbus_get3.Text = (pdata[0]>> 2 & 0x01).ToString();
modbus_get4.Text = (pdata[0]>>3 & 0x01).ToString();
}
else if (radioButton2.Checked) //REG
{
UInt16[] pdata = new UInt16[4];
ret = zmcaux.ZAux_Modbus_Get4x(g_handle, m_ModbusStart, 4, pdata);
modbus_get1.Text = pdata[0].ToString();
modbus_get2.Text = pdata[1].ToString();
modbus_get3.Text = pdata[2].ToString();
modbus_get4.Text = pdata[3].ToString();
}
else if (radioButton3.Checked) //LONG
{
int[] pdata = new int[4];
ret = zmcaux.ZAux_Modbus_Get4x_Long(g_handle, m_ModbusStart, 4, pdata);
modbus_get1.Text = pdata[0].ToString();
modbus_get2.Text = pdata[1].ToString();
modbus_get3.Text = pdata[2].ToString();
modbus_get4.Text = pdata[3].ToString();
}
else if (radioButton4.Checked) //IEEE
{
float[] pdata = new float[4];
ret = zmcaux.ZAux_Modbus_Get4x_Float(g_handle, m_ModbusStart, 4, pdata);
modbus_get1.Text = pdata[0].ToString();
modbus_get2.Text = pdata[1].ToString();
modbus_get3.Text = pdata[2].ToString();
modbus_get4.Text = pdata[3].ToString();
}
}
private void button_write3_Click(object sender, EventArgs e) //MODBUS写入
{
int ret = 0;
if (g_handle == (IntPtr)0)
{
MessageBox.Show("未链接到控制器!", "提示");
return;
}
ushort m_ModbusStart = Convert.ToUInt16(modbus_start.Text);
if (radioButton1.Checked) //BIT
{
float[] idata = new float[4];
idata[0] = Convert.ToSingle(modbus_set1.Text);
idata[1] = Convert.ToSingle(modbus_set2.Text);
idata[2] = Convert.ToSingle(modbus_set3.Text);
idata[3] = Convert.ToSingle(modbus_set4.Text);
byte[] pdata = new byte[1];
pdata[0] = (byte)0;
for(int i =0;i<4;i++)
{
if (idata[i] != 0)
{
pdata[0] += (byte)(0x1 << i );
}
}
ret = zmcaux.ZAux_Modbus_Set0x(g_handle, m_ModbusStart, 4, pdata); //pdata按位存储MODBUS_BIT
}
else if (radioButton2.Checked) //REG
{
UInt16[] pdata = new UInt16[4];
pdata[0] = Convert.ToUInt16(modbus_set1.Text);
pdata[1] = Convert.ToUInt16(modbus_set2.Text);
pdata[2] = Convert.ToUInt16(modbus_set3.Text);
pdata[3] = Convert.ToUInt16(modbus_set4.Text);
ret = zmcaux.ZAux_Modbus_Set4x(g_handle, m_ModbusStart, 4, pdata);
}
else if (radioButton3.Checked) //LONG
{
int[] pdata = new int[4];
pdata[0] = Convert.ToInt32(modbus_set1.Text);
pdata[1] = Convert.ToInt32(modbus_set2.Text);
pdata[2] = Convert.ToInt32(modbus_set3.Text);
pdata[3] = Convert.ToInt32(modbus_set4.Text);
ret = zmcaux.ZAux_Modbus_Set4x_Long(g_handle, m_ModbusStart, 4, pdata);
}
else if (radioButton4.Checked) //IEEE
{
float[] pdata = new float[4];
pdata[0] = Convert.ToSingle(modbus_set1.Text);
pdata[1] = Convert.ToSingle(modbus_set2.Text);
pdata[2] = Convert.ToSingle(modbus_set3.Text);
pdata[3] = Convert.ToSingle(modbus_set4.Text);
ret = zmcaux.ZAux_Modbus_Set4x_Float(g_handle, m_ModbusStart, 4, pdata);
}
}
12.VR寄存器读取写入
private void button_read4_Click(object sender, EventArgs e) //VR读取
{
int ret = 0;
if (g_handle == (IntPtr)0)
{
MessageBox.Show("未链接到控制器!", "提示");
return;
}
ushort m_VrStart = Convert.ToUInt16(vr_start.Text);
float[] m_VrShow = new float[4];
ret = zmcaux.ZAux_Direct_GetVrf(g_handle, m_VrStart, 4, m_VrShow);
vr_get1.Text = m_VrShow[0].ToString();
vr_get2.Text = m_VrShow[1].ToString();
vr_get3.Text = m_VrShow[2].ToString();
vr_get4.Text = m_VrShow[3].ToString();
}
private void button_write4_Click(object sender, EventArgs e) //VR写入
{
int ret = 0;
if (g_handle == (IntPtr)0)
{
MessageBox.Show("未链接到控制器!", "提示");
return;
}
ushort m_VrStart = Convert.ToUInt16(vr_start.Text);
float[] m_VrSet = new float[4];
m_VrSet[0] = Convert.ToSingle(vr_set1.Text);
m_VrSet[1] = Convert.ToSingle(vr_set2.Text);
m_VrSet[2] = Convert.ToSingle(vr_set3.Text);
m_VrSet[3] = Convert.ToSingle(vr_set4.Text);
ret = zmcaux.ZAux_Direct_SetVrf(g_handle, m_VrStart, 4, m_VrSet);
}
13.Table寄存器读取写入
private void button_read5_Click(object sender, EventArgs e) //TABLE读取
{
int ret = 0;
if (g_handle == (IntPtr)0)
{
MessageBox.Show("未链接到控制器!", "提示");
return;
}
ushort m_TableStart = Convert.ToUInt16(table_strat.Text);
float[] m_TableShow = new float[4];
ret = zmcaux.ZAux_Direct_GetTable(g_handle, m_TableStart, 4, m_TableShow);
table_get1.Text = m_TableShow[0].ToString();
table_get2.Text = m_TableShow[1].ToString();
table_get3.Text = m_TableShow[2].ToString();
table_get4.Text = m_TableShow[3].ToString();
}
private void button_write5_Click(object sender, EventArgs e) //TABLE写入
{
int ret = 0;
if (g_handle == (IntPtr)0)
{
MessageBox.Show("未链接到控制器!", "提示");
return;
}
ushort m_TableStart = Convert.ToUInt16(table_strat.Text);
float[] m_TableSet = new float[4];
m_TableSet[0] = Convert.ToSingle(table_set1.Text);
m_TableSet[1] = Convert.ToSingle(table_set2.Text);
m_TableSet[2] = Convert.ToSingle(table_set3.Text);
m_TableSet[3] = Convert.ToSingle(table_set4.Text);
ret = zmcaux.ZAux_Direct_SetTable(g_handle, m_TableStart, 4, m_TableSet);
}
(四)调试与监控
编译运行例程,同时连接ZDevelop软件进行调试,对运动控制的轴参数和运动情况进行监控。
通过IO监控部分,可以明确的观察到对应的哪个in口在目前状态下有信号,判断触发的信号输入位置(由于仿真器观察更加方便,此处以仿真器的输入显示为例)。
通过寄存器的读取写入可以在ZDevelop软件上的视图→寄存器视图→选择对应的寄存器以及对应地址进行观察到对应寄存器是否进行改变,如图:
如调用动作直线插补0将界面上数据下发到下位机全局数组中,利用全局变量内容调用指定运动运动全局数组指定距离对应运动,可进入ZDevelop的调试→启动调试→监视界面,可以进行输入需要监控的全局变量或者数组的对应索引,查看变量的实时变化,调用的对应动作。如图所示:
视频地址:http://www.zmotion.com.cn/video/实时性程序的运行和读写控制.mp4
本次,正运动技术EtherCAT运动控制卡实时程序的运行和读写控制,就分享到这里。