EtherCAT主站——SOEM(学习笔记1)

目录

1、SOEM 库架构分析

2、硬件抽象层

3、中间层

4、SOEM1.3.1源代码目录介绍:


1、SOEM 库架构分析

        SOEM 是 Simple Open EtherCAT Master Library 的缩写,是瑞典 rt-lab 提供 的一个开源 EtherCAT 主站协议库 SOEM 库使用 C 语言编写,可以在 windows 以及 Linux 平台上运行,并也可以方便地移植到嵌入式平台上。         SOEM 支持 CoE SoE FoE 以及分布式时钟。 SOEM 直接将 EtherCAT 帧 通过 MAC 发送和接收,因此它不支持 EoE 。 SOEM 库由若干模块组成,最底层提供硬件和操作系统抽象层,从而可以 方便地将 SOEM 库移植到不同的系统平台上。 SOEM 库架构如图 所示:

         SOEM 库采用分层设计,并且提供了一个抽象层,将 SOEM 协议栈与具体 操作系统和硬件分开,使得 SOEM 在理论上可以移植到任意操作系统和硬件平 台之上。

        抽象层由 OSAL 和 OSHW 两个模块组成,OSAL 是操作系统抽象层,OSHW 是硬件抽象层,移植的主要内容就是对 OSAL OSHW 具体 API 实现,在新的 操作系统和硬件平台上的重写。注意,SOEM 可以不需要 OS 操作系统,直接在时间和定时器相关的 API 是必须实现的。

2、硬件抽象层

        硬件抽象层即OSHW 模块,它为上层提供网络服务,是最重要的实现。 SOEM 移植的核心工作就是对此模块的移植,后文会详细介绍具体的实现。         OSHW 模块由 oshw.h/oshw.c nicdrv.h/nicdrv.c 四个文件组成。 oshw.h/.c 主要实现网络小端和本地大小端的转换,nicdrv.h/.c 是网络驱动,主要实现 EtherCAT 帧的发送和接收, SOEM 的移植主要是对网络驱动的实现。 oshw.h/.c 主要提供字节顺序转换的服务。数据在网络上传输时,总是先传 输高位比特,然后传低位比特。所以,网络采用的是大端方式。而具体一个硬 件平台,可能采用大端也可能是小端。 不同平台上 nicdrv 模块的逻辑结构基本相同,需要实现的是三个底层 API 1 )网口的初始化 (2) MAC 层帧发送 (3) MAC 层帧接收

3、中间层

        抽象层之上的模块(除了应用层)属于 SOEM 的中间层,是 EtherCAT 协议 栈的具体实现,包含 BASE 模块、 MAIN 模块、 CONFIG 模块、 CONFIGDC 模 块、COE 等。各层的功能如下:
功能

ethercatbase

将工业应用数据组装成 EtherCAT 帧, 顺序寻址、广播方式、配置地址、逻辑地址方式对从站读写

ethercatmain

读写从站 EEPROM 提供邮箱模式的非过程数据读写和三缓冲模式的过程数据 PDO 读写

ethercatconfig

初始化从站控制器的寄存器,配置从站 FMMU

ethercatdc

提供分布式时钟,实现主从站之间时钟同步

ethercatsoe

CANOpen Over EtherCAT

ethercatfoe

File Over EtherCAT

ethercatsoe

Sercos Over EtherCAT

4、SOEM1.3.1源代码目录介绍:

doc:帮助文档、
osal:操作系统抽象层,主要是用于符合OSADL和实时进程创建。也就是说:发送EtherCAT数据包不能抖动太大,如果直接使用linux提供的原生线程,可能实时性无法满足。需要对Linux内核打上实时补丁,我们采用PREEMPT_RT
oshw:硬件抽象层,网卡的接口封装,不同操作系统对网卡操作不一样,linux下我们要求网卡支持混杂模式。
soem:EtherCAT主站的核心代码。包括COE,FOE等等。

5、代码讲解

main函数先开线程:

int main(int argc, char *argv[])
{printf("SOEM (Simple Open EtherCAT Master)\nSimple test\n");if (argc > 1){      /* create thread to handle slave error handling in OP */
//      pthread_create( &thread1, NULL, (void *) &ecatcheck, (void*) &ctime);   osal_thread_create(&thread1, 128000, &ecatcheck, (void*) &ctime);/* start cyclic part */simpletest(argv[1]);}else{printf("Usage: simple_test ifname1\nifname = eth0 for example\n");}   printf("End program\n");return (0);
}

 开了线程之后,调用simpletest函数,并把网卡名作为输入参数。

进入simpletest,第一步是调用ec_init函数:

int ec_init(const char * ifname)
{return ecx_init(&ecx_context, ifname);
}

注意一下ecx_context,这个结构体定义如下:

struct ecx_context
{/** port reference, may include red_port */ecx_portt      *port;/** slavelist reference */ec_slavet      *slavelist;/** number of slaves found in configuration */int            *slavecount;/** maximum number of slaves allowed in slavelist */int            maxslave;/** grouplist reference */ec_groupt      *grouplist;/** maximum number of groups allowed in grouplist */int            maxgroup;/** internal, reference to eeprom cache buffer */uint8          *esibuf;/** internal, reference to eeprom cache map */uint32         *esimap;/** internal, current slave for eeprom cache */uint16         esislave;/** internal, reference to error list */ec_eringt      *elist;/** internal, reference to processdata stack buffer info */ec_idxstackT   *idxstack;/** reference to ecaterror state */boolean        *ecaterror;/** internal, position of DC datagram in process data packet */uint16         DCtO;/** internal, length of DC datagram */uint16         DCl;/** reference to last DC time from slaves */int64          *DCtime;/** internal, SM buffer */ec_SMcommtypet *SMcommtype;/** internal, PDO assign list */ec_PDOassignt  *PDOassign;/** internal, PDO description list */ec_PDOdesct    *PDOdesc;/** internal, SM list from eeprom */ec_eepromSMt   *eepSM;/** internal, FMMU list from eeprom */ec_eepromFMMUt *eepFMMU;/** registered FoE hook */int            (*FOEhook)(uint16 slave, int packetnumber, int datasize);/** registered EoE hook */int            (*EOEhook)(ecx_contextt * context, uint16 slave, void * eoembx);/** flag to control legacy automatic state change or manual state change */int            manualstatechange;
};

于是,变量ecx_context又把总线运行的一系列信息放在了这里:

ecx_contextt  ecx_context = {&ecx_port,          // .port          =&ec_slave[0],       // .slavelist     =&ec_slavecount,     // .slavecount    =EC_MAXSLAVE,        // .maxslave      =&ec_group[0],       // .grouplist     =EC_MAXGROUP,        // .maxgroup      =&ec_esibuf[0],      // .esibuf        =&ec_esimap[0],      // .esimap        =0,                  // .esislave      =&ec_elist,          // .elist         =&ec_idxstack,       // .idxstack      =&EcatError,         // .ecaterror     =0,                  // .DCtO          =0,                  // .DCl           =&ec_DCtime,         // .DCtime        =&ec_SMcommtype[0],  // .SMcommtype    =&ec_PDOassign[0],   // .PDOassign     =&ec_PDOdesc[0],     // .PDOdesc       =&ec_SM,             // .eepSM         =&ec_FMMU,           // .eepFMMU       =NULL,               // .FOEhook()NULL,               // .EOEhook()0                   // .manualstatechange
};

再然后,ecx_init调用了ecx_setupnic:套娃套娃

int ecx_init(ecx_contextt *context, const char * ifname)
{return ecx_setupnic(context->port, ifname, FALSE);
}

最终是这个函数:

int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary) 
{int i;char ifn[IF_NAME_SIZE];int unit_no = -1;   ETHERCAT_PKT_DEV * pPktDev;/* Get systick info, sysClkRateGet return ticks per second */usec_per_tick =  USECS_PER_SEC / sysClkRateGet();/* Don't allow 0 since it is used in DIV */if(usec_per_tick == 0)usec_per_tick = 1;/* Make reference to packet device struct, keep track if the packet* device is the redundant or not.*/if (secondary){pPktDev = &(port->redport->pktDev);pPktDev->redundant = 1;}else{pPktDev = &(port->pktDev);pPktDev->redundant = 0;}/* Clear frame counters*/pPktDev->tx_count = 0;pPktDev->rx_count = 0;pPktDev->overrun_count = 0;/* Create multi-thread support semaphores */port->sem_get_index = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE);/* Get the dev name and unit from ifname * We assume form gei1, fei0... */memset(ifn,0x0,sizeof(ifn));for(i=0; i < strlen(ifname);i++){if(isdigit(ifname[i])){strncpy(ifn, ifname, i);unit_no = atoi(&ifname[i]);break;}}/* Detach IP stack *///ipDetach(pktDev.unit,pktDev.name);pPktDev->port = port;/* Bind to mux driver for given interface, include ethercat driver pointer * as user reference *//* Bind to mux */pPktDev->pCookie = muxBind(ifn,unit_no, mux_rx_callback, NULL, NULL, NULL, MUX_PROTO_SNARF, "ECAT SNARF", pPktDev);if (pPktDev->pCookie == NULL){/* fail */NIC_LOGMSG("ecx_setupnic: muxBind init for gei: %d failed\n", unit_no, 2, 3, 4, 5, 6);goto exit;}/* Get reference tp END obje */pPktDev->endObj = endFindByName(ifn, unit_no);if (port->pktDev.endObj == NULL){/* fail */NIC_LOGMSG("error_hook:  endFindByName failed, device gei: %d not found\n",unit_no, 2, 3, 4, 5, 6);goto exit;}if (secondary){/* secondary port struct available? */if (port->redport){port->redstate                   = ECT_RED_DOUBLE;port->redport->stack.txbuf       = &(port->txbuf);port->redport->stack.txbuflength = &(port->txbuflength);port->redport->stack.rxbuf       = &(port->redport->rxbuf);port->redport->stack.rxbufstat   = &(port->redport->rxbufstat);port->redport->stack.rxsa        = &(port->redport->rxsa);/* Create mailboxes for each potential EtherCAT frame index */for (i = 0; i < EC_MAXBUF; i++){port->redport->msgQId[i] = msgQCreate(1, sizeof(M_BLK_ID), MSG_Q_FIFO);if (port->redport->msgQId[i] == MSG_Q_ID_NULL){NIC_LOGMSG("ecx_setupnic: Failed to create redundant MsgQ[%d]",i, 2, 3, 4, 5, 6);goto exit;}}ecx_clear_rxbufstat(&(port->redport->rxbufstat[0]));}else{/* fail */NIC_LOGMSG("ecx_setupnic: Redundant port not allocated",unit_no, 2, 3, 4, 5, 6);goto exit;}}else{port->lastidx           = 0;port->redstate          = ECT_RED_NONE;port->stack.txbuf       = &(port->txbuf);port->stack.txbuflength = &(port->txbuflength);port->stack.rxbuf       = &(port->rxbuf);port->stack.rxbufstat   = &(port->rxbufstat);port->stack.rxsa        = &(port->rxsa);/* Create mailboxes for each potential EtherCAT frame index */for (i = 0; i < EC_MAXBUF; i++){port->msgQId[i] = msgQCreate(1, sizeof(M_BLK_ID), MSG_Q_FIFO);if (port->msgQId[i] == MSG_Q_ID_NULL){NIC_LOGMSG("ecx_setupnic: Failed to create MsgQ[%d]",i, 2, 3, 4, 5, 6);goto exit;}}ecx_clear_rxbufstat(&(port->rxbufstat[0]));}/* 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;exit:return 0;}

简单来说,就是分配收发缓冲区地址,打开硬件,再把数据包头写到每一个发送缓冲区首部,免得后续每次都写。另外初始化了一些保护关键代码段的互斥锁。如果是裸跑的话,在保护关键代码的时候可能要考虑用开关中断来实现了。再一个,可以看到,这里实际上是可以打开第二个网口的。两个网口,一个作为输出,一个作为输入。这个按实际情况来做吧,目前见到的应用,多数是只用一个网口的。

这里的重点在于这个port。可以看到,实际上这里是用了在ethercatmain.c文件中定义的全局变量:

ecx_portt               ecx_port;

还记得之前说的那个ecx_context吗?对,这个ecx_port就是ecx_context里面的。

在nicdrv.h文件中,这个exc_portt定义如下:

typedef struct ecx_port
{/** Stack reference */   ec_stackT stack;/** Packet device instance */ETHERCAT_PKT_DEV pktDev;/** rx buffers */ec_bufT rxbuf[EC_MAXBUF];/** rx buffer status */int rxbufstat[EC_MAXBUF];/** rx MAC source address */int rxsa[EC_MAXBUF];/** transmit buffers */ec_bufT txbuf[EC_MAXBUF];/** transmit buffer lengths */int txbuflength[EC_MAXBUF];/** temporary tx buffer */ec_bufT txbuf2;/** temporary tx buffer length */int txbuflength2;/** last used frame index */int lastidx;/** current redundancy state */int redstate;/** pointer to redundancy port and buffers */ecx_redportt *redport;   /** Semaphore to protect single resources */SEM_ID  sem_get_index;/** MSG Q for receive callbacks to post into */MSG_Q_ID  msgQId[EC_MAXBUF];
} ecx_portt;

这里EC_MAXBUF是ethercattype.h文件当中的宏定义:

#define EC_MAXBUF          16

该文件在SOEM文件夹当中。还有ec_bufT的定义

#define EC_MAXECATFRAME    1518

#define EC_BUFSIZE         EC_MAXECATFRAME

typedef uint8 ec_bufT[EC_BUFSIZE];

这个1518数字看着眼熟。看看EtherCAT数据帧格式就一目了然了:

顺便也说一下这个ecx_redportt结构体:

/** pointer structure to buffers for redundant port */
typedef struct ecx_redport
{/** Stack reference */   ec_stackT stack;/** Packet device instance */ETHERCAT_PKT_DEV pktDev;/** rx buffers */ec_bufT rxbuf[EC_MAXBUF];/** rx buffer status */int rxbufstat[EC_MAXBUF];/** rx MAC source address */int rxsa[EC_MAXBUF];/** MSG Q for receive callbacks to post into */MSG_Q_ID  msgQId[EC_MAXBUF];
} ecx_redportt;

    依然是在nicdrv.h当中定义。这就是一个只有接收功能的ecx_portt的阉割版本,没有发送缓冲区及其状态标志,因为收发过程中的关键代码保护互斥量已经有了,所以这里连互斥量都省了。细心的你可能在ecx_portt当中发现一个问题:


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部