Qual'è il giusto approccio (programmazione) per un robottino autonomo?

Buonasera a tutti,
mi sto costruendo un piccolo robottino sterzante (con le ruote anteriori) che vorrei fosse in grado di muoversi liberamente evitando gli ostacoli... fin qui nulla di strano!
immagine dal cad
Premesso che ho testato tutti i movimenti singolarmente comandati da una app che mi son creato....
...Adesso arriva il momento di combinarli assieme in programmazione.
E qui mi sorge un dubbio... dai vari tutorial che ho trovato sul web si utilizza spesso una sorta di "macchina a stati" utilizzando il costrutto

switch(stato)
{
   case x:
   //istruzioni
   break;
}

Per quanto abbia abbandonato lo studio di Java da qualche anno ricordo perfettamente che questo costrutto era considerato "odioso" e da evitare per quanto possibile.
E' lontano da me definire questo costrutto giusto o sbagliato....
Sono però a chiedere un consiglio su come approcciarsi nel modo migliore per combinare assieme varie fasi degli automatismi.
Grazie anticipatamente a chi mi consiglia e/o aiuta!
Andrea

Sicuramente l'utilizzo della macchina a stati finiti è suggerito in tantissime casistiche dove una serie di azioni devo essere eseguite in modo indipendente e/o sequenziale.
Nel tuo caso può anche andare bene bisogna capire se la tipologia di azioni che devi fare possano essere descritte e sviluppate come macchina a stati oppure no.
Può essere una buona base di partenza assieme, e quello è indispensabile, allo sviluppo di un programma che non presenti (dove non strettamente necessario) parti bloccanti in modo che tutta la parte "evita ostacoli" sia la più fluida e reattiva possibile

1 Like

Con java sul PC c'è il sistema event driver fornito in buona parte dal sistema operativo, il quale non è presente sul tuo rover. Già questa sola differenza fa si che lo switch non sia utile in java. In generale senza un RTOS si fatica a fare il rover, con RTOS una volta capito come usarlo (e non è facile) non ci si deve preoccupare di task che richiedono 500ms per essere eseguiti, in quanto li spezzetta in 10ms o meno. Senza RTOS dobbiamo scrivere il task che si completa in 500ms in modo che ceda il controllo ogni tot tempo. Questo comporta la conoscenza delle tempistiche o metriche di ogni funzione.

Una volta chiamata una funzione essa detiene il controllo fino al momento in cui esegue codice che cede il controllo. Con RTOS c'è una entità che da il controllo e lo toglie ad ogni task (che di fatto è una funzione).

Riguardo allo switch case è impensabile usarlo quando gli stati sono tanti, ad esempio 50 case sicuramente non rientrano più in una schermata e abbiamo perso uno dei vantaggi che è quello che ci permette di capire la sequenza con un colpo d'occhio. switch case o meno gli stati esistono sempre, lo switch permette di concentrarli in una schermata anziché distribuirli tra le funzioni (o task in caso di RTOS). Lo switch può tornare utile per le modalità operative, ad esempio per entrare in modalità diagnosi e test, sempre che lo switch sia corto.

Quindi riassumendo non devi guardare allo switch come soluzione, ma a come viene usato lo switch per rendere il sistema "reattivo" anche se non event driver. Ad esempio se trovi un modo alternativo allo switch che funzioni allo stesso modo garantendo "reattività" questo modo avrà sia vantaggi che svantaggi. In breve se il tuo sketch è composto da 10 funzioni e ognuna si tiene il controllo per 1000 ms, chiamando una dopo l'altra il loop ricomincia dopo 1000 x 10 = 10000 ms. Altra cosa importante è cedere il controllo al loop principale che inizia sempre con una routine di polling degli ingressi. Quindi chiamo funcA() (si tiene il controllo per 1ms) e restituisco il controllo al main loop che esegue la routine di lettura degli ingressi. Ora è il turno di funcB() stessa cosa.

Ciao.

1 Like

Intanto grazie per le info.
Da quello che posso intuire senza ancora aver scritto nessuna riga di codice per la "macchina a stati" quello che penso è che qualora il listato entra nello switch da qui non dovrebbe più uscire. Quindi la funzione loop() non si ripete. O perlomeno, quello che penso, non dovrebbe ripetersi e tutto dovrebbe essere demandato a chiamata di funzioni in un unico ciclo loop senza che questo venga interrotto e/o ripetuto.
Provo a spiegarmi meglio con un esempio.
Appena parte il loop chiamo la funzione di farlo andare avanti ( da qualche parte bisogna pur iniziare :D) e diciamo entra nel primo stato. Dentro al primo stato eseguo ogni tot millis() il controllo ostacolo. Se incontra un'ostacolo farà qualcos'altro ma prima di tutto passerà allo stato 2 (che è quello di fermarsi). Sempre dentro a questo stato 2, dopo aver fatto un controllo su quale sia la direzione migliore da prendere girerà in una direzione e ritornerebbe allo stato 1 (avanti). Così facendo il loop non si dovrebbe ripetere perchè rimarrà sempre all'interno dello switch/case/break.

int distanzaOstacolo;
int stato = 1;
void setup()
{
}

void loop() 
{
  switch(stato)
  {
    case 1:
    avanti();
    controlloOstacolo();
    if(distanzaOstacolo < 5)
    {
      stato = 2;
    }
    break;
    case 2:
    stop();
    //muove ultrasioni a destra e a sinistra per capire quale sia la direzione migliore
    //sterza le ruote nella direzione migliore
    stato = 1;
    break;
    //.... continua con altri 5/6 stati senza praticamente mai uscire dallo switch
  }
}

void controlloOstacolo()
{
  //esegue il controllo ostacolo ogni tot millisecondi senza usare delay()
  //imposto la variabile distanzaOstacolo
}
void avanti()
{
  //si muove in avanti
}
void stop()
{
  //ferma il rover
}

Sbaglio qualcosa??

Lo switch ha questa dote grazie al comando break. Prendo il case 1 commentando la if che decide di cambiare stato. Adesso il solo break una volta eseguito scarta tutti gli altri case e si arriva al return implicito della funzione loop, cioè la graffa di chiusura }. A questo punto il loop cede il controllo al chiamante che è la funzione main (non visibile poiché interna al core lib), main ha il controllo e chiama nuovamente la funzione loop.

La variabile stato ha conservato il valore 1 quindi lo switch seleziona il case 1. La storia si ripete all'infinito, hai introdotto nel main loop l'esecuzione del codice contenuto nel case1. Questo meccanismo è fondamentale per fare in modo di introdurre nel loop codice che deve essere sempre eseguito indipendentemente dalla variabile stato che può essere temporizzato e disabilitato/abilitato. Ci sarà sempre del codice che vuoi venga eseguito
in modo indipendente dallo switch, pensa solo al blink, che potresti abilitare/disabiitare, cambiare frequenza di lampeggio al fin di indicare una anomalia.

Pulsanti, sensori richiedono del codice e che questo venga eseguito ad intervalli regolari e l'unico posto dove inserirlo nel loop è alla fine o meglio all'inizio.

Adesso hai scritto codice che terminato il case 1, cede il controllo al loop sia che tu abbia selezionato il case 2 o meno grazie proprio al break. Così quando selezioni il case 2 da dentro il case 1 stai programmando cosa accadrà nel prossimo ciclo di loop (programmi il futuro). Però sostieni che dovrebbe rimanere confinato dentro lo switch case, cosa sconsigliata perché è strada che non spunta.

Ti fornisco dei link che però sarebbe bene li studiassi e smontassi il codice per capire come funziona, se devi aprire i link solo per dargli una occhiata lascia stare non aprirli proprio.

Sapevi che nella teoria delle state machine esistono gli entry state e gli exit state, l'entry state è utilissimo ed è facile usarlo, mente per gli exit state lo switch case non si presta molto per implementarlo.

Nota anche che se dentro un case non si presenta una o più condizioni per selezionare il prossimo case, si finisce per rimanere intrappolati senza potere eseguire il resto dei case. Quindi ad esempio si può uscire per tempo scaduto e salvare da qualche parte questa anomalia in modo che il case selezionato possa tenere in considerazione ciò, che tradotto significa che il case 2 si aspetta di essere selezionato da condizioni e non per timeout in tal caso case2 potrebbe comportarsi diversamente dal solito.

PS: trovare il numero di stati necessari e il loro nome e la semantica è lavoro complesso, che prende tanto tempo.

Ciao.

Immensamente grazie!!!!
Sono uno che "vuole" capire! Quindi i link inviati li studierò per bene!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.