Macchina a stati finiti (FSM) basata su switch case

AmericanDreamer:
guarda, non mi ci metto nemmeno

Oh, ma non lo pretendevo mica, era un esempio. Comunque descritto a parole è molto più semplice:

  • al primo giro si impostano le variabili per la modifica, si aggiorna il display, si "resetta" il tempo attività 't'
  • se passa 1 minuto senza attività encoder (variabili onQualchecosa prodotte da altra funzione) si esce
  • se si preme a lungo si esce
  • se si ruota l'encoder si modifica ora o minuti (sottostato gestito con variabile 's')
  • se si clicca sull'encoder si passa da regolazione ora a minuti a uscita (con write RTC se avvenute modifiche)
  • ogni azione sull'encoder "resetta" il tempo e, se valida, genera un determinato beep acustico

tutto qui :slight_smile:

Questo è uno degli 8 stati della 'appMachine' principale che comanda il display e interagisce con l'utente.

Poi ci sono altre 7 macchine che svolgono le funzioni accessorie, come leggere l'encoder (funzione postata in questo thread) o suonare una melodia.

Come parametrizziamo a "regole" le operazioni descritte sopra?

per nulla facile visto così

Guardare la lavatrice in funzione non è male. Specie se ha qualche anno sulle spalle e ogni tanto "sbarella".
Ti fa capire il concetto di "risoluzione dei problemi". un ottimo esercizio anche per una macchina a stati finiti.
Un altro elettrodomestico da poter osservare senza destare sospetti, la macchina per il pane.

Sarà perché sono manutentore

Ma non mi attrae guardare una lavatrice

Ne ho abbastanza di guardare movimentatori pneumatici e avvitatori

AmericanDreamer:
per nulla facile visto così

È questo che intendevo. Un conto è una soluzione compatta, elegante, pulita, che si adatta a un tipo di problema ben preciso, un conto è elaborare qualsiasi cosa del mondo reale, compreso il realizzare macchine con più stati attivi contemporaneamente (sempre scomponibili in diverse macchine singole, come le cinque incastonate una dentro l'altra, e sincronizzate tra loro, del diagramma seguente):

Qualcosa di simile a quello che voleva fare American Dreamer lo trovate qui. La stessa libreria è installabile dal Gestore di librerie dell'IDE arduino.

Spesso qui viene chiesto come sostituire delay() con millis, lo mostro di seguito prendendo spunto da questo post di un utente: link

Non posto tutto il codice della macchina a stati che potete vedere qui

case 1:
      if (oneShotSwitch) {
          // se oneShotSwitch == true stampa "case 1"
          Serial.println("case 1");
      }
      break;
  case 'D':
      if (oneShotSwitch) {
          // se oneShotSwitch == true 
          gLed = 0;
          rLed = 0;
          bLed = 0;
          wtv020sd16p.playVoice(0);
      }

      //delay(73000); // commentato
      if (elapsedTime >= 73000) // questo codice non impegna la cpu in modo esclusivo per 73 secondi
          currentState = 'E';
      break;
  case 'E':
      wtv020sd16p.stopVoice();
      break;

Ciao.

Qui un esempio di cancello domotica su spunto di questo post

L'interfaccia comandi è banale ed è basata sul valore della variabile command, che per comodità
gli viene assegnato un comando nella funzione void serialEvent().

I comandi da inviare dal monitor seriale sono i seguenti:
define carattere
C_APRI '1'
C_CHIUDI '2'
C_STOP '3'

// stati di main machine
#define CHIUSO 0
#define IN_APERTURA 1
#define STOP 2
#define APERTO 3
#define IN_CHIUSURA 4

// comandi
#define C_APRI '1'
#define C_CHIUDI '2'
#define C_STOP '3'

boolean oneShotSwitch;
uint32_t elapsedTime;
uint32_t mainTimer;
byte currentState = 0;
byte oldState = ~currentState;
byte command = 0;


void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

void serialEvent() {
  uint8_t d = Serial.read();
  if (d != 255)
    command = d;
}

void loop() {
  // put your main code here, to run repeatedly:
  oneShotSwitch = false;
  if (currentState != oldState) {
    oneShotSwitch = true;
    mainTimer = millis();
  }
  oldState = currentState;
  elapsedTime = millis() - mainTimer;

  switch (currentState) {
    case CHIUSO:
      if (oneShotSwitch) {

        Serial.println("Cancello chiuso");
      }

      if (command == C_APRI)
        currentState = IN_APERTURA;
      break;
    case IN_APERTURA:
      if (oneShotSwitch) {

        Serial.println("Cancello in apertura");
      }
      if (command == C_STOP)
        currentState = STOP;
      // Ci impiega 10 secondi per aprirsi e poi passa
      // allo stato APEERTO
      if (elapsedTime >= 10000)
        currentState = APERTO;
      break;
    case STOP:
      if (oneShotSwitch) {

        Serial.println("Cancello in STOP");
      }
      if (command == C_APRI)
        currentState = IN_APERTURA;
      if (command == C_CHIUDI)
        currentState = IN_CHIUSURA;
      break;

    case APERTO:
      if (oneShotSwitch) {

        Serial.println("Cancello aperto");
      }
      // Rimane aperto per 10 secondi poi passa allo stato IN_CHIUSURA
      if (elapsedTime >= 10000)
        currentState = IN_CHIUSURA;
      break;

    case IN_CHIUSURA:
      if (oneShotSwitch) {

        Serial.println("Cancello in chiusura");
      }
      if (command == C_STOP)
        currentState = STOP;
      // Impiega 10 secondi per chiudersi poi passa allo stato CHIUSO
      if (elapsedTime >= 10000)
        currentState = CHIUSO;
      break;

  }
  command = 0;
}

Sempre in merito alle macchine a stati finiti, noto che in rete come sempre si trovano informazioni che alle volte sono totalmente scorrette da risultare lampanti, altre volte portano a conclusioni errate anche coloro i quali hanno già esperienza. Per aggiungere confusione alla confusione dico che impiegare uno o più macchine a stati non equivale a realizzare un sistema multitasking, questo perché il concetto di task applicato ad una MCU priva di supporto al multitask è una forzatura. Inoltre su un sistema (o MCU con supporto al multitask) multitasking la suddivisione in stati di una applicazione è ancora utile, desiderata e fattibile per cui la macchina a stati finiti non è da confondere con il multitask.

La macchina a stati aiuta lo sviluppatore a sfruttare al meglio il tempo CPU, per fare ciò il programmatore deve evitare l'uso dell'istruzione delay, ma anche i cicli for e while possono essere deleteri.
I cicli possono essere deleteri o incompatibili con l'applicazione quando impegnano in modo esclusivo la CPU per un tempo considerato troppo lungo per l'applicazione in questione.

Impegnare la CPU in un for che per essere eseguito richiede 1 secondo, comporta tempi di risposta almeno di 1 secondo. Potremmo dire che il sistema non risponde in modo sufficientemente reattivo a quanto desiderato.
Si risolve questo problema dividendo il processo lungo 1 secondo in tanti piccoli pezzetti, diciamo 10, 1/10=0.1s (100ms). Quindi si impegna la CPU per 100ms in modo esclusivo si prende nota di ciò che è stato fatto e si lascia la CPU libera di processare altro codice presente nel loop. Se riusciamo a fare ciò possiamo dire che il sistema (o meglio il programma) ha tempi di reazione di circa 100mS. Bastano 100mS?, non bastano? tutto dipende da cosa si vuole ottenere.

Il multitask in una MCU della serie ATmega è realizzabile ed è stato fatto in vari modi incluso il così desiderato time-sharing (tempo condiviso), ora si che il concetto di task è più calzante. Il principiante sarà libero di usare delay a tutto spiano ma ad un costo; aumento di complessità, minore tempo CPU a disposizione, numero limitato di task (il limite qui è la RAM). Ma ci sono anche vantaggi.

Quindi riassumendo con la macchina a stati finiti non otteniamo il multitasking, ma sfruttiamo al meglio il tempo CPU, cosa peraltro vantaggiosa anche su un sistema multitasking.

Ciao.

Maurotec:
Sempre in merito alle macchine a stati finiti, noto che in rete come sempre si trovano informazioni che alle volte sono totalmente scorrette da risultare lampanti, altre volte portano a conclusioni errate anche coloro i quali hanno già esperienza. Per aggiungere confusione alla confusione dico che impiegare uno o più macchine a stati non equivale a realizzare un sistema multitasking, questo perché il concetto di task applicato ad una MCU priva si supporto al multitask è una forzatura.

Forse questa tua precisazione non è un caso, visto che proprio stamattina ho consigliato un articolo che parla erroneamente di multitasking! Personalmente quella parte dell'articolo non l'ho presa in considerazione, ma onestamente non ho pensato che per un principiante potesse essere fuorviante, e mi dispiace, quindi modificherò il post.

Grazie
Federico

Maurotec:
Quindi riassumendo con la macchina a stati finiti non otteniamo il multitasking, ma sfruttiamo al meglio il tempo CPU, cosa peraltro vantaggiosa anche su un sistema multitasking.

... un sistema a "stati finiti" e spesso usato anche in ambienti "multitask" (che, ti assicuro, girano molto bene su AVR di fascia superiore ... 1284P o 2560 ... meglio il primo, ha il doppio della SRAM).

Come hai ben detto, le due cose sono totalmente distinte e non vanno assolutamente confuse tra loro.

Guglielmo

Il multitasking (più lavori in parallelo) può essere:

  • reale (più core o processori, uno per ogni processo)
  • virtuale (preempitivo o cooperativo, suddivisione del tempo)

Le macchine a stati di cui si parla qui, quando usate per
portare avanti più compiti in parallelo rientrano
nel mt. virtuale cooperativo (lo switch tra i task è realizzato
via software scrivendo codice non bloccante).

Le ISR (routine di servizio interrupt) rientrano invece
nel mt. virtuale preempitivo (lo switch tra i task è realizzato
via hardware con interrupt, timer ecc).

I task poi possono essere:

  • concorrenti (mt.reale, virtuale preempitivo, ISR, hanno problemi di sincronizzazione intrinsechi)
  • non concorrenti (mt. cooperativo, nessun problema di sincronizzazione intrinseco... a parte quelli introdotti per errore dal programmatore ad un livello più alto)

Riassumendo: c'è parallelismo (multitasking) quando vi è più di un compito portato avanti (in modo reale su più core o a fettine di tempo su una sola CPU), e vi è anche concorrenza quando più task possono accedere asincronamente alle stesse risorse (ad esempio alle stesse variabili condivise).

Una singola macchina a stati non è in se stessa multitasking, ma è il modo più semplice per realizzarlo su un piccolo micro con pochissime risorse, e per di più evita la concorrenza a basso livello tra processi del vero mt. preempitivo / reale.

Forse questa tua precisazione non è un caso, visto che proprio stamattina ho consigliato un articolo che parla erroneamente di multitasking!

Si ok, visto la correzione.

Quell'articolo credo di averlo "linkato" anche io altre volte, ho solo colto la palla al balzo.

ma onestamente non ho pensato che per un principiante potesse essere fuorviante, e mi dispiace, quindi modificherò il post.

Un tempo la parola "multitasking" era usata solo in informatica, oggi la si vuole applicare anche alle persone.
Considero questa parola ancora riservata all'ambito scientifico, lo sdoganamento a tutti i costi non mi piace.

C'è anche da dire che "parallelismo" e "multitasking" sono due cose differenti. Nel multitask di parallelo non c'è nulla, due task non possono essere eseguiti in parallelo, ma a ciascuno viene concesso l'uso della CPU per un tempo prestabilito, scaduto questo tempo si fa la stessa cosa nei confronti dell'altro task.

TASK0 10ms
TASK1 10ms

Mi pare evidente che TASK0 viene eseguito prima di TASK1, per cui sono in sequenza temporale che è
il contrario di parallelo. Non importa se TASK0 contiene 100 istruzioni e TASK1 10.

Riassumendo: c'è parallelismo (multitasking) quando vi è più di un compito portato avanti

Io non ci riesco, non è quanto accade, forse è una semplificazione su cui appoggiarsi per spiegare al principiante cose complesse. Per me c'è parallelismo quando due istruzioni elementari vengono eseguite allo stesso tempo.

Ciao.

Maurotec:
... Nel multitask di parallelo non c'è nulla, due task non possono essere eseguiti in parallelo, ma a ciascuno viene concesso l'uso della CPU per un tempo prestabilito, scaduto questo tempo si fa la stessa cosa nei confronti dell'altro task. ...

... occhio Mauro, ormai sono disponibili MCU (... quindi NON CPU ma microcontrollori) muti-core (es. dsPIC33CHxxxx di Microchip) dove quindi il parallelismo è veramente possibile (ed ampiamente utilizzato) :wink:

Guglielmo

Mi sembra che sia la strada intrapresa anche dagli altri produttori, ST, NXP ecc. Ma si tratta di due MCU ognuna potenzialmente indipendenti poste nello stesso case e interconnesse tramite un canale di comunicazione. Mi pare evidente che le due CPU si possono (si troveranno) ad eseguire codice elementare in parallelo, ma si tratta di applicazioni potenzialmente indipendenti per ogni MCU, ovvio che se si adotta questa soluzione si desidera anche farle comunicare tra di loro.

Avremo quindi stampanti 3d in cui i motori non perdono un passo mentre l'interfaccia utente sarà fluida e reattiva.

Ci sono combinazioni simili con ARM M4 + M0+, da ST e gli altri.

Comunque anche su Atmega c'è parallelismo, il timer va avanti anche quando la cpu è impegnata ad eseguire un istruzione. Possiamo considerare questo parallelismo? no.

Ciao.

Maurotec:
... si tratta di due MCU ognuna potenzialmente indipendenti poste nello stesso case e interconnesse tramite un canale di comunicazione.

Ci sono varie tecniche per implementare il "multi-core" e, in ogni caso, il risultato NON cambia ... hai più task che effettivamente girano in parallelo e possono avere dati in comune :wink:

Maurotec:
Comunque anche su Atmega c'è parallelismo, il timer va avanti anche quando la cpu è impegnata ad eseguire un istruzione. Possiamo considerare questo parallelismo? no.

Quello no, ma come la mattiamo con le CIP (Core Independent Peripherals), che sono sempre più utilizzate, sempre più potenti, sempre più complesse e che operano in modo completamente svincolato dalla MCU? ... non è un multitask nel senso "classico" del termine, ma sicuramente hanno raggiunto una complessità tale da svolgere veri e propri "compiti" in parallelo al "core" segnaladogli solo quando il "compito" è finito :wink:

Guglielmo

Maurotec:
Io non ci riesco, non è quanto accade, forse è una semplificazione su cui appoggiarsi per spiegare al principiante cose complesse. Per me c'è parallelismo quando due istruzioni elementari vengono eseguite allo stesso tempo.

Non ti do torto :slight_smile: , ma forse è diverso il punto di vista. Normalmente in informatica si parla di multitasking (multi processo) e parallelismo a livello di sistema, quindi per processo non si intende l'istruzione elementare.
Da questo punto di vista, quindi, un sistema multi processo è tale a prescindere da come venga realizzato (reale su multi core, time sharing su single core), l'effetto finale per l'utente è lo stesso.
Mia moglie dice sempre che cucina, stira e guarda la tv contemporaneamente, tralasciando il fatto che di solito brucia il ragù, dal suo punto di vista sta lavorando in multitasking (time sharing).
Quello che voglio dire è che non possiamo parlare di parallelismo in generale, ma bisogna distinguere a che livello.
Se parliamo di parallelismo a livello di istruzioni, hai perfettamente ragione, ma se parliamo di processi, allora il multitasking è parallelismo.

Con Arduino, se riesco a far lampeggiare un led (processo) mentre un buzzer bippa (processo), posso dire di aver realizzato un sistema multitasking? I processi lavorano in parallelo? Dal punto di vista del sistema prototipo, probabilmente si, o almeno ci si avvicina molto :slight_smile:

Spero di non aver frainteso ed essere stato chiaro :wink:

Federico

Federico66:
Quello che voglio dire è che non possiamo parlare di parallelismo in generale, ma bisogna distinguere a che livello. Se parliamo di parallelismo a livello di istruzioni, hai perfettamente ragione, ma se parliamo di processi, allora il multitasking è parallelismo.

Esattamente, è questione di livello di analisi :wink: Tra l'altro dal punto di vista dei processi un giro di loop (o ciclo di scansione se si parla di PLC) ha idealmente una durata infinitamente piccola, cosa che fisicamente non è vera neppure per le singole istruzioni eseguite su più core.