文章目录
- 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;
- 通过广播写的方式写寄存器0x900。各个端口会锁存数据帧第一个前导码到达的时间。0~3总共四个端口锁存的时间分别存储于0x900、0x904、0x908、0x90C四个地址中。
- 获取主站当前的时间,并将时间转换为基于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模块。
-
hasdc:查看从站是否包含DC,这个变量是在ecx_config_init函数中进行赋值的。在该函数中读取寄存器0x008的值。通过结果的bit2来判断是否包含DC
-
topology:表示ESC中打开的端口个数。这个变量是在ecx_config_init函数中进行赋值的。在该函数中读取寄存器0x110的值。通过结果来判断端口是否打开,每打开一个,topology加1。
-
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;
}
- 将当前从站各个端口锁存的时间清0,获取当前从站的parent编号。
- 如果parent使用的端口数大于2,说明parent上面连接了多个从站,当前从站是一个分支的第一个从站。用parenthold记录下parent。
- 如果当前从站只是用了一个端口,说明当前从站是一个分支的最后一个从站。如果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;
-
如果当前节点是第一个包含DC的节点,则将节点0的下一个节点编号指向当前DC节点;将当前节点的前一个节点编号指向节点0;
-
如果不是第一个包含DC的节点,则将前一个DC节点的下一个节点编号指向当前节点,将当前节点的上一个节点编号指向上一个节点。将所有包含DC的节点记录成一个链表
-
将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);
- 读取从站port0、port1、port2、port3的锁存的时间,分别存储在DCrtA、DCrtB、DCrtC、DCrtD中。
- 读取从站0x0918的从站本地时间,以主站当前的时间为系统时间,计算从站时间与本地时间的偏差。
- 将时间偏差写入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之中。