文章目录
- 问题1:初始化不进入OP状态
- 问题2:PDO通讯数据不对
主站硬件:STM32F405+LAN8720A
主站软件:SOEM
问题1:初始化不进入OP状态
现象描述:主站初始化过程中,打印信息显示状态一直在safe-op,AL-state(寄存器0x134)中的值为0,SSC中的配置信息正常打印
排查过程:如果AL-state有报错,那么应该先根据报错来进行排查。这次AL-state没有报错,正常来讲,流程正确的话应该进入OP状态的。个人理解,从safe-op到op状态,需要两个条件:1、发送状态切换请求;2、发送有效过程数据。
首先排查发送状态切换请求是否成功:通过wck的值就可以看到了。
其次排查过程数据发送:通过wireshake抓包可以看到过程数据的包,包长度正常,逻辑地址正常,内容可以忽略,因为现在还没开始填充,内容全是0。数据包有了,发送的长度也符合我们PDO所映射数据的长度,但是为什么通信不成功呢?进一步排查,FMMU映射是否正确,抓取FMMU映射的数据包,如下图所示:
物理地址0x1100是我配置的SM2的地址;物理地址0x1100是我配置的SM3的地址。按照习惯来,一般我们SM0、SM1、SM2、SM3分别对应的是邮箱输出(写)、邮箱输入(读)、过程数据输出(写)、过程数据输入(读);可是这里是SM2对应的FMMU配置的是读,SM3对应的配置是写。问题应该是出在了这里。
通过源码来分析原因:
首先我们在初始化配置的时候,会调用ecx_config_init,会默认初始化SMtype,为1,2,3,4(表示邮箱输出(写)、邮箱输入(读)、过程数据输出(写)、过程数据输入(读))
ecx_config_init()
.....
if (context->slavelist[slave].mbx_l>0)
{
context->slavelist[slave].SMtype[0] = 1;
context->slavelist[slave].SMtype[1] = 2;
context->slavelist[slave].SMtype[2] = 3;
context->slavelist[slave].SMtype[3] = 4;
context->slavelist[slave].SM[0].StartAddr = htoes(context->slavelist[slave].mbx_wo);
context->slavelist[slave].SM[0].SMlength = htoes(context->slavelist[slave].mbx_l);
context->slavelist[slave].SM[0].SMflags = htoel(EC_DEFAULTMBXSM0);
context->slavelist[slave].SM[1].StartAddr = htoes(context->slavelist[slave].mbx_ro);
context->slavelist[slave].SM[1].SMlength = htoes(context->slavelist[slave].mbx_rl);
context->slavelist[slave].SM[1].SMflags = htoel(EC_DEFAULTMBXSM1);
context->slavelist[slave].mbx_proto =
ecx_readeeprom(context, slave, ECT_SII_MBXPROTO, EC_TIMEOUTEEP);
}
.....
但是在IOmap的时候,会调用ecx_readPDOmapCA来读取电机程序里的对象字典0x1C00、0x1C12、0x1C13,注意是,是电机MCU中的对象字典,不是SSC,EPROM的。0x1C00保存的就是SMtype,程序会根据读取到的数据来修改我们初始化好的SMtype的值,接下来IOmap中会配置SM2、SM3、FMMU,在进行SM2和SM3配置的时候是根据EEPROM的数据信息来配置,在进行FMMU配置的时候会通过SMtype来判断SM的方向,然后根据方向来配置FMMU的方向。通过Twincat上位机,发现电机中0x1c00中的值是2,1,3,4;正好和习惯的配置相反,然而提供的SSC中的xml的配置却是和习惯的相同,这就造成了SM和FMMU配置的方向不正确。最终通过将程序改为如下解决了烧录验证,可以跳转到OP状态。
/* 修改后的ecx_readPDOmapCA 函数 */
int ecx_readPDOmapCA(ecx_contextt *context, uint16 Slave, int *Osize, int *Isize)
{
int wkc, rdl;
int retVal = 0;
uint8 nSM, iSM, tSM;
int Tsize;
uint8 SMt_bug_add;
*Isize = 0;
*Osize = 0;
SMt_bug_add = 0;
rdl = sizeof(ec_SMcommtypet);
context->SMcommtype->n = 0;
/* read SyncManager Communication Type object count Complete Access*/
wkc = ecx_SDOread(context, Slave, ECT_SDO_SMCOMMTYPE, 0x00, TRUE, &rdl, context->SMcommtype, EC_TIMEOUTRXM);
/* positive result from slave ? */
if ((wkc > 0) && (context->SMcommtype->n > 2))
{
/* make nSM equal to number of defined SM */
nSM = context->SMcommtype->n - 1;
/* limit to maximum number of SM defined, if true the slave can't be configured */
if (nSM > EC_MAXSM)
{
nSM = EC_MAXSM;
ecx_packeterror(context, Slave, 0, 0, 10); /* #SM larger than EC_MAXSM */
}
/* iterate for every SM type defined */
for (iSM = 2 ; iSM <= nSM ; iSM++)
{
/* 如果电机代码里1C00中的配置与电机SSC中的xml配置的不一样,以XML中为准 */
if(context->slavelist[Slave].SMtype[iSM] != context->SMcommtype->SMtype[iSM])
tSM = context->slavelist[Slave].SMtype[iSM];
// tSM = context->SMcommtype->SMtype[iSM];
// start slave bug prevention code, remove if possible
if((iSM == 2) && (tSM == 2)) // SM2 has type 2 == mailbox out, this is a bug in the slave!
{
SMt_bug_add = 1; // try to correct, this works if the types are 0 1 2 3 and should be 1 2 3 4
}
if(tSM)
{
tSM += SMt_bug_add; // only add if SMt > 0
}
// end slave bug prevention code
/* 源代码中的话,屏蔽掉这句话 */
// context->slavelist[Slave].SMtype[iSM] = tSM;
/* check if SM is unused -> clear enable flag */
if (tSM == 0)
{
context->slavelist[Slave].SM[iSM].SMflags =
htoel( etohl(context->slavelist[Slave].SM[iSM].SMflags) & EC_SMENABLEMASK);
}
if ((tSM == 3) || (tSM == 4))
{
/* read the assign PDO */
Tsize = ecx_readPDOassignCA(context, Slave, ECT_SDO_PDOASSIGN + iSM );
/* if a mapping is found */
if (Tsize)
{
context->slavelist[Slave].SM[iSM].SMlength = htoes((Tsize + 7) / 8);
if (tSM == 3)
{
/* we are doing outputs */
*Osize += Tsize;
}
else
{
/* we are doing inputs */
*Isize += Tsize;
}
}
}
}
}
/* found some I/O bits ? */
if ((*Isize > 0) || (*Osize > 0))
{
retVal = 1;
}
return retVal;
}
int ecx_readPDOmap(ecx_contextt *context, uint16 Slave, int *Osize, int *Isize)
{
int wkc, rdl;
int retVal = 0;
uint8 nSM, iSM, tSM;
int Tsize;
uint8 SMt_bug_add;
*Isize = 0;
*Osize = 0;
SMt_bug_add = 0;
rdl = sizeof(nSM); nSM = 0;
/* read SyncManager Communication Type object count */
wkc = ecx_SDOread(context, Slave, ECT_SDO_SMCOMMTYPE, 0x00, FALSE, &rdl, &nSM, EC_TIMEOUTRXM);
/* positive result from slave ? */
if ((wkc > 0) && (nSM > 2))
{
/* make nSM equal to number of defined SM */
nSM--;
/* limit to maximum number of SM defined, if true the slave can't be configured */
if (nSM > EC_MAXSM)
nSM = EC_MAXSM;
/* iterate for every SM type defined */
for (iSM = 2 ; iSM <= nSM ; iSM++)
{
rdl = sizeof(tSM); tSM = 0;
/* read SyncManager Communication Type */
wkc = ecx_SDOread(context, Slave, ECT_SDO_SMCOMMTYPE, iSM + 1, FALSE, &rdl, &tSM, EC_TIMEOUTRXM);
if (wkc > 0)
{
/* 如果电机代码里1C00中的配置与电机SSC中的xml配置的不一样,以XML中为准 */
if(context->slavelist[Slave].SMtype[iSM] != tSM)
tSM = context->slavelist[Slave].SMtype[iSM];
// start slave bug prevention code, remove if possible
if((iSM == 2) && (tSM == 2)) // SM2 has type 2 == mailbox out, this is a bug in the slave!
{
SMt_bug_add = 1; // try to correct, this works if the types are 0 1 2 3 and should be 1 2 3 4
}
if(tSM)
{
tSM += SMt_bug_add; // only add if SMt > 0
}
if((iSM == 2) && (tSM == 0)) // SM2 has type 0, this is a bug in the slave!
{
tSM = 3;
}
if((iSM == 3) && (tSM == 0)) // SM3 has type 0, this is a bug in the slave!
{
tSM = 4;
}
// end slave bug prevention code
/* 源代码中的话,屏蔽掉这句话 */
// context->slavelist[Slave].SMtype[iSM] = tSM;
/* check if SM is unused -> clear enable flag */
if (tSM == 0)
{
context->slavelist[Slave].SM[iSM].SMflags =
htoel( etohl(context->slavelist[Slave].SM[iSM].SMflags) & EC_SMENABLEMASK);
}
if ((tSM == 3) || (tSM == 4))
{
/* read the assign PDO */
Tsize = ecx_readPDOassign(context, Slave, ECT_SDO_PDOASSIGN + iSM );
/* if a mapping is found */
if (Tsize)
{
context->slavelist[Slave].SM[iSM].SMlength = htoes((Tsize + 7) / 8);
if (tSM == 3)
{
/* we are doing outputs */
*Osize += Tsize;
}
else
{
/* we are doing inputs */
*Isize += Tsize;
}
}
}
}
}
}
/* found some I/O bits ? */
if ((*Isize > 0) || (*Osize > 0))
{
retVal = 1;
}
return retVal;
}
初次之外,ecx_config_init函数中也做了一点改动,这里主要是将EEPROM中读取到的值给到SMtype,之前是用的默认的值
/************** ecx_config_init *************/
/* SII SM section */
nSM = ecx_siiSM(context, slave, context->eepSM);
if (nSM>0)
{
context->slavelist[slave].SM[0].StartAddr = htoes(context->eepSM->PhStart);
context->slavelist[slave].SM[0].SMlength = htoes(context->eepSM->Plength);
context->slavelist[slave].SM[0].SMflags =
htoel((context->eepSM->Creg) + (context->eepSM->Activate << 16));
/*****读取EEPROM内存中的SM类型 */
context->slavelist[slave].SMtype[0] = context->eepSM->PDIctrl;
/*************/
SMc = 1;
while ((SMc < EC_MAXSM) && ecx_siiSMnext(context, slave, context->eepSM, SMc))
{
context->slavelist[slave].SM[SMc].StartAddr = htoes(context->eepSM->PhStart);
context->slavelist[slave].SM[SMc].SMlength = htoes(context->eepSM->Plength);
context->slavelist[slave].SM[SMc].SMflags =
htoel((context->eepSM->Creg) + (context->eepSM->Activate << 16));
/*****读取EEPROM内存中的SM类型 */
context->slavelist[slave].SMtype[SMc] = context->eepSM->PDIctrl;
/*************/
SMc++;
}
}
问题2:PDO通讯数据不对
测试与电机通讯,发现对象字典写不进去,例如工作模式6060、6040。仔细检查PDO配置的代码,代码没问题。链接TWinCAT,可以看到1600的部分的内容确实是我程序里写的内容。但是1C12中的内容却是1700。在主站初始化的时候去读取1C12中的数据来计算IOmap的输出数据长度。debug的时候看到读取到的1C12的数据也1600,映射长度也是程序里设置的长度。说明当时确实是将1600成功写入了1C12。但是现在TWinCaT中看到的是1700,说明数据后来又被改回来了 ,至于是SOEM改的还是电机自己改的,还不太清楚。尝试将程序里的1600也改为1700,问题解决。另外关于PDO的映射有时候映射的数据需要补位一个无意义的映射,目前还不清楚这个补位的原则和道理。
0的部分的内容确实是我程序里写的内容。但是1C12中的内容却是1700。在主站初始化的时候去读取1C12中的数据来计算IOmap的输出数据长度。debug的时候看到读取到的1C12的数据也1600,映射长度也是程序里设置的长度。说明当时确实是将1600成功写入了1C12。但是现在TWinCaT中看到的是1700,说明数据后来又被改回来了 ,至于是SOEM改的还是电机自己改的,还不太清楚。尝试将程序里的1600也改为1700,问题解决。另外关于PDO的映射有时候映射的数据需要补位一个无意义的映射,目前还不清楚这个补位的原则和道理。