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 库架构如图 所示:
EtherCAT主站——SOEM(学习笔记1)_第1张图片

         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

EtherCAT主站——SOEM(学习笔记1)_第2张图片

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

EtherCAT主站——SOEM(学习笔记1)_第3张图片

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数据帧格式就一目了然了:
EtherCAT主站——SOEM(学习笔记1)_第4张图片

顺便也说一下这个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当中发现一个问题:

你可能感兴趣的:(SOEM,linux,运维,服务器)