Pagine

giovedì 11 febbraio 2010

Controllo desktop remoto Win-Linux

Premessa

Oggi mio cugino mi ha scritto una mail nella quale mi chiedeva se conoscessi un modo per connettersi da Windows a Linux tramite desktop remoto.
Dato che son riuscito da poco tempo a convincerlo ad installare Linux e poichè sempre più persone, ultimamente, si stanno avvicinando a questo sistema operativo, in questo articolo parlerò proprio di come sia possibile accedere da Windows (o Mac,FreeBSD,Linux) ad un sistema Linux.

Client e Server

Per riuscire nel nostro scopo dobbiamo sfruttare la tecnologia client/server di un programma che si chiama VNC.
In pratica dovremmo installare sulla macchina Linux il server VNC e sulla macchina che usiamo per connetterci a Linux (che chiameremo host) un client VNC.
L'host potrà essere un computer con Windows ma nessuno ci vieta di usare un Machintosh o un FreeBSD o un altro Linux.

Lato server

Sulla nostra postazione Linux dovrò installare il server VNC. Per fare questo propongo vino, un server molto leggero e trasparente per gli utenti che usano Gnome come ad esempio su Ubuntu.
Si vada quindi su

Sistema->Amministrazione->Gestione pacchetti

Da qui si ricerchi "vino" e si installi il software. Se trovate anche il pacchetto vino-preferences separato dal precedente, installate anche quello.
A questo punto aprite un terminale (per esempio premendo ALT+F2 e nella finestra che appare srivendo gnome-terminal e premendo invio) e digitate vino-preferences.
Dovrebbe apparire una finestra come la seguente:

Qui sarà necessario cliccare sulle due opzioni:
  • Consentire agli altri utenti di visualizzare il proprio desktop
  • Consentire agli altri utenti di controllare il proprio desktop
e segnarvi il numerino dopo il due punti nella scritta "vncviever nome:0"

Lato client

A questo punto passiamo al sistema client.
Gli utenti Windows possono installare, ad esempio "TightVNC": bisogna installarlo e avviarlo (in questo articolo non mi dilungo a spiegare i passaggi data la semplicità del software).
Quando vi chiede a quale PC volete collegarvi scrivete:
indirizzo_ip_macchina_linux:numero_indicato_precedentemente
ad esempio:
192.168.1.1:0

Se tutto va bene vi troverete sul desktop remoto e potrete controllare la vostra macchina Linux in remoto.
Con qualche accorgimento sul router casalingo si può accedere anche quando siete in ufficio o da amici alla vostra macchina casalinga ma... questo discorso non verrà sviluppato in questa sede.

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"

venerdì 5 febbraio 2010

Multitasking Arduino: millis() -- PARTE 2

Un esempio pratico: inseguitore di luce

Nell'articolo precedente abbiamo visto la funzione millis() ed il suo utilizzo.
In questo post si vuole evidenziare con un esempio i passaggi da seguire per passare da uno sketch con il delay() ad uno con il millis().

Lo scenario

Per cercare di spiegare meglio il tutto partiamo con un esempio pratico.
Si immagini di avere due sensori di luce a distanza di pochi centimetri e posizionati a 90 gradi.
In mezzo a loro due servo motori che ruotano rispetto ciascuno di essi a seconda che la luce sia puntata verso un sensore o verso l'altro.
Questo sistema simula, in piccolo, il funzionamento di un inseguitore solare di pannelli fotovoltaici...



Primo sketch con delay...

Il primo esperimento è stato effettuato con il metodo classico usando il delay().
Prima di rappresentare il codice ricapitoliamo le esigenze:
  1. Leggere il sensore 1 e il sensore 2
  2. Muovere a seconda della luce i due motori in direzione della luce stessa
  3. Stampare a video le posizioni dei motori e l'intensità di luce rilevata
Alcune precisazioni sono d'obbligo per comprendere appieno il codice:
  1. I motore, per come è progettato, tra un comando e l'altro, desidera circa 15ms di ritardo
  2. Una persona, per leggere correttamente, deve avere un output circa ogni 500-1000ms
  3. Nell'articolo si da per scontato che il lettore conosca arduino, i fondamenti della sua programmazione e il collegamento di sensori analogici e componenti di base
Procediamo quindi con lo sketch:
#include  

#define SENSORELUCE1 0

#define
SENSORELUCE2 5

int letturaluce1=0;
int letturaluce2=0;
Servo servo1; //crea l'oggetto per il controllo del primo servo
Servo servo2; //crea l'oggetto per il controllo del secondo servo
int posservo1; //immagazzina la posizione del primo servo
int posservo2; //immagazzina la posizione del secondo servo

void setup(){
pinMode (SENSORELUCE1, INPUT);
pinMode (SENSORELUCE2, INPUT);
servo1.attach(9); //attaches the first servo on pin 9 to the servo object
servo2.attach(10); //attaches the second servo on pin 10 to the servo object
Serial.begin(9600);
}

void loop(){
letturaluce1=analogRead(SENSORELUCE1);
posservo1= analogRead(letturaluce1); // reads the value of the light sensor 1 (value between 0 and 1023)
posservo1=map(posservo1,0,1023,0,179); // scale it to use it with the servo (value between 0 and 180)
servo1.write(posservo1); // sets the servo position according to the scaled value
delay(15); // waits for the servo to get there

letturaluce2=analogRead(SENSORELUCE2);
posservo2= analogRead(letturaluce2); // reads the value of the light sensor 2 (value between 0 and 1023)
posservo2=map(posservo2,0,1023,0,179); // scale it to use it with the servo (value between 0 and 180)
servo2.write(posservo2); // sets the servo position according to the scaled value
delay(15); // waits for the servo to get there

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);
delay(1000); //solo per facilitare la lattura dei dati
}


Lo sketch funziona correttamente ma a causa dei continui delay() inserit che, abbiamo visto nelle precisazioni precedenti sono indispensabili, rende il programma macchinoso.
In particolare si riscontrano i seguenti problemi:

  • Se la luce cambia posizione velocemente, i servomotori non riescono a seguirla. Questo avviene perchè non aspettano solo 15ms tra un comando e il successivo bensì 1030ms (1000ms per la lettura e 15+15 per ciascun motore); si pensi se si inserissero altri elementi... il tempo si dilaterebbe aumentando questo probelma in maniera esponenziale.
  • Il movimento dei motori risulta a scatti ed estremamente impreciso. Ciò avviene perchè tra un comando e il successivo non passano 15ms (necessari al corretto funzionamento del motore ed impercettibili per l'occhio umano) bensì, come abbiamo visto al punto prima 1030ms (che per l'occhio umano risultano ben identificabili)
  • Un altro problema importante è che la lettura del sensore non è sempre molto accurata e può variare anche sensibilmente da una rilevazione alla successiva. In questo modo si genera un movimento "confuso" dei motori che rilevano valori anche molto discordanti da una volta all'altra

Il sistema risulta, nel suo complesso, lento a reagire ed estremamente impreciso oltre a dare una sensazione di scatto continuo.
Provate per credere :-)

Da queste "anomalie" nasce la necessità di modificare lo sketch e rendere il sistema più preciso e reattivo.
In particolare i primi due problemi verranno eliminati utilizzando la funzione millis() al posto del delay().
Il terzo problema al contrario con una media di rilevazioni come spiegato di seguito.

Vediamo quindi il codice seguente:


#include  

#define
SENSORELUCE1 0
#define SENSORELUCE2 5

int letturaluce1=0;
int letturaluce2=0;
int luce1;
int luce2;
int luce1temp;
int luce2temp;
Servo servo1; //crea l'oggetto per il controllo del primo servo
Servo servo2; //crea l'oggetto per il controllo del secondo servo
int posservo1;
int posservo2;

Fino a questo punto il programma non subisce alcuna modifica rispetto la versione precedente.
Le variabili prima dichiarate continuano a presentarsi anche in questo caso.
Successivamente, però, si dichiarano le variabili-tempo presentate nel precedente articolo.
Ad ogni variabile-tempo viene associato un evento.

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

time è la variabile che contiene "lo scorrere" del tempo; servotime1 e 2 rappresentano gli eventi associati al movimento dei motori; letturadati e lettura_luce rappresentano gli eventi associati alla lettura dei due sensori di luce.
La variabile k servirà come contatore per evitare le letture dei sensori "sballate".
Questo problema è stato anticipato prima; l'algoritmo che ci viene in aiuto, infatti, sarà semplicemente fare "x" letture consecutive e farne la media.
Considerando che arduino fa 16 milioni di operazioni al secondo, anche facendo 20 letture a distanza di 5ms, ogni frazione di secondo avremmo una rilevazione molto accurata del valore di luce circostante ed il calcolo della media smorza eventuali picchi anomali nei rilevamenti.

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;
}

Come spiegato nell'articolo precedente, si associa l'evento ad un if.
Ogni 5ms si effettua una lettura di entrambi i sensori ma nel frattempo il programma continua a "girare" senza nessun impedimento.

   if(k=20){

k=0;
letturaluce1=luce1temp/20;
letturaluce2=luce2temp/20;
luce1temp=0;
luce2temp=0;
}

Questo if risolve il problema della poca accuratezza nella misura.
Come detto due paragrafi fa, si effettuano k letture (in tal caso 20 ma nessuno vieta di aumentare o diminuire questo valore) al termine delle quali si effettua una media e si azzera il valore di k.
I valori di riferimento saranno sempre letturaluce1 e letturaluce2 che varieranno il proprio valore ogni 20 misure.

   if(time>servotime1+15){

posservo1= letturaluce1;
posservo1=map(posservo1,0,1023,0,179); //scala il valore da usare tra 0 e 180.
servo1.write(posservo1); //imposta la posizione del servo1
servotime1=millis();
}


if(time>servotime2+15){
posservo2= letturaluce2;
posservo2=map(posservo2,0,1023,0,179);//scala il valore da 0 a 180
servo2.write(posservo2); // imposta la posizione del servo2.
servotime2=millis();
}

Queste istruzioni dovrebbero essere chiare dopo la lettura del precedente articolo.
In entrambi i casi si rileva la intensità della luce, si posiziona il servo corrispondente rimappando il valore con la funzione map e si "azzerano" le variabile-tempo servotime1 e servotime2 assegnando loro il numero di millisecondi correnti.

   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.print(letturaluce2,DEC);
Serial.print("Posizione motore 2 : ");
Serial.println(posservo2,DEC);
Serial.print("Differenza: ");
Serial.println(letturaluce1-letturaluce2);
letturadati=millis();
}
}

Anche queste istruzioni seguono lo stesso ragionamento precedente.
In questo caso, addirittura, possiamo permetterci il lusso di "aspettare" 3000ms e non solo 1000ms tra una scrittura e la successiva (rendendo il tutto più leggibile all'utente finale) perchè il programma non si blocca ma continua a girare ed eseguire le azioni.

Tutto questo rende il movimento dei servo motori estremamente fluido.
L'osservatore non vedrà alcuno scatto e noterà anche una dinamica del sistema estremamente veloce con un "inseguimento" della luce decisamente reattivo.
Il programma nel suo complesso è decisamente utilizzabile.
Tuttavia presenta ancora un piccolo problemino: è lungo...
La lunghezza del codice proposto si può risolvere, come vedremo nel prossimo articolo con la programmazione a stati finiti.

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

Riporto il codice dell'intero sketch visto in questo articolo per facilitare il lettore che volesse testarlo:

#include  

#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;
letturaluce_time=millis();
}
if(k=20){
k=0;
letturaluce1=luce1temp/20;
letturaluce2=luce2temp/20;
luce1temp=0;
luce2temp=0;
}

if(time>servotime1+15){
posservo1= analogRead(letturaluce1); // reads the value of the light sensor 1 (value between 0 and 1023)
posservo1=map(posservo1,0,1023,0,179); // scale it to use it with the servo (value between 0 and 180)
servo1.write(posservo1); // sets the servo position according to the scaled value
servotime1=millis();
}

if(time>servotime2+15){
posservo2= analogRead(letturaluce2); // reads the value of the light sensor 2 (value between 0 and 1023)
posservo2=map(posservo2,0,1023,0,179); // scale it to use it with the servo (value between 0 and 180)
servo2.write(posservo2); // sets the servo position according to the scaled value
servotime2=millis();
}

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();
}
}


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

Argomento successivo: "Ardino e programmare a stati finiti"

giovedì 4 febbraio 2010

Multitasking Arduino: millis() -- PARTE 1

Premessa

Quando si iniziano a muovere i primi passi con Arduino, un ottimo punto di partenza per comprenderne il funzionamento è senza dubbio la pagina del playground.
Tramite gli spezzoni di codice presenti in questa sezione del sito, si è in grado di muovere i primi passi di programmazione sulla scheda.
Tuttavia, nel momento in cui la situazione diventa più articolata quando, cioè, si aggiungono due, tre, quattro, dieci componenti.... tutto diventa più difficile da seguire ed il grado di complicazione può diventare anche molto elevato.
In tal caso può agevolare molto il concetto di programmazione a stati finiti che andremo a vedere in articoli futuri ma anche l'utilizzo della funzione millis() che tratteremo in questo articolo e nei prossimi post e che permette una sorta di multitasking.

millis() vs delay()

Apparentemente le due funzioni non sono in contrapposizione ed effettivamente a livello logico è così.

La funzione delay(n) permette di fermare l'esecuzione del programma per n millisecondi. Tutto si blocca, la scheda è ferma e non rileva alcun input così come non invia alcun output.
Di conseguenza, se nel frattempo succede qualcosa e i sensori attaccati al sistema rilevano variazioni interessanti... non succederà nulla!
Questa opzione che può sembrare dannosa è, al contrario, estremamente importante in svariati scenari.
Se consideriamo che Arduino effettua circa 16 milioni di operazioni al secondo, sarebbe complesso leggere l'output di un comando su uno schermo se questo viene aggiornato a tale frequenza. In tal caso si usa il delay tra un avviso ed un altro aspettando almeno 500ms (mezzo secondo)...
Un altro caso è la lettura di un sensore. Si pensi ad un sensore di luce o temperatura; sono due sensori con una inerzia piuttosto elevata e per questo sarebbe inutile interrogarli 16 milioni volte in un secondo ma è sufficiente 1 volta al secondo o anche meno... Da qui l'uso di delay().

Al contrario millis() restituisce il numero di millisecondi passati dall'accensione del sistema con arduino. La funzione millis() può essere immagazzinata in una variabile unsigned long int: questo tipo di dati occupa molto spazio in memoria ma permette di arrivare a circa 50 giorni di autonomia prima di subire un azzeramento del valore.

Nei prossimi post andremo ad analizzare meglio queste differenze tra l'uso di delay() e millis().

Usare millis() in uno sketch

Il numero di millisecondi dalla accensione della scheda deve essere immagazzinato in una variabile all'inizio di loop() dopo essere stata inizializzata nel setup e quindi aggiornato ad ogni ciclo.
Tale variabile che chiameremo genericamente time e sarà il punto di riferimento per tutte le altre azioni/eventi.

A questo punto il ciclo di loop inizia a "girare" e si presenta un evento come, ad esempio, la lettura di un sensore o la scrittura su una seriale.
L'operazione viene effettuata la prima volta.
Se però, un evento differente o il medesimo evento si deve ripetere a distanza di tempo si presenta un problema.
In tal caso, infatti, si tende ad usare il delay per "bloccare" l'esecuzione dello sketch in attesa dell'evento successivo.
Il tutto funziona bene se i componenti in gioco sono pochi (2 o 3) ma in caso contrario si nota un rallentamento del sistema, un funzionamento "a scatti" o malfunzionamenti vari...

Per ovviare a questo problema si può assegnare ad ogni evento una variabile-tempo e se passano un tot di millisecondi tra l'evento ed il successivo, allora viene dato il comando di esecuzione tramite un if.

Tale spiegazione sembra piuttosto macchiavellica ma vediamo un esempio per capire meglio.
Leggete il codice seguente:
void setup(){
time=millis();
letturaluce_time=mills();
servotime=millis();
letturadati=millis();
}

void loop(){
time=millis();

if(time>letturaluce_time+5){
luce1=analogRead(SENSORELUCE1);
letturaluce_time=millis();
}

if(time>servotime1+15){
servo1.write(posservo1);
servotime1=millis();
}

if(time>letturadati+3000){
Serial.print("Sensore 1 : ");
Serial.print(letturaluce1,DEC);
Serial.print(" Posizione motore 1 : ");
Serial.println(posservo1,DEC);
Serial.print(" Differenza : ");
Serial.println(letturaluce1-letturaluce2);
letturadati=millis();
}
}

Cerchiamo di scoprire la dinamica di questo programma.
  • Nella funzione setup() vengono inizializzate le variabili time e quelle che segnano il tempo di ciascuno evento.
  • Successivamente la variabile time viene aggiornata all'inizio del loop() ad ogni ciclo
  • Si presuppongono tre eventi distinti: la lettura di un sensore (ogni 5 ms), il movimento di un servomotore (ogni 15ms) e la scrittura di dati sulla seriale (ogni 3000ms).
    Se usassimo il delay bloccheremo il programma ed è per questo che nello sketch sopra riportato si usa il millis().
Il concetto è il seguente:
  • Se sono passati x millisecondi fai ciò che è specificato nell'if ed aggiorna la variabile-tempo riferita all'evento.
Si noti infatti che ogni evento è associato ad un if(time>variabiletempo+millisecondi).
All'interno dell'if vengono svolte alcune azioni.
Al termine del if, l'ultima istruzione è l'aggiornamento della variabile-tempo corrispondente assegnandogli un nuovo valore di millis().

Questo è il concetto che sta alla base del multitasking con arduino ed il modo per eliminare il delay nei programmi sostituendolo con millis().
Nei prossimi post approfondiremo l'argomento.

Articolo successivo "Multitasking Arduino: millis() -- PARTE 2"