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