Il formalismo grafcet permette di disegnare/descrivere macchine a stati/processi che consentono flussi di esecuzione paralleli con più stati attivi contemporaneamente.
Le macchine a stati più volte discusse in questo forum rappresentano il caso limite (sottoinsieme del grafcet) in cui un solo stato alla volta può essere attivo.
Nel grafcet invece come caso limite possiamo avere anche tutti gli stati contemporaneamente attivi/eseguiti.
È evidente quindi che non basta una singola variabile di fase/stato, ma ne occorre una per ogni stato.
Affinché il flusso globale rappresentato con un grafcet possa funzionare, è necessario valutare tutte le condizioni di transizione prima di iniziare ad aggiornare gli stati, in modo da evitare ad esempio che uno stato non venga mai eseguito se è contemporaneamente vera la sua attivazione e disattivazione, anzi, in questo caso lo stato deve rimanere attivo.
Quindi bisognerebbe in sequenza:
- eseguire gli stati attivi
- valutare tutte le condizioni
- disattivare gli stati a monte delle condizioni vere
- attivare gli stati a valle delle condizioni vere
Mi sono chiesto se ci potesse essere una "sintassi" semplice e vicina al modo consueto di ragionare a stati:
- se stato X
- se evento Y
- azione Z, eventuale transizione W
Sono giunto alla seguente struttura generale che fa uso di diversi array (ma potrebbe anche essere un array di struct, cambia poco). L'importante sono i due cicli for posti prima e dopo dell'esecuzione degli stati. In sostanza non si modifica direttamente l'array s[ ] (stati attuali), ma un array nuovi stati (ns[]) che viene azzerato all'inizio di ogni giro, e solo alla fine ricopiato nell'array stati attuali (aggiornamento sincrono). Però, siccome la disattivazione in questo modo diventa implicita, in ogni stato bisogna sempre confermare esplicitamente la permanenza nello stato attuale, e questa è l'unica differenza rispetto alla sintassi "solita":
- se stato X
- se evento Y
- azione Z, eventuale transizione W
- altrienti mantieni stato attuale
La novità maggiore è la sincronizzazione tra più flussi paralleli, che deve garantire il mantenimento dell'attivazione degli stati finali (nell'esempio 4 e 12, oppure 3 5 e 8 ) fino al momento in cui la condizione di transizione della sincronizzazione diventa vera.
Per avere anche l'indicazione di stato appena attivato (per eseguire delle operazioni solo al primo giro) e della durata di uno stato (per gestire dei timeout), si usano gli array sp[] (stato precedente) e t[] (tempi iniziali).
Tramite delle macro è possibile semplificare notevolmente la sintassi.
Esempio1, doppio lampeggiatore:
// COLLEGAMENTI HARDWARE
#define LED1 3 // acceso LOW
#define LED2 4 // acceso LOW
#define ONLEVEL LOW
#define OFFLEVEL HIGH
#define INGRESSO 2 // premuto HIGH
#define PRESSLEVEL HIGH
// MACRO PER MACCHINA A STATI
#define TRASCORSO(i) now - t[i]
#define INIZIO(i) s[i] && !sp[i]
#define ATTIVA(i) ns[i] = 1
#define MANTIENI(i) ns[i] = 1
#define FASE(i) s[i]
// DATI DI LAVORO PER MACCHINA A STATI
#define NSTATI 5
byte s[NSTATI] = { 0 }; // stati attuali
byte sp[NSTATI] = { 0 }; // stati precedenti
byte ns[NSTATI] = { 0 }; // nuovi stati futuri
uint32_t t[NSTATI]; // tempi di inizio stato
//-----------------------------------------------------------------------------
void setup(){
pinMode(LED1, OUTPUT);
digitalWrite(LED1, OFFLEVEL);
pinMode(LED2, OUTPUT);
digitalWrite(LED2, OFFLEVEL);
pinMode(INGRESSO, INPUT);
s[0] = 1; // STATO ATTIVO INIZIALE
}
//-----------------------------------------------------------------------------
void loop(){
byte self;
uint32_t now = millis();
bool start = (digitalRead(INGRESSO) == PRESSLEVEL);
//-------------------------------------------------------------------------
// AZZERAMENTO STATI FUTURI E SALVA TEMPI INIZIO FASI
//-------------------------------------------------------------------------
for (byte i=0; i<NSTATI; i++) { ns[i]=0; if (INIZIO(i)) { t[i]=now; } }
//-------------------------------------
// STATO 0
//-------------------------------------
if (FASE(0)){
if (start) { ATTIVA(1); ATTIVA(3); }
else { MANTIENI(0); }
}
//-------------------------------------
// STATO 1 (40ms led1 acceso)
//-------------------------------------
self = 1;
if (FASE(self)){
if (INIZIO(self)) { digitalWrite(LED1, ONLEVEL); }
if (TRASCORSO(self)>40) { ATTIVA(2); }
else { MANTIENI(self); }
}
//-------------------------------------
// STATO 2 (300ms led1 spento)
//-------------------------------------
self = 2;
if (FASE(self)){
if (INIZIO(self)) { digitalWrite(LED1, OFFLEVEL); }
if (TRASCORSO(self)>300) { ATTIVA(1); }
else { MANTIENI(self); }
}
//-------------------------------------
// STATO 3 (400ms led2 acceso)
//-------------------------------------
self = 3;
if (FASE(self)){
if (INIZIO(self)) { digitalWrite(LED2, ONLEVEL); }
if (TRASCORSO(self)>400) { ATTIVA(4); }
else { MANTIENI(self); }
}
//-------------------------------------
// STATO 4 (400ms led2 spento)
//-------------------------------------
self = 4;
if (FASE(self)){
if (INIZIO(self)) { digitalWrite(LED2, OFFLEVEL); }
if (TRASCORSO(self)>400) { ATTIVA(3); }
else { MANTIENI(self); }
}
//-------------------------------------------------------------------------
// AGGIORNAMENTO SINCRONO STATI
//-------------------------------------------------------------------------
for (byte i=0; i<NSTATI; i++) { sp[i] = s[i]; s[i] = ns[i]; }
}
La variabile 'self', di Pythonica ispirazione, serve per non riscrivere innumerevoli volte il numero della fase corrente.
continua...