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

news2024/11/25 10:27:20

文章目录

        • 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.coloradmin.cn/o/2035920.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++11相关新特性(列表初始化、右值引用、可变参数模版)

目录 C11相关新特性 列表初始化 初始化简单变量 初始化容器 decltype关键字 C 11新增的容器 左值引用和右值引用 左值与右值 左值引用与右值引用 左值引用和右值引用的相互转化 右值引用的使用 拷贝构造函数与移动构造函数 赋值重载函数与移动赋值重载函数 元素插…

ZABBIX邮件监控发送信息

前言&#xff1a;本地邮箱&#xff0c;第三方邮箱&#xff0c;第三方邮箱加脚本 1、本地邮箱测试 #cd /home # ls laiyingx [rootzabbix ~]# vim /etc/postfix/main.cf /mydestination $myhostname, localhost.$mydomain, localhost,$mydomain [rootzabbix ~]# systemctl…

Python 函数返回yield还是return?这是个问题

如果你刚入门 Python&#xff0c;你可能之前没有遇到过yield。虽然它看起来很奇怪&#xff0c;但它是你编码工具库中的一个重要工具。在成为 Python 大师的道路上&#xff0c;你必须掌握它。 返回列表的函数 假设有一个函数&#xff0c;它可以一次性生成一系列值&#xff0c;…

代理服务器在HTTP请求中的应用:Ruby实例

摘要 在现代互联网架构中&#xff0c;代理服务器是不可或缺的组件&#xff0c;它提供了访问控制、数据加密、缓存和匿名访问等多种功能。本文将介绍代理服务器的基本概念&#xff0c;并以Ruby编程语言为例&#xff0c;展示如何在HTTP请求中使用代理服务器&#xff0c;包括设置…

树莓派4 AV没有视频输出

使用AV接口输出&#xff0c;没有画面 需要在config.txt文件中 增加配置 enable_tvout1config.txt 中的 dtoverlayvc4-kms-v3d 行末尾添加,composite&#xff1a; dtoverlayvc4-kms-v3d,composite默认情况下&#xff0c;输出 NTSC 复合视频。要选择不同的模式&#xff0c;请在…

python信息熵与信息增益

前言 最近在读几篇华为杯的优秀论文&#xff0c;都是关于数据预测相关的&#xff0c;准确来说是时间序列预测&#xff0c;在数据处理部分发现了一个有趣的内容“信息熵”&#xff0c;之前在周志华老师的西瓜书上决策树剪枝部分看到过&#xff0c;在数据降维的部分看到还是第一…

关于springboot的拦截器能力源码分析

首先你得有web环境&#xff0c;这个就不说了&#xff0c;springboot下很简单。 一、拦截器使用 我们先来使用一下拦截器。 步骤1、先创建一个Controller RestController RequestMapping("/test") public class MyController {GetMapping("/test/{name}"…

HAProxy原理及实例

目录 目录 haproxy简介 haproxy的基本信息 haproxy下载并查看版本 haproxy的基本配置信息 global配置 ​编辑多进程和多线程 启用多进程 启用多线程 haproxy开启多线程和多进程有什么用 proxies配置 defaults frontend backend listen socat工具 实例&#xff1a…

ESP32 SNTP 网络校时 钟表显示

8月12日(2) 例程环境&#xff1a;Windows 11、Visual Studio Code、IDF_V5.2.1、LVGL_V8.3.11、HelloBug ESP32 Pilot开发板 源码获取&#xff1a;https://item.taobao.com/item.htm?ftt&id652537645861 向商家索取对应源码 SNTP (Simple Network Time Protocol) 是一种简…

中科亿海微SoM模组——电机驱动板

电机驱动板 电机驱动板作为驱动电机的重要组成部分&#xff0c;被广泛应用于工业自动化、消费电子、汽车、家用电器等应用领域。在工业自动化中&#xff0c;电机驱动板主要用于控制机器人、数控机床、输送带等设备&#xff0c;确保其高效、精准地运行。在消费电子和家用电器中…

【ARM Coresight Debug 工具系列 -- Trace32 | ARM-DS5 | OpenOCD JLINK 关系与差】

请阅读【ARM Coresight SoC-400/SoC-600 专栏导读】 文章目录 常用debug工具差异介绍Trace32ARM DS-5OpenOCDJ-Link 关系与差异差异 示例比较使用 Trace32 进行实时跟踪使用 ARM DS-5 进行高级调试使用 OpenOCD 进行开源调试 Summary 常用debug工具差异介绍 在嵌入式系统开发和…

阿里淘天校招校招开始啦,欢迎投递~

淘天校招&校招开始啦&#xff0c;欢迎投递~ 后续继续推出技术类面试资料&#xff0c;有问题也可咨询哦&#xff01; 校招内推码&#xff08;25年10月前均有效&#xff09; 社招内推码&#xff08;长期有效&#xff09;

Tarjan(五)vDCC缩点

Tarjan(五) vDCC点双联通分量&#xff1a; 需要之前的前置知识&#xff0c;需要搞懂什么是割点。在tarjan(2)中有介绍到。 点双连通分量是指在一个无向图中&#xff0c;如果一个子图是点双连通的&#xff08;即去掉该子图中的任意一个节点后&#xff0c;剩余的图仍然是连通的&a…

电商平台产品ID|CDN与预渲染|前端边缘计算

技术实现 都是通过ID拿到属性&#xff0c;进行预渲染html&#xff0c;通过 oss 分发出去 详情页这种基本都是通过 ssr 渲染出来&#xff0c;然后上缓存 CDN 分发到边缘节点来处理&#xff0c;具体逻辑可以参考 淘宝——EdgeRoutine边缘计算&#xff08;CDNServerless 边缘计算…

深度解析HAProxy:构建高可用负载均衡的终极指南

目录 haproxy配置文件组成 实验环境 haproxy安装 haproxy的配置文件说明 全局配置段global 多进程和多线程配置 代理配置段proxies server配置说明 实验相关配置 测试效果&#xff1a; haproxy的状态页 socat命令 socat命令的一些常用示例 HAProxy的调度算法 静…

Oracle事务是怎么练成的

什么是事务 事务是数据库管理系统执行过程的一个逻辑单位&#xff0c;由一系列有限的数据库操作序列构成&#xff0c;事务必须满足‌ACID属性。ACID理论是数据库中最重要的概念之一&#xff0c;分别代表原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consisten…

人工智能GPU算力评估分析

GPU算力评估 一、 关于训练GPU的带宽 大模型训练算力需求&#xff1a;总算力(Tlops)6倍模型参数量训练数据token量&#xff0c;精准高效满足大规模训练需求。 需要把那么计算量和通信量的比例是多少&#xff1f; 3&#xff1a;指的是一次正向两次反向&#xff0c;反向是梯度…

程序员职场升级攻略:学AI技能,稳步迈向月薪破万之路

在人工智能高速发展的今天&#xff0c;AI技术已经成为职场人士提升收入的有力武器。许多人通过学习AI技能&#xff0c;成功跻身高收入行业&#xff0c;实现了月薪破万的目标。本文将揭秘高收入行业与城市&#xff0c;并提供一条清晰的学习路线&#xff0c;助你成为AI领域的一员…

ubuntu:更新阿里云apt源

前言 我用vmware也搭建了ubuntu服务器&#xff0c;并同样发现apt几乎完全用不了&#xff08;系统默认用的是清华源&#xff0c;可能较老了&#xff09; 更新阿里云apt源 1、去阿里云官网找系统对应的apt源配置 阿里云镜像&#xff1a;阿里巴巴开源镜像站-OPSX镜像站-阿里云开发…

Unity教程(九)角色攻击的改进

Unity开发2D类银河恶魔城游戏学习笔记 Unity教程&#xff08;零&#xff09;Unity和VS的使用相关内容 Unity教程&#xff08;一&#xff09;开始学习状态机 Unity教程&#xff08;二&#xff09;角色移动的实现 Unity教程&#xff08;三&#xff09;角色跳跃的实现 Unity教程&…