OpenEtherCATsociety / SOEM

Simple Open Source EtherCAT Master
Other
1.34k stars 683 forks source link

Slave loses 0x1c32 configuration #609

Closed Davidinos closed 2 years ago

Davidinos commented 2 years ago

Goodmorning, I'm trying to configure an Ethercat net with a Panasonic Minas-A6B motor slave. Starting from red_test example, my initialization code loop is the following:

// Cyclic function used for bringing all slaves from INIT to OPERATIONAL state
void LoopEcatManager()
{
    static osal_timert tself = {0};
    int cnt, i, j, oloop, iloop;

    switch(ecatManager.state)
    {
        case ECAT_MASTER_NOT_INIT:
            break;

        case ECAT_MASTER_INIT:
            /* find and auto-config slaves */
            if (ec_config_init(FALSE) > 0)
            {
                PRINTF("%d slaves found and configured.\n", ec_slavecount);

                // Timeout for checking STATE_PRE_OP
                osal_timer_start (&tself, EC_TIMEOUTSTATE * 4);
                ecatManager.state = ECAT_MASTER_PREOP_IN_PROGRESS;
            }
            else
            {
                ecatManager.state = ECAT_MASTER_ERROR;
                PRINTF("No slaves found!\n");
                ecatManager_abort();
            }
            break;

   case ECAT_MASTER_PREOP_IN_PROGRESS:
       ec_readstate();
       // wait for all slaves to reach PRE_OP state
       if(ec_slave[0].state == EC_STATE_PRE_OP)
       {
           static bool go=false;
           PRINTF("All slaves set in EC_STATE_PRE_OP.\n");

            osal_timer_start (&tself, EC_TIMEOUTSTATE * 4);
            ecatManager.state = ECAT_MASTER_PREOP;
       }
       else if(osal_timer_is_expired (&tself))
       {
           ecatManager.state = ECAT_MASTER_ERROR;
           PRINTF("Could not set EC_STATE_PRE_OP.\n");
           ecatManager_abort();
       }
       break;

   case ECAT_MASTER_PREOP:
       if(osal_timer_is_expired (&tself))
       {
           for(int i=1; i<=ec_slavecount; i++)
           {
               if(ecatManager.slaveInitCallback[i]!=0)
                   ecatManager.slaveInitCallback[i](i);
           }

           // configure IOMap
           int iomap_size = ec_config_map(&IOmap);
           PRINTF("SOEM IOMap size: %d\n", iomap_size);

           // configure DC options for every DC capable slave found in the list
           ec_configdc();

           // // Timeout for checking STATE_SAFE_OP
           osal_timer_start (&tself, EC_TIMEOUTSTATE * 4);
           ecatManager.state = ECAT_MASTER_SAFEOP_IN_PROGRESS;
       }
       break;

   case ECAT_MASTER_SAFEOP_IN_PROGRESS:
       ec_readstate();
       // wait for all slaves to reach SAFE_OP state
       if(ec_slave[0].state == EC_STATE_SAFE_OP)
       {
           expectedWKC = (ec_group[0].outputsWKC * 2) + ec_group[0].inputsWKC;
           PRINTF("Calculated workcounter %d\n", expectedWKC);

           ecatManager.state = ECAT_MASTER_SAFEOP;
       }
       else if(osal_timer_is_expired (&tself))
       {
           ecatManager.state = ECAT_MASTER_ERROR;
           PRINTF("Could not set EC_STATE_SAFE_OP.\n");
           ecatManager_abort();
       }
       break;

   case ECAT_MASTER_SAFEOP:

       PRINTF("All slaves set in EC_STATE_SAFE_OP.\n");

       /*
        This section attempts to bring all slaves to operational status. It does so
        by attempting to set the status of all slaves (ec_slave[0]) to operational,
        then proceeding through 40 send/recieve cycles each waiting up to 50 ms for a
        response about the status.
        */
        ec_slave[0].state = EC_STATE_OPERATIONAL;
        ec_send_processdata();
        ec_receive_processdata(EC_TIMEOUTRET);

        ec_writestate(0);
        /*int chk = 40;
        do
        {
            ec_send_processdata();
            ec_receive_processdata(EC_TIMEOUTRET);
            ec_statecheck(0, EC_STATE_OPERATIONAL, 50000); // 50 ms wait for state check
        } while (chk-- && (ec_slave[0].state != EC_STATE_OPERATIONAL));*/

        // Timeout for checking STATE_OPERATIONAL
        osal_timer_start (&tself, EC_TIMEOUTSTATE * 4);
        ecatManager.state = ECAT_MASTER_OPERATIONAL_IN_PROGRESS;
        break;

   case ECAT_MASTER_OPERATIONAL_IN_PROGRESS:
       ec_readstate();
       // wait for all slaves to reach SAFE_OP state
       if(ec_slave[0].state == EC_STATE_OPERATIONAL)
       {
           PRINTF("All slaves set in OPERATIONAL.\n");

            oloop = ec_slave[0].Obytes;
            if ((oloop == 0) && (ec_slave[0].Obits > 0)) oloop = 1;
            if (oloop > 8) oloop = 8;
            iloop = ec_slave[0].Ibytes;
            if ((iloop == 0) && (ec_slave[0].Ibits > 0)) iloop = 1;
            if (iloop > 8) iloop = 8;
            // activate cyclic process data
            dorun = true;

            // Timeout for Loop ethercat cycle
            osal_timer_start (&tself, ECAT_MANAGER_LOOP_CYCLE_NS);
            ecatManager.state = ECAT_MASTER_OPERATIONAL;
       }
       else if(osal_timer_is_expired (&tself))
       {
           PRINTF("OPERATIONAL state not set, exiting.\n");
           ec_readstate();
           for(i = 1; i<=ec_slavecount ; i++)
           {
               if(ec_slave[i].state != EC_STATE_OPERATIONAL)
               {
                   PRINTF("Slave %d State=0x%2.2x StatusCode=0x%4.4x : %s\n",
                           i, ec_slave[i].state, ec_slave[i].ALstatuscode, ec_ALstatuscode2string(ec_slave[i].ALstatuscode));
               }
           }

           ecatManager.state = ECAT_MASTER_ERROR;
           PRINTF("Could not set EC_STATE_OP.\n");
           ecatManager_abort();
       }
       break;

        case ECAT_MASTER_OPERATIONAL:
            if(osal_timer_is_expired (&tself))
            {
                ecatManager_check(0);
                osal_timer_start (&tself, ECAT_MANAGER_LOOP_CYCLE_NS);
            }
            break;

        case ECAT_MASTER_ERROR:
            break;
    }
} 

My questions are:

  1. Is it correct to call ec_configdc() right after ec_config_map(&IOmap) ?
  2. With the callback functions:
       for(int i=1; i<=ec_slavecount; i++)
        {
            if(ecatManager.slaveInitCallback[i]!=0)
                ecatManager.slaveInitCallback[i](i);
        }

    SDO write operations are called for each slave. Is it the correct place to put them (right before ec_config_map(&IOmap))?

  3. After each SDO write operation, an SDO read operation is done for checking, The problem that I'm facing concerns 0x1c32 register values . I write 2 in index 0x1c32, subindex 1 for DC sync operation and the next SDO read operation confirms the setting. However, once that the slave is in OP state, when I read again the 0x1c32, subindex 1 with SDO read, I get 0. Why does the register change its values? For information, in index 0x1c32, subindex 4, the Sync Mode Supported oddly is 7.

Thank you. Davidino

P.S. I know that using SDOs operation with the slave in OP state affects the comunication stability, but with the motor still, as long as it doesn't warn error, it shouldn't matter.

ArthurKetels commented 2 years ago

You should not try to manipulate the SDO's of 0x1c32. In very rare cases you can, with certain slaves, but normally these are read only parameters. See : https://infosys.beckhoff.com/english.php?content=../content/1033/ethercatsystem/2469122443.html&id= The fact that you enable SYNC0 will change the slave internal configuration and the sync mode in 0x1c32:01 will reflect this.

Davidinos commented 2 years ago

Nuova immagine bitmap

Hello @ArthurKetels, thank you very much for your prompt reply. I agree with you. As shown in attachment datasheet screenshot, when DC sync mode is chosen, 0x1c32, subindex 1 should change automatically when passing from PreOP to SafeOP. But it doesn't, because when I read with SDO, I always read 0. That's the reason why I tried to force it, unsuccesfully.

ArthurKetels commented 2 years ago

From your above datasheet it is clear that the drive expects SYNC0 to be started before SafeOP. The side effect of ec_config_map() is that it starts the transition to SafeOP. So ec_configDC() needs to be started before this. And in turn this means that the master and slave need to be synchronized before SafeOP. You can read in other posts here how to do that.

DIns76 commented 2 years ago

Hello, @Davidinos Are you found out where the problem came from? I got the same problem with SOEM v1.3.1, and I'm using Panasonic Minas-A6B as well

No matter what I wrote through ecx_SDOwrite() , I got nothing , no feedback from servo driver( no alarm); And I tried writing SDO in state Init and Pre-OP , nothing happened yet; When using ec_SDOread() , I always got 0, just like what happened to you; my test code just as simple as tutorial

if (ec_init(""))
{
    if (ec_config_init(TRUE) > 0)
    {
        if ((ec_slavecount >= 1))       //assuming just 1 slave found here
        {
            ec_configdc();      //1
            PdoMappings(ec_slavecount );        //where I Mapping PDOs using ecx_SDOwrite()
            ec_config_map(&_iomap); //2
                    ec_dcsync0(slc,TRUE,1000000,0); //3; 
                    //No matter what order of function calls(1,2,3), never works with Minas-A6B
        }
    }
    else
    {
        printf("no slave found!");
    }
}

Before this servo, I just drove a Yaskawa Servo , which works besides comes with a startup Sync Error; Which turns out that PDO rx timeout, still to be fixed; But when I disable the Error check, just works fine; Pretty new to SOEM, I know there probably wrong using, please let me know.

And I checked the differences between the lastest release 1.4.0 with 1.3.1, nothing useful in sight yet; https://github.com/OpenEtherCATsociety/SOEM/compare/v1.3.1...v1.4.0

Davidinos commented 2 years ago

Hello @ArthurKetels and @DIns76 , actually yes, I could find a solution that I write below for checking. I've based my solution on this thread: https://github.com/OpenEtherCATsociety/SOEM/issues/520#issuecomment-852381531

.....
   case ECAT_MASTER_PREOP_IN_PROGRESS:
       ec_readstate();
       // wait for all slaves to reach PRE_OP state
       if(ec_slave[0].state == EC_STATE_PRE_OP)
       {
           PRINTF("All slaves set in EC_STATE_PRE_OP.\n");

            // Modifying PDOs need delay
            osal_timer_start (&tself, EC_TIMEOUTSTATE * 4);
            ecatManager.state = ECAT_MASTER_PREOP;
       }
       else if(osal_timer_is_expired (&tself))
       {
           ecatManager.state = ECAT_MASTER_ERROR;
           PRINTF("Could not set EC_STATE_PRE_OP.\n");
           ecatManager_abort();
       }
       break;

   case ECAT_MASTER_PREOP:
       if(osal_timer_is_expired (&tself))
       {
           // The PDOs configuration must be done before ec_config_map
           // Consider to use instead int (*PO2SOconfigx)(ecx_contextt * context, uint16 slave);
           for(int i=1; i<=ec_slavecount; i++)
           {
               if(ecatManager.slaveInitCallback[i]!=0)
                   ecatManager.slaveInitCallback[i](i);
           }

           // configure DC options for every DC capable slave found in the list
           ec_configdc();

           // This flag prevent ec_config_map to switch to SafeOp (state change must be done manually)
           ecx_context.manualstatechange = 1;
           // configure IOMap and don't start transition to SafeOP yet
           int iomap_size = ec_config_map(&IOmap);
           PRINTF("SOEM IOMap size: %d\n", iomap_size);

        // Enable sync0 output for the first slave and second one
        ec_dcsync0(MINASA6, TRUE, 500000, 0);
        ec_dcsync0(ARDUINO_BAUSANO, TRUE, 500000, 0);

           osal_timer_start (&tself, EC_TIMEOUTSTATE * 4);
           ecatManager.state = ECAT_MASTER_SYNC_IN_PROGRESS;
       }
       break;

   case ECAT_MASTER_SYNC_IN_PROGRESS:
       // Enable PDOs communication for syncing master and slave and wait
       dorun = true;
       if(osal_timer_is_expired (&tself))
       {
           ecatManager.state = ECAT_MASTER_SYNC;
       }
       break;

   case ECAT_MASTER_SYNC:
       // Manually change the state to SafeOP
       ec_slave[0].state = EC_STATE_SAFE_OP;
       ec_writestate(0);

       osal_timer_start (&tself, EC_TIMEOUTSTATE * 4);
       ecatManager.state = ECAT_MASTER_SAFEOP_IN_PROGRESS;
       break;

   case ECAT_MASTER_SAFEOP_IN_PROGRESS:
       ec_readstate();
       // wait for all slaves to reach SAFE_OP state
       if(ec_slave[0].state == EC_STATE_SAFE_OP)
       {
           PRINTF("All slaves set in EC_STATE_SAFE_OP.\n");

           expectedWKC = (ec_group[0].outputsWKC * 2) + ec_group[0].inputsWKC;
           PRINTF("Calculated workcounter %d\n", expectedWKC);

           ecatManager.state = ECAT_MASTER_SAFEOP;
       }
       else if(osal_timer_is_expired (&tself))
       {
           ecatManager.state = ECAT_MASTER_ERROR;
           PRINTF("Could not set EC_STATE_SAFE_OP.\n");
           ecatManager_abort();
       }
       break;
......

In this way register 0x1c32 and 0x1c33 subindex1,2 populate automatically correctly. I start PDOs with slaves still in PREOP so that I reach the syncronization (see where I set dorun variable). I have two questions:

  1. Shoud I call ec_dcsync0() for every single slave for enable DC sysncronism mechanism (as far as I know ec_configdc only calculates delay among slaves and with master). I need to call it for MinasA6 for populating 0x1c32 and I need to enable for Bausano board because it expose the SYNC output of Microchip LAN9252. Watching that signal I can monitor how the sync and jitter are.
  2. Are the steps shown above correct?

Thank you for your help, Davidinos

ArthurKetels commented 2 years ago

Hi @Davidinos, great piece of code you posted. This is very helpful for others facing the same problems. I like programmers that use state machines to solve these kind of temporal issues.

I would indeed recommend to use PO2SOconfigx hook. Then the automatic recovery functions of SOEM will work correctly.

  1. ec_dcsysnc0() should be called for each slave separately that wants to use the SYNC functionality. Keep in mind that some slaves want to have both SYNC0 and SYNC1 configured. Then you need to call ec_dcsync01(). Because this information is not available in the online slave data (only accessible from the ESI xml) SOEM can not provide automatic configuration. I have been working with the EtherCAT organisation for years to try to implement this. But even then there are architecture reasons to configure slaves in a more complex way that can not be automated. For example to stagger the SYNC0 offsets between slaves to do sub-sampling or remove resonances.
  2. Yes, these steps are fine. One optimization is to send a burst of FRMW packets during state ECAT_MASTER_SYNC_IN_PROGRESS. The PDO exchange only distributes clocks once each PDO cycle. The faster and the more FRMW packets are send, the quicker the system synchronizes.

Again, good job!

Davidinos commented 2 years ago

Hello @ArthurKetels, thank you for your compliments, I'm blushing :). And thank you for your suggestions and clarifications. I'm going to use FRMW packets, instead of setting dorun variable, for obtaining the syncronization. In this way I could set dorun variable and start effective PDOs communication with slaves in OP state as it was before.

Thank you again for your help. Davidinos

ArthurKetels commented 2 years ago

Advisable is to start PDO transfers in safe-OP. Most slaves will throw an error if they do not receive PDOs in safe-OP.

ArthurKetels commented 2 years ago

Another tip is to send FRMW packets with zero timeout value. It is no use the master waits for the packets to return. It is more efficient to send them bakc-to-back with no delay. As configured slave address you should take the reference slave. context->slavelist[context->grouplist[group].DCnext].configadr