EtherCAT学习:电机调试问题总结与解决方案

文章目录

  • 问题1:初始化不进入OP状态
  • 问题2:PDO通讯数据不对
  • 主站硬件:STM32F405+LAN8720A

    主站软件:SOEM

    问题1:初始化不进入OP状态

    现象描述:主站初始化过程中,打印信息显示状态一直在safe-op,AL-state(寄存器0x134)中的值为0,ESC中的配置信息正常打印

    排查过程:如果AL-state有报错,那么应该先根据报错来进行排查。这次AL-state没有报错,正常来讲,流程正确的话应该进入OP状态的。个人理解,从safe-op到op状态,需要两个条件:1、发送状态切换请求;2、发送有效过程数据。

    首先排查发送状态切换请求是否成功:通过wck的值就可以看到了。

    其次排查过程数据发送:通过wireshake抓包可以看到过程数据的包,包长度正常,逻辑地址正常,内容可以忽略,因为现在还没开始填充,内容全是0。数据包有了,发送的长度也符合我们PDO所映射数据的长度,但是为什么通信不成功呢?进一步排查,FMMU映射是否正确,抓取FMMU映射的数据包,如下图所示:

    物理地址0x1100是我配置的SM2的地址;物理地址0x1400是我配置的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中的对象字典,不是ESC的EEPROM中的。0x1C00保存的就是SMtype,程序会根据读取到的数据来修改我们初始化好的SMtype的值,接下来IOmap中会配置SM2、SM3、FMMU,在进行SM2和SM3配置的时候是根据EEPROM的数据信息来配置,在进行FMMU配置的时候会通过SMtype来判断SM的方向,然后根据方向来配置FMMU的方向。通过Twincat上位机,发现电机中0x1c00中的值是2,1,3,4;正好和习惯的配置相反,然而提供的ESC中的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的映射有时候映射的数据需要补位一个无意义的映射,目前还不清楚这个补位的原则和道理。

    物联沃分享整理
    物联沃-IOTWORD物联网 » EtherCAT学习:电机调试问题总结与解决方案

    发表评论