EtherCAT SOEM源码分析 - ec_init

ec_init

SOEM主站一切开始的地方始于ec_init, 它是EtherCAT主站初始化的入口。初始化SOEM 主站,并绑定到socket到ifname。

/** Initialise lib in single NIC mode
 * @param[in] ifname   = Dev name, f.e. "eth0"
 * @return >0 if OK
 * @see ecx_init
 */
int ec_init(const char * ifname)
{
   return ecx_init(&ecx_context, ifname);
}
  1. ifname 名词解释
    ifname 是网络接口名称(interface name)的缩写。在 SOEM 的 slaveinfo 程序中,ifname 用于指定要绑定和通信的本地网络接口,比如 “eth0”、“enp2s0”、“eno1” 等。
  2. NIC名词解释
    NIC(Network Interface Card,网络接口卡)是计算机或其他设备用于连接计算机网络的硬件设备。它通常也被称为“网卡”。NIC 负责在设备和网络之间进行数据的收发和转换,实现设备与局域网(LAN)、广域网(WAN)等网络的物理连接。常见的 NIC 类型包括以太网卡(Ethernet Card)、无线网卡(Wi-Fi Card)等。在 EtherCAT 等工业通信场景中,NIC 就是主站或从站与网络通信的物理接口。

ecx_init

这段代码定义了一个名为 ecx_init 的函数,用于初始化 EtherCAT 通信上下文。函数有两个参数:context 是指向 ecx_contextt 结构体的指针,表示 EtherCAT 的上下文环境;ifname 是一个字符串,表示要使用的网络接口名称(如 “eth0”)。在函数体内,ecx_init 调用了 ecx_setupnic 函数,并传递了 context->port、ifname 和 FALSE 作为参数。这里,context->port 是ecx_portt类型的指针,指向了ethercatmain.c中的ecx_port,ifname 指定了网络接口名称,FALSE 通常表示不启用冗余模式。ecx_setupnic 负责实际的网络接口初始化工作。

/** Initialise lib in single NIC mode
 * @param[in]  context = context struct
 * @param[in] ifname   = Dev name, f.e. "eth0"
 * @return >0 if OK
 */
int ecx_init(ecx_contextt *context, const char * ifname)
{
   return ecx_setupnic(context->port, ifname, FALSE);
}

ecx_setupnic 的实现因平台而异,这部分实现位于oshw层,支持多种不同的平台,譬如linux, rtk, vxworks, macosx,win32等等。

ecx_conext定义

从ecx_conext变量定义可以看出,其内容几乎都是指向具体业务逻辑的指针,在初始化时根据实际情况进行初始化。

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     =
    &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
    NULL,               // .userdata
};

ecx_setupnic(linux)

这段代码实现了 EtherCAT 主站库中用于将网络接口卡(NIC)与原始套接字(RAW socket)连接的基础设置函数 ecx_setupnic。其主要作用是为 EtherCAT 通信初始化网络端口,支持主/冗余两种模式。secondary为FALSE,这里暂不分析冗余模式。在非冗余模式中,初始化互斥锁(用于多线程安全),重置端口状态和缓冲区指针,并同样清空接收缓冲区状态。

接下来,函数通过 socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ECAT)) 创建一个原始套接字,用于直接收发 EtherCAT 协议的数据包。如果套接字创建失败,函数直接返回 0 表示失败。

随后,代码设置了套接字的接收和发送超时时间,并启用 SO_DONTROUTE 选项,确保数据包不经过路由器。通过 ioctl 系列调用,函数获取并设置指定网卡(ifname)的接口索引和标志位,将网卡设置为混杂模式和广播模式,以便能够收发所有经过的数据包。

然后,函数将套接字绑定到指定的网络接口和 EtherCAT 协议。最后,为所有发送缓冲区预先设置以太网头部,并将接收缓冲区状态初始化为“空”。如果所有操作都成功,函数返回 1,否则返回 0。

/** Basic setup to connect NIC to socket.
 * @param[in] port        = port context struct
 * @param[in] ifname      = Name of NIC device, f.e. "eth0"
 * @param[in] secondary   = if >0 then use secondary stack instead of primary
 * @return >0 if succeeded
 */
int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary)
{
   int i;
   int r, rval, ifindex;
   struct timeval timeout;
   struct ifreq ifr;
   struct sockaddr_ll sll;
   int *psock;
   pthread_mutexattr_t mutexattr;

   rval = 0;
   if (secondary)
   {
      /* secondary port struct available? */
      if (port->redport)
      {
         /* when using secondary socket it is automatically a redundant setup */
         psock = &(port->redport->sockhandle);
         *psock = -1;
         port->redstate                   = ECT_RED_DOUBLE;
         port->redport->stack.sock        = &(port->redport->sockhandle);
         port->redport->stack.txbuf       = &(port->txbuf);
         port->redport->stack.txbuflength = &(port->txbuflength);
         port->redport->stack.tempbuf     = &(port->redport->tempinbuf);
         port->redport->stack.rxbuf       = &(port->redport->rxbuf);
         port->redport->stack.rxbufstat   = &(port->redport->rxbufstat);
         port->redport->stack.rxsa        = &(port->redport->rxsa);
         ecx_clear_rxbufstat(&(port->redport->rxbufstat[0]));
      }
      else
      {
         /* fail */
         return 0;
      }
   }
   else
   {
      pthread_mutexattr_init(&mutexattr);
      pthread_mutexattr_setprotocol(&mutexattr  , PTHREAD_PRIO_INHERIT);
      pthread_mutex_init(&(port->getindex_mutex), &mutexattr);
      pthread_mutex_init(&(port->tx_mutex)      , &mutexattr);
      pthread_mutex_init(&(port->rx_mutex)      , &mutexattr);
      port->sockhandle        = -1;
      port->lastidx           = 0;
      port->redstate          = ECT_RED_NONE;	// No redundancy, single NIC mode
      port->stack.sock        = &(port->sockhandle);
      port->stack.txbuf       = &(port->txbuf);
      port->stack.txbuflength = &(port->txbuflength);
      port->stack.tempbuf     = &(port->tempinbuf);
      port->stack.rxbuf       = &(port->rxbuf);
      port->stack.rxbufstat   = &(port->rxbufstat);
      port->stack.rxsa        = &(port->rxsa);
      ecx_clear_rxbufstat(&(port->rxbufstat[0]));
      psock = &(port->sockhandle);
   }
   /* we use RAW packet socket, with packet type ETH_P_ECAT */
   *psock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ECAT));
   if(*psock < 0)
      return 0;

   timeout.tv_sec =  0;
   timeout.tv_usec = 1;
   r = 0;
   r |= setsockopt(*psock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
   r |= setsockopt(*psock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
   i = 1;
   r |= setsockopt(*psock, SOL_SOCKET, SO_DONTROUTE, &i, sizeof(i));
   /* connect socket to NIC by name */
   strcpy(ifr.ifr_name, ifname);
   r |= ioctl(*psock, SIOCGIFINDEX, &ifr);
   ifindex = ifr.ifr_ifindex;
   strcpy(ifr.ifr_name, ifname);
   ifr.ifr_flags = 0;
   /* reset flags of NIC interface */
   r |= ioctl(*psock, SIOCGIFFLAGS, &ifr);
   /* set flags of NIC interface, here promiscuous and broadcast */
   ifr.ifr_flags = ifr.ifr_flags | IFF_PROMISC | IFF_BROADCAST;
   r |= ioctl(*psock, SIOCSIFFLAGS, &ifr);
   /* bind socket to protocol, in this case RAW EtherCAT */
   sll.sll_family = AF_PACKET;
   sll.sll_ifindex = ifindex;
   sll.sll_protocol = htons(ETH_P_ECAT);
   r |= bind(*psock, (struct sockaddr *)&sll, sizeof(sll));
   /* 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));
   if (r == 0) rval = 1;

   return rval;
}

互斥锁

pthread_mutexattr_setprotocol 是 POSIX 线程库中的一个函数,用于为互斥锁属性对象 mutexattr 指定互斥锁的协议类型。第二个参数 PTHREAD_PRIO_INHERIT 表示启用优先级继承机制。当多个线程竞争同一个互斥锁时,如果低优先级线程持有锁而高优先级线程等待锁,低优先级线程会临时“继承”高优先级线程的优先级,直到释放锁为止。这可以有效防止优先级反转问题,提高实时系统的可靠性。

pthread_mutexattr_init(&mutexattr);
pthread_mutexattr_setprotocol(&mutexattr  , PTHREAD_PRIO_INHERIT);
pthread_mutex_init(&(port->getindex_mutex), &mutexattr);
pthread_mutex_init(&(port->tx_mutex)      , &mutexattr);
pthread_mutex_init(&(port->rx_mutex)      , &mutexattr);

原始套接字

这一行代码的作用是创建一个原始套接字(RAW socket),用于直接收发 EtherCAT 协议的数据帧。

  • PF_PACKET 指定使用数据链路层(即直接操作以太网帧),
  • SOCK_RAW 表示原始套接字,可以收发未经内核协议栈处理的原始数据包,
  • htons(ETH_P_ECAT) 指定协议类型为 EtherCAT(以太网类型 0x88A4)。
    这与 EtherCAT 主站的底层通信密切相关。只有通过这种方式创建的套接字,主站程序才能直接与网卡进行 EtherCAT 帧的收发,而不经过 TCP/IP 协议栈,实现实时性和协议兼容性。这一行是整个 SOEM 网络初始化和数据收发的基础,后续所有的 bind、ioctl、setsockopt 等操作,都是围绕这个套接字进行配置和使用的。
*psock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ECAT));

与NIC绑定

通过 strcpy(ifr.ifr_name, ifname); 将网络接口名(如 “eth0”)复制到 ifr 结构体中。随后,ioctl(*psock, SIOCGIFINDEX, &ifr); 获取该接口的索引号,并存储到 ifindex 变量中。再次设置接口名后,将 ifr.ifr_flags 置零,并通过 ioctl(*psock, SIOCGIFFLAGS, &ifr); 重置接口标志位。

接下来,将接口标志位设置为混杂模式(IFF_PROMISC)和广播模式(IFF_BROADCAST),这样网卡可以接收所有经过的数据包,包括不是发给本机的数据包,这对于 EtherCAT 这类工业实时通信协议非常重要。通过 ioctl(*psock, SIOCSIFFLAGS, &ifr); 应用这些标志设置。

最后,配置 sll 结构体,将其设置为数据链路层(AF_PACKET),指定接口索引和 EtherCAT 协议类型(ETH_P_ECAT),并通过 bind 函数将套接字绑定到该网络接口和协议上。这样,后续通过该套接字收发的数据包就会直接在指定网卡和 EtherCAT 协议下进行传输。

/* connect socket to NIC by name */
strcpy(ifr.ifr_name, ifname);
r |= ioctl(*psock, SIOCGIFINDEX, &ifr);
ifindex = ifr.ifr_ifindex;
strcpy(ifr.ifr_name, ifname);
ifr.ifr_flags = 0;
/* reset flags of NIC interface */
r |= ioctl(*psock, SIOCGIFFLAGS, &ifr);
/* set flags of NIC interface, here promiscuous and broadcast */
ifr.ifr_flags = ifr.ifr_flags | IFF_PROMISC | IFF_BROADCAST;
r |= ioctl(*psock, SIOCSIFFLAGS, &ifr);
/* bind socket to protocol, in this case RAW EtherCAT */
sll.sll_family = AF_PACKET;
sll.sll_ifindex = ifindex;
sll.sll_protocol = htons(ETH_P_ECAT);
r |= bind(*psock, (struct sockaddr *)&sll, sizeof(sll));

初始化默认Ethernet headers

首先,for (i = 0; i < EC_MAXBUF; i++) 循环遍历所有发送缓冲区(txbuf)和接收缓冲区状态(rxbufstat)。在每次循环中,调用 ec_setupheader(&(port->txbuf[i])) 为每个发送缓冲区设置以太网头部,确保每个数据包都带有正确的EtherCAT帧头。随后,将对应的接收缓冲区状态 port->rxbufstat[i] 设置为 EC_BUF_EMPTY,表示该缓冲区当前为空,可以安全接收新数据。

循环结束后,单独对 port->txbuf2 也调用了一次 ec_setupheader,这通常是用于特殊用途的额外发送缓冲区,也需要初始化帧头。

for (i = 0; i < EC_MAXBUF; i++)
{
   ec_setupheader(&(port->txbuf[i]));
   port->rxbufstat[i] = EC_BUF_EMPTY;
}
ec_setupheader(&(port->txbuf2));

ec_setupheader

用于初始化以太网帧头部。参数 p 是一个指向以太网头部结构体(ec_etherheadert)的指针。

在函数内部,首先将 p 强制类型转换为 ec_etherheadert * 类型,并赋值给 bp。接下来,bp->da0、bp->da1 和 bp->da2 被设置为 0xffff,并通过 htons 函数进行主机字节序到网络字节序的转换。这三个字段共同表示以太网帧的目标MAC地址,这里被设置为广播地址(所有位为1),意味着该帧会被网络上的所有设备接收。

随后,bp->sa0、bp->sa1 和 bp->sa2 分别被设置为主站MAC地址(priMAC 数组中的值),同样经过字节序转换。这些字段表示以太网帧的源MAC地址。

最后,bp->etype 被设置为 ETH_P_ECAT,同样经过字节序转换,表示该帧的数据类型为 EtherCAT 协议。

/** Fill buffer with ethernet header structure.
 * Destination MAC is always broadcast.
 * Ethertype is always ETH_P_ECAT.
 * @param[out] p = buffer
 */
void ec_setupheader(void *p)
{
   ec_etherheadert *bp;
   bp = p;
   bp->da0 = htons(0xffff);
   bp->da1 = htons(0xffff);
   bp->da2 = htons(0xffff);
   bp->sa0 = htons(priMAC[0]);
   bp->sa1 = htons(priMAC[1]);
   bp->sa2 = htons(priMAC[2]);
   bp->etype = htons(ETH_P_ECAT);
}

/** ethernet header definition */
PACKED_BEGIN
typedef struct PACKED
{
   /** destination MAC */
   uint16  da0,da1,da2;
   /** source MAC */
   uint16  sa0,sa1,sa2;
   /** ethernet type */
   uint16  etype;
} ec_etherheadert;
PACKED_END
<think>好的,我需要帮助用户寻找关于EtherCAT SOEM的简单测试方法或示例。首先,用户提到了他们参考了站内的两个引用,一个是关于配置系统的步骤,另一个是关于SOEM的tutorial.txt文件的翻译,其中提到了ec_send_processdata和ec_receive_processdata函数的使用。用户现在想了解如何用SOEM进行简单测试。 我应该先回顾用户提供的引用内容。引用1是关于配置EtherCAT主站的步骤,包括创建符号链接和配置文件。这可能是在安装和配置SOEM环境时需要完成的步骤。引用2提到了SOEM中的两个关键函数:发送和处理数据帧的ec_send_processdata和ec_receive_processdata,以及如何进入OP状态。用户可能需要一个简单的示例代码来演示这些函数的使用,以及如何初始化和配置从站设备。 接下来,我需要组织一个步骤化的回答,确保结构清晰。可能需要包括以下步骤:环境配置、初始化主站、扫描从站、配置从站状态、进入操作状态、主循环中的数据发送和接收,以及最后的关闭步骤。同时,用户可能需要编译和运行的命令,比如使用gcc编译示例代码。 另外,用户可能需要了解如何检查从站是否正确连接,例如通过读取设备ID。在示例代码中,可能需要使用ecx_initecx_config_init函数来初始化和配置从站,然后使用ecx_statecheck函数来检查从站的状态。进入OP状态可能需要使用ecx_writestate函数。 需要确保代码示例中的关键函数和步骤都有解释,例如ec_send_processdata和ec_receive_processdata的作用,以及如何处理工作计数器(wkc)。此外,用户可能需要注意事项,比如权限问题,需要使用sudo运行程序,或者配置正确的网络接口。 最后,生成相关问题,帮助用户进一步深入,比如如何诊断从站连接问题,如何配置多个从站,或者如何处理数据同步问题。同时,确保引用正确,引用1和引用2在适当的位置标注。 现在,将这些思路整合成一个结构化的回答,确保每个步骤清晰,代码示例正确,并且注意事项和编译命令都包含在内。同时,按照用户的要求,使用中文回答,行内数学公式正确使用$...$,独立公式用$$...$$,不过在这个问题中可能不需要数学公式。检查是否有需要补充或修正的地方,比如是否需要更多细节关于配置文件的修改,或者如何确定从站的索引位置。</think>### EtherCAT SOEM简单测试方法 #### 1. 环境配置 首先需完成EtherCAT主站环境搭建,包括符号链接和配置文件配置[^1]: ```bash $ sudo ln -s /usr/local/etherlab/bin/ethercat /usr/bin/ $ sudo ln -s /usr/local/etherlab/etc/init.d/ethercat /etc/init.d/ethercat $ sudo mkdir -p /etc/sysconfig $ sudo cp /usr/local/etherlab/etc/sysconfig/ethercat /etc/sysconfig/ethercat ``` #### 2. 编写测试代码 以下是一个基于SOEM的**最小测试示例**,实现从站设备状态切换与数据收发: ```c #include <stdio.h> #include <stdlib.h> #include "ethercat.h" #define EC_TIMEOUTRET 2000 // 超时时间(ms) int main() { /* 初始化主站 */ if (ec_init("eth0") <= 0) { // 替换为实际网卡名 printf("Failed to initialize EtherCAT master\n"); return -1; } /* 扫描从站 */ if (ec_config_init(FALSE) <= 0) { printf("No slaves found\n"); return -1; } printf("%d slaves detected\n", ec_slavecount); /* 配置从站状态 */ ec_slave[0].state = EC_STATE_OPERATIONAL; ec_writestate(0); // 设置首个从站状态 /* 主循环 */ int wkc; while (1) { ec_send_processdata(); // 发送输出数据 wkc = ec_receive_processdata(EC_TIMEOUTRET); // 接收输入数据 if (wkc != EC_TIMEOUTRET) { printf("Working counter: %d\n", wkc); } } ec_close(); // 关闭主站 return 0; } ``` #### 3. 关键函数说明 - `ec_init()`: 初始化EtherCAT主站,参数为网络接口名 - `ec_config_init()`: 扫描并配置从站设备 - `ec_send_processdata()`: 发送过程数据(PDO) - `ec_receive_processdata()`: 接收过程数据,返回工作计数器(wkc)[^2] #### 4. 编译与运行 ```bash $ gcc test_soem.c -o test -lsoem $ sudo ./test # 需要root权限 ``` #### 5. 验证步骤 1. 确认物理连接:通过`ethercat slaves`命令检查从站是否在线 2. 观察输出:正常运行时应持续输出`Working counter`值 3. 状态检查:使用`ethercat states`确认从站进入$OP$状态 #### 注意事项 - 需确保网卡支持EtherCAT,建议使用Intel I210等专用网卡 - 若出现超时错误,检查网络接口名称和电缆连接 - 实际应用中需添加错误处理逻辑
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值