大家好,欢迎来到IT知识分享网。
目录
1、SOEM 库架构分析
主站协议库
。
SOEM
库使用
C
语言编写,可以在
windows 以及 Linux
平台上运行,并也可以方便地移植到嵌入式平台上。
CoE
,
SoE
,
FoE
以及分布式时钟。
SOEM
直接将
EtherCAT
帧 通过 MAC
发送和接收,因此它不支持
EoE
。 SOEM 库由若干模块组成,最底层提供硬件和操作系统抽象层,从而可以 方便地将 SOEM
库移植到不同的系统平台上。
SOEM
库架构如图
所示:
SOEM 库采用分层设计,并且提供了一个抽象层,将 SOEM 协议栈与具体 操作系统和硬件分开,使得 SOEM 在理论上可以移植到任意操作系统和硬件平 台之上。
抽象层由 OSAL 和 OSHW 两个模块组成,OSAL 是操作系统抽象层,OSHW 是硬件抽象层,移植的主要内容就是对 OSAL 和 OSHW 具体 API 实现,在新的 操作系统和硬件平台上的重写。注意,SOEM 可以不需要 OS 操作系统,直接在时间和定时器相关的 API 是必须实现的。
2、硬件抽象层
模块,它为上层提供网络服务,是最重要的实现。
SOEM 移植的核心工作就是对此模块的移植,后文会详细介绍具体的实现。
和
nicdrv.h/nicdrv.c
四个文件组成。
oshw.h/.c
主要实现网络小端和本地大小端的转换,nicdrv.h/.c
是网络驱动,主要实现 EtherCAT 帧的发送和接收,
SOEM
的移植主要是对网络驱动的实现。 oshw.h/.c 主要提供字节顺序转换的服务。数据在网络上传输时,总是先传 输高位比特,然后传低位比特。所以,网络采用的是大端方式。而具体一个硬 件平台,可能采用大端也可能是小端。
nicdrv
模块的逻辑结构基本相同,需要实现的是三个底层
API
:
1
)网口的初始化
MAC
层帧发送
MAC
层帧接收
3、中间层
的中间层,是
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源代码目录介绍:
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, , &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];
顺便也说一下这个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当中发现一个问题:
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/110811.html

