Molte volte, soprattutto quando si chiede l’interazione con l’utente, dobbiamo prevedere il caso che lo stesso si “distragga” e non porti a termine l’operazione richiesta entro un certo tempo. Questo può ad esempio accadere quando si creano interfacce utente/Arduino su seriale oppure su display LCD/grafici. Facciamo un esempio: all’utente viene chiesto di inserire un numero/testo oppure di effettuare una scelta a video. Ma che succede se l’utente si allontana e non termina l’operazione? La richiesta rimarrà a video, sospesa, in attesa di un input. Ma se questa interfaccia è accessibile da persone diverse dall’utente? Prendiamo il caso di un circuito che gestisce qualcosa dotato di display LCD e di tastiera. Generalmente questo tipo di progetto ha un menu di gestione con il quale configurare le varie opzioni senza dover programmare ogni volta la memoria del microcontrollore. L’accesso al menu di gestione viene generalmente protetto con una password proprio per limitare l’accesso ad uno od a pochi utenti (generalmente “admin”, amministratori). Ma se durante la gestione dei parametri, l’admin si allontana e lascia l’interfaccia di gestione aperta? Il primo che passa può alterare il funzionamento dell’intero dispositivo! E’ bene quindi prevedere un sistema di uscita a tempo (in inglese “timeout”) affinché trascorso un certo intervallo senza ricevere input, il programma esca dal menu di gestione e torni alla normale modalità operativa. Personalmente uso 2 modi per gestire i timeout: un modo che prevede un’uscita assoluta, sia che l’utente interagisca con l’interfaccia sia che non lo faccia, ed un modo che inizia a contare il tempo trascorso solo quando c’è inattività da parte dell’utente. Cerco di spiegare meglio le due modalità.
- 1° caso: l’utente deve inserire la password di accesso entro, ipotizziamo, 10 secondi. Sia che prema i tasti sia che non li prema, il software conta comunque 10 secondi. Se l’utente inserisce la password entro il tempo previsto, il programma continua poi con il controllo della stessa; se, invece, trascorrono i 10 secondi prima che l’utente abbia inserito la password, il programma torna alla modalità operativa normale.
- 2° caso: all’utente è chiesto di interagire con l’interfaccia, ad esempio navigare tra le voci del menu di configurazione oppure inserire un dato con la tastiera. In questo caso si deve lasciare il tempo all’utente di poter immettere i dati, quindi andremo a controllare se il timeout occorre solo quando non c’è attività da parte dell’utente (ossia l’utente non sta premendo nessun tasto) mentre andremo ad azzerare il tempo trascorso non appena viene premuto qualcosa.
Analizziamo il primo caso. Per semplicità, in questo primo test, non useremo la connessione con il PC per non appesantire il codice e renderlo di difficile comprensione. Il circuito che realizzeremo sarà il seguente: Necessitiamo di una scheda Arduino, di una breadboard, di un pulsante, di un LED rosso e di un resistore da 220 ohm. Prendiamo il LED rosso e colleghiamo il catodo del LED a massa con il resistore ed il suo anodo al pin 8. Poi colleghiamo un’estremità del pulsante a massa e l’altra (attenzione a prendere piedini dei lati opposti, altrimenti fate un corto circuito) al pin 7 di Arduino. Per il pulsante non usiamo nessun resistore di pull-up o pull-down perché attiveremo la pull-up interna al pin. Questo è il codice da caricare sull’Arduino:
/* EXAMPLE OF A TIMEOUT TO EXIT AN INTERACTIVE
SESSION BETWEEN USER AND ARDUINO
This example is released under the Creative Commons
License BY-NC-SA 4.0 ITA or newer
Author: Leonardo Miliani
Website for explanatory article and newer versions
of this code: www.leonardomiliani.com
Release: 20140811-1.0
*/
//pins declaration
const byte INPUT_BUTTON = 7;
const byte OUTPUT_LED = 8;
//global variables
const unsigned long timeout = 10000; //the timeout in milliseconds
//setup
void setup() {
//pins configuration
pinMode(OUTPUT_LED, OUTPUT); //LED pin as output
pinMode(INPUT_BUTTON, INPUT_PULLUP); //button pin as input w/internal pull-up
}
//loop
void loop() {
//has the user pressed the button?
if (!digitalRead(INPUT_BUTTON)) {
//wait for a while, to avoid bounces
delay(30);
if (!digitalRead(INPUT_BUTTON)) { //the button is still pressed, it wasn't a bounce
interactWithUser();
}
}
}
//interaction with user
void interactWithUser() {
unsigned long startingTime; //used to store the starting moment
boolean pressed = false; //used to know if the user has pressed the button
//wait for the release of the button
while(!digitalRead(INPUT_BUTTON));
digitalWrite(OUTPUT_LED, HIGH); //LED on
//loop
startingTime = millis();
do {
//has the user pressed the button?
if (!digitalRead(INPUT_BUTTON)) {
//debounce
delay(30);
if (!digitalRead(INPUT_BUTTON)) {
//the user has pressed the button
pressed = true;
break;
}
}
} while ((!pressed) && ((millis() - startingTime) < timeout));
//this loop will only end when the user has pressed the button or it goes in timeout
//check if hte user has pressed the button in time
if (pressed) {
//fast blinking for confirmation
for (byte i = 0; i < 6; i++) {
digitalWrite(OUTPUT_LED, HIGH);
delay(50);
digitalWrite(OUTPUT_LED, LOW);
delay(50);
}
} else {
//light off the LED
digitalWrite(OUTPUT_LED, LOW);
}
}
Il cuore del codice è il ciclo do..while inserito nella funzione interactWithUser. Per far sì che tutto funzioni abbiamo usato 2 variabili: pressed, che contiene se il pulsante è stato premuto, e startingTime, che contiene quando è iniziato il controllo del timeout (il valore del timeout è predefinito in una costante ad inizio codice). Prima del ciclo do..while basta porre a false la variabile di controllo e memorizzare il corrente valore di millis(). All’interno del ciclo controlleremo se l’utente preme il pulsante: in caso affermativo, porremo a true la variabile e poi, con un break, usciremo immediatamente dal ciclo do..while. Questo ciclo continua finché la variabile di stato è false (quindi l’utente non ha premuto niente) ed il controllo non è andato in timeout, timeout che viene gestito con il test del while: “se la differenza fra il valore corrente di millis e quello registrato ad inizio ciclo è maggiore di timeout allora esci dal ciclo“. Questo è il controllo più semplice. Aspetta il verificarsi di una condizione (che può essere anche complessa, ad esempio l’inserimento di una password da seriale oppure tramite tastiera) ed esce non appena si verifica oppure il tempo a disposizione termina. Il secondo caso è un pò diverso. Ogni volta che viene premuto il pulsante, dobbiamo riazzerare il timer affinché il timeout si verifichi solo dopo che l’utente ha terminato di interagire col nostro dispositivo per un certo lasso di tempo. Il codice è simile al precedente: al posto dell’uscita immediata, ogni volta che premiamo il pulsante il codice resetta il timer, facendo ripartire da 0 il controllo sul timeout.
/* EXAMPLE #2 OF A TIMEOUT TO EXIT AN INTERACTIVE
SESSION BEtWEEN USER AND ARDUINO
This example is released under the Creative Commons
License BY-NC-SA 4.0 ITA or newer
Author: Leonardo Miliani
Website for explanatory article and newer versions
of this code: www.leonardomiliani.com
Release: 20140811-1.0
*/
//pins declaration
const byte INPUT_BUTTON = 7;
const byte OUTPUT_LED = 8;
//global variables
const unsigned long timeout = 10000; //the timeout in milliseconds
//setup
void setup() {
//pins configuration
pinMode(OUTPUT_LED, OUTPUT); //LED pin as output
pinMode(INPUT_BUTTON, INPUT_PULLUP); //button pin as input w/internal pull-up
}
//loop
void loop() {
//has the user pressed the button?
if (!digitalRead(INPUT_BUTTON)) {
//wait for a while, to avoid bounces
delay(30);
if (!digitalRead(INPUT_BUTTON)) { //the button is still pressed, it wasn't a bounce
iteractWithUser();
}
}
}
//iteraction with user
void iteractWithUser() {
unsigned long startingTime; //used to store the starting moment
//wait for the release of the button
while(!digitalRead(INPUT_BUTTON));
digitalWrite(OUTPUT_LED, HIGH); //LED on
//loop
startingTime = millis();
do {
//has the user pressed the button?
if (!digitalRead(INPUT_BUTTON)) {
//debounce
delay(30);
if (!digitalRead(INPUT_BUTTON)) {
//the user has pressed the button
//fast blinking for confirmation
digitalWrite(OUTPUT_LED, LOW);
delay(100);
digitalWrite(OUTPUT_LED, HIGH);
delay(100);
startingTime = millis(); //we reset the initial time
}
}
} while ((millis() - startingTime) < timeout);
//this loop will only end when the it goes in timeout
//light off the LED
digitalWrite(OUTPUT_LED, LOW);
}
Come si vede dal codice, ogni volta che l’utente preme il pulsante, il codice emette un veloce lampeggio del LED poi salva nuovamente il corrente valore di millis, di modo che il test di uscita del ciclo do..while faccia terminare l’iterazione dopo che sia trascorso il prestabilito valore di timeout ma, questa volta, dall’ultima pressione del pulsante e non dalla prima, come nell’esempio precedente. Ovviamente questi codici sono solo dimostrativi. Starà al lettore adattarli alle proprie esigenze, affinché possano gestire casi molto più complessi di quelli analizzati qui.