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当中发现一个问题:
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
