Ethercat学习-SOEM主站源码解析(DC部分)

文章目录

        • SOEM DC模式源码简介
        • 示例用图
        • ecx_porttime
        • ecx_parentport
        • ecx_configdc
          • 如果从站不支持DC
          • 如果从站支持DC

SOEM DC模式源码简介
示例用图

本文中都会围绕着这个图来讲,从站的port编号依次为0,3,1,2

在这里插入图片描述

在SOEM中,与DC相关的文件是ethercatdc.c、ethercatdc.h。在这里面主要用到的是ecx_configdc、ecx_dcsync0、ecx_dcsync01、ecx_porttime、ecx_prevport、ecx_parentport

ecx_porttime
static int32 ecx_porttime(ecx_contextt *context, uint16 slave, uint8 port)
{
......
......
}

这个函数很简单,就是根据输入的从站编号和端口编号返回端口锁存的时间,也就是示例图中的tA0,tB0,tC0…tE1,tB2,tA1。

ecx_parentport
static uint8 ecx_parentport(ecx_contextt *context, uint16 parent)
{......return parentport;
}

查找与当前从站相连的前一个从站(parent)的端口。输入的是parent编号。当前从站的parent编号是在ecx_config_init中计算的。以示例图为例,SlaveA的parent是0,表示master;SlaveB的parent是1,表示从站1;SlaveC的parent是2,表示从站2;SlaveE的parent是2,表示从站2…这个函数默认输入端口是port0,然后从port3开始查找使用的端口,当找到使用端口后会返回该端口号,并将该端口的使用标记改为未打开,这样防止重复计算。例如SlaveC的parent是SlaveB,计算出为port1,然后将其标记为未打开,这样下次再计算SlaveE的parentport的时候就会跳过port1,得到port2。

ecx_configdc
context->slavelist[0].hasdc = FALSE;
context->grouplist[0].hasdc = FALSE;

初始化标志位为flase.

ht = 0; 
ecx_BWR(context->port, 0, ECT_REG_DCTIME0, sizeof(ht), &ht, EC_TIMEOUTRET); 
mastertime = osal_current_time();
mastertime.sec -= 946684800UL;  
mastertime64 = (((uint64)mastertime.sec * 1000000) + (uint64)mastertime.usec) * 1000;
  1. 通过广播写的方式写寄存器0x900。各个端口会锁存数据帧第一个前导码到达的时间。0~3总共四个端口锁存的时间分别存储于0x900、0x904、0x908、0x90C四个地址中。
  2. 获取主站当前的时间,并将时间转换为基于2001-01-01的ns(纳秒)时间
   for (i = 1; i <= *(context->slavecount); i++){context->slavelist[i].consumedports = context->slavelist[i].activeports;if (context->slavelist[i].hasdc){............}else{............}}

轮询配置每个从站,首先判断从站是否包含DC模块。

  1. hasdc:查看从站是否包含DC,这个变量是在ecx_config_init函数中进行赋值的。在该函数中读取寄存器0x008的值。通过结果的bit2来判断是否包含DC

  2. topology:表示ESC中打开的端口个数。这个变量是在ecx_config_init函数中进行赋值的。在该函数中读取寄存器0x110的值。通过结果来判断端口是否打开,每打开一个,topology加1。

  3. activeports:表示使用的端口,bit0bit3分别对应port0port3,置1表示打开。

如果从站不支持DC
 context->slavelist[i].DCrtA = 0;context->slavelist[i].DCrtB = 0;context->slavelist[i].DCrtC = 0;context->slavelist[i].DCrtD = 0;parent = context->slavelist[i].parent;/* if non DC slave found on first position on branch hold root parent */if ( (parent > 0) && (context->slavelist[parent].topology > 2))parenthold = parent;/* if branch has no DC slaves consume port on root parent */if ( parenthold && (context->slavelist[i].topology == 1)){ecx_parentport(context, parenthold);parenthold = 0;}
  1. 将当前从站各个端口锁存的时间清0,获取当前从站的parent编号。
  2. 如果parent使用的端口数大于2,说明parent上面连接了多个从站,当前从站是一个分支的第一个从站。用parenthold记录下parent。
  3. 如果当前从站只是用了一个端口,说明当前从站是一个分支的最后一个从站。如果parenthold不等于0,说明分支中所有的从站都不支持DC模块。因为如果有一个从站包含DC模块,parenthold被置0。调用ecx_parentport将这条分支所连接的parent端口标记为未使用,防止后续计算的时候连接端口搞错。例如示例用图,假设slaveC、slaveD均不包含DC 模块,那么在slaveC的时候进入else的会触发第一个if判断,通过parenthold记住slaveC的parent,后续slaveD再进入else的时候,会进入第二个if判断,最后通过调用ecx_parentport来将port1标记为未使用。这样以后在获取slaveE的parentport的时候就是port2了。
如果从站支持DC
         if (!context->slavelist[0].hasdc){context->slavelist[0].hasdc = TRUE;context->slavelist[0].DCnext = i;context->slavelist[i].DCprevious = 0;context->grouplist[context->slavelist[i].group].hasdc = TRUE;context->grouplist[context->slavelist[i].group].DCnext = i;}else{context->slavelist[prevDCslave].DCnext = i;context->slavelist[i].DCprevious = prevDCslave;}/* this branch has DC slave so remove parenthold */parenthold = 0;prevDCslave = i;
  1. 如果当前节点是第一个包含DC的节点,则将节点0的下一个节点编号指向当前DC节点;将当前节点的前一个节点编号指向节点0;

  2. 如果不是第一个包含DC的节点,则将前一个DC节点的下一个节点编号指向当前节点,将当前节点的上一个节点编号指向上一个节点。将所有包含DC的节点记录成一个链表

  3. 将parenthold清0,保存当前从站编号到prevDCslave,下一个循环使用

         (void)ecx_FPRD(context->port, slaveh, ECT_REG_DCTIME0, sizeof(ht), &ht, EC_TIMEOUTRET);context->slavelist[i].DCrtA = etohl(ht);/* 64bit latched DCrecvTimeA of each specific slave */(void)ecx_FPRD(context->port, slaveh, ECT_REG_DCSOF, sizeof(hrt), &hrt, EC_TIMEOUTRET);/* use it as offset in order to set local time around 0 + mastertime */hrt = htoell(-etohll(hrt) + mastertime64);/* save it in the offset register */(void)ecx_FPWR(context->port, slaveh, ECT_REG_DCSYSOFFSET, sizeof(hrt), &hrt, EC_TIMEOUTRET);(void)ecx_FPRD(context->port, slaveh, ECT_REG_DCTIME1, sizeof(ht), &ht, EC_TIMEOUTRET);context->slavelist[i].DCrtB = etohl(ht);(void)ecx_FPRD(context->port, slaveh, ECT_REG_DCTIME2, sizeof(ht), &ht, EC_TIMEOUTRET);context->slavelist[i].DCrtC = etohl(ht);(void)ecx_FPRD(context->port, slaveh, ECT_REG_DCTIME3, sizeof(ht), &ht, EC_TIMEOUTRET);context->slavelist[i].DCrtD = etohl(ht);
  1. 读取从站port0、port1、port2、port3的锁存的时间,分别存储在DCrtA、DCrtB、DCrtC、DCrtD中。
  2. 读取从站0x0918的从站本地时间,以主站当前的时间为系统时间,计算从站时间与本地时间的偏差。
  3. 将时间偏差写入0x0920中。
 nlist = 0;if (context->slavelist[i].activeports & PORTM0){plist[nlist] = 0;tlist[nlist] = context->slavelist[i].DCrtA;nlist++;}if (context->slavelist[i].activeports & PORTM3){plist[nlist] = 3;tlist[nlist] = context->slavelist[i].DCrtD;nlist++;}if (context->slavelist[i].activeports & PORTM1){plist[nlist] = 1;tlist[nlist] = context->slavelist[i].DCrtB;nlist++;}if (context->slavelist[i].activeports & PORTM2){plist[nlist] = 2;tlist[nlist] = context->slavelist[i].DCrtC;nlist++;}

根据从站端口的激活情况,将端口号和对应端口的所存时间分别存放在plist和tlist中。

 entryport = 0;if((nlist > 1) && (tlist[1] < tlist[entryport])){entryport = 1;}if((nlist > 2) && (tlist[2] < tlist[entryport])){entryport = 2;}if((nlist > 3) && (tlist[3] < tlist[entryport])){entryport = 3;}entryport = plist[entryport];context->slavelist[i].entryport = entryport;/* consume entryport from activeports */context->slavelist[i].consumedports &= (uint8)~(1 << entryport);

根据端口的时间来找出从站输入端口,时间最早的那个是输入的端口。

 parent = i;do{child = parent;parent = context->slavelist[parent].parent;}while (!((parent == 0) || (context->slavelist[parent].hasdc)));

找出距离当前从站最近的一个支持DC的从站。从站的编号保存在parent中。

context->slavelist[i].parentport = ecx_parentport(context, parent);
if (context->slavelist[parent].topology == 1)
{context->slavelist[i].parentport = context->slavelist[parent].entryport;
}

获取parent从站与当前节点相连接的端口号。

如下图所示的特殊从站连接情况,从站A的输入端口不是端口0,而是端口2。在这种情况下,从站B被SOEM主站识别为slave1,从站C为slave2,从站A为slave3。在这种连接情况下,从站A的parent是从站C,端口使用数量为1,context->slavelist[parent].topology == 1成立。

在这里插入图片描述

dt1 = 0;
dt2 = 0;
/* delta time of (parentport - 1) - parentport */
/* note: order of ports is 0 - 3 - 1 -2 */
/* non active ports are skipped */
dt3 = ecx_porttime(context, parent, context->slavelist[i].parentport) -ecx_porttime(context, parent,ecx_prevport(context, parent, context->slavelist[i].parentport));
/* current slave has children */
/* those children's delays need to be subtracted */
if (context->slavelist[i].topology > 1)  
{dt1 = ecx_porttime(context, i,ecx_prevport(context, i, context->slavelist[i].entryport)) -ecx_porttime(context, i, context->slavelist[i].entryport);
}
/* we are only interested in positive difference */
if (dt1 > dt3) dt1 = -dt1;

计算传输延时,dt3就是parent从站中(输出端口的时间-输入端口的时间);dt1就是当前从站中(输出端口的时间-输入端口时间),公式之前推理过,可以套用公式来理解。

当dt1>dt3的时候,其实就是前面的特殊连接情况时发生的,此时dt3 = 0,因此dt1 = -dt1,为了后面计算延时为正数。

if ((child - parent) > 1)
{dt2 = ecx_porttime(context, parent,ecx_prevport(context, parent, context->slavelist[i].parentport)) -ecx_porttime(context, parent, context->slavelist[parent].entryport);
}
if (dt2 < 0) dt2 = -dt2;

(child - parent)>1,说明child和parent中间不止一个从站,就像示例用途的slaveE 和slaveB。此时计算延时就需要加上信号经过slaveC、slaveD的延时时间。dt2就是这段时间。

context->slavelist[i].pdelay = ((dt3 - dt1) / 2) + dt2 +context->slavelist[parent].pdelay;
ht = htoel(context->slavelist[i].pdelay);
/* write propagation delay*/
(void)ecx_FPWR(context->port, slaveh, ECT_REG_DCSYSDELAY, sizeof(ht), &ht, EC_TIMEOUTRET);

计算参考从站到当前从站的延时,并将数据写入到从站寄存器0x920之中。

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

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

相关文章

【vulnhub】Broken: Gallery靶机

靶机安装 下载地址&#xff1a;Broken: Gallery ~ VulnHub 信息收集 靶机IP发现 nmap 192.168.93.0/24 端口扫描 nmap -A 192.168.93.167 -p- 目录扫描 dirsearch -u http://192.168.93.167 页面访问&#xff0c; 没有可用的信息 尝试22端口的ssh进行爆破 hydra -L roc…

算法的学习笔记——二进制中 1 的个数(牛客JZ15)

&#x1f600;前言 在计算机科学中&#xff0c;二进制是计算和存储数据的基础。理解二进制中的基本运算有助于我们解决各种编程问题。一个经典的问题是&#xff1a;给定一个整数&#xff0c;如何快速计算该整数的二进制表示中1的个数。 &#x1f3e0;个人主页&#xff1a;尘觉主…

【计算机毕设】基于SpringBoot的教育局综合信息管理平台-学生端

&#x1f497;博主介绍&#xff1a;✌全平台粉丝5W,高级大厂开发程序员&#x1f603;&#xff0c;博客之星、掘金/知乎/华为云/阿里云等平台优质作者。 【源码获取】关注并且私信我 【联系方式】&#x1f447;&#x1f447;&#x1f447;最下边&#x1f447;&#x1f447;&…

在Windows上用Visual Studio编译OpenCV

在Windows上编译开源项目&#xff0c;有时候让人痛不欲生&#xff0c;有时候却出奇地顺利。OpenCV属于后者。本文记录这次愉快的过程。 注&#xff1a;OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和机器学习软件库。它提供了大…

全面介绍 Apache Doris 数据灾备恢复机制及使用示例

引言 Apache Doris 作为一款 OLAP 实时数据仓库&#xff0c;在越来越多的中大型企业中逐步占据着主数仓这样的重要位置&#xff0c;主数仓不同于 OLAP 查询引擎的场景定位&#xff0c;对于数据的灾备恢复机制有比较高的要求&#xff0c;本篇就让我们全面的介绍和示范如何利用这…

PyTorch 基础学习(3) - 张量的数学操作

下面是关于PyTorch中常见数学操作的概述和教程&#xff0c;包括逐点运算、比较操作、线性代数操作等&#xff0c;突出每个操作的重点用法和示例。 逐点操作 (Pointwise Operations) 1. torch.abs 功能: 计算输入张量的每个元素的绝对值。用法: torch.abs(input)示例:import …

c++继承(二)

一、友元函数的继承 友元函数不能被继承&#xff0c;就像爸爸的朋友不是你的朋友&#xff0c;如果要有友元函数&#xff0c;在子类重新定义一个。 二、静态成员的继承 静态成员的继承仍然是那个成员&#xff0c;普通成员的继承是不同的。 父类的静态成员属于当前类&#xf…

20240813 每日AI必读资讯

Flux生成网红博主因太逼真爆火&#xff01;有人用Claude写代码识破“AI美女” - Flux生成的情侣合照逼真程度达到恐怖级别&#xff0c;挑战人类视觉辨识能力。 - 网友发现Flux生成的照片几乎完美&#xff0c;但仍有细微瑕疵可供识别。 - 有人利用Flux等工具制作逼真的YouTub…

[upload]-[GXYCTF2019]BabyUpload1-笔记

尝试上传.htaccess和图片和一句话木马提示 php文件提示 响应头可以看到 构造一句话图片木马如下&#xff1a; <script languagephp>eval($_POST[cmd]);</script> 上传成功 必须增加文件夹下jpg后缀解析php .htaccess如下 <FilesMatch "jpg">Set…

浙大数据结构慕课课后题(06-图2 Saving James Bond - Easy Version)(拯救007)

题目要求&#xff1a; This time let us consider the situation in the movie "Live and Let Die" in which James Bond, the worlds most famous spy, was captured by a group of drug dealers. He was sent to a small piece of land at the center of a lake fi…

LabVIEW中CANopen 读取程序解读

这段程序用于创建 CANopen 接口&#xff0c;并读取 CANopen CAN 帧消息。以下是详细的解读&#xff1a; 左侧部分 node-ID (U8): 指定节点 ID&#xff0c;用于标识 CANopen 网络中的设备。CANopen interface (U32): 指定 CANopen 接口。baud rate (U32): 设置波特率&#xff0…

vulnhub系列:sp eric

vulnhub系列&#xff1a;sp eric 靶机下载 一、信息收集 nmap扫描存活&#xff0c;根据mac地址寻找IP nmap 192.168.23.0/24nmap扫描端口&#xff0c;开放端口&#xff1a;22、80 nmap 192.168.23.189 -p- -A -sV -Pndirb 扫描目录&#xff0c;.git 源码&#xff0c;admin…

向上or向下调整建堆 的时间复杂度的本质区别的讲解

知识点&#xff1a;&#xff08;N代表节点数&#xff0c;h代表高度&#xff09; 1&#xff1a;高度为h的满二叉树节点个数N为 2^&#xff08;h&#xff09;-1 即N 2^&#xff08;h&#xff09;-1 2&#xff1a;所以h log&#xff08;N1&#xff09; 一&#xff1a;向上…

C++STL详解(四)——vector类的具体实现

在上篇文章中&#xff0c;我们已经学习了vector的具体接口使用方法&#xff0c;在本篇文章中&#xff0c;我们将学习实现一个vector容器。 目录 一.vector各函数接口总览 二.vector当中的私有成员 三.默认成员函数 3.1构造函数 3.1.1构造函数1 3.1.2构造函数2 3.1.3构造…

百数移动端重大更新:全面优化,用户体验再升级!

本次发布的优化更新的功能&#xff0c;主要是为了提升用户的移动端使用体验。此次改版不仅优化了控件样式&#xff0c;提升视觉与交互体验&#xff0c;还在子表单功能上实现了重大突破&#xff0c;如新增复制、插入行功能等。 同时&#xff0c;新增功能——数据加载&#xff0…

Python之简单了解pylab绘图工具和汇编语言

《Python入门经典以解决计算问题为导向的Python编程实践》89-93页的笔记。 用pylab对数据绘图最小的通用计算 用pylab对数据绘图 PyLab是Matplotlib面向对象绘图库的过程界面。Matplotlib是整个软件包&#xff1b; matplotlib.pyplot是Matplotlib中的一个模块&#xff1b;而P…

【物联网】(蓝牙篇)微信小程序ios如何自动打开蓝牙

微信小程序打开蓝牙的便捷之道——微信小程序ios如何自动打开蓝牙 随着智能手机蓝牙技术和物联网产品的普及&#xff0c;很多人在使用微信小程序时&#xff0c;都希望能够更便捷地打开蓝牙功能。 在iOS系统上&#xff0c;由于其封闭性和权限控制严格&#xff0c;使得自动打开蓝…

扩散模型理论与公式推导——详细过程速览与理解加深

参考&#xff1a; [1] Ho J, Jain A, Abbeel P. Denoising diffusion probabilistic models[J]. Advances in neural information processing systems, 2020, 33: 6840-6851. [2] 扩散模型/Diffusion Model原理讲解_哔哩哔哩_bilibili [3] 扩散模型公式推导_扩散模型数学推导-C…

10、java程序流程控制之二:分支语句(switch-case结构)、循环结构(for循环)(经典案例)

java程序流程控制之二&#xff1a; Ⅰ、分支语句&#xff1a;switch-case1、switch-case 分支结构&#xff1a;其一、描述&#xff1a;其二、代码为&#xff1a;其三、截图为&#xff1a; 2、switch-case 分支结构的案例1&#xff1a;判断是否合格其一、描述&#xff1a;其二、…

Docker数据管理和网络管理

文章目录 一、Docker数据管理Docker容器的分层哪些数据需要持久化容器数据持久保存方式数据卷&#xff08;data volume&#xff09;数据卷的使用场景数据卷的特点数据卷使用方法实际例子 二、网络管理Docker安装完成后默认的网络设置创建容器后的网络配置修改默认网络设置容器名…