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

发布于:2024-08-13 ⋅ 阅读:(167) ⋅ 点赞:(0)

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之中。


网站公告

今日签到

点亮在社区的每一天
去签到