主站软件:SOEM
现象描述:主站初始化过程中,打印信息显示状态一直在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++;} }
测试与电机通讯,发现对象字典写不进去,例如工作模式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的映射有时候映射的数据需要补位一个无意义的映射,目前还不清楚这个补位的原则和道理。