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

embedded/2024/9/25 14:35:28/

文章目录

        • 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.ppmy.cn/embedded/94389.html

相关文章

CRC32 JAVA C#实现

项目中用到CRC32进行校验得地方&#xff0c;需要用到C#和java进行对比&#xff1a; 一、C#实现&#xff1a; class CRC32Cls { protected ulong[] Crc32Table; //生成CRC32码表 public void GetCRC32Table() { ulong Crc; …

删除一个git项目的所有提交历史

1. 切换到仓库的根目录&#xff1a; 确保你处于要操作的 Git 仓库的根目录。 2. 删除所有本地分支的历史记录&#xff1a; 首先&#xff0c;切换到一个临时分支&#xff0c;以避免在 master 分支上直接操作&#xff1a; git checkout --orphan temp-branch 3. 添加所有文件…

想基于AI大模型创业,还想少交学费?这些坑你必须知道

‍ ‍ 自2022年末ChatGPT问世以来&#xff0c;国内的大模型赛道发生了翻天覆地的变化。作为一种类似蒸汽机工业革命的契机&#xff0c;谁都不想错过这波浪潮。在巨额资金的推动下&#xff0c;明星产品层出不穷。海外的OpenAI、Meta、Anthropic等&#xff0c;国内的智谱、MinMax…

面向对象编程与Scala:掌握核心概念与应用

面向对象编程与Scala&#xff1a;掌握核心概念与应用 1. 引言 Scala 是一种融合了面向对象编程&#xff08;OOP&#xff09;和函数式编程&#xff08;FP&#xff09;特性的编程语言。它为开发者提供了强大的工具来创建高效且灵活的软件。面向对象编程是一种编程范式&#xff…

JSON与Jsoncpp库:数据交换的灵活选择

目录 引言 一.JSON简介 二. Jsoncpp库概述 三. Jsoncpp核心类介绍 3.1 Json::Value类 3.2 序列化与反序列化类 四. 实现序列化 五. 实现反序列化 结语 引言 在现代软件开发中&#xff0c;数据交换格式扮演着至关重要的角色。JSON&#xff08;JavaScript Object Notati…

3D摄影棚布光软件:Set A Light 3D Studio for Mac 激活版

Set A Light 3D Studio 是一款专业的照明模拟软件&#xff0c;专为摄影师和电影制作人设计&#xff0c;用于规划和设计照片拍摄的照明效果。 以下是关于这款软件的一些主要特点和功能&#xff1a; 虚拟照明工作室&#xff1a;Set A Light 3D Studio 提供了一个虚拟的照明工作室…

完美解决浏览器的输入框自动填入时,黄色背景问题,以及图标被遮住问题(最新)

用图说话↓↓↓ 首先用代码解决黄色背景问题&#xff0c;box-shadow颜色设置透明即可&#xff0c;延时渲染时间可修改为更久 :deep(input:-webkit-autofill) {box-shadow: 0 0 0 1000px transparent !important;/* 浏览器记住密码的底色的颜色 */-webkit-text-fill-color: #f…

Linux使用学习笔记1到2 命令行与shell 基础运维命令

在学习使用ubuntu等各种喜他构建服务器的过程中遇到很多问题&#xff0c;意识到只是跟着网络的教程没办法管理好一个完整的应用部署和运行。遂开始学习linux基本知识&#xff0c;以应对服务器常见问题和软件的使用和维护。 shell 望文生义&#xff0c;大概意思是一个外壳&…