本文已参与「开源摘星计划」,欢迎正在阅读的你加入。活动链接:https://github.com/weopenprojects/WeOpen-Star

目录

1.背景

2.EtherCAT主站软件方案

3. 移植过程

3.1 RT-Thread 下载

3.2 Some移植

3.2.1 osal.c移植

3.2.2 oshw.c移植

3.2.3 nicdrv.c移植

3.2.4 net_hook.c实现

3.2.5 some基本功能测试

4. 运动控制测试

5. 总结


1.背景

最近计划DIY一个EtherCAT控制器,一直在看资料和选型,初步定了NUC980的方案,主要是看中NUC980的RAM比较大,采购还算方便(最近缺芯,大家都懂)。

选定硬件之后,NUC980跑什么系统呢?从以往经验来看,ARM9一般跑linux比较多,资源也好找,同时官方提供BSP。但这次任务有点特殊,EtherCAT对实时性要求比较高,linux不是最合适的。结合MCU开发经验,就准备上个RTOS,以前M3和M4的芯片,主要用FreeRTOS,最近也开始用RT-Thread。新唐官方也推出了NUC980的RT-Thread版BSP,对开发者非常友好,最终决定了NUC980+RT-Thread的方案。

2.EtherCAT主站软件方案

EtherCAT本身还是比较复杂的,我们就不自己造轮子了,考虑用开源方案,毕竟硬件成本这么低,商用方案是真的用不起啊!

现在开源主站主要就两种,SOEM和IGH(相关的资料网上很多,这儿就不展开了),IGH只支持linux,所以只能选SOME,最新版本是SOEM1.4,本次移植就基于该版本。

3. 移植过程

3.1 RT-Thread 下载

这个可以到github下载,最近Gitee也更新了。移植EtherCAT之前,首先把Nuvoton的BSP跑起来。这个参考官方的文档就可以了。本次移植基于最新发布的RT-Thread release 4.1.0。

3.2 Some移植

下载soem-1.4.0,将整个目录放在rt-thread项目里,下图是我的目录,供参考

在some-1.4.0及其子目录中需要手工编辑SConscript脚本。

import os
from building import *objs = []
cwd  = GetCurrentDir()
list = os.listdir(cwd)for item in list:if os.path.isfile(os.path.join(cwd, item, 'SConscript')):objs = objs + SConscript(os.path.join(item, 'SConscript'))Return('objs')

Some移植主要是三个文件 osal.c,oshw.c和nicdrv.c。

osal.c 主要是微秒及的延时和定时函数;

oshw.c 主要是网络端和本机端数据的大小端转换;

nicdrv.c 主要是网络数据收发。

Some已经给出了很多操作系统移植,我的移植是基于rtk,这个是嵌入式系统实现,和我们的开发环境最接近。

3.2.1 osal.c移植

主要内容是实现osal_usleep和osal_gettimeofday两个函数。

我开始思路是自定义一个定时器用于EtherCAT,当时用了Timer4。等实现差不多了,发现系统时钟用的是Timer5,很多地方功能重复。最终和系统共用Timer5,省了个Timer,代码也简化了不少。下面就是改动过的相关代码,osal_timer_init这个初始化函数要在启动EhterCAT功能之前调用

#include <osal.h>
#include <time.h>
#include <sys/time.h>#include <rtthread.h>
#include <rtdevice.h>#include "NuMicro.h"
#include "drv_sys.h"static rt_uint32_t us_ticks;void osal_timer_init(void)
{rt_uint32_t cmp = ETIMER_GetCompareData(5);us_ticks = 1 * cmp / (1000000 / RT_TICK_PER_SECOND);rt_kprintf("rt-thread hwtimer5 1us = %d ticks\n", us_ticks);
}#define  timercmp(a, b, CMP)                                \(((a)->tv_sec == (b)->tv_sec) ?                           \((a)->tv_usec CMP (b)->tv_usec) :                        \((a)->tv_sec CMP (b)->tv_sec))
#define  timeradd(a, b, result)                             \do {                                                      \(result)->tv_sec = (a)->tv_sec + (b)->tv_sec;           \(result)->tv_usec = (a)->tv_usec + (b)->tv_usec;        \if ((result)->tv_usec >= 1000000)                       \{                                                       \++(result)->tv_sec;                                  \(result)->tv_usec -= 1000000;                        \}                                                       \} while (0)
#define  timersub(a, b, result)                             \do {                                                      \(result)->tv_sec = (a)->tv_sec - (b)->tv_sec;           \(result)->tv_usec = (a)->tv_usec - (b)->tv_usec;        \if ((result)->tv_usec < 0) {                            \--(result)->tv_sec;                                   \(result)->tv_usec += 1000000;                         \}                                                       \} while (0)#define USECS_PER_SEC   1000000
#define USECS_PER_TICK  (USECS_PER_SEC / CFG_TICKS_PER_SECOND)/* Workaround for rt-labs defect 776.* Default implementation of udelay() didn't work correctly when tick was* shorter than one millisecond.*/
//void udelay (uint32_t us)
//{tick_t ticks = (us / USECS_PER_TICK) + 1;task_delay (ticks);
//}//int gettimeofday(struct timeval *tp, void *tzp)
//{
//   tick_t tick = tick_get();
//   tick_t ticks_left;//   ASSERT (tp != NULL);//   tp->tv_sec = tick / CFG_TICKS_PER_SECOND;//   ticks_left = tick % CFG_TICKS_PER_SECOND;
//   tp->tv_usec = ticks_left * USECS_PER_TICK;
//   ASSERT (tp->tv_usec < USECS_PER_SEC);//   return 0;
//}int osal_usleep (uint32 usec)
{//udelay(usec);/*ajustment for precision*///usec -= usec / 4080;usec -= usec / 1500;/*rt_hw_us_delay work for a delay less than 16us*/do{if(usec>=1000){rt_hw_us_delay(1000);usec -= 1000;}else{rt_hw_us_delay(usec);usec = 0;}}while(usec>0);return 0;
}int osal_gettimeofday(struct timeval *tv, struct timezone *tz)
{
//   return gettimeofday(tv, tz);RT_ASSERT (tv != NULL);rt_uint32_t timer_tick,rt_tick;rt_base_t level = rt_hw_interrupt_disable();timer_tick = ETIMER_GetCounter(5);rt_tick = rt_tick_get();rt_hw_interrupt_enable(level);tv->tv_sec = rt_tick/1000;tv->tv_usec = (rt_tick % 1000)*1000 + timer_tick / us_ticks;return 0;
}ec_timet osal_current_time (void)
{struct timeval current_time;ec_timet return_value;osal_gettimeofday (&current_time, 0);return_value.sec = current_time.tv_sec;return_value.usec = current_time.tv_usec;return return_value;
}void osal_timer_start (osal_timert * self, uint32 timeout_usec)
{struct timeval start_time;struct timeval timeout;struct timeval stop_time;osal_gettimeofday (&start_time, 0);timeout.tv_sec = timeout_usec / USECS_PER_SEC;timeout.tv_usec = timeout_usec % USECS_PER_SEC;timeradd (&start_time, &timeout, &stop_time);self->stop_time.sec = stop_time.tv_sec;self->stop_time.usec = stop_time.tv_usec;
}boolean osal_timer_is_expired (osal_timert * self)
{struct timeval current_time;struct timeval stop_time;int is_not_yet_expired;osal_gettimeofday (&current_time, 0);stop_time.tv_sec = self->stop_time.sec;stop_time.tv_usec = self->stop_time.usec;is_not_yet_expired = timercmp (&current_time, &stop_time, <);return is_not_yet_expired == 0;
}void *osal_malloc(size_t size)
{return rt_malloc(size);
}void osal_free(void *ptr)
{rt_free(ptr);
}int osal_thread_create(void *thandle, int stacksize, void *func, void *param)
{
//   thandle = task_spawn ("worker", func, 6,stacksize, param);
//   if(!thandle)
//   {
//      return 0;
//   }return 1;
}int osal_thread_create_rt(void *thandle, int stacksize, void *func, void *param)
{
//   thandle = task_spawn ("worker_rt", func, 15 ,stacksize, param);
//   if(!thandle)
//   {
//      return 0;
//   }return 1;
}

3.2.2 oshw.c移植

不需做什么工作。

3.2.3 nicdrv.c移植

主要修改就是调用自己的网络发送和接收函数,我把它们命名为net_send和net_recv。这两个函数最好的实现是直接操作网卡(或者叫emac),我现在的实现参考了tcpdump的方法,在协议栈中加钩子(hook)实现,这样对原来系统影响最小,网口除了EtherCAT,还可以当正常的网口用。

ecx_setupnic函数中创建mutex(这个按照rt-thread格式改一下即可),安装网络钩子

ecx_closenic函数中删除mutex,卸载网络钩子。

/** Basic setup to connect NIC to socket.* @param[in] port        = port context struct* @param[in] ifname      = Name of NIC device, f.e. "eth0"* @param[in] secondary   = if >0 then use secondary stack instead of primary* @return >0 if succeeded*/
int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary)
{int i;int rVal;int *psock;//   rVal = bfin_EMAC_init((uint8_t *)priMAC);
//   if (rVal != 0)
//      return 0;if (secondary){/* secondary port struct available? */if (port->redport){/* when using secondary socket it is automatically a redundant setup */psock = &(port->redport->sockhandle);*psock = -1;port->redstate                   = ECT_RED_DOUBLE;port->redport->stack.sock        = &(port->redport->sockhandle);port->redport->stack.txbuf       = &(port->txbuf);port->redport->stack.txbuflength = &(port->txbuflength);port->redport->stack.tempbuf     = &(port->redport->tempinbuf);port->redport->stack.rxbuf       = &(port->redport->rxbuf);port->redport->stack.rxbufstat   = &(port->redport->rxbufstat);port->redport->stack.rxsa        = &(port->redport->rxsa);ecx_clear_rxbufstat(&(port->redport->rxbufstat[0]));}else{/* fail */return 0;}}else{port->getindex_mutex = rt_mutex_create ("getindex_mutex", RT_IPC_FLAG_PRIO);port->tx_mutex = rt_mutex_create ("tx_mutex", RT_IPC_FLAG_PRIO);port->rx_mutex = rt_mutex_create ("rx_mutex", RT_IPC_FLAG_PRIO);port->sockhandle        = -1;port->lastidx           = 0;port->redstate          = ECT_RED_NONE;port->stack.sock        = &(port->sockhandle);port->stack.txbuf       = &(port->txbuf);port->stack.txbuflength = &(port->txbuflength);port->stack.tempbuf     = &(port->tempinbuf);port->stack.rxbuf       = &(port->rxbuf);port->stack.rxbufstat   = &(port->rxbufstat);port->stack.rxsa        = &(port->rxsa);ecx_clear_rxbufstat(&(port->rxbufstat[0]));psock = &(port->sockhandle);if(install_hook(port, ifname)==0){rt_kprintf("ecx_setupnic fail\n"); return 0; //fail}}/* setup ethernet headers in tx buffers so we don't have to repeat it */for (i = 0; i < EC_MAXBUF; i++){ec_setupheader(&(port->txbuf[i]));port->rxbufstat[i] = EC_BUF_EMPTY;}ec_setupheader(&(port->txbuf2));return 1;
}/** Close sockets used* @param[in] port        = port context struct* @return 0*/
int ecx_closenic(ecx_portt *port)
{rt_mutex_delete(port->getindex_mutex);rt_mutex_delete(port->tx_mutex);rt_mutex_delete(port->rx_mutex);uninstall_hook(port);return 0;
}

3.2.4 net_hook.c实现

主要实现EtherCAT数据帧收发,中间加了个环形缓冲区用于接收。具体原理就是在网卡加个钩子函数,有数据来的时候先经过钩子函数,我们把EtherCAT数据帧截住,不传给原来的lwip协议栈;如果要发送数据,就直接调用发送函数,绕过lwip协议栈。这样也不影响lwip协议栈工作。

#include <rtthread.h>#include "netif/ethernetif.h"
#include "nicdrv.h"#include <drv_gpio.h>
#include <rtdevice.h>/******************************************************************************
* receive fifo buf
*/
#define HOOK_RX_BUFSIZE 10static uint8_t netfrmbuf[HOOK_RX_BUFSIZE][1540];
static int netfrmbuf_cnt[HOOK_RX_BUFSIZE];
static int netfrm_head = 0;
static int netfrm_tail = 0;
static int netfrm_full = 0;int hook_rx_dump = 0;
int hook_tx_dump = 0;static inline void tx_led_flash(void)
{static int c = 0;//rt_kprintf("c = %d\n",c);if(++c%2)rt_pin_write(NU_GET_PININDEX(NU_PB, 8), PIN_LOW);elsert_pin_write(NU_GET_PININDEX(NU_PB, 8), PIN_HIGH);
}/******************************************************************************
* store netif and old function addr
*/
static struct netif *netif = RT_NULL;
static netif_linkoutput_fn link_output;
static netif_input_fn input;/******************************************************************************
* hex dump
*/
#define __is_print(ch) ((unsigned int)((ch) - ' ') < 127u - ' ')
static void hex_dump(const rt_uint8_t *ptr, rt_size_t buflen)
{unsigned char *buf = (unsigned char *)ptr;int i, j;RT_ASSERT(ptr != RT_NULL);for (i = 0; i < buflen; i += 16){rt_kprintf("%08X: ", i);for (j = 0; j < 16; j++)if (i + j < buflen)rt_kprintf("%02X ", buf[i + j]);elsert_kprintf("   ");rt_kprintf(" ");for (j = 0; j < 16; j++)if (i + j < buflen)rt_kprintf("%c", __is_print(buf[i + j]) ? buf[i + j] : '.');rt_kprintf("\n");}
}/******************************************************************************
* rx/tx hook function
*/
/* get tx data */
static err_t _netif_linkoutput(struct netif *netif, struct pbuf *p)
{return link_output(netif, p);
}/* get rx data */
static err_t _netif_input(struct pbuf *p, struct netif *inp)
{if(p->tot_len>=14){char *data = p->payload;if(data[12]=='\x88' && data[13]=='\xa4') //filter for ethercat frame{if(netfrm_full == 0){pbuf_copy_partial(p, netfrmbuf[netfrm_tail], p->tot_len, 0);netfrmbuf_cnt[netfrm_tail] = p->tot_len;netfrm_tail = (netfrm_tail+1) % HOOK_RX_BUFSIZE;if(netfrm_tail==netfrm_head)netfrm_full = 1;}//rt_kprintf("tail = %d, full = %d\n", netfrm_tail, netfrm_full);}}return input(p, inp);
}/******************************************************************************
* hook install
*/
int install_hook(ecx_portt *port, const char *ifname)
{struct eth_device *device;rt_base_t level;netfrm_head = 0;netfrm_tail = 0;netfrm_full = 0;if(netif == RT_NULL){rt_kprintf("hook installing on  %s\n", ifname);device = (struct eth_device *)rt_device_find(ifname);if (device == RT_NULL){rt_kprintf("hook install error 'device == RT_NULL'\n");return 0;}if ((device->netif == RT_NULL) || (device->netif->linkoutput == RT_NULL)){rt_kprintf("hook install error '(device->netif == RT_NULL) || (device->netif->linkoutput == RT_NULL)'\n");return 0;}rt_kprintf("device %s found: 0x%x \n", ifname, (uint32_t)device);}else{rt_kprintf("device %s hook already installed, must be uninstall it before intall new on\n", ifname);}netif = device->netif;//install netdev hooklevel = rt_hw_interrupt_disable();link_output = netif->linkoutput;netif->linkoutput = _netif_linkoutput;input = netif->input;netif->input = _netif_input;rt_hw_interrupt_enable(level);rt_kprintf("hook installed on %s\n", ifname);return 1;
}/******************************************************************************
* hook uninstall
*/
int uninstall_hook(ecx_portt *port)
{rt_base_t level;//uninstall netdev hookif(netif != RT_NULL){level = rt_hw_interrupt_disable();netif->input = input;netif->linkoutput = link_output;rt_hw_interrupt_enable(level);netif = RT_NULL;}rt_kprintf("hook uninstalled\n");return 1;
}/******************************************************************************
* netdev send/recv api
*/
int net_send(unsigned char *data, int len)
{int ret = -1;struct pbuf *p;tx_led_flash();p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_POOL);if (p != NULL){pbuf_take(p, data, len);if(hook_tx_dump){rt_kprintf("send --- len=%d>>>\n",len);hex_dump(p->payload, p->tot_len);}_netif_linkoutput(netif,p);pbuf_free(p);ret = len;}else{rt_kprintf("net_send alloc buffer error\n"); }return ret;
}int net_recv(unsigned char *data, int len)
{if(netfrm_full == 0 && netfrm_tail==netfrm_head){return 0;}int total = netfrmbuf_cnt[netfrm_head];if(total > len) total = len;rt_memcpy(data, netfrmbuf[netfrm_head], total);netfrm_head = (netfrm_head+1) % HOOK_RX_BUFSIZE;if(netfrm_tail==netfrm_head)netfrm_full = 0;if(hook_rx_dump){rt_kprintf("recv <<<---\n");hex_dump(data, total);rt_kprintf("head = %d, tail = %d, full = %d\n", netfrm_head, netfrm_tail, netfrm_full);}return total;
}
void net_hook_test(int argc, char **argv)
{unsigned char frame[] = "\xff\xff\xff\xff\xff\xff\x01\x01\x01\x01\x01\x01\x88\xa4\x0d\x10\
\x08\x01\x00\x00\x03\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";install_hook(NULL, "e0");net_send(frame,60);rt_thread_mdelay(20000);uninstall_hook(NULL);
}
MSH_CMD_EXPORT(net_hook_test, net_hook_test sample on 'e0');

3.2.5 some基本功能测试

采用官方的slave_info测试代码,测试主要分为时钟测试和soem EtherCAT协议栈基本功能测试。在终端中输入 soem_test + 回车即可。

我接了一个汇川IS620N驱动器,下面是输出的部分内容:

Slave:1Name:IS620NOutput size: 96bitsInput size: 224bitsState: 4Delay: 0[ns]Has DC: 1DCParentport:0Activeports:1.0.0.0Configured address: 1001Man: 00100000 ID: 000c0108 Rev: 00010001SM0 A:1000 L: 128 F:00010026 Type:1SM1 A:1400 L: 128 F:00010022 Type:2SM2 A:1800 L:  12 F:00010064 Type:3SM3 A:1c00 L:  28 F:00010020 Type:4FMMU0 Ls:00000000 Ll:  12 Lsb:0 Leb:7 Ps:1800 Psb:0 Ty:02 Act:01FMMU1 Ls:0000000c Ll:  28 Lsb:0 Leb:7 Ps:1c00 Psb:0 Ty:01 Act:01FMMUfunc 0:1 1:2 2:0 3:0MBX length wr: 128 rd: 128 MBX protocols : 04CoE details: 0d FoE details: 00 EoE details: 00 SoE details: 00Ebus current: 0[mA]only LRD/LWR:0

4. 运动控制测试

基础工作做好以后,我们就能真正的控制电机运行了。在控制电机运行之前,还需要了解CIA402相关的规范,启动伺服需要按照规范要求,按顺序来。

程序主要流程如下,具体代码见附件。

        a)初始化时钟 osal_timer_init

        b)初始化网卡ec_init

        c)等待进入INIT态

        d)初始化驱动器(is620n)ec_config_init

        e)DC配置

        f)申请并等待进入Pre-OP态

        g)配置过程数据TxPDO/RxPDO(自定义函数process_data_config)

        h)配置FMMU ec_config_map

        i)申请并等待进入Safe-OP态

        j)设置CSP模式

        k)发送和接收过程数据1次,触发SLAVE

        l)申请并等待进入OP态

        m)进入过程数据收发循环

#define __is_print(ch) ((unsigned int)((ch) - ' ') < 127u - ' ')
static void hex_dump(const rt_uint8_t *ptr, rt_size_t buflen)
{unsigned char *buf = (unsigned char *)ptr;int i, j;RT_ASSERT(ptr != RT_NULL);for (i = 0; i < buflen; i += 16){rt_kprintf("%08X: ", i);for (j = 0; j < 16; j++)if (i + j < buflen)rt_kprintf("%02X ", buf[i + j]);elsert_kprintf("   ");rt_kprintf(" ");for (j = 0; j < 16; j++)if (i + j < buflen)rt_kprintf("%c", __is_print(buf[i + j]) ? buf[i + j] : '.');rt_kprintf("\n");}
}static char IOmap[4096];
typedef struct  __attribute__((__packed__))
{unsigned char  mode_byte;unsigned short control_word;long  dest_pos;unsigned short error_word;unsigned short status_word;long  cur_pos;
}SERVO_DATA_T;
typedef struct
{SERVO_DATA_T servo_data[3];
}SERVOS_T;SERVOS_T *servos = (SERVOS_T *)IOmap;void view_slave_data()
{hex_dump(IOmap,32);
}static void echo_time()
{struct timeval tp;osal_gettimeofday(&tp, 0);printf("****cur time = %d,%03d,%03d(us)\n", tp.tv_sec,tp.tv_usec/1000,tp.tv_usec%1000);
}#define EC_TIMEOUTMON 500int expectedWKC;
boolean needlf;
volatile int wkc;
boolean inOP;
uint8 currentgroup = 0;void process_data_config()
{u8_t     ind;for(int slave = 1; slave <= *ecx_context.slavecount; slave++){//rpdo------------//1c12.0safe_SDCwrite_b(slave, 0x1c12, 0, 0);safe_SDCwrite_w(slave, 0x1c12, 1, 0x1600);//1600ind = 0;safe_SDCwrite_b(slave, 0x1600, 0, 0);safe_SDCwrite_dw(slave, 0x1600, ++ind, htoel(0x60600008));//6060h(控制模式)safe_SDCwrite_dw(slave, 0x1600, ++ind, htoel(0x60400010));//6040h(控制字)safe_SDCwrite_dw(slave, 0x1600, ++ind, htoel(0x607a0020));//607Ah(目标位置)safe_SDCwrite_b(slave, 0x1600, 0, ind);//1c12.0safe_SDCwrite_b(slave, 0x1c12, 0, 1);//tpdo-------------//1c13.0safe_SDCwrite_b(slave, 0x1c13, 0x00, 0);safe_SDCwrite_w(slave, 0x1c13, 0x01, 0x1a00);//1a00ind = 0;safe_SDCwrite_b(slave, 0x1a00, 0, 0);safe_SDCwrite_dw(slave, 0x1a00, ++ind, htoel(0x603F0010));//603Fh(错误码)safe_SDCwrite_dw(slave, 0x1a00, ++ind, htoel(0x60410010));//6041h(状态字)safe_SDCwrite_dw(slave, 0x1a00, ++ind, htoel(0x60640020));//6064h(位置反馈)safe_SDCwrite_b(slave, 0x1a00, 0, ind);//1c13.0safe_SDCwrite_b(slave, 0x1c13, 0, 1);safe_SDCwrite_b(slave, 0x6060, 0, 1);//viewSDO(slave, 0x6060, 0, 1);}
}void servo_switch_op()
{int sta;for(int slave = 1; slave <= *ecx_context.slavecount; slave++){int idx = slave - 1;sta = servos->servo_data[idx].status_word & 0x3ff;//printf("servo_switch_op: slave %d [6041]=%04x\n",slave,servos->servo_data[idx].status_word ); if(servos->servo_data[idx].status_word & 0x8){servos->servo_data[idx].control_word = 0x8;
//          printf("***slave %d control=%04x\n",slave,servos->servo_data[idx].control_word ); continue;}//printf("servo_switch_op: slave %d sta=%04x\n", slave, sta ); switch(sta){case 0x250:case 0x270:servos->servo_data[idx].control_word = 0x6;break;case 0x231:servos->servo_data[idx].control_word = 0x7;break;case 0x233:servos->servo_data[idx].control_word = 0xf;break;default://servos->servo_data[idx].control_word = 0x6;break;}//printf("slave %d control=%04x\n",slave,servos->servo_data[idx].control_word ); }}
void servo_switch_idle()
{int sta;for(int slave = 1; slave <= *ecx_context.slavecount; slave++){servos->servo_data[slave-1].control_word = 0x0;}
}
void sv660n_config(char *ifname)
{needlf = FALSE;inOP = FALSE;osal_timer_init();ecx_context.manualstatechange = 1;printf("========================\n");printf("sv660 config\n");echo_time();if (ec_init(ifname)){printf("ec_init on %s succeeded.\n",ifname);//init statusprintf("\nRequest init state for all slaves\n");ec_slave[0].state = EC_STATE_INIT;//request INIT state for all slaves ec_writestate(0);//显示1状态 /* wait for all slaves to reach SAFE_OP state */ec_statecheck(0, EC_STATE_INIT,  EC_TIMEOUTSTATE * 3);if (ec_slave[0].state != EC_STATE_INIT ){printf("Not all slaves reached init state.\n");ec_readstate();for(int i = 1; i<=ec_slavecount ; i++){if(ec_slave[i].state != EC_STATE_INIT){printf("Slave %d State=0x%2x StatusCode=0x%04x : %s\n", i, ec_slave[i].state, ec_slave[i].ALstatuscode, ec_ALstatuscode2string(ec_slave[i].ALstatuscode));}}}echo_time();//if ( ec_config(FALSE, &IOmap) > 0 )wkc = ec_config_init(0/*usetable*/);if (wkc > 0){ec_configdc();ec_dcsync0(1, TRUE, 2000000, 50); // SYNC0 on slave 1while(EcatError) printf("%s", ec_elist2string());printf("%d slaves found and configured.\n",ec_slavecount);/* request pre_op for slave */printf("\nRequest pre_op state for all slaves\n");ec_slave[0].state = EC_STATE_PRE_OP | EC_STATE_ACK;ec_writestate(0);//故障复位
//          safe_SDCwrite_w(1,0x200d, 0x2, 1);
//          safe_SDCwrite_w(1,0x200d, 0x2, 0);  //现在应该在pre_op状态//显示2状态  process_data_config(); //config tpdo/rpdo//config fmmuec_config_map(IOmap);/* request safe_op for slave */ec_slave[0].state = EC_STATE_SAFE_OP;ec_writestate(0);//safe-opexpectedWKC = (ec_group[0].outputsWKC * 2) + ec_group[0].inputsWKC;printf("Calculated workcounter %d\n", expectedWKC);/* wait for all slaves to reach SAFE_OP state */ec_statecheck(0, EC_STATE_SAFE_OP,  EC_TIMEOUTSTATE * 3);if (ec_slave[0].state != EC_STATE_SAFE_OP ){printf("Not all slaves reached safe operational state.\n");ec_readstate();for(int i = 1; i<=ec_slavecount ; i++){if(ec_slave[i].state != EC_STATE_SAFE_OP){printf("Slave %d State=0x%2x StatusCode=0x%04x : %s\n", i, ec_slave[i].state, ec_slave[i].ALstatuscode, ec_ALstatuscode2string(ec_slave[i].ALstatuscode));}}}else{//显示4状态    //启动伺服servos->servo_data[0].mode_byte = 8; //csp mode//op statusprintf("Request operational state for all slaves\n");expectedWKC = (ec_group[0].outputsWKC * 2) + ec_group[0].inputsWKC;printf("Calculated workcounter %d\n", expectedWKC);ec_slave[0].state = EC_STATE_OPERATIONAL;// send one valid process data to make outputs in slaves happyec_send_processdata();wkc = ec_receive_processdata(EC_TIMEOUTRET*3);printf("--->workcounter %d\n", wkc);//view_slave_data();// request OP state for all slaves ec_writestate(0);int chk = 200;// wait for all slaves to reach OP state do{ec_send_processdata();ec_receive_processdata(EC_TIMEOUTRET);printf("--->workcounter %d\n", wkc);ec_statecheck(0, EC_STATE_OPERATIONAL, 50000);}while (chk-- && (ec_slave[0].state != EC_STATE_OPERATIONAL));if (ec_slave[0].state == EC_STATE_OPERATIONAL ){printf("<<<Operational>>> state reached for all slaves.\n");inOP = TRUE;osal_timert t;osal_timer_start(&t, 1000);// cyclic loopfor(int i = 1; i <= 10000; i++){servo_switch_op();if(servos->servo_data[0].control_word==7){servos->servo_data[0].dest_pos = servos->servo_data[0].cur_pos;//printf("cur pos = %ld\n", servos->servo_data[0].cur_pos);}if(servos->servo_data[0].control_word==0xf){servos->servo_data[0].dest_pos += 3000;}while(osal_timer_is_expired(&t)==FALSE);osal_timer_start(&t, 1000);ec_send_processdata();wkc = ec_receive_processdata(EC_TIMEOUTRET);if(wkc >= expectedWKC){//printf("~~~~WKC %d \n", wkc);}if(wkc <=0 ){printf("Error.\n");break;}//osal_usleep(1000);}inOP = FALSE;}else{printf("Not all slaves reached operational state.\n");ec_readstate();for(int i = 1; i<=ec_slavecount ; i++){if(ec_slave[i].state != EC_STATE_OPERATIONAL){printf("Slave %d State=0x%2.2x StatusCode=0x%4.4x : %s\n",i, ec_slave[i].state, ec_slave[i].ALstatuscode, ec_ALstatuscode2string(ec_slave[i].ALstatuscode));}}}//init statusprintf("\nRequest init state for all slaves\n");ec_slave[0].state = EC_STATE_INIT;//request INIT state for all slaves ec_writestate(0);}} else {printf("No slaves found!\n");}echo_time();printf("End soem, close socket\n");// stop SOEM, close socket ec_close();       }else{printf("ec_init on %s failed.\n",ifname);}printf("IOMAP addr = 0x%08x\n", (uint32_t)IOmap);printf("========================\n");view_slave_data();
}

在进入数据数据收发循环后,按次序发送控制字启动伺服(6040h发送6,7和15),然后就可以不断发送新的控制位置让电机转起来了!

5. 总结

整个移植过程还是充满了挑战,主要也是因为今年才开始接触EtherCAT,很多概念是边学边用,网上也参考了不少同学的帖子。很多人反应汇川的伺服用SOEM驱动DC同步模式总是有问题,确实遇到了很多奇奇怪怪的问题。经过这两个月的折腾,总算开了个头,基础打好了。

下一步可优化的就是现在的网络移植改用直接操作emac,这样可以减少网络抖动。

附件程序里还参考本站贴子移植了uffs文件系统,编译如果有问题,可能还需要下载uffs、ramdisk、optparse和netutils包。

完整程序包下载

在RT-Thread上移植EtherCAT开源主站SOEM1.4.0相关推荐

  1. 基于rt thread smart构建EtherCAT主站

    我把源码开源到到了gitee,https://gitee.com/rathon/rt-thread-smart-soem 有兴趣的去可以下载下来跑一下 软件工程推荐用vscode 打开.rt thre ...

  2. u盘安装linux 提示no such device_IGH EtherCAT 开源主站安装及测试

    1.Linux系统: 采用的系统版本: Ubun 14.04.1 64位: 内核版本: 3.13.0-12-generic 具体型号: 2.去IGH官网下载1.5.2版本. 3.编译源码 (1)解压下 ...

  3. RT Thread Free Modbus移植问题整理

    RT Thread Free Modbus移植问题整理 问题描述: 在读写寄存器中,写数据正常,只能读1个寄存器的值,多个值会异常. 在移植过程中发现串口(或RS485)数据接收长度异常. 一.环境描 ...

  4. 正点原子delay函数移植到rt thread操作系统(HAL库)

    正点原子教程中涉及到的操作系统只涉及了UCOS的教程,其中例程的system文件夹中的delay.c函数只是适配了UCOS. 下面将delay.c函数移植到rt thread中,使用的bsp是rt t ...

  5. rt thread studio使用QBOOT和片外flash实现OTA升级

    我们这里要使用单片机外部flash作为OTA的下载分区,外部flash硬件连接关系 PB3-->SPI3_CLK PB4-->SPI3_MISO PB5-->SPI3_MOSI PE ...

  6. Yeelink平台使用——远程控制 RT Thread + LwIP+ STM32

    1.前言     [2014年4月重写该博文]     经过若干时间的努力终于搞定了STM32+LwIP和yeelink平台的数据互通,在学习的过程中大部分时间花在以太网协议栈学习上,但是在RT Th ...

  7. xpt 2046的触摸屏 rt thread设备驱动框架

    1 基于rtt 开发触摸屏驱动 准备使用rtt 框架 , 驱动xpt 2046的触摸屏, 翻阅大量资料发现, 大部分文章强调的是时序图, 而且很多代码要么直接操作寄存器, 要么是io 口模拟, 只能用 ...

  8. 关于RT thread系统节拍时钟的配置

    关于RT thread系统节拍时钟的配置                  -----本文基于rt-thread-3.1.3版本编写 首先,使用RTthread OS时,要配置(或者明白)它的系统节拍 ...

  9. RT Thread根据开发板制作BSP方法

    之前一直不懂怎么使用RT Thread的软件包,感谢网上的大神,看了你们的博客后大概了解一些,在此做下记录.用RT Thread软件包需要RT Thread的系统,但是RT Thread和RT Thr ...

  10. 腾讯物联网操作系统TencentOS tiny线上移植大赛,王者机器人、QQ公仔、定制开发板等礼品等你来拿 !

    腾讯物联网操作系统TencentOS tiny线上移植大赛,王者机器人.QQ公仔.定制开发板等礼品等你来拿 ! 一.产品介绍 TencentOS tiny是腾讯面向物联网领域开发的实时操作系统,具有低 ...

最新文章

  1. 这家芯片公司98%员工学历不足本科,却要布局全产业链冲刺IPO,网友:认真的吗?...
  2. numpy在折线图上添加取值_见识matplotlib:不常见的一面,折线图
  3. java sum_java math.sum
  4. linux 恢复数据
  5. Tensorflow线程队列与IO操作
  6. image copy oracle,RMAN删除image copy时遇到的问题
  7. JDK下载与安装、 Eclipse下载与使用、 Tomcat下载与使用、 MySQL安装与使用
  8. 有了这三个神器工具集,应用开发想怎么玩就怎么玩
  9. 学习索引结构的一些案例——Jeff Dean在SystemML会议上发布的论文(上)
  10. 安卓抓包软件_你们要的抓包神器!以及抓包原理
  11. VB用API控制输入法状态
  12. 服务器win10系统开机慢,Win10系统开机慢怎么办 windows10开机慢的解决方法
  13. 三 APPIUM Android自动化 测试初体验(转)
  14. InnoDB Plugin 1.0.2 for MySQL 5.1.30 (GA) Released
  15. 16进制颜色代码转RGB代码
  16. react native与夜神模拟器结合使用运行安卓平台
  17. Burp Suite安装和使用方法
  18. 前端大串讲,狂神,狂神和飞哥
  19. 红到发紫的人工智能,2019运势如何?
  20. 知星社:学会了什么?

热门文章

  1. 教你如何用C语言做一个简单的贪吃蛇
  2. 学校计算机房要求,学校机房建设包含哪些内容以及相关的标准(参考)一
  3. delphi2007安装说明(备忘)
  4. 通过代理查看Myeclipse的主页
  5. html5音频插件js,jquery音乐播放器插件jsRapAudio
  6. 冰点还原精灵和惠普增霸卡安装软件的方法
  7. 如何对下载的文件进行MD5校验
  8. 数学建模中的整数规划总结及姜启源第4章(1-3)的解析
  9. Python解析JSON对象
  10. Java后台开发一:环境搭建