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,                  // .manualstatechangeNULL,               // .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 modeport->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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/87337.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/87337.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/87337.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

84、原理解析-SpringApplication创建初始化流程

84、原理解析-SpringApplication初始化流程 # SpringApplication创建初始化流程原理解析 SpringApplication的创建和初始化是Spring Boot应用启动的关键步骤&#xff0c;主要包括以下过程&#xff1a; ## 1. 创建SpringApplication实例 ### 1.1 调用构造函数 - 当调用SpringApp…

【数理逻辑】 选择公理与集值映射

目录 选择公理1. 有限指标集 I I I2. 可数无限指标集 I I I &#xff08;简称为 ACC 或 ACω&#xff09;3. 不可数无限指标集 I I I4. 选择公理的层级与数学应用5. 选择公理的深层意义 集值映射的选择函数1. 选择公理的核心作用2. 不同情况下的依赖性分析3. AC 的必要性证明…

微信小程序使用wx.chooseImage上传图片时进行压缩,并添加时间水印

在微信小程序的开发过程&#xff0c;经常会使用自带的api(wx.chooseImage)进行图片拍照或选择图片进行上传&#xff0c;有时图片太大&#xff0c;造成上传和下载时过慢&#xff0c;现对图片进行压缩后上传&#xff0c;以下是流程和代码 一、小程序的版本选择了3.2.5&#xff0…

RAII简介

&#x1f4e6; 一、技术原理简介&#xff1a;RAII是个“托管狂魔” 想象你有个健忘的朋友&#xff0c;每次借东西都会忘记归还。RAII&#xff08;Resource Acquisition Is Initialization&#xff0c;资源获取即初始化&#xff09;就是C派来的“超级管家”&#xff1a; “你负…

微信小程序入门实例_____打造你的专属单词速记小程序

上次通过天气查询小程序&#xff0c;我们初探了微信小程序开发的世界。这次&#xff0c;咱们再挑战一个有趣又实用的项目 ——“单词速记小程序”。无论是学生党备考&#xff0c;还是上班族提升英语&#xff0c;都能用得上&#xff01;接下来就跟着我&#xff0c;一步一步把它做…

gateway白名单存储nacos,改成存储数据库

前言 很久没写博客了&#xff0c;csdn都开始ai润色了&#xff0c;之前都是看相应框架的源码看了个遍&#xff0c;感觉底层原理都差不多&#xff0c;这阵子着手改造了下gateway中的白名单&#xff0c;之前白名单存储到nacos&#xff0c;要改成存到数据库。里面涉及到浅浅的源码…

ubentu服务器版本安装Dify

Docker 中安装Dify 首先安装Docker 1. 克隆Dify代码仓库 从github克隆 Dify 源代码至要本地环境。 我的ubentu服务器版本&#xff0c;我把源代码下载到 /var/下 在var文件夹下执行 git clone https://github.com/langgenius/dify.git执行成功后&#xff0c;进入Dify源代码的…

Redis分布式锁实战:从入门到生产级方案

目录 一、为什么需要分布式锁&#xff1f; 二、Redis分布式锁核心特性 三、实现方案与代码详解 方案1&#xff1a;基础版 SETNX EXPIRE 原理 代码示例 问题 方案2&#xff1a;Redisson框架&#xff08;生产推荐&#xff09; 核心特性 代码示例 优势 方案3&#xff…

【Redis】StringRedisTemplate 和 RedisTemplate 的区别

StringRedisTemplate 和 RedisTemplate 是 Spring Data Redis 提供的两种用于操作 Redis 的模板类&#xff0c;它们的核心区别在于 序列化方式 和 操作的数据类型。以下是两者的主要区别和使用建议&#xff1a; ✅ 1. 数据类型支持 类名支持的数据类型说明RedisTemplate支持所…

docker-compose快速搭建redis集群

目录结构 redis-cluster/ ├── config/ │ ├── master.conf │ ├── slave1.conf │ └── slave2.conf └── docker-compose.yml配置文件内容 1. config/master.conf # Redis主节点配置 port 6379 bind 0.0.0.0 protected-mode no logfile "redis-mas…

SpringCloud系列(39)--SpringCloud Gateway常用的Route Predicate

前言&#xff1a;在上一节中我们实现了SpringCloud Gateway的动态路由 &#xff0c;而在本节中我们将着重介绍各种Route Predicate的作用。 1、可以到官方文档里查看常用的Route Predicate的种类 https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.REL…

渐变色的进度条控件

近日&#xff0c;用VB.net2003重写了一个渐变色的进度条控件。主要有以下功能&#xff1a; 支持自定义进度条分段数量&#xff0c;可拆分为多个步骤&#xff1b;每个步骤可独立显示完成百分比及渐变色效果。 每个步骤均可配置任务名称和描述&#xff1b;运行时能实时显示当前执…

【DICOM后处理】qt+vs 实现DICOM数据四视图显示

目录 1、DICOM四视图2、vtkImageViewer2 实现二维平面图显示3、vtkVolume实现三维体数据显示4、实现界面图 1、DICOM四视图 DICOM四视图通常指同时显示医学影像的四个不同平面或视角&#xff0c;用于全面分析三维数据&#xff08;如CT、MRI等&#xff09;。 标准四视图布局&a…

Google Maps 安装使用教程

一、Google Maps 简介 Google Maps 是谷歌提供的地图服务&#xff0c;通过其 JavaScript API&#xff0c;开发者可以在网页中嵌入地图&#xff0c;添加标记、路径、地理编码、路线导航等功能&#xff0c;适用于位置展示、物流追踪、LBS 应用等场景。 二、获取 Google Maps API…

Nginx+Keepalived实现前台服务高可用

现阶段项目开发往往采用前后台分离&#xff0c;前台常用的技术有vue、react等&#xff0c;前台代码部署在nginx中&#xff0c;代码中配置了后台服务的网关地址&#xff0c;由网关向后台分发服务请求&#xff0c;架构示意图如下&#xff1a; 在上述架构图中&#xff0c;如果Ngin…

Gradio全解13——MCP协议详解(5)——Python包命令:uv与uvx实战

Gradio全解13——MCP协议详解&#xff08;5&#xff09;——Python包命令&#xff1a;uv与uvx实战 第13章 MCP协议详解13.5 Python包命令&#xff1a;uv与uvx实战13.5.1 uv核心亮点与常用命令1. uv介绍2. 安装与项目管理3. 脚本与工具4. Python版本与pip接口 13.5.2 uv核心指令…

OD 算法题 B卷【求最小步数】

文章目录 求最小步数 求最小步数 求从坐标零点到坐标点n的最小步数&#xff0c;一次只能沿着横坐标轴向左或向右移动2或3&#xff1b;途经的坐标点可以为负数&#xff1b; 输入描述: 坐标点n 输出描述: 从坐标零点移动到坐标点n的最小步数 n在【1,10^9】 示例1 输入&#xf…

Elasticsearch 集群升级实战指引—7.x 升级到 8.x

升级Elasticsearch集群从7.x到8.x是一项复杂且关键的任务&#xff0c;涉及重大版本变更&#xff08;如API调整、配置变更、安全功能强制启用等&#xff09;&#xff0c;可能影响集群的性能和稳定性。结合您提到的业务量增长导致索引写入变慢的问题&#xff0c;本指引不仅提供详…

JWT学习总结

文章目录 前置知识Authorization头部和 CookieCRSF攻击 JWT概念JWT认证流程使用Springboot整合JWTJwtUtil JWT案例控制器JWT拦截器注册拦截器结果 session VS Jwt 前置知识 Authorization头部和 Cookie Authorization 头部和 Cookie 是 HTTP 协议中两种不同的身份认证 / 信息…

阿里云消息队列 Apache RocketMQ 创新论文入选顶会 ACM FSE 2025

近日&#xff0c;由阿里云消息团队发表的 Apache RocketMQ 创新论文被 CCF-A 类软件工程顶级会议 FSE 2025 Industry Track 录用。 ACM FSE&#xff08;The ACM International Conference on the Foundations of Software Engineering&#xff09;是享有盛誉的国际学术会议&…