Macchina a stati finiti, cos'è quando e perché usarla in Arduino

L'OLED, però, a che distanza può stare?...

Ho inserito un mega, quindi si può usare il classico display 7 segmenti.

Quanta corrente tira fuori il Mega? Sulle singole porte 10mA, mi sembra?

Ma non si tratta di un circuito reale, l'ho specificato fin da subito.
E' solo per la simulazione e non complicare inutilmente il disegnino.

Una prima versione implementata secondo le specifiche che ha ricordato @Datman
Manca ancora l'attraversamento pedonale, appena ho un po' di tempo libero lo aggiungo usando una seconda FSM che interagisce con la prima.

Edit:
Aggiunto semaforo pedonale... mancherebbe il display per il tempo residuo.

io invece sono andato avanti lato SW, sempre secondo le nostre idee (io e il fratello)
al momento è C, senza classi, ma provvederemo ora della fine

comunque il sistema è pressocchè finito, manca solo creare l'HW e caricare le condizioni e gli stati

prima di passare allo HW pensavo di porre una domanda

una FSM generica richiede che le condizioni siano generiche
però spesso, per non dire spessissimo le condizioni si basano su un tempo
potrebbe essere interessante specificare due "tipi" di condizioni, una basata sul tempo ed una generica?
per semplificare eventuali "macchine semplici" ovvero semplici timer?

adesso vado a vedere nel pregresso
per intanto accettiamo consigli

ecco lo scheletro di FSM universale che abbiamo fatto oggi

/*
   Una nuova idea DDD
   Del Dinamico Duo

   Sentitevi liberi di copiare
   Sentitevi liberi di trarre ispirazione
   Sentitevi liberi di dare un cenno di ringraziamento



   Creato con IDE 1.8.10
*/


// si tratta di una FSM "eseguita" da un motore richiamato in loop
// cicla un insieme di condizioni, che se avverate commutano tra uno stato e il successivo
// e un insieme di stati, che specificano le azioni da fare, in ingresso, durante ed in uscita


// per le condizioni
// qui decido che non ci possono essere più di 256 stati
typedef struct
{
   byte attuale; // stato attuale
   byte successore; // prossimo
   bool (* test)(void); // la "vera" condizione da rispettare
} condizione_t;
// mi sono ispirato a uno che faceva testare qui anche il tempo, ma non è "puro"


// e adesso gli stati
typedef struct
{
   byte id; // identificatore numerico
   void (* ingresso)(void); // puntatore alla funzione di ingresso
   void (* uscita)(void); // puntatore alla funzione di uscita
   void (* mantenimento)(void); // puntatore alla funzione di mantenimento
} stato_t;



// tutte le funzioni di test

bool pulsantestart(void)
{
   return digitalRead(2);
   // naturalmente potrebbe anche non essere hard-coded
   // qui non mi interessa la purezza del linguaggio, bado alla FSM, non all HW
}
//TBC..............


// dove mantengo lo stato attuale
stato_t attuale = {0};
// qui invece mi interessa la purezza del linguaggio, ne farò una variabile privata della classe, alla fine del lavoro

// l'array di stati, solo per esempio e per compilare
stato_t stato[5] = {0};

// l'array di condizioni
condizione_t condizione[5] = {0};
byte CONDIZIONI = sizeof condizione / sizeof condizione[0];



// il motore della FSM, basta citarla in una loop non bloccante
// non che ci sia poi molto, si tratta di banalita'
void esegui(void)
{
   // fa il test di tutte le condizioni della FSM
   for (byte i = 0; i < CONDIZIONI; i++)
   {
      if (condizione[i].attuale == attuale.id)
      {
         if (condizione[i].test())
         {
            // esco dallo stato attuale, eseguendo la sua azione d'uscita
            if (attuale.uscita)
            {
               attuale.uscita();
            }

            //entro nello stato prossimo
            attuale = stato[condizione[i].successore];

            // eseguo la sua azione di ingresso
            if (attuale.ingresso)
            {
               attuale.ingresso();
            }
         }
      }

      //ad ogni if, più spesso ancora che ad ogni loop
      // eseguo la funzione di mantenimento

      if (attuale.mantenimento)
      {
         attuale.mantenimento();
      }
   }
}



void setup(void)
{
}

void loop(void)
{
   esegui();
   // richiama il motore della FSM
}

l'idea è di tenerlo aggiornato, come fosse un blog, così chi consulta questa discussione ha anche idea del processo creativo

Infatti io ho considerato 3 possibilità per attivare una transizione di stato:

  • una semplice variabile bool;
  • una funzione di callback che ritorna un bool per consentire maggior flessibilità;
  • il timeout stesso dello stato attivo.

grazie

Aggiunto attraversamento pedonale e display a 7 segmenti.
come indicato da Datman, il giallo inizia a lampeggiare prima del corrispondente sulla strada B.
O forse era il verde a dover lampeggiare? :sweat_smile: vabbè, ad ogni modo è sufficiente evitare di chiamare la funzione di callback blinkYellow() quando lo stato del semaforo pedonale è stYellow

come hanno fatto notare nella discussione gemella non bisogna sovraccaricare lo OP (di quella discussione) con multiplex o similia
già è tanto una FSM

quindi adesso cambio obiettivo e vado verso una definizione "plana" di HW

se ci sono tante lampade mettiamo tante escite, una bella MEGA e abbiamo fatto, facile da comprendere-meno carne al fuoco

comunque lato SW duro e puro abbiamo finito, credo
abbiamo messo la gestione del tempo, come discusso ieri, a parte rispetto alla gestione di condizioni generiche
(ammett che qui ho copiato senza vergogna dal mio ispiratore di qualche anno fa, dopo che l'altro ieri mi ero permesso di criticarlo che la sua era una soluzione "impura"
faccio ammenda

ecco la versione semi-definitiva della macchina

/*
   Una nuova idea DDD
   Del Dinamico Duo

   Sentitevi liberi di copiare
   Sentitevi liberi di trarre ispirazione
   Sentitevi liberi di dare un cenno di ringraziamento



   Creato con IDE 1.8.10
*/


// si tratta di una FSM "eseguita" da un motore richiamato in loop
// cicla un insieme di condizioni, che se avverate commutano tra uno stato e il successivo
// e un insieme di stati, che specificano le azioni da fare, in ingresso, durante ed in uscita


// per le condizioni
// qui decido che non ci possono essere più di 256 stati
typedef struct
{
   byte attuale; // stato attuale
   byte successore; // prossimo
   bool (* test)(void); // la "vera" condizione da rispettare
   unsigned long int tempo; // timeout dello stato, occhio che è in AND logico con la funzione di condizione
   // volendo farlo in OR logico basta fare due condizioni pari stato
   // se invece mancasse il tempo e/o la funzione la parte mancante sarebbe ignorata
   // attenzione all'ordine come sono state dichiarati i membri
   // ricordare che in inizializzazione il C considera 0 i membri successivi non inizializzati
} condizione_t;



// e adesso gli stati
typedef struct
{
   byte id; // identificatore numerico
   void (* ingresso)(void); // puntatore alla funzione di ingresso
   void (* uscita)(void); // puntatore alla funzione di uscita
   void (* mantenimento)(void); // puntatore alla funzione di mantenimento
} stato_t;



// tutte le funzioni di test

bool pulsantestart(void)
{
   return digitalRead(2);
   // naturalmente potrebbe anche non essere hard-coded
   // qui non mi interessa la purezza del linguaggio, bado alla FSM, non all HW
}
//TBC..............


// dove mantengo lo stato attuale
stato_t attuale = {0};
unsigned long int timestart;
// qui invece mi interessa la purezza del linguaggio, ne farò una variabile privata della classe, alla fine del lavoro

// l'array di stati, solo per esempio e per compilare
stato_t stato[5] = {0};

// l'array di condizioni
condizione_t condizione[5] = {0};
byte CONDIZIONI = sizeof condizione / sizeof condizione[0];



// il motore della FSM, basta citarla in una loop non bloccante
// non che ci sia poi molto, si tratta di banalita'
void esegui(void)
{
   // fa il test di tutte le condizioni della FSM
   for (byte i = 0; i < CONDIZIONI; i++)
   {
      if (condizione[i].attuale == attuale.id)
      {
         if (millis() - timestart >= condizione[i].tempo) // se non inizializzato .tempo è zero, quindi condizione tempo sempre soddisfatta
         {
            // serve di evitare di tentar di eseguire una condizione non inizializzata, MODE valutazione di corto circuito ON
            if (!condizione[i].test || condizione[i].test())
            {
               // esco dallo stato attuale, eseguendo la sua azione d'uscita
               if (attuale.uscita)
               {
                  attuale.uscita();
               }

               // resetto il timer
               timestart = millis();
               //entro nello stato prossimo
               attuale = stato[condizione[i].successore];

               // eseguo la sua azione di ingresso
               if (attuale.ingresso)
               {
                  attuale.ingresso();
               }
            }
         }
      }

      //ad ogni passo di for, più spesso ancora che ad ogni loop
      // eseguo la funzione di mantenimento

      if (attuale.mantenimento)
      {
         attuale.mantenimento();
      }
   }
}



void setup(void)
{
}

void loop(void)
{
   esegui();
   // richiama il motore della FSM
}

diciamo semidefinitiva perchè è ancora da tradurre in C++ e ogggetti

ma arriveremo

adesso c'è da definire l'HW

le particolarità che magari sfuggono ad una prima lettura:
il tempo e il test di condizione sono in AND logico, se manca uno o l'altro si considerano soddisfatti
se li si vuole in OR logico si raddoppia la condizione (aveva ragione quello la, che le chiamava regole, condizione è ambiguo)

se una funzione di callback non viene inizializzata viene saltata, con un test al valore del suo puntatore

ti offendi se prendiamo il tuo circuito da wokwi, pari pari?

ci sembra inutile fare due volte questo lavoro

per la parte SW in vece ci divertiamo a farlo

Assolutamente no!
Anzi in realtà lo avevo postato all'inizio proprio per avere una base comune da cui partire.
Ovviamente se avete migliorie da proporre fate pure, cosi le riporto anche sul mio,

1 Like

grazie

buonasera a tutti voi

abbiamo collaudato un po' la FSM

sullo schema di Cotenstant, che ringraziamo ancora per lasciarlo usare

abbiamo fatto una tripla intermittenza, un triplo lampeggiatore

se mi permettete vorrei spiegare il percorso, non tanto l'arrivo, perché lo so che tanto non arriveremo ai livelli di alcuni qui

comincio col dire che ho chiamato le condizioni "regole", è effettivamente più comprensibile

poi abbiamo creato due funzioni
una spegne accende un led e ne spegne un'altro
l'altra funzione viceversa
insomma un lampeggiatore
i piedini naturalmente indirizzati attraverso un array

// tutte le funzioni richiamate
void f1(void)
{
   // fase 1 del lampeggio
   digitalWrite(out[0], 1);
   digitalWrite(out[1], 0);
}


void f2(void)
{
   // fase 2 del lampeggio
   digitalWrite(out[0], 0);
   digitalWrite(out[1], 1);
}

poi gli stati che usano queste funzioni

stato_t stato[] =
{
   {0, f1}, // si legge: numero stato, funzione di ingresso,di uscita, di mantenimento, tra parentesi graffe, seguita da virgola se NON e' l'ultimo stato
   {1, f2}
};
// banale, uno stato accende un led e spegne un'altro, il secondo stato inverte

e le regole che aoounto regolano le transizioni

// l'array di regole
regola_t regola[] =
{
   {0, 1, 0, 2000}, // si legge: da stato 0 verso 1, nessuna funzione di test, timeout di 1000
   {1, 0, 0, 2000} // si legge: da stato 1 verso 0, nessuna funzione di test, timeout di 500
};
byte REGOLE = sizeof regola / sizeof regola[0];

è giaà cos' la macchina funziona e òampeggia i due led

per dimostrare che non è bloccante basta aggiungere un lampeggio estemporaneo alla loop

   static unsigned long int mytime = 0;

   if (millis() - mytime >= 200)
   {
      pinMode(53, OUTPUT);
      mytime = millis();
      digitalWrite(53, !digitalRead(53));
   }

e si ottene un led che lampeggia indipendente dalla FSM

ma si può fare di meglio
perchè mettendo un secondo lampeggio estemporaneo in una funzione void f(void) si puo usarla come mantenimento e si ottiene un terzo lampeggio che pur essendo asincrono rispetto alla FSM è attivo solo quando uno stato richiama come mantenimento quella funzione

lasciamo qui il programma a questo stadio
prossima fase eliminare il numero dello stato nella struct di stato, lo crediamo ridondante

sempre lieti di leggere commenti

Il dinamico duo

/*
   Una nuova idea DDD
   Del Dinamico Duo

   Sentitevi liberi di copiare
   Sentitevi liberi di trarre ispirazione
   Sentitevi liberi di dare un cenno di ringraziamento



   Creato con IDE 1.8.10
*/


// si tratta di una FSM "eseguita" da un motore richiamato in loop
// cicla un insieme di condizioni, che se avverate commutano tra uno stato e il successivo
// e un insieme di stati, che specificano le azioni da fare, in ingresso, durante ed in uscita


// per le condizioni
// qui decido che non ci possono essere più di 256 stati
typedef struct
{
   byte attuale; // stato attuale
   byte successore; // prossimo
   bool (* test)(void); // la "vera" condizione da rispettare
   unsigned long int tempo; // timeout dello stato, occhio che è in AND logico con la funzione di condizione
   // volendo farlo in OR logico basta fare due condizioni pari stato
   // se invece mancasse il tempo e/o la funzione la parte mancante sarebbe ignorata
   // attenzione all'ordine come sono state dichiarati i membri
   // ricordare che in inizializzazione il C considera 0 i membri successivi non inizializzati
} regola_t;



// e adesso gli stati
typedef struct
{
   byte id; // identificatore numerico
   void (* ingresso)(void); // puntatore alla funzione di ingresso
   void (* uscita)(void); // puntatore alla funzione di uscita
   void (* mantenimento)(void); // puntatore alla funzione di mantenimento
} stato_t;





// i vari piedini di input ed output
byte in[] = {52};
byte out[] = {49, 43};
// ricordarsi di inizializzarli


// tutte le funzioni di test
bool pulsantestart(void)
{
   return digitalRead(in[0]);
   // qui non mi interessa la purezza del linguaggio, bado alla FSM, non all HW
}


// tutte le funzioni richiamate
void f1(void)
{
   // fase 1 del lampeggio
   digitalWrite(out[0], 1);
   digitalWrite(out[1], 0);
}


void f2(void)
{
   // fase 2 del lampeggio
   digitalWrite(out[0], 0);
   digitalWrite(out[1], 1);
}

void f3(void)
{
   static unsigned long int mytime = 0;

   if (millis() - mytime >= 255)
   {
      pinMode(47, OUTPUT);
      mytime = millis();
      digitalWrite(47, !digitalRead(47));
   }
}


// dove mantengo lo stato attuale
stato_t attuale = {0};
unsigned long int timestart;
// qui invece mi interessa la purezza del linguaggio, ne farò una variabile privata della classe, alla fine del lavoro

// l'array di stati
stato_t stato[] =
{
   {0, f1, 0, f3}, // si legge: numero stato, funzione di ingresso,di uscita, di mantenimento, tra parentesi graffe, seguita da virgola se NON e' l'ultimo stato
   {1, f2}
};
// banale, uno stato accende un led e spegne un'altro, il secondo stato inverte


// l'array di regole
regola_t regola[] =
{
   {0, 1, 0, 2000}, // si legge: da stato 0 verso 1, nessuna funzione di test, timeout di 1000
   {1, 0, 0, 2000} // si legge: da stato 1 verso 0, nessuna funzione di test, timeout di 500
};
byte REGOLE = sizeof regola / sizeof regola[0];



// il motore della FSM, basta citarla in una loop non bloccante
// non che ci sia poi molto, si tratta di banalita'
void esegui(void)
{
   // fa il test di tutte le condizioni della FSM
   for (byte i = 0; i < REGOLE; i++)
   {
      if (regola[i].attuale == attuale.id)
      {
         if (millis() - timestart >= regola[i].tempo) // se non inizializzato .tempo è zero, quindi condizione tempo sempre soddisfatta
         {
            // serve di evitare di tentar di eseguire una condizione non inizializzata, MODE valutazione di corto circuito ON
            if (!regola[i].test || regola[i].test())
            {
               // esco dallo stato attuale, eseguendo la sua azione d'uscita
               if (attuale.uscita)
               {
                  attuale.uscita();
               }

               // resetto il timer
               timestart = millis();
               //entro nello stato prossimo
               attuale = stato[regola[i].successore];

               // eseguo la sua azione di ingresso
               if (attuale.ingresso)
               {
                  attuale.ingresso();
               }
            }
         }
      }

      //ad ogni passo di for, più spesso ancora che ad ogni loop
      // eseguo la funzione di mantenimento

      if (attuale.mantenimento)
      {
         attuale.mantenimento();
      }
   }
}



void setup(void)
{
   //  qui inizializzo i piedini
   for (byte i = 0; i < sizeof in / sizeof in[0]; i++)
   {
      pinMode(in[i], INPUT_PULLUP);
   }

   for (byte i = 0; i < sizeof out / sizeof out[0]; i++)
   {
      pinMode(out[i], OUTPUT);
   }
}

void loop(void)
{
   static unsigned long int mytime = 0;

   if (millis() - mytime >= 200)
   {
      pinMode(53, OUTPUT);
      mytime = millis();
      digitalWrite(53, !digitalRead(53));
   }

   esegui();
   // richiama il motore della FSM
}

sono andato avanti un poco
sempre nell'ottica di mostrare una via alternativa

ho creato il concetto di macchiana FSM
una struttura che contiene tutto il necessario per identificare una fsm
array degli stati (puntatore)
array delle regole (puntatore
dimensione array delle regole (byte)
stato attuale (byte)
tempo generale di macchina, per i timer (unsigned long)

questa fsm si inizializza coi giusti puntatori e poi si passa per riferimento alla funzione che rappresenta il motore di esecuzione delle fsm

ecco l'attuale stato dei lavori

/*
   Una nuova idea DDD
   Del Dinamico Duo

   Sentitevi liberi di copiare
   Sentitevi liberi di trarre ispirazione
   Sentitevi liberi di dare un cenno di ringraziamento
   Creato con IDE 1.8.10
*/


// si tratta di una FSM "eseguita" da un motore richiamato in loop
// cicla un insieme di condizioni, che se avverate commutano tra uno stato e il successivo
// e un insieme di stati, che specificano le azioni da fare, in ingresso, durante ed in uscita


// per le condizioni
// qui decido che non ci possono essere più di 256 stati
typedef struct
{
   byte attuale; // stato attuale
   byte successore; // prossimo
   bool (* test)(void); // la "vera" condizione da rispettare
   unsigned long int tempo; // timeout dello stato, occhio che è in AND logico con la funzione di condizione
   // volendo farlo in OR logico basta fare due condizioni pari stato
   // se invece mancasse il tempo e/o la funzione la parte mancante sarebbe ignorata
   // attenzione all'ordine come sono state dichiarati i membri
   // ricordare che in inizializzazione il C considera 0 i membri successivi non inizializzati
} regola_t;



// e adesso gli stati
typedef struct
{
   //   byte id; // identificatore numerico
   void (* ingresso)(void); // puntatore alla funzione di ingresso
   void (* uscita)(void); // puntatore alla funzione di uscita
   void (* mantenimento)(void); // puntatore alla funzione di mantenimento
} stato_t;


// aggiunta 9/3/24
// definisco una fsm come una struttura di
// -array di stati (puntatore a)
// -array di regole (puntatore a)
// -dimensione dell'array di regole (byte)
// -indice dell stato attuale (byte)
// -tempo per il tymer generale della singola FSM (unsigned long int)
// altri timer devono essere gestiti separatamente
typedef struct
{
   stato_t * stato;
   regola_t * regola;
   byte regole;
   byte attuale;
   unsigned long int timestart;
} fsm_t;


// i vari piedini di input ed output
byte in[] = {52};
byte out[] = {49, 43};
// ricordarsi di inizializzarli


// tutte le funzioni di test
bool pulsantestart(void)
{
   return digitalRead(in[0]);
   // qui non mi interessa la purezza del linguaggio, bado alla FSM, non all HW
}


// tutte le funzioni richiamate
void f1(void)
{
   // fase 1 del lampeggio
   digitalWrite(out[0], 1);
   digitalWrite(out[1], 0);
}


void f2(void)
{
   // fase 2 del lampeggio
   digitalWrite(out[0], 0);
   digitalWrite(out[1], 1);
}

void f3(void)
{
   static unsigned long int mytime = 0;

   if (millis() - mytime >= 255)
   {
      pinMode(47, OUTPUT);
      mytime = millis();
      digitalWrite(47, !digitalRead(47));
   }
}


// l'array di stati
stato_t stato[] =
{
   {f1}, // si legge: funzione di ingresso, di uscita, di mantenimento, tra parentesi graffe, seguita da virgola se NON e' l'ultimo stato
   {f2},
};
// banale, uno stato accende un led e spegne un'altro, il secondo stato inverte


// l'array di regole
regola_t regola[] =
{
   { 0, 1, 0, 2000}, // si legge: da stato 1 verso 2, nessuna funzione di test, timeout
   { 1, 0, 0, 2000}  // si legge: da stato 2 verso 1, nessuna funzione di test, timeout
};


// creo la fsm
fsm_t semaforo = {stato, regola, sizeof regola / sizeof regola[0], 0, 0}; // array di stati, array di regole, dimensione array, stato attuale, timestart



void setup(void)
{
   Serial.begin(9600);

   //  qui inizializzo i piedini
   for (byte i = 0; i < sizeof in / sizeof in[0]; i++)
   {
      pinMode(in[i], INPUT_PULLUP);
   }

   for (byte i = 0; i < sizeof out / sizeof out[0]; i++)
   {
      pinMode(out[i], OUTPUT);
   }
}

void loop(void)
{
   static unsigned long int mytime = 0;

   if (millis() - mytime >= 200)
   {
      pinMode(53, OUTPUT);
      mytime = millis();
      digitalWrite(53, !digitalRead(53));
   }

   fsm(semaforo);
   // richiama il motore della FSM
}




// il motore della FSM, basta citarla in una loop non bloccante
// occhio, qui uso una 
void fsm(fsm_t & macchina)
{
   // cicla le condizioni della FSM
   for (byte i = 0; i < macchina.regole; i++)
   {
      if (macchina.regola[i].attuale == macchina.attuale)
      {
         if (millis() - macchina.timestart > macchina.regola[i].tempo) // se non inizializzato regola.tempo è zero, quindi condizione tempo sempre soddisfatta
         {
            // serve di evitare di tentar di eseguire una condizione non inizializzata, MODE valutazione di corto circuito ON
            if (!macchina.regola[i].test || macchina.regola[i].test())
            {
               // esco dallo stato attuale, eseguendo la sua azione d'uscita
               if (macchina.stato[macchina.attuale].uscita)
               {
                  macchina.stato[macchina.attuale].uscita();
               }

               // resetto il timer
               macchina.timestart = millis();
               //entro nello stato prossimo
               macchina.attuale = macchina.regola[i].successore;

               // eseguo la sua azione di ingresso
               if (macchina.stato[macchina.attuale].ingresso)
               {
                  macchina.stato[macchina.attuale].ingresso();
               }
            }
         }
      }

      //ad ogni passo di for, più spesso ancora che ad ogni loop
      // eseguo la funzione di mantenimento

      if (macchina.stato[macchina.attuale].mantenimento)
      {
         macchina.stato[macchina.attuale].mantenimento();
      }
   }
}

come vedete proseguo a piccoli passi singolarmente comprensibili

prossimo:
farne una libreria entrocontenuta (un solo file)
e dare dimostrazione di due fsm che lavorano assieme

altro piccolo grande passo

una libreria separata, entrocontenuta (un solo file dotH per poterla tenere nel percorso di ricerca)

in questa maniera il programma si riduce alla mera dichiarazione di:
funzioni di callback, sia di esecuzione che di test
stati
regole di cambio stato
riempimento della variable fsm con le varie cose dichiarate
e fine, la FSM fa tutto

eccola

//                                  AFSDRhodense.h
// sempre una nuova idea dal dinamico duo
// ispirata da lavori trovati sul forum di Arduino


// si tratta di una FSM "eseguita" da un motore richiamato in loop
// cicla un insieme di condizioni, che se avverate commutano tra uno stato e il successivo
// e un insieme di stati, che specificano le azioni da fare, in ingresso, durante ed in uscita


// per le condizioni
// qui decido che non ci possono essere più di 256 stati
typedef struct
{
   byte attuale; // stato attuale
   byte successore; // prossimo
   bool (* test)(void); // la "vera" condizione da rispettare
   unsigned long int tempo; // timeout dello stato, occhio che è in AND logico con la funzione di condizione
   // volendo farlo in OR logico basta fare due condizioni pari stato
   // se invece mancasse il tempo e/o la funzione la parte mancante sarebbe ignorata
   // attenzione all'ordine come sono state dichiarati i membri
   // ricordare che in inizializzazione il C considera 0 i membri successivi non inizializzati
} regola_t;



// e adesso gli stati
typedef struct
{
   void (* ingresso)(void); // puntatore alla funzione di ingresso
   void (* uscita)(void); // puntatore alla funzione di uscita
   void (* mantenimento)(void); // puntatore alla funzione di mantenimento
} stato_t;


// aggiunta 9/3/24
// definisco una fsm come una struttura di
// -array di stati (puntatore a)
// -array di regole (puntatore a)
// -dimensione dell'array di regole (byte)
// -indice dell stato attuale (byte)
// -tempo per il timer generale della singola FSM (unsigned long int)
// altri timer devono essere gestiti separatamente
typedef struct
{
   stato_t * stato;
   regola_t * regola;
   byte regole;
   byte attuale;
   unsigned long int timestart;
} fsm_t;




// il motore della FSM, basta citarla in una loop non bloccante
// occhio, qui uso una variabile parametro riferimento
void fsm(fsm_t & macchina)
{
   // cicla le condizioni della FSM
   for (byte i = 0; i < macchina.regole; i++)
   {
      if (macchina.regola[i].attuale == macchina.attuale)
      {
         if (millis() - macchina.timestart > macchina.regola[i].tempo) // se non inizializzato regola.tempo è zero, quindi condizione tempo sempre soddisfatta
         {
            // serve di evitare di tentar di eseguire una condizione non inizializzata, MODE valutazione di corto circuito ON
            if (!macchina.regola[i].test || macchina.regola[i].test())
            {
               // esco dallo stato attuale, eseguendo la sua azione d'uscita
               if (macchina.stato[macchina.attuale].uscita)
               {
                  macchina.stato[macchina.attuale].uscita();
               }

               // resetto il timer
               macchina.timestart = millis();
               //entro nello stato prossimo
               macchina.attuale = macchina.regola[i].successore;

               // eseguo la sua azione di ingresso
               if (macchina.stato[macchina.attuale].ingresso)
               {
                  macchina.stato[macchina.attuale].ingresso();
               }
            }
         }
      }

      //ad ogni passo di for, più spesso ancora che ad ogni loop
      // eseguo la funzione di mantenimento

      if (macchina.stato[macchina.attuale].mantenimento)
      {
         macchina.stato[macchina.attuale].mantenimento();
      }
   }
}

chiamata AFSHRhodense.
perchè è un automa a stati finiti deterministico fatto a Rho

naturalmente il programma adesso puù diventare significativamente più corto

/*
   Una nuova idea DDD
   Del Dinamico Duo

   Sentitevi liberi di copiare
   Sentitevi liberi di trarre ispirazione
   Sentitevi liberi di dare un cenno di ringraziamento
   Creato con IDE 1.8.10
*/

#include "AFSDRhodense.h"


// i vari piedini di input ed output
byte in[] = {52};
byte out[] = {49, 43, 25, 29, 27};
// ricordarsi di inizializzarli


// tutte le funzioni di test
bool pulsantestart(void)
{
   return !digitalRead(in[0]);
   // qui non mi interessa la purezza del linguaggio, bado alla FSM, non all HW
}


// tutte le funzioni richiamate
void f1(void)
{
   // fase 1 del lampeggio
   Serial.println("Macchina 1, stato 0");
   digitalWrite(out[0], 1);
   digitalWrite(out[1], 0);
}


void f2(void)
{
   // fase 2 del lampeggio
   Serial.println("Macchina 1, stato 1");
   digitalWrite(out[0], 0);
   digitalWrite(out[1], 1);
}

void f3(void)
{
   static unsigned long int mytime = 0;

   if (millis() - mytime >= 255)
   {
      pinMode(47, OUTPUT);
      mytime = millis();
      digitalWrite(47, !digitalRead(47));
   }
}


// l'array di stati
stato_t stato[] =
{
   {f1, 0, f3}, // si legge: funzione di ingresso, di uscita, di mantenimento, tra parentesi graffe, seguita da virgola se NON e' l'ultimo stato
   {f2},
};
// banale, uno stato accende un led e spegne un'altro, il secondo stato inverte


// l'array di regole
regola_t regola[] =
{
   { 0, 1, 0, 2000}, // si legge: da stato 1 verso 2, nessuna funzione di test, timeout
   { 1, 0, 0, 2000}  // si legge: da stato 2 verso 1, nessuna funzione di test, timeout
};


// creo la fsm
fsm_t semaforo = {stato, regola, sizeof regola / sizeof regola[0], 0, 0}; // array di stati, array di regole, dimensione array, stato attuale, timestart



// una seconda macchina con altri stati, regole e funzioni

void g1(void)
{
   Serial.println("ingresso stato 0 della seconda macchina");
   digitalWrite(out[2], 0);
   digitalWrite(out[3], 1);
}

void g2(void)
{
   Serial.println("ingresso stato 1 della seconda macchina");
   digitalWrite(out[2], 1);
   digitalWrite(out[3], 0);
}
void g3(void)
{
   Serial.println("ingresso stato 3 della seconda macchina");
   digitalWrite(out[4], 1);
}
void g4(void)
{
   digitalWrite(out[4], 1);
}
void g5(void)
{
   digitalWrite(out[4], 0);
}


stato_t statom2[] =
{
   {g1},
   {g2},
   {g3, g5}
};

regola_t regola2[] =
{
   {0, 1, 0, 1500}, // un secondo emezzo instato 0 poi in stato 1
   {1, 0, 0, 3500}, // 3 secondi e mezzo in stato 1, poi in stato 0
   {1, 2, pulsantestart, 0}, // oppure se in stato 1 e si preme il pulsante va in stato 2
   {2, 0, 0, 1500} // un seocondo e mezzo in stato 2 poi va in stato 0
};

fsm_t m2 = {statom2, regola2, 4};


void setup(void)
{
   Serial.begin(9600);

   //  qui inizializzo i piedini
   for (byte i = 0; i < sizeof in / sizeof in[0]; i++)
   {
      pinMode(in[i], INPUT_PULLUP);
   }

   for (byte i = 0; i < sizeof out / sizeof out[0]; i++)
   {
      pinMode(out[i], OUTPUT);
   }
}

void loop(void)
{
   static unsigned long int mytime = 0;

   if (millis() - mytime >= 200)
   {
      pinMode(53, OUTPUT);
      mytime = millis();
      digitalWrite(53, !digitalRead(53));
   }

   fsm(semaforo);
   fsm(m2);
   // richiama il motore della FSM
}

come vedete il programma fino alla riga 126 inizializza le due fsm
dalla 129 alla 143 esegue una setup() che praticamente è obbligata

dalla 147 alla 154 fa tutt'altro che eseguire la (le) FSM

su 159 rigeh di programma ci sembra che il risultato di avere una FSM facile da realizzare sia ottenuto

pur essendo praticamente tutto C (di C++ c'è solo una funzione con argomento reference) crediamo non serva andare avanti sulla fsm
semmai serve di concentrarsi sul semaforo vero e proprio, che abbiamo finora ignorato

ah, ci tiene il fratello a spiegare cosa fa il programma di prova
due fsm
la prima lampeggia tra due led
la funzione di mantenimento dello stato 0 esegue un lampeggiatore estemporaneo che quindi lampeggia solo nello stato 0
la seconda lampeggia anchessa tra de led, ma solo dallo stato 1, se premuto il tasto passa allo stato 2, dove accende all'ingresso e spegne all'uscita un terzo led

per dimostrare il fatto che nessuna delle due FSM sia bloccante la loop esegue "estemporaneamente" un suo lampeggiatore, similmente allo stato 0 della prima fsm

, anzi mi sa che ne faccio una funzione standard di questo lampeggiatore, verrrà comoda (e qui comunque parlo sempre io e non il fratello)

OK arrivati praticamente al fondo, fatto il semaforo

allora, per non dare la pappa fatta (noi qui siamo a dare indicazioni a chi ha voglia di lavorare ed imparare, non programmi a chi aspetta solo di avere l apappa fatta e la tavola apparecchiata

si comincia con la famosa funzione di lampeggio estemporaneo, che ho preannunciato ieri
il fratello qui, Ducembarr non è daccordo sull'uso, ma questo mese decido io
la funzione, mettetevela dove vi fa comodo

// per far lampeggiare un led, estemporaneo e senza preventive dichiarazioni di tempi o piedini

void lampex (byte pin, unsigned long int tempo){
   static unsigned long int mytime = tempo;

   if (millis() - mytime >= tempo)
   {
      pinMode(pin, OUTPUT);
      mytime = millis();
      digitalWrite(pin, !digitalRead(pin));
   }
}

poi ci aggiungo le dichiarazioni dei pin, in una scheda a parte, per non appesantire troppo la lettura del programma (grazie @cotestatnt per l'idea)

// dichiarazione dei pin usati dal semaforo

// sono 5 semafori con tre colori ciascuno
// li chiamo R1 G1 V1 R2 G2 e via così
#define R1 25
#define G1 27
#define V1 29

#define R2 31
#define G2 33
#define V2 35

#define R3 37
#define G3 39
#define V3 41

#define R4 43
#define G4 45
#define V4 47

#define R5 49
#define G5 51
#define V5 53
// semaforo 5 è il pedonale

// inoltre abbiamo 52 il buzzer
#define BUZZER 52

// e il display
#define SEG_A 7
#define SEG_B 8
#define SEG_C 9
#define SEG_D 10
#define SEG_E 11
#define SEG_F 12
#define SEG_G 13

// adesso carico l'array di piedini
byte uscita[] = {R1, G1, V1, R2, G2, V2, R3, G3, V3, R4, G4, V4, R5, G5, R5, BUZZER, SEG_A, SEG_B, SEG_C, SEG_D, SEG_E, SEG_F, SEG_G};


// aggiungiamo il pulsante
#define PULSANTE 52
byte ingresso[] = {PULSANTE};

chiamatela pin.h e includetela
la stessa cosa per gli stati, le callback e le regole

// listiamo gli stati, che poi li formalizzo

// stato 0: avvio, tutte le luci spente, accende una luce per volta 1 secondo poi spegne, una per volta per limitare i consumi in macchina reale
// stato 1: rosso sui pali 1 e 3, verde sui pali 2 e 4 (funzionamento normale via 2<>4
// stato 2: rosso sui pali 1 e 3, verde sui pali 2 e 4, giallo sul palo 4
// stato 3: rosso sui pali 1,3 e 4 verde su 2 (tempo supplementare al due solo per la svolta)
// stato 4: rosso 1,3,4 verde e giallo al 2
// stato 5: rosso 1,2,3,4 (attesa cambio semaforo
// satto 6: rosso 2,4 verde 1,3
// stato 7: rosso 2,4 verde 1,3 giallo 1,3
// stato 8: rosso 1,2,3,4
//e qui si ricomincia

//quindi le regole

/*
    0 da stato 1 a stato 1 1 secondo (più il tempo della sua eventuale funzione di uscita
    1 da stato 2 a stato 2 un minuto, ridotto a 15 secondi nelle prove
    2 da stato 3 a stato 3 9 secondi
    3 da stato 4 a stato 4 30 secondi, ridotto a 10 nelle prove
    4 da stato 5 a stato 5 9 secondi
    5 da stato 6 a stato 6 5 secondi
    6 da stato 7 a stato 7 un minuto ridotto a 15 nelle prove
    7 da stato 8 a stato 8 9 secondi
    8 da stato 0 a stato 1 5 secondi
*/

// 9 regole tutte a tempo


//e adesso le funzioni

/*
   stato 0 ha solo la funzione di uscita u0(), che fa il prova lampade
   stato 1 ha solo ingresso i1(), accende R1, R3 , V2, V4
   stato 2 ha solo ingresso i2(), accende G4
   stato 3 ha solo i3(), spegne V4,G4 accende R4
   stato 4 ha solo i4(), accende G2
   stato 5 ha solo i5(), spegne G2, V2 accende R2
   stato 6 ha solo i6(), accende V1,V3, spegne R1,R3
   stato 7 ha solo i7(), spegne
   stato 8 ha solo i8(), spegne G1,G3,V1,V3 accende R1,R3
*/

// facciamo così:
// sono 15 lampade, uso una descrizione delle lampade accese o spente, in un array di char
// R1G1V1RG2V2 accetera
// e lo faccio eseguire a una sola funzione generica
// basta ricordarsi che la sequenza delle lampade è la sequenza dei pin

void accendilampade(char * lampade)
{
   // accende e spegne le 15 lampade
   for (byte i = 0; i < 15; i++)
   {
      if (lampade[i] == 'x') // x per spento
      {
         digitalWrite(uscita[i], 0);
      }
      else
      {
         digitalWrite(uscita[i], 1);
      }
   }
}


void i1(void)// ricordare che le stringhe sono concatenate dal compilatore
//                 RGV     RGV     RGV     RGV     RGV
//                 111     222     333     444     ppp
{
   accendilampade("xxx"   "xxx"   "xxx"   "xxx"   "xxx");
}
void i2(void)
{
   accendilampade("RG"   "Rxx"   "xxx"   "xxx"   "xxx");
}
void i3(void)
{
   accendilampade("RGV"  "RGx"   "Rxx"   "xxx"   "xxx");
}
void i4(void)
{
   accendilampade("xGV"   "RGV"   "RGx"   "Rxx"   "xxx");
}
void i5(void)
{
   accendilampade("xxV"   "xGV"   "RGV"   "RGx"   "Rxx");
}
void i6(void)
{
   accendilampade("xxx"   "xxV"   "xGV"   "RGV"   "RGx");
}
void i7(void)
{
   accendilampade("xxx"   "xxx"   "xxx"   "xxx"   "xxx");
}
void i8(void)
{
   accendilampade("xxx"   "xxx"   "xxx"   "xxx"   "xxx");
}



void u0(void)
{
   // provalampade
   for (byte i = 0; i < sizeof uscita / sizeof uscita[0]; i++)
   {
      digitalWrite(uscita[i], 1);
      delay(500);
      digitalWrite(uscita[i], 0);
   }
}





stato_t stato[] =
{
   {u0, u0},
   {i1},
   {i2},
   {i3},
   {i4},
   {i5},
   {i6},
   {i7},
   {i8}
};


regola_t regola[] =
{
   {0, 1, 0, 100},
   {1, 2, 0, 100},
   {2, 3, 0, 100},
   {3, 4, 0, 100},
   {4, 5, 0, 100},
   {5, 6, 0, 100},
   {6, 7, 0, 100},
   {7, 8, 0, 100},
   {8, 1, 0, 100}
};

fsm_t semaforo = {stato, regola, sizeof regola / sizeof regola[0]}; // * stato, * regole, numero regole, stato attuale (non necessario), tempo all'avvio (non necessario)

chiamatela stati.h ed includetela

il programma vero e proprio è piuttosto corto a questo punto

/*
   Una nuova idea DDD
   Del Dinamico Duo

   Sentitevi liberi di copiare
   Sentitevi liberi di trarre ispirazione
   Sentitevi liberi di dare un cenno di ringraziamento
   Creato con IDE 1.8.10
*/

#include "AFSDRhodense.h"
#include "DinamicoDuo.h"
#include "pin.h"
#include "stati.h"


void setup(void)
{
   //  qui inizializzo i piedini
   for (byte i = 0; i < sizeof ingresso / sizeof ingresso[0]; i++)
   {
      pinMode(ingresso[i], INPUT_PULLUP);
   }

   for (byte i = 0; i < sizeof uscita / sizeof uscita[0]; i++)
   {
      pinMode(uscita[i], OUTPUT);
   }
}

void loop(void)
{
   lampex(7, 200);
   fsm(semaforo);
   // richiama il motore della FSM
}

la libreria dinamicoduo.h contiene praticamente solo la lampex()
(il resto non ci interessa)

naturalmente il lavoro duro è ancora molto indietro, si tratta di riprendere la stati.h e descrivere tutti i successivi stati della FSM e le loro corrispondenti uscite, ma a questo punto a noi due non interessa più continuare, il lavoro è svolto e il problema risolto

al prossimo problema che ci appassionerà (sperando che sia presto)

1 Like

Ma il tag code c'è

Non riesco a capire

@gpb01 Guglielmo, hai qualche idea?

c'è una '}' ed uno ' ' (spazio) sulla riga dell'ultimo code

Grazie

Messo a posto

Avevo fatto un 'copia per il forum'
Non so come ho sbagliato