Pagine

martedì 9 febbraio 2010

Arduino e programmare stati finiti

Premessa

Dopo aver visto come simulare il multitasking con Arduino grazie alla funzione millis() nella prima e seconda parte dell'articolo, andiamo a vedere la programmazione a stati finiti.
Il problema fondamentale che iniziava a presentarsi nella seconda parte è che il programma tende a diventare piuttosto mastodontico mano a mano che si aggiungono nuovi elementi che ineragiscono tra loro.
Dopo poco tempo l'utente inizia a sentire l'esigenza di dividere il codice in funzioni: si tende, quindi, a raccogliere le istruzioni in "gruppi" organizzati per rendere lo sketch più facile da leggere e gestire --i romani dicevano "Divide et impera"--.
Non è scopo di questo articolo vedere cosa sia una funzione e come si passino argomenti ad essa; l'autore presuppone che il lettore sappia creare una funzione sull'IDE di Arduino.
Fatta questa doverosa premessa andiamo a vedere come iniziare la programmazione a stati finiti su Arduino ed alleggerire la lettura del codice dei nostri sketch.

La programmazione a stati finiti

La programmazione a stati finiti consiste nel dividere il codice in blocchi diversi.
Tali blocchi sono racchiusi in una funzione che rappresenta un singolo evento e raccoglie porzioni di codice appartenenti al medesimo "livello logico" e nel quale si aggiorna, alla fine, la variabile-tempo (rif. prima parte e seconda parte dell'articolo) associata riassegnandogli il millis().
Si riprenda l'esempio visto in precedenza (rif. fondo pagina della seconda parte) e vediamo nella pratica in cosa consista.
Lo riporto qui di seguito per comodità del lettore.
#include <Servo.h>
#define SENSORELUCE1 0
#define SENSORELUCE2 5
int letturaluce1=0;
int letturaluce2=0;
int luce1;
int luce2;
int luce1temp;
int luce2temp;
Servo servo1;
Servo servo2;
int posservo1;
int posservo2;

int k;

unsigned long time;
unsigned long servotime1;
unsigned long servotime2;
unsigned long letturadati;
unsigned long letturaluce_time;

void setup(){
pinMode (SENSORELUCE1, INPUT);
pinMode (SENSORELUCE2, INPUT);
servo1.attach(9);
servo2.attach(10);
Serial.begin(9600);

time=millis();
servotime1=millis();
servotime2=millis();
letturadati=millis();
letturaluce_time=millis();

k=0;
luce1=0;
luce2=0;
luce1temp=0;
luce2temp=0;
}

void loop(){
time=millis();

if(time>letturaluce_time+5){
luce1=analogRead(SENSORELUCE1);
luce2=analogRead(SENSORELUCE2);
k=k+1;
luce1temp=luce1temp+luce1;
luce2temp=luce2temp+luce2;
}
if(k=20){
k=0;
letturaluce1=luce1temp/20;
letturaluce2=luce2temp/20;
luce1temp=0;
luce2temp=0;
}

if(time>servotime1+15){
posservo1= analogRead(letturaluce1);
posservo1=map(posservo1,0,1023,0,179);
servo1.write(posservo1);
servotime1=millis();
}

if(time>servotime2+15){
posservo2= analogRead(letturaluce2);
posservo2=map(posservo2,0,1023,0,179);
servo2.write(posservo2);
servotime2=millis();
}

if(time>letturadati+3000){
Serial.print("Sensore 1 : ");
Serial.print(letturaluce1,DEC);
Serial.print(" Posizione motore 1 : ");
Serial.println(posservo1,DEC);
Serial.print("Sensore 2 : ");
Serial.print(letturaluce2,DEC);
Serial.print(" Posizione motore 2 : ");
Serial.println(posservo2,DEC);
Serial.print(" Differenza : ");
Serial.println(letturaluce1-letturaluce2);
letturadati=millis();
}
}
Il codice già ampiamente spiegato permette di far muovere due servo motori al variare dell'intensità di luce che insiste su due sensori di luce perpendicolari fra loro.
Questo codice si "divide" in porzioni ben definite:
  • La sezione in cui si leggono i sensori
  • La sezione in cui si fanno muovere i motori
  • La sezione nella quale si invia alla seriale un output.
Queste sezioni si possono raccogliere in apposite funzioni e si sfrutta una peculiarità dell'IDE di arduino: i tab.
Ogni funzione infatti si aggiunge ad un tab differente: per fare questo si clicchi la freccina verso destra nell'angolo in alto a destra dell'IDE e poi su "New Tab".
Si dia un nome alla tab che DEVE essere uguale al nome della funzione che si scriverà all'interno.
Come si vede in figura quindi, si aggiungeranno tre Tab; una per ciascuna funzione:
  • leggiLuce
  • muoviServoMotori
  • scriviSuOutput
Riporto di seguito ciò che si scriverà nei tre tab.

Leggi Luce

Questa funzione racchiude le istruzioni che arduino deve svolgere per leggere i sensori di luce.
In particolare si notino le 20 letture necessarie per "ammortizzare" gli sbalzi del sensore analogico e il reset della variabile tempo alla fine della funzione.
void leggiLuce(){
if(time>letturaluce_time+5){
luce1=analogRead(SENSORELUCE1);
luce2=analogRead(SENSORELUCE2);
k=k+1;
luce1temp=luce1temp+luce1;
luce2temp=luce2temp+luce2;
}
if(k=20){
k=0;
letturaluce1=luce1temp/20;
letturaluce2=luce2temp/20;
luce1temp=0;
luce2temp=0;
}
letturaluce_time=millis();
}

Muovi servo motori

Questa funzione, come si intuisce, raccoglie le istruzioni sul movimento dei Servi.
Anche in questo caso si noti l'azzeramento della variabile-tempo al fondo della funzione.

/* Muove i SERVI*/

void muoviServoMotori(){
if(time>servotime1+15){
posservo1= analogRead(letturaluce1);
posservo1=map(posservo1,0,1023,0,179);
servo1.write(posservo1);
servotime1=millis();
}

if(time>servotime2+15){
posservo2= analogRead(letturaluce2);
posservo2=map(posservo2,0,1023,0,179);
servo2.write(posservo2);
servotime2=millis();
}
}

Scrivi su Outout

Infine lo scrivi output si comporta "a livello logico" come le funzioni viste in precedenza. Esso scrive sulla seriale le informazioni da visualizzare all'utente ogni 3 secondi.
void scriviSuOutput(){
if(time>letturadati+3000){
Serial.print("Sensore 1: ");
Serial.println(letturaluce1,DEC);
Serial.print("Posizione motore 1: ");
Serial.println(posservo1,DEC);
Serial.print("Sensore 2: ");
Serial.println(letturaluce2,DEC);
Serial.print("Posizione motore 2: ");
Serial.println(posservo2,DEC);
Serial.print("Differenza : ");
Serial.println(letturaluce1-letturaluce2);
letturadati=millis();
}
}

Sketch principale

A questo punto lo sketck principale si riduce sensibilmente.
Ogni tab racchiude le porzioni di codice e ci possiamo soffermare sulla dinamica del programma.
#include <Servo.h>
#define SENSORELUCE1 0
#define SENSORELUCE2 5
int letturaluce1=0;
int letturaluce2=0;
int luce1;
int luce2;
int luce1temp;
int luce2temp;
Servo servo1;
Servo servo2;
int posservo1;
int posservo2;

int k;


unsigned long time;
unsigned long servotime1;
unsigned long servotime2;
unsigned long letturadati;
unsigned long letturaluce_time;

void setup(){
pinMode (SENSORELUCE1, INPUT);
pinMode (SENSORELUCE2, INPUT);
servo1.attach(9);
servo2.attach(10);
Serial.begin(9600);

time=millis();
servotime1=millis();
servotime2=millis();
letturadati=millis();
letturaluce_time=millis();
k=0;
luce1=0;
luce2=0;
luce1temp=0;
luce2temp=0;
}

void loop(){
time=millis();

leggiLuce();
muoviServoMotori();
scriviSuOutput();

}

Come si può vedere la funzione loop() viene ridotta veramente all'osso.
Questo sketck si riduce ad un banale programmino.
Nel loop non è stata aggiunta alcuna "logica" ma è facile immaginare degli if che determinano l'attivazione di una o più funzioni a seconda di determinati eventi...

Conclusione

Frequentando il forum di Arduino, noto che le persone spesso entrano in crisi nel momento in cui gli sketch aumentano di dimensioni.
Spesso si tende a desistere nei propri progetti per questo motivo.
Trovo che l'applicazione dei consigli dati in questi tre articoli (multitasking + programmazione a stati finiti) risolva la maggior parte delle problematiche a livello di programmazione dovuta all'aggiunta di più componenti all'arduino.
Personalmente incontro maggiori difficoltà nelle saldature ma questo è un problema della mia mancanza di manualità :-)

Argomento precedente: "Multitasking Arduino: millis() -- PARTE 2"

25 commenti:

  1. grande vittorio. Se continui così mi commuovo ;)-

    veramente interessante.

    RispondiElimina
  2. Dopo quella sulla programmazione dell'ATMEGA, questa mi servirà davvero tanto per il mio progettone di elicottero XD

    Thanks

    RispondiElimina
  3. Uhmmm.
    Progetto sull'elicottero?
    Dicci di più mi ha sempre appassionato questo genere di progetti!!

    RispondiElimina
  4. Ciaoo Vittorio
    Sono veramente soddisfatto dei tuoi articoli!
    MI hai fatto capire tante belle cose..grazie

    RispondiElimina
  5. Grazie Massimiliano,
    commenti come questi sono sempre graditi e stimolo a proseguire!

    RispondiElimina
  6. Mi piace tanto quell'if(k=20) ;)

    RispondiElimina
  7. Ciao,

    grazie mille per i 3 articoli, li ho trovati davvero utili.

    Ero difatti indeciso se fiondarmi in questa nuova esperienza, arduino, o no, ma dopo questi chiarimenti direi di poter provare!

    La mia paura era propio che essendo procedurale, non c'era alcun metodo per raggirare il problema del delay!

    RispondiElimina
  8. Ciao Davide,
    mi fa estremamente piacere leggere questo commento.
    A presto,
    Vittorio

    RispondiElimina
  9. ahahah!
    e io che stavo diventando matto per 2 operazioni "parallele"...
    Grazie mille!

    RispondiElimina
  10. ciao vittorio,

    sto cercando di risolvere un piccolo problema.
    Dico subito che sono neofita riguardo arduino&co. :)
    ho realizzato attraverso i tutorial sul sito di arduino un semplice circuito con una fotoresistenza e l'ho fatto girare su arduino con lo sketch "standard firmata" e tramite maxuino riesco a ricevere il segnale.

    ho provato allora ad aggiungere un'altra fotoresistenza, ma non arriva nessun segnale :(
    come devo operare sullo sketch per ricevere entrambi i valori?

    ti ringrazio in anticipo :)

    RispondiElimina
  11. Grande queste tre 3 lezioni sono state veramente utili.

    RispondiElimina
  12. Grazie calucio24.
    Sei davvero gentile :-P

    RispondiElimina
  13. Ciao a tutti .... una domanda forse stupida.... cosa succede " la mattina dopo"?
    ovvero l'inseguitore si feram gaurdandi ad ovest... riesce da solo all'alba a perceipre che viene luce da una direzione di 180°?

    Grazie

    RispondiElimina
  14. ciao Vittorio
    GRAZIE !
    pensa che questo problema dei listati lunghissimi e che a volte diventano incomprensibili se non mettendo continue note per capire a cosa fanno riferimento alcune funzioni, me lo sono posto parecchie volte, e avevo parzialmente risolto cercando di ottimizzare il codice in blocchi funzionali.

    del sistema della programmazione a stati finiti aprendo altre " Schede " " Tab " tramite la freccetta a destra, (neanche me ne ero accorto a che servisse :-) ), non ne evevo mai sentito parlare addirittura !.

    ma perche non mettere quel menu nel menu in alto ??? almeno uno lo vede e si chiede a cosa serve......

    comunque sei un GRANDE !
    arrigrazie !!

    ciao
    Salvatore B

    RispondiElimina
    Risposte
    1. Grazie a te Salvatore.
      Se questi brevi articoli possono servire ne sono solo contento :-)

      Elimina
  15. bell'articolo.
    mi togli una curiosita, come riesci ad inserire in blogspot.it il codice Fortattato e colorato correttamente ?
    grazie
    ciao

    RispondiElimina
    Risposte
    1. Ciao Roberto, ci sono diverse utility che fanno questo lavoro.
      Una volta ne avevo trovato uno online e se fai una ricerca ne troverai senza dubbio diversi. Per quel codice in particolare usai una utility per Linux che si chiamava htmltidy: un po' ostica ma funzionale no? :-)

      Elimina
  16. Prima di tutto mi unisco ai tanti complimenti inviati: meritatissimi!
    devo inserire i rintocchi per una campana (ora e quarti d'ora) all'interno di un ciclo di un orologio con LCD con un delay di circa 1 sec. il tempo max dei rintocchi potrebbe essere di 24+3 sec. durante questo periodo di tempo vorrei mantenere l'LCD funzionante ed attive le altre funzioni. puoi darmi qualche consiglio? intanto mi leggo i tuoi articoli. grazie molte Feliciano

    RispondiElimina
  17. Ciao Feliciano, l'unico consiglio che posso darti che di mettere un tempo di delay tra un rintocco e l'altro di un secondo circa. In questo modo l'LCD dovrebbe rimanere tranquillamente acceso perchè la funzione millis deciderà se far partire l'impulso di suonare la campana o mantenere lo schermo acceso

    RispondiElimina
  18. Complimenti Vittorio, e mille grazie per i 3 articoli.
    Nozioni essenziali per redigere al meglio sketch particolarmente lunghi o complessi.
    Grazie!
    Paolo

    RispondiElimina
  19. Complimenti Vittorio, e mille grazie per i 3 articoli.
    Nozioni essenziali per redigere al meglio sketch particolarmente lunghi o complessi.
    Grazie!
    Paolo

    RispondiElimina
  20. Ti ringrazio vivamente di questi articoloni con spiegazioni.. :) buon tutto

    RispondiElimina
  21. Ciao Vittorio! Sei un grande. Hai reso semplice e chiaro un argomento che è caustico! Ti ringrazio davvero tanto! Sto facendo un progetto per l'azienda in cui lavoro e sto utilizzando arduino! E' un sistema che gestirà le sirene dello stabilimento mediante doppino e DTMF. Avrò un master con arduino mega e circa 30 slave con arduino uno. Ti ringrazio per i consigli. Il tuo lavoro è veramente prezioso!

    Ciao by Davide

    RispondiElimina
  22. sono Daniele Negrato e sto sperimentando Arduino uno, ma siccome sono abituato a lavorare con i PLC, volevo sapere se esisteva un programma che utilizzi il linguaggio dei PLC per Arduino.
    Grazie dell'attenzione.
    buona serata

    RispondiElimina