SylixOS EtherCAT 开发
本节主要介绍在 IGC 系列控制器上如何使用 SylixOS-IgH 对 EtherCAT 伺服电机进行控制。
环境准备
设备列表
序号 | 设备名称 | 数量 | 备注 |
---|---|---|---|
1 | IGC1503 控制器 | 1 | 必选 |
2 | EtherCAT 伺服电机系统 | 1 | 必选[1 个伺控制器 + 1 个伺服电机] |
3 | 交换机 | 1 | 可选 |
说明:
- 本节例程的代码适用于 IGC 系列控制器的 IGC1500、IGC1503。
- EtherCAT 伺服电机系统,本例使用 1 套汇川 EtherCAT 伺服电机系统(1 个 SV660NS2R8I 型伺服控制器和 1 台 400W 伺服电机)。
软件列表
序号 | 软件名称 | 数量 | 备注 |
---|---|---|---|
1 | RealEvo-IDE | 1 | 必选(版本需大于 5.0.4) |
2 | PuTTY 终端工具下载 | 1 | 必选 |
示例工程列表
序号 | 名称 | 数量 | 说明 |
---|---|---|---|
1 | app_igh_user | 1 | SylixOS 使用开源 IgH EtherCAT 主站控制伺服电机运转。 |
下载列表
前提条件
通过“入门开发”中 SylixOS Modbus 开发 教程,熟练掌握 SylixOS 开发环境 RealEvo-IDE 的基本使用方法。如:新建工程,添加依赖文件及依赖路径,配置目标设备和工程编译运行等操作。
控制电机转动
案例使用 SylixOS-IgH 进行应用开发,对单个电机进行 CSP 模式控制,实际效果完成对单个 EtherCAT 伺服电机的匀速运动控制。
说明:
- CSP 模式:Cyclic sync position mode 周期同步位置模式。
- 周期旋转幅度与编码器位数相关,例程默认步进值为 30000。
步骤 1:连接硬件设备
准备一台 IGC1503 控制器,一根网线。网线一端接入电脑,另一端接入控制器 X1 网口,网口默认 IP 为 192.168.1.253。
准备一套 EtherCAT 伺服电机系统,一根网线或 EtherCAT 专用网线。网线一端接入 IGC1503 控制器网口 X2,另一端接入伺服控制器用于 EtherCAT 通讯的 IN 端口。
IGC1503 控制器和伺服电机系统上电。
步骤 2:创建 Base 工程
参考“入门开发 > SylixOS Modbus 开发”中 步骤 4 操作,完成 Base 工程的创建。
步骤 3:创建 IgH 应用工程
打开 RealEvo-IDE 软件,选择主菜单 > File > New > SylixOS App,创建 app_igh_user 工程。在 Project name 中输入工程名称,如 app_igh_user,单击 Next。
单击 Workspace 选择依赖的 base 工程,选择后单击 Finish 完成应用工程创建。
步骤 4:添加应用工程依赖
复制 libigh.rar 解压后的文件夹内容,放入新建的 app_igh_user 工程中。整体结构如图:
添加 IgH App 工程依赖头文件路径。
添加 IgH App 工程依赖库文件。
配置依赖库文件路径。
步骤 5:编写 IgH 控制代码
编写 app_igh_user 工程的 src 文件夹下的 app_igh_user.c。包含系统 C 库头文件、IgH 主站协议栈以及 ecatdc 同步库头文件。
#include <sched.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include "ecrt.h" #include "ecatdc.h" #include "struct.h"
app_igh_user.c:配置从站位置、偏移、厂商 ID、产品 ID。
说明:
- 由于只有一个电机,所以 alias,position 均为 0。
- vendor_id 以及 product_id 每个电机不同,需要根据实际进行配置,此处使用的是汇川伺服 SV660NS2R8I 型号。
/* * ethercat slave info(alias, position) */ #define SLAVE_POS_1 0,0 /* * vendor_id product_id */ #define VID_PID 0x00100000, 0x000c010d
app_igh_user.c:配置实时任务绑核参数、电机运行模式以及步进值。
IGC1500 以及 IGC1503 均是四核 CPU,0 核默认为系统服务以及中断处理核,实时任务可以绑定到 1-3 核,此处为 3 核。
不同电机支持的模式不同,但基本都支持 CSP 模式,此案例需要电机进入 CSP 模式。
电机步进值,此处要结合电机编码器位数来实际编程,过大会导致电机转速过快报错。
/* * cycle task bind core num */ #define BIND_CORE (3) /* * Motor Operation mode define */ #define MOTOR_OPERATION_MODE OP_CSP_MODE /* * Motor move step */ #define MOTOR_INCREASE_STEP (30000) /* * max slave num */ #define SLAVE_MOTOR_POS_MAX (1)
在 app_igh_user 工程的 src 文件夹下新建 struct.h 头文件并对控制模式部分模块进行宏定义。
#include <stdint.h> #define OP_PP_MODE (0x01) #define OP_PV_MODE (0x03) #define OP_TP_MODE (0x04) #define OP_CSP_MODE (0x08) #define OP_CSV_MODE (0x09)
ECAT 标准文档《Cia-402-2》上关于控制模式配置字 0x6060 的模式配置说明:
app_igh_user.c:定义所需要的 IgH 全局变量。
/* * EtherCAT * igh ethercat master ptr */ static ec_master_t *master = NULL; /* * domain data */ static ec_master_state_t master_state = {}; static ec_domain_t *domain1_output = NULL; static ec_domain_state_t domain1_output_state = {}; static ec_domain_t *domain1_input = NULL; static ec_domain_state_t domain1_input_state = {}; static uint8_t *domain1_output_pd = NULL; static uint8_t *domain1_input_pd = NULL; /* * slave config */ static ec_slave_config_t *sc[SLAVE_MOTOR_POS_MAX]; static ec_slave_config_state_t sc_state[SLAVE_MOTOR_POS_MAX] = {}; /* * offset info */ static offset pdo_offset[SLAVE_MOTOR_POS_MAX]; /* * communication cycle */ static unsigned int cycle_ns = 1000000; /* * user global var */ static volatile tTxPDO driverData[SLAVE_MOTOR_POS_MAX]; static volatile uint32_t uiDirection = 0; static volatile uint32_t uiMoveStep = MOTOR_INCREASE_STEP; static BOOL bRun = TRUE; static BOOL bResetDone = FALSE; static uint32_t uiResetCounter = 0;
app_igh_user.c:配置 PDO 映射表。
说明:
这里只映射 ECAT 电机的基本条目,具体可参考电机使用手册或者 《Cia-402-2》。
/* * RXPDO * index sub index bit size describe * 0x607a 0x00 32 target position * 0x6040 0x00 16 control word * 0x6060 0x00 8 mode of operation * * TXPDO * index sub index bit size describe * 0x6064 0x00 32 actual position * 0x6041 0x00 16 status word * 0x606c 0x00 32 actual velocity * 0x6061 0x00 8 sidplay mode of operation * 0x603f 0x00 16 error code * 0x6077 0x00 16 actual torque */ static ec_pdo_entry_info_t device_pdo_entries[] = { {0x607a, 0x00, 32}, {0x6040, 0x00, 16}, {0x6060, 0x00, 8}, {0x6064, 0x00, 32}, {0x6041, 0x00, 16}, {0x606c, 0x00, 32}, {0x6061, 0x00, 8}, {0x603f, 0x00, 16}, {0x6077, 0x00, 16}, }; static ec_pdo_info_t device_pdos[] = { {0x1600, 3, device_pdo_entries + 0}, {0x1a00, 6, device_pdo_entries + 3}, };
struct.h: 定义本地 PDO 数据结构体以用于数据映射:
说明:
- #pragma pack() 使编译器取消对齐。
- #pragma pack(1) 使编译器 1 字节对齐。
/* * Process Data,Offsets for PDO entries */ #pragma pack(4) typedef struct { unsigned int target_pos; unsigned int ctrl_word; unsigned int operation_mode; unsigned int actual_pos; unsigned int status_word; unsigned int actual_velocity; unsigned int display_mode; unsigned int error_code; unsigned int actual_torque; }offset; #pragma pack() #pragma pack(1) typedef struct _TxPDO { int32_t actual_pos; uint16_t status_word; int32_t actual_speed; int8_t display_mode; uint16_t error_code; int16_t actual_torque; }tTxPDO; #pragma pack()
app_igh_user.c:配置 Sync Manager 设备 PDO 信息。
static ec_sync_info_t device_syncs[] = { {0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE}, {1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE}, {2, EC_DIR_OUTPUT, 1, device_pdos + 0, EC_WD_ENABLE}, {3, EC_DIR_INPUT, 1, device_pdos + 1, EC_WD_DISABLE}, {0xff} };
app_igh_user.c:进行 domain 数据域挂载,对设备端 PDO 以及本地内存进行映射。
const static ec_pdo_entry_reg_t domain1_output_regs[] = { {SLAVE_POS_1, VID_PID, 0x607a, 0, &pdo_offset[0].target_pos}, {SLAVE_POS_1, VID_PID, 0x6040, 0, &pdo_offset[0].ctrl_word}, {SLAVE_POS_1, VID_PID, 0x6060, 0, &pdo_offset[0].operation_mode}, {} }; const static ec_pdo_entry_reg_t domain1_input_regs[] = { {SLAVE_POS_1, VID_PID, 0x6064, 0, &pdo_offset[0].actual_pos}, {SLAVE_POS_1, VID_PID, 0x6041, 0, &pdo_offset[0].status_word}, {SLAVE_POS_1, VID_PID, 0x606c, 0, &pdo_offset[0].actual_velocity}, {SLAVE_POS_1, VID_PID, 0x6061, 0, &pdo_offset[0].display_mode}, {SLAVE_POS_1, VID_PID, 0x603f, 0, &pdo_offset[0].error_code}, {SLAVE_POS_1, VID_PID, 0x6077, 0, &pdo_offset[0].actual_torque}, {} };
app_igh_user.c:所使用到的函数提前声明,具体实现在后续步骤。
/* * function declarations */ static void exit_proc (); static int ecMasterCycTaskCreate (void *arg); static void *taskCyclicRefSlave (VOID *pArg); static void check_domain_state (void); static void check_master_state (void); static void cyclic_task_data_proc (uint8_t slave); static void reset_task (uint8_t slave); static void cyclic_task (uint8_t slave);
app_igh_user.c:编写主站启动以及配置从站基本流程代码。
int main(int argc, char **argv) { /* * set ctrl+C action: exit IgH master request */ atexit(exit_proc); /* * request igh ethercat master instance */ master = ecrt_request_master(0); if (!master) { printf("request master error\r\n"); return (-1); } /* * get bus info */ ec_master_info_t ecMasterInfo; if ( 0 == ecrt_master(master, &ecMasterInfo) ){ printf("Bus scan successful - %d slaves.\n", ecMasterInfo.slave_count); } /* * show slave info */ for (unsigned int i = 0; i < ecMasterInfo.slave_count; i++) { ec_slave_info_t slave_info; ecrt_master_get_slave ( master, 0, &slave_info ); printf ( "slave[%d]-- vendor_id:0x%04x, product_code:0x%04x \n",\ i, slave_info.vendor_id, slave_info.product_code ); } /* * create output domain */ domain1_output = ecrt_master_create_domain(master); if (!domain1_output) { printf("[%s]Failed to create domain1_output data pointer.\n" , __func__ ); return (-1); } /* * create input domain */ domain1_input = ecrt_master_create_domain(master); if (!domain1_input) { printf("[%s]Failed to create domain1_input data pointer.\n" , __func__ ); return (-1); } /* * config slave */ for(int i = 0; i < SLAVE_MOTOR_POS_MAX ; i++) { if (!(sc[i] = ecrt_master_slave_config(master, 0, i, VID_PID))) { printf("[%s]Failed to get slave configuration for slave %d!\n" , __func__ ,i ); return (-1); } } /* * config slaves pdos */ for (int i = 0; i < SLAVE_MOTOR_POS_MAX ; i++) { if (ecrt_slave_config_pdos(sc[i], EC_END, device_syncs)) { printf("[%s]Failed to configure slave PDOs,slave %d!\n" , __func__ ,i ); return (-1); } } /* * register master output domain */ if (ecrt_domain_reg_pdo_entry_list(domain1_output, domain1_output_regs)) { printf("[%s]output PDO entry registration failed!\n" , __func__ ); return (-1); } /* * register master input domain */ if (ecrt_domain_reg_pdo_entry_list(domain1_input, domain1_input_regs)) { printf("[%s]input PDO entry registration failed!\n" , __func__ ); return (-1); } /* * dc sync init */ setSlaveDcRef(master, sc[0]); for(int i = 0; i < SLAVE_MOTOR_POS_MAX ; i++) { ecrt_slave_config_dc(sc[i], 0x0300, cycle_ns , cycle_ns / 2, 0, 0); } /* * activate igh ethercat master */ if (ecrt_master_activate(master)) { return (-1); } else { printf("Activating master succes!\n"); } /* * get domain memory address */ if (!(domain1_output_pd = ecrt_domain_data(domain1_output))) { printf("[%s]Failed to get domain_output data pointer.\n" , __func__); return (-1); } if (!(domain1_input_pd = ecrt_domain_data(domain1_input))) { printf("[%s]Failed to get domain_input data pointer.\n" , __func__); return (-1); } /* * reset user data */ memset(driverData, 0, sizeof(driverData)); /* * create real time thread */ ecMasterCycTaskCreate(NULL); while (bRun) { /* * print process status */ volatile tTxPDO *data = &driverData[0]; char param[1024] = {0}; sprintf(param, "status_word:%x mode:%x actual_pos:%d actual_speed %d error_code:%x actual_torque:%d\r\n", data->status_word, data->display_mode, data->actual_pos, data->actual_speed, data->error_code, data->actual_torque); printf(param); sleep(2); } ecrt_master_deactivate(master); return 0; }
app_igh_user.c:退出函数 exit_proc 代码。
说明:
当用户使用 kill 方式或者 CTRL + C 方式退出程序时调用该接口进行释放主站资源。
static void exit_proc() { bRun = 0; ecrt_master_deactivate(master); printf("exit!\n"); }
app_igh_user.c:创建 ECAT 实时收发任务。
INT ecMasterCycTaskCreate (void *arg) { INT iRet = 0; pthread_attr_t RtAttr = {0}; pthread_t RtTid = 0; pthread_attr_init(&RtAttr); RtAttr.schedparam.sched_priority = 255 - 15; pthread_attr_setname(&RtAttr, "ec_cyclic_task"); iRet = pthread_create(&RtTid, &RtAttr, taskCyclicRefSlave, (void *)arg); if (iRet != ERROR_NONE) { return (PX_ERROR); } return (ERROR_NONE); } VOID *taskCyclicRefSlave (VOID *pArg) { printf("Cycle Thread Started.\n"); (void)pArg; cpu_set_t cpuset = {{0}}; INT iRet = 0; /* * 设置线程CPU亲和性 */ CPU_ZERO(&cpuset); CPU_SET(BIND_CORE, &cpuset); iRet = pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); if (iRet != ERROR_NONE) { printf("cycecMastertask bind cpu core failed.\n"); return (NULL); } /* * 设置CPU强绑核 */ CPU_ZERO(&cpuset); if (sched_cpuaffinity_get_np(sizeof(LW_CLASS_CPUSET), &cpuset)) { printf("CPU strongly affinity schedule get fail: %s.\n", lib_strerror(errno)); return (NULL); } if (!CPU_ISSET(BIND_CORE, &cpuset)) { CPU_SET(BIND_CORE, &cpuset); if (sched_cpuaffinity_set_np(sizeof(LW_CLASS_CPUSET), &cpuset)) { printf("CPU strongly affinity schedule set fail: %s.\n", lib_strerror(errno)); return (NULL); } } /* * 设置周期任务启动时间 */ setDcStartTime(master, cycle_ns); printf("start run! Move step %d\n", uiMoveStep); while (bRun) { /* * 周期调度 */ waitPeriod(master, cycle_ns); ecrt_master_receive(master); ecrt_domain_process(domain1_output); ecrt_domain_process(domain1_input); check_domain_state(); check_master_state(); if (!bResetDone) { reset_task(0); } else { cyclic_task(0); } ecrt_domain_queue(domain1_output); ecrt_domain_queue(domain1_input); ecrt_master_async_data(master); syncDistributedClocks(master); ecrt_master_send(master); updateMasterClock(); } printf("Cycle Thread Exit.\n"); return (NULL); }
app_igh_user.c:Domain 状态监测函数 check_domain_state() 代码。
static void check_domain_state (void) { ec_domain_state_t ds = {}; ec_domain_state_t ds1 = {}; ecrt_domain_state(domain1_output, &ds); if (ds.working_counter != domain1_output_state.working_counter) { printf("Domain1_output: WC %u.\n", ds.working_counter); } if (ds.wc_state != domain1_output_state.wc_state) { printf("Domain1_output: State %u.\n", ds.wc_state); } domain1_output_state = ds; ecrt_domain_state(domain1_input, &ds1); if (ds1.working_counter != domain1_input_state.working_counter) { printf("Domain1_input: WC %u.\n", ds.working_counter); } if (ds1.wc_state != domain1_input_state.wc_state) { printf("Domain1_input: State %u.\n", ds.wc_state); } domain1_input_state = ds1; }
app_igh_user.c:主站状态检测函数 check_master_state() 代码。
static void check_master_state (void) { ec_master_state_t ms; ecrt_master_state(master, &ms); if (ms.slaves_responding != master_state.slaves_responding) { printf("%u slave(s).\n", ms.slaves_responding); } if (ms.al_states != master_state.al_states) { printf("AL states: 0x%02X.\n", ms.al_states); } if (ms.link_up != master_state.link_up) { printf("Link is %s.\n", ms.link_up ? "up" : "down"); } master_state = ms; }
app_igh_user.c:周期拷贝过程数据函数 cyclic_task_data_proc() 代码。
static void cyclic_task_data_proc (uint8_t slave) { tTxPDO *data = &driverData[slave]; static uint16_t errorCode[SLAVE_MOTOR_POS_MAX]; if(data == NULL) return ; memcpy((uint8_t *)data, domain1_input_pd, sizeof(tTxPDO)); if(errorCode[slave] != EC_READ_U16(domain1_input_pd + pdo_offset[slave].error_code)) { printf("slave %d errorCode %X , last:%X...\r\n",slave , EC_READ_U16(domain1_input_pd + pdo_offset[slave].error_code), errorCode[slave]); errorCode[slave] = EC_READ_U16(domain1_input_pd + pdo_offset[slave].error_code); } }
app_igh_user.c:程序启动后,根据周期数来使用控制字对电机进行消除故障并复位,复位函数代码 reset_task() 如下所示。
void reset_task (uint8_t slave) { int32_t target_pos = 0; uint16_t status = 0; uiResetCounter++; /* * 根据时序周期进行复位电机并最终使能到 OP 状态 */ switch(uiResetCounter) { case RESETC_CLEAR : EC_WRITE_S8(domain1_output_pd + pdo_offset[slave].ctrl_word, CW_STATUS_CLEAR); break; case RESETC_FAULT : EC_WRITE_S8(domain1_output_pd + pdo_offset[slave].ctrl_word, CW_RESET_FAULT); break; case RESETC_SHUTDOWN : EC_WRITE_S8(domain1_output_pd + pdo_offset[slave].ctrl_word, CW_SHUT_DOWN); break; case RESETC_SWITCHON : EC_WRITE_S8(domain1_output_pd + pdo_offset[slave].ctrl_word, CW_SWITCH_ON); break; case RESETC_ENABLEOP : EC_WRITE_S8(domain1_output_pd + pdo_offset[slave].ctrl_word, CW_ENABLE_OP); EC_WRITE_S8(domain1_output_pd + pdo_offset[slave].operation_mode, MOTOR_OPERATION_MODE); target_pos = EC_READ_S32(domain1_input_pd + pdo_offset[slave].actual_pos); EC_WRITE_S32(domain1_output_pd + pdo_offset[slave].target_pos, target_pos); break; case RESETC_DONE : status = EC_READ_U16(domain1_input_pd + pdo_offset[slave].status_word); if ( (status & SW_OP_ENABLE) == SW_OP_ENABLE) { bResetDone = TRUE; printf("reset done !\n"); } else { bResetDone = FALSE; uiResetCounter = 0; printf("reset false , try to reset again !\n"); } break; } }
struct.h:复位周期点枚举类 ENUM_RESET 定义。
typedef enum { RESETC_CLEAR = 100, RESETC_FAULT = 1000, RESETC_SHUTDOWN = 2000, RESETC_SWITCHON = 3000, RESETC_ENABLEOP = 4000, RESETC_DONE = 4500, } ENUM_RESET;
struct.h:控制字 0x6040 操作码宏定义。
#define CW_STATUS_CLEAR (0x00) #define CW_SHUT_DOWN (0x06) #define CW_SWITCH_ON (0x07) #define CW_ENABLE_OP (0x0F) #define CW_RESET_FAULT (0x80)
ECAT 标准文档 《Cia-402-2》上关于控制字 0x6040 的操作说明:
struct.h:状态字 0x6041 编码部分宏定义。
#define SW_STATUS_MASK (0XF) #define SW_SWITCHON_DISABLE (0X40) #define SW_OP_ENABLE (0X27) #define SW_STATUS_FAULT (0x8)
ECAT 标准文档《Cia-402-2》上关于状态字 0x6041 的编码说明:
app_igh_user.c:用户控制逻辑代码 cyclic_task() 编写。
说明:
控制程序每周期读取当前实际位置,并以此为基准增加固定的步进值 uiMoveStep,最后写入到目标位置 PDO 0x607a,实现控制电机 CSP 模式转动。
void cyclic_task (uint8_t slave) { uint16_t status = 0; int32_t current_pos = 0, target_pos = 0; cyclic_task_data_proc(slave); status = EC_READ_U16(domain1_input_pd + pdo_offset[slave].status_word); current_pos = EC_READ_S32(domain1_input_pd + pdo_offset[slave].actual_pos); if ( (status & SW_OP_ENABLE) == SW_OP_ENABLE) { switch (uiDirection) { case 0: target_pos = current_pos + uiMoveStep; break; case 1: target_pos = current_pos - uiMoveStep; break; default: break; } EC_WRITE_S32(domain1_output_pd + pdo_offset[slave].target_pos, target_pos); } else { EC_WRITE_U16(domain1_output_pd + pdo_offset[slave].ctrl_word, CW_SHUT_DOWN); bResetDone = FALSE; } }
完整 app_igh_user 工程请参考 app_igh_user。
最终的工程文件结构
步骤 6:下载部署应用程序
上传到设备端的文件清单如下:
上传文件 | 说明 | 上传路径 |
---|---|---|
/libigh/ec_master.ko | ECAT 主站协议栈 | /lib/modules/ec_master.ko |
/libigh/ethercat | ECAT 命令行工具 | /usr/bin/ethercat |
/libigh/rt_netcard.ko | ECAT 实时网卡驱动 | /lib/modules/rt_netcard.ko |
/Release/strip/app_igh_user | ECAT 应用程序 | /apps/app_igh_user/app_igh_user (app 工程创建默认已添加) |
添加上述列表上传路径,最终如图所示:
右击 app_igh_user 工程,选择 SylixOS > Upload,上传工程文件。
步骤 7:配置 IgH 实时网卡
打开 PuTTY 工具,单击 Session 选项,选中 Telnet,输入 IP,输入端口,单击 Open,登录 IGC1503 控制器。
使用 ifconfig 命令查看当前板卡网络接口配置。
[root@sylixos:/etc]# ifconfig _X2 Link encap: Ethernet HWaddr: 02:81:01:6e:98:96 Dev: gmac Ifidx: 3 DHCP: D4 D6 Spd: N/A inet addr: 10.4.0.230 netmask: 255.255.0.0 gateway: 10.4.0.1 broadcast: 10.4.255.255 inet6 addr: fe80::81:1ff:fe6e:9896 Scope:Link<T0> UP BROADCAST MULTICAST MTU:1500 Metric:1 collisions:0 txqueue:0 tcpaf:2 tcpwnd:65535 RX ucast packets:0 nucast packets:0 dropped:0 TX ucast packets:0 nucast packets:0 dropped:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) _X1 Link encap: Ethernet HWaddr: 02:82:01:6e:98:96 Dev: emac Ifidx: 2 DHCP: D4 D6 Spd: 100 Mbps inet addr: 192.168.1.253 netmask: 255.255.255.0 gateway: 192.168.1.1 broadcast: 192.168.1.255 inet6 addr: fe80::82:1ff:fe6e:9896 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 collisions:0 txqueue:0 tcpaf:2 tcpwnd:65535 RX ucast packets:77 nucast packets:30049 dropped:0 TX ucast packets:63 nucast packets:10 dropped:0 RX bytes:2428933 (2.3 MB) TX bytes:7192 (7.0 KB) lo0 Link encap: Local Loopback Dev: N/A Ifidx: 1 DHCP: D4 D6 Spd: N/A inet addr: 127.0.0.1 netmask: 255.0.0.0 P-to-P: 127.0.0.1 broadcast: N/A inet6 addr: ::1 Scope:Loopback UP LOOPBACK RUNNING MTU:0 Metric:1 collisions:0 txqueue:0 tcpaf:2 tcpwnd:262140 RX ucast packets:8 nucast packets:0 dropped:0 TX ucast packets:8 nucast packets:0 dropped:0 RX bytes:468 (468.0 B) TX bytes:468 (468.0 B) dns0: 0.0.0.0 dns1: 0.0.0.0 dns2: 0.0.0.0 dns3: 0.0.0.0 default device is: null list net interface: 3
配置 /etc/ethercat_cfg.ini 文件,明确 IgH 使用的通讯网口。
说明:
- Dev 可以配置的网络设备名为 ifconfig 命令显示的 _X2:Dev:gmac _X1:Dev:emac。
- 这里请配置使用 gmac 口作为 ECAT 通讯口。
[ethercat] Dev=gmac
步骤 8:加载 IgH 驱动模块
加载 IgH 主站协议栈模块。
[root@sylixos:/]# insmod /lib/modules/ec_master.ko use MAC addr: 02:81:01:6e:98:96 module /lib/modules/ec_master.ko register ok, handle: 0x43ea0d10
加载 IgH 实时网卡驱动模块。
[root@sylixos:/]# insmod /lib/modules/rt_netcard.ko use Dev: gmac EtherCAT port. module /lib/modules/rt_netcard.ko register ok, handle: 0x4400f050
使用 ethercat 命令行工具查看主站状态。
[root@sylixos:/]# ethercat master Master0 Phase: Idle Active: no Slaves: 1 Ethernet devices: Main: 02:81:01:6e:98:96 (attached) Link: UP Tx frames: 922981 Tx bytes: 78044260 Rx frames: 922980 Rx bytes: 78044200 Tx errors: 7998 Tx frame rate [1/s]: 1000 1000 1000 Tx rate [KByte/s]: 58.6 69.3 80.1 Rx frame rate [1/s]: 1000 1000 1000 Rx rate [KByte/s]: 58.6 69.3 80.1 Common: Tx frames: 922981 Tx bytes: 78044260 Rx frames: 922980 Rx bytes: 78044200 Lost frames: 0 Tx frame rate [1/s]: 1000 1000 1000 Tx rate [KByte/s]: 58.6 69.3 80.1 Rx frame rate [1/s]: 1000 1000 1000 Rx rate [KByte/s]: 58.6 69.3 80.1 Loss rate [1/s]: 0 0 0 Frame loss [%]: 0.0 0.0 0.0 Distributed clocks: Reference clock: Slave 0 DC reference time: 0 Application time: 0 2000-01-01 00:00:00.000000000
使用 ethercat 命令行工具查看从站状态及设备名。
[root@sylixos:/]# ethercat slaves 0 0:0 PREOP + SV660_1Axis_00913
步骤 9:查看运行效果
进入“/apps/app_igh_user”目录,输入“./app_igh_user”命令运行程序。当程序正常运行,周期打印状态时,可以看到电机正在匀速同向转动。
注意:
如果电机未正常转动,请确认连接的电机设备 PID、VID 是否和代码中设置的一致。 程序运行后,终端会通过“vendor_id:0x1xxxxx,product_code:0xxxxx”打印信息显示实际连接电机的 PID、VID。用户需暂停并退出当前运行程序,将真实的电机 PID 和 VID 配置到 步骤 5 第 2 步的“VID_PID”宏参。之后,再次将工程重新编译、上传,执行运行命令。
[root@sylixos:/]# cd /apps/app_igh_user
[root@sylixos:/apps/app_igh_user]# ./app_igh_user
Bus scan successful - 1 slaves.
slave[0]-- vendor_id:0x100000, product_code:0xc010d
EtherCAT WARNING 0: 2 datagrams UNMATCHED!
Activating master succes!
status_word:0 mode:0 actual_pos:0 actual_speed 0 error_code:0 actual_torque:0
Cycle Thread Started.
dc ref time 59153390712788
start run! Move step 30000
1 slave(s).
AL states: 0x02.
Link is up.
First master diff: 24759.
AL states: 0x01.
AL states: 0x02.
Domain1_output: WC 1.
Domain1_output: State 2.
AL states: 0x04.
Domain1_input: WC 1.
Domain1_input: State 2.
AL states: 0x08.
status_word:0 mode:0 actual_pos:0 actual_speed 0 error_code:0 actual_torque:0
status_word:0 mode:0 actual_pos:0 actual_speed 0 error_code:0 actual_torque:0
reset done !
status_word:1637 mode:8 actual_pos:1272238852 actual_speed 1408000 error_code:0 actual_torque:-1
status_word:1637 mode:8 actual_pos:1275071660 actual_speed 1472000 error_code:0 actual_torque:-1
status_word:1637 mode:8 actual_pos:1277902500 actual_speed 1472000 error_code:0 actual_torque:-1