Lampeggio del led e controllo sequenziale
Si vuole ora rivisitare l'esercizio del lampeggio del led e risolverlo usando la procedura indicata per il controllo sequenziale.
Prima di cominciare però rinfreschiamoci un po' la memoria. L'esercizio prevedeva che nel circuito di figura
il led coollegato al piedino 13 lampeggi con un periodo di due secondi; un secondo acceso e un secondo spento.
Il programma che risolve genericamente il problema è banale:
#define ledpin 13tuttavia non esplicita bene il funzionamento del controllo sequenziale così come visto quando si è parlato di linguaggio UML e in particolare di SFC.
void setup() { pinMode(ledpin, OUTPUT); } void loop() { digitalWrite(ledpin, HIGH); // turn the LED on (HIGH is the voltage level) delay(1000); // wait for a second digitalWrite(ledpin, LOW); // turn the LED off by making the voltage LOW delay(1000); // wait for a second }
Proviamo per esercizio ad analizzare il problema nell'ottica di un controllo sequenziale.
Per prima cosa è necessario individuare gli stati che ovviamente saranno due:
- Stato 0: led spento
- Stato 1: led acceso
Le transizioni invece saranno legate al passare dell'intervallo di tempo di 1 secondo.
Il diagramma SFC del controllo è quindi:
Il diagramma si traduce in un ciclo con all'interno un'istruzione "switch" per il controllo dello stato e delle relative transizioni. Nel caso di Arduino UNO R3 si può utilizzare il void loop() per il ciclo e un'istruzione switch() per il controllo dello stato, lo svolgimento delle azioni e il controllo delle transizioni secondo il seguente schema di esempio:
void loop(){
switch (stato){
case <stato 1>:
Azioni relative allo stato 1
Controllo della transizione relativa allo stato 1
Impostazione nuovo stato
case <stato 2>:
Azioni relative allo stato 2
Controllo della transizione relativa allo stato 2
Impostazione nuovo stato
}
}
Lo schema si traduce semplicemente nel seguente programma (blocking mode).
#define ledpin 13
#define spento 0 // definizione degli stati
#define acceso 1
int stato; // variabile che contiene lo stato del sistema
void setup() {
pinMode(ledpin, OUTPUT);
stato = 0; // stato iniziale
}
void loop() {
switch (stato){
case spento:
// Azione
digitalWrite(ledpin, LOW);
// Controllo transizione
delay(1000);
// Nuovo stato
stato = 1;
break;
case acceso:
// Azione
digitalWrite(ledpin, HIGH);
// Controllo transizione
delay(1000);
// Nuovo stato
stato = 0;
break;
}
// istruzioni relative ad un altro controllo
}
Guardando il listato si osserva che la parte indicata come "istruzioni relative ad un altro controllo" viene computata solo in seguito ad passaggio di stato.
Infatti, per esempio, alla conclusione dello stato "spento" (dopo 1 secondo di led spento), si aggiorna lo stato in "acceso" e per opera dell'istruzione break si salta all'istruzione immediatamente successiva all'istuzione switch(). Si passa quindi ad eseguire le "istruzioni relative ad un altro controllo", per poi ricominciare e rientrare nello switch() computando questa volta le istruzioni relative allo stato 1. Per l'altro passaggio di stato accade la stessa cosa.
Sarebbe auspicabile che invece le istruzioni relative ad un altro controllo venissero processate in continuazione, ovvero sarebbe auspicabile che l'algoritmo non fosse blocking mode.
Chiaramente con alcune modifiche questo è sempre possibile e qui sotto viene proposta una soluzione:
#define ledpin 13
int stato; // variabile che contiene lo stato del sistema
/* azione è una variabile di controllo. Se il suo valore
è HIGH è necessario eseguire le azioni relative allo stato
corrente (che vanno eseguite solo la prima volta), se
il suo valore è LOW le operazioni sono già state eseguite
in una delle iterazioni precedenti
*/
bool azione;
unsigned long timenow; // variabile di appoggio per il conteggio dei millisecondi
unsigned long lastUpdate; // variabile di appoggio per il conteggio dei millisecondi da quando è avvenuto l'ultimo evento nel LED
unsigned long interval; // semiperiodo del led in millisecondi
void setup() {
pinMode(ledpin, OUTPUT);
stato = 0; // stato iniziale
azione = HIGH;
}
void loop() {
if (azione == HIGH){
azione = LOW; // Gestione delle azioni relative ad uno stato, le azioni
// devono essere esuguite una volta solo, quindi si può
// subito aggiornare il valore di azione
switch (stato){
case 0:
// Azione
digitalWrite(ledpin, LOW);
lastUpdate = millis();
interval = 1000;
break;
case 1:
// Azione
digitalWrite(ledpin, HIGH);
lastUpdate = millis();
interval = 1000;
break;
}
}
/* Qui potrebbero venir messe le istruzioni per mandare il microcontrollore
in low power mode in attesa di un evento di interrupt in seguito ad un
cambiamento delle condizioni. Il cambiamento delle condizioni comporta
un monitoraggio delle condizioni relative alle transizioni per effettuare
se necessario un cambiamento di stato.
L'assenza delle istruzioni comporta che il monitoraggio delle condizioni
è continuo
*/
switch (stato){ // Gestione delle trasizioni relative ai passaggi di stato
case 0:
// Controllo transizione
timenow = millis();
if (timenow >= lastUpdate+interval){
// Nuovo stato
stato = 1;
azione = HIGH; // se cambia lo stato è necessario svolgere l'azione relativa
}
break;
case 1:
// Controllo transizione
timenow = millis();
if (timenow >= lastUpdate+interval){
// Nuovo stato
stato = 0;
azione = HIGH; // se cambia lo stato è necessario svolgere l'azione relativa
}
break;
}
// istruzioni relative ad un altro controllo
}
Si evidenzia come la proposta fatta suddivida la gestione delle azioni relative ad uno stato (da svolgere solo una volta quando lo stato viene raggiunto) dal controllo delle condizioni relative ad una transizione tra stati che invece deve essere fatto di continuo. Una scrittura di questo tipo è compatibile con l'utilizzo degli stati a basso consumo dei microcontrollori.