目录
1、SOEM 库架构分析
2、硬件抽象层
3、中间层
4、SOEM1.3.1源代码目录介绍:
SOEM 库采用分层设计,并且提供了一个抽象层,将 SOEM 协议栈与具体 操作系统和硬件分开,使得 SOEM 在理论上可以移植到任意操作系统和硬件平 台之上。
抽象层由 OSAL 和 OSHW 两个模块组成,OSAL 是操作系统抽象层,OSHW 是硬件抽象层,移植的主要内容就是对 OSAL 和 OSHW 具体 API 实现,在新的 操作系统和硬件平台上的重写。注意,SOEM 可以不需要 OS 操作系统,直接在时间和定时器相关的 API 是必须实现的。
层 | 功能 |
ethercatbase |
将工业应用数据组装成 EtherCAT 帧,
顺序寻址、广播方式、配置地址、逻辑地址方式对从站读写
|
ethercatmain |
读写从站 EEPROM ,
提供邮箱模式的非过程数据读写和三缓冲模式的过程数据 PDO 读写
|
ethercatconfig |
初始化从站控制器的寄存器,配置从站 FMMU
|
ethercatdc |
提供分布式时钟,实现主从站之间时钟同步
|
ethercatsoe |
CANOpen Over EtherCAT
|
ethercatfoe |
File Over EtherCAT
|
ethercatsoe |
Sercos Over EtherCAT
|
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当中发现一个问题: