17 декабря 2012 г.

EtherCAT State Machine

Часто на ПНР приходится сталкиваться с крайне неблагоприятной электромагнитной обстановкой. Не на конкретном предприятии, а вообще повсеместно. Можно сказать, в 50% случаев — или сеть перегружена потребителями, либо отсутствует нормальное заземление и т.п. факторы. В ряде случаев у управляющего ПЛК могут возникать трудности с подключением к приводам по цифровой шине.

Поскольку я в своей работе ориентируюсь на EtherCAT, как на самую гибкую, быструю и надёжную промышленную сеть, то рассмотрю вполне реальный случай, где проблемы со связью были решены только программными методами. Можно сказать, «на коленке».

Итак, описание проблемы: после включения системы управления, привода, находящиеся в сети EtherCAT, зависают в состоянии INIT (инициализация) и отказываются автоматически переходить в состояние OP (эксплуатация). Соответственно, никак не реагируют на управляющие команды ПЛК. Ситуация осложнялась тем, что проблема эта носила случайный характер: два раза подряд система могла включиться без ошибок, затем, раз десять зависнуть на этапе инициализации.

Для наглядности привожу схему т.н. «EtherCAT State Machine» (если кому интересно — можно заглянуть на www.ethercat.org в раздел описания технологии), которая универсальна для всех устройств, обслуживаемых в EtherCAT-сети:


С задачей установки/смены состояния ведомого устройства в EtherCAT-сети вполне успешно (как показала практика) справляется функциональный блок FB_EcSetSlaveState, реализованный под TwinCAT (см. TcEtherCAT.lib). В паре с ним хорошо работает блок FB_EcGetSlaveState, читающий фактическое состояние ведомого. На их основе и была написана небольшая программа, переводящая привод из INIT в OP. Все комментарии на английском, т.к. мне часто приходится обмениваться кусками программ с забугорными коллегами. Однако, там и без слов всё понятно.

Сначала описываем свой функциональный блок FB_ETHERCAT_STATUS:

FUNCTION_BLOCK FB_ETHERCAT_STATUS
VAR_INPUT
   bEnable: BOOL;                                               (*Enabling FB on rising edge*)
   sNetId: STRING;                                              (*Master's EtherCAT NetId*)
   nSlaveAddr: UINT;                                            (*Slave's EtherCAT address*)
END_VAR
VAR_OUTPUT
   bDone: BOOL;                                                 (*Successfully done*)
   iMessage: STRING (10);                                       (*String message*)
   bError: BOOL;                                                (*Error detected*)
   nErrId: UDINT;                                               (*ADS error code*)
   stActualState: ST_EcSlaveState;                              (*Actual slave status*)
END_VAR
VAR
   iStep: INT;
   fbTrigger: R_TRIG;
   fbGetSlaveState: FB_EcGetSlaveState;
   fbSetSlaveState: FB_EcSetSlaveState;
END_VAR


Теперь пишем тело блока:

CASE iStep OF
   0:  (*Starting*)
       fbTrigger(CLK := bEnable);
          IF fbTrigger.Q THEN
             iMessage := 'PROCESSING';                          (*Status message*)
             bError := FALSE;                                   (*Resetting error*)
             iStep := iStep + 1;
          END_IF;

   1:  (*Reading slave's EtherCAT status*)
       fbGetSlaveState(
          sNetId := sNetId,
          nSlaveAddr := nSlaveAddr,
          bExecute := TRUE);
             IF fbGetSlaveState.bBusy THEN

                iStep := iStep + 1;
             END_IF;

   2:  (*Still reading slave's EtherCAT status*)
       fbGetSlaveState(bExecute := TRUE);
          IF NOT(fbGetSlaveState.bBusy) THEN
             (*Successful*)
             IF NOT(fbGetSlaveState.bError) THEN
                stActualState := fbGetSlaveState.state;         (*Actual slave's status*)
                fbGetSlaveState(bExecute := FALSE);             (*Resetting FB*)
                iStep := iStep + 1;                             (*Next*)
             (*Error*)
             ELSE
                iMessage := 'ERROR';                            (*Status message*)
                bError := TRUE;                                 (*Error detected*)
                nErrId := fbGetSlaveState.nErrId;               (*Saving error ID*)
                fbGetSlaveState(bExecute := FALSE);             (*Resetting FB*)
                iStep := 0;
             END_IF;
          END_IF;

   3:  (*Checking if slave's EtherCAT status equals 'OP'*)
       IF stActualState.deviceState = EC_DEVICE_STATE_OP THEN   (*Slave is in 'OP' status*)
          iMessage := 'OP';                                     (*Status message*)
          bDone := TRUE;                                        (*Done*)
          iStep := 0;
       ELSE                                                     (*Slave isn't in 'OP' status*)
          iStep := iStep + 1;
       END_IF;

   4:  (*Setting slave into 'INIT' status*)
       fbSetSlaveState(
          sNetId := sNetId,
          nSlaveAddr := nSlaveAddr,
          bExecute := TRUE,
          reqState := EC_DEVICE_STATE_INIT);
             IF fbSetSlaveState.bBusy THEN
                iStep := iStep + 1;
             END_IF;

   5:  (*Still setting slave into 'INIT' status*)
       fbSetSlaveState(bExecute := TRUE);
          IF NOT(fbSetSlaveState.bBusy) THEN
             (*Successful*)
             IF NOT(fbSetSlaveState.bError) THEN
                fbSetSlaveState(bExecute := FALSE);             (*Resetting FB*)
                iStep := iStep + 1;
             (*Error*)
             ELSE
                iMessage := 'ERROR';                            (*Status message*)
                bError := TRUE;                                 (*Error detected*)
                nErrId := fbSetSlaveState.nErrId;               (*Saving error ID*)
                fbSetSlaveState(bExecute := FALSE);             (*Resetting FB*)
                iStep := 0;
             END_IF;
          END_IF;

   6:  (*Reading slave's EtherCAT status*)
       fbGetSlaveState(
          sNetId := sNetId,
          nSlaveAddr := nSlaveAddr,
          bExecute := TRUE);
             IF fbGetSlaveState.bBusy THEN
                iStep := iStep + 1;
             END_IF;

   7:  (*Still reading slave's EtherCAT status*)
       fbGetSlaveState(bExecute := TRUE);
          IF NOT(fbGetSlaveState.bBusy) THEN
             (*Successful*)
             IF NOT(fbGetSlaveState.bError) THEN
                stActualState := fbGetSlaveState.state;         (*Actual slave's status*)
                fbGetSlaveState(bExecute := FALSE);             (*Resetting FB*)
                iStep := iStep + 1;
             (*Error*)
             ELSE
                iMessage := 'ERROR';                            (*Status message*)
                bError := TRUE;                                 (*Error detected*)
                nErrId := fbGetSlaveState.nErrId;               (*Saving error ID*)
                fbGetSlaveState(bExecute := FALSE);             (*Resetting FB*)
                iStep := 0;
             END_IF;
          END_IF;

   8:  (*Checking if slave's EtherCAT status equals 'INIT'*)
       IF stActualState.deviceState = EC_DEVICE_STATE_INIT THEN (*Slave is in 'INIT' status*)
          iMessage := 'INIT';                                   (*Status message*)
          iStep := iStep + 1;
       ELSE                                                     (*Slave isn't in 'INIT' status*)
          iMessage := 'ERROR';                                  (*Status message*)
          bError := TRUE;                                       (*Error detected*)
          iStep := 0;
       END_IF;

   9:  (*Setting slave into 'OP' status*)
       fbSetSlaveState(
          sNetId := sNetId,
          nSlaveAddr := nSlaveAddr,
          bExecute := TRUE,
          reqState := EC_DEVICE_STATE_OP);
             IF fbSetSlaveState.bBusy THEN
                iStep := iStep + 1;
             END_IF;

   10: (*Still setting slave into 'OP' status*)
       fbSetSlaveState(bExecute := TRUE);
          IF NOT(fbSetSlaveState.bBusy) THEN
             (*Successful*)
             IF NOT(fbSetSlaveState.bError) THEN
                fbSetSlaveState(bExecute := FALSE);             (*Resetting FB*)
                iStep := iStep + 1;
             (*Error*)
             ELSE
                iMessage := 'ERROR';                            (*Status message*)
                bError := TRUE;                                 (*Error detected*)
                nErrId := fbSetSlaveState.nErrId;               (*Saving error ID*)
                fbSetSlaveState(bExecute := FALSE);             (*Resetting FB*)
                iStep := 0;
             END_IF;
          END_IF;

   11: (*Reading slave's EtherCAT status*)
       fbGetSlaveState(
          sNetId := sNetId,
          nSlaveAddr := nSlaveAddr,
          bExecute := TRUE);
             IF fbGetSlaveState.bBusy THEN
                iStep := iStep + 1;
             END_IF;

   12: (*Still reading slave's EtherCAT status*)
       fbGetSlaveState(bExecute := TRUE);
          IF NOT(fbGetSlaveState.bBusy) THEN
             (*Successful*)
             IF NOT(fbGetSlaveState.bError) THEN
                stActualState := fbGetSlaveState.state;         (*Actual slave's status*)
                fbGetSlaveState(bExecute := FALSE);             (*Resetting FB*)
                iStep := iStep + 1;
             (*Error*)
             ELSE
                iMessage := 'ERROR';                            (*Status message*)
                bError := TRUE;                                 (*Error detected*)
                nErrId := fbGetSlaveState.nErrId;               (*Saving error ID*)
                fbGetSlaveState(bExecute := FALSE);             (*Resetting FB*)
                iStep := 0;
             END_IF;
          END_IF;

   13: (*Checking if slave's EtherCAT status equals 'OP'*)
       IF stActualState.deviceState = EC_DEVICE_STATE_OP THEN   (*Slave is in 'OP' status*)
          iMessage := 'OP';                                     (*Status message*)
          iStep := iStep + 1;
       ELSE                                                     (*Slave isn't in 'OP' status*)
          iMessage := 'ERROR';                                  (*Status message*)
          bError := TRUE;                                       (*Error detected*)
          iStep := 0;
       END_IF;

   14: (*Setting 'OP' status successfully done*)
       bDone := TRUE;                                           (*Setting 'OP' status done*)
       iStep := 0;                                              (*Done*)
END_CASE;


Вышеприведённый код, в принципе, работает. Но в некоторых случаях, если это, конечно, не противоречит выполнению технологического процесса, можно и вовсе обойтись и простым перезапуском системы исполнения TwinCAT на управляющем ПЛК. Делается это при помощи функционального блока TC_Restart (см. TcUtilities.lib), приводить пример использования которого нет смысла ввиду его простоты.

Комментариев нет:

Отправить комментарий