Il controllo del flusso di esecuzione in Arduino

Una volta appresi i primi rudimenti su come utilizzare gli operatori per manipolare i valori contenuti nelle variabili, non siamo tuttavia ancora in grado di farci alcunché di concreto, a parte qualcosa di basilare utilizzando qualche componente elettronico (accendere un led, acquisire dati da un sensore, azionare un motore servo ecc.). Per questa ragione possiamo avvalerci di alcune istruzioni utili a controllare il flusso dei nostri programmi, come ad esempio eseguire un’istruzione se il valore acquisito da un sensore scende al di sotto di un parametro stabilito, oppure eseguire un’istruzione per un certo numero di volte (ciò che in gergo tecnico si chiama ciclo).

Istruzioni di selezione

Istruzione if

L’istruzione if, detta anche test condizionale, ha lo scopo di controllare se una certa condizione si verifica. La sua sintassi è la seguente

if(<condizione>)
{
    //codice da eseguire se la condizione è vera
}

Il blocco di istruzioni che deve essere eseguito se il test if è vero, viene racchiuso tra parentesi graffe. In realtà, se l’istruzione da eseguire è unica, è possibile ometterle, tuttavia per maggiore chiarezza è buona norma inserire sempre le parentesi e aggiungere dei rientri al codice scritto al loro interno (in questo caso ho utilizzato quattro spazi, ma è possibile usare per comodità anche il carattere di tabulazione). L’ide di Arduino inserisce automaticamente il rientro del codice e la parentesi di chiusura non appena si digita la parentesi graffa aperta (un altro buon motivo per usarle sempre!). Le regole per la scrittura del corpo delle istruzioni all’interno di if valgono per tutte le altre istruzioni in uno sketch Arduino, compreso il corpo delle funzioni (che approfondiremo in un prossimo articolo).

Ad esempio, se volessimo verificare che il valore di una certa variabile sia maggiore di 5, dovremmo scrivere il seguente codice:

int miaVariabile = 7;

if(miaVariabile > 5)
{
    Serial.println("miaVariabile è maggiore di 5");
}

Cosa succede se assegnamo a miaVariabile il valore 4?

Istruzione if..else

Spesso un test condizionale non è sufficiente; potremmo ad esempio voler eseguire del codice se miaVariabile non è maggiore di 5. Possiamo quindi aggiungere al test condizionale un ramo else. In questo caso il codice assumerà l’aspetto seguente:

if(miaVariabile > 5)
{
    Serial.println("miaVariabile è maggiore di 5");
}
else
{
    Serial.println("miaVariabile non è maggiore di 5");
}

Provate a cambiare il valore di mia variabile e a cercare di capire quale blocco di codice verrà eseguito.

Istruzione if..if else..else

Qualora un solo ramo else non fosse sufficiente a gestire la logica di un nostro progetto Arduino, possiamo inserire quanti rami if..else if vogliamo. Ad esempio, se volessimo eseguire determinate istruzioni nel caso in cui miaVariabile rientri in certi range di valori, scriveremmo:


if(miaVariabile >= 0 && miaVariabile <= 5)
{
    //codice eseguito se miaVariabile assume un valore compreso tra 0 e 5
}
else if(miaVariabile > 5 && miaVariabile <= 10)
{
    //codice eseguito se miaVariabile assume un valore compreso tra 6 e 10
}
else
{
    //codice eseguito in tutti gli altri casi
}

Provate ad aggiungere tutti i rami else if che riuscite a immaginare.

Istruzione switch..case

A volte, quando si hanno molte condizioni da testare per singoli valori assunti da una variabile, le istruzioni if..else if..else possono essere scomode da utilizzare. In questo caso è l’istruzione switch risulta decisamente più agile. Pensiamo ad esempio al caso di un tastierino numerico da cui è possibile acquisire numeri compresi tra zero e nove; con l’istruzione switch..case, possiamo gestire ogni tasto come segue:

switch(miaVariabile)
{
    case 0:
        Serial.println("Hai premuto il tasto 0");
        break;
    case 1:
        Serial.println("Hai premuto il tasto 1");
        break;
    case 2:
        Serial.println("Hai premuto il tasto 2");
        break;
    case 3:
        Serial.println("Hai premuto il tasto 3");
        break;
    case 4:
        Serial.println("Hai premuto il tasto 4");
        break;
    case 5:
        Serial.println("Hai premuto il tasto 5");
        break;
    case 6:
        Serial.println("Hai premuto il tasto 6");
        break;
    case 7:
        Serial.println("Hai premuto il tasto 7");
        break;
    case 8:
        Serial.println("Hai premuto il tasto 8");
        break;
    case 9:
        Serial.println("Hai premuto il tasto 9");
        break;
    default:
        Serial.println("Tasto non riconosciuto");
        break;
}

Abbiamo passato tra parentesi tonde all’istruzione switch miaVariabile; all’interno del corpo di switch tra parentesi graffe, le varie istruzioni case verificano di volta in volta se miaVariabile assume i valori 1, 2, …9 e “deviano” il flusso di esecuzione verso il codice corrispondente alla condizione verificata; se nessuna condizione case si verifica, viene eseguito il codice all’interno del ramo default. L’istruzione break interrompe il flusso di esecuzione, impedendo al compilatore di testare tutti gli altri case.

Provate anche in questo caso a cambiare il valore di miaVariabile e ad immaginare quale ramo case sarà eseguito

È possibile raggruppare più casi per gestire più condizioni, con la seguente sintassi:

switch(<condizione>) {
    case <condizione_1>:
    case <condizione_2>:
    case <condizione_3>:
    case <condizione_4>:
    case <condizione_5>:
        break;
}

Come esercizio il lettore può provare a convertire il codice di esempio dell’istruzione if..if else..else in un’istruzione switch..case

Istruzioni di ripetizione

Come abbiamo accennato nell’introduzione a questo articolo, Arduino ci mette a disposizione delle istruzioni per eseguire un certo numero di volte un blocco di codice, dette anche istruzioni di iterazione o cicli.

Istruzione while

La prima basilare istruzione di iterazione è il ciclo while, che ripeterà il blocco di codice al suo interno fin quando la condizione passata in ingresso è verificata. La sintassi del comando è la seguente:

while(<condizione>)
{
    //codice da eseguire all'interno del ciclo
}

proviamo ad esempio a ripetere un blocco di codice 10 volte:

//variabile di comodo che incrementeremo ad ogni iterazione
int indice = 1;
while(indice <= 10)
{
    //stampiamo sul monitor seriale il numero di iterazioni
    Serial.print("Iterazione numero ");
    Serial.println(indice);
    //incrementiamo la variabile di un'unità
    indice++;
}

Nella parte finale del ciclo la variabile indice viene incrementata di un’unità, in modo che alla decima iterazione il ciclo si interrompa.

Come esercizio il lettore provi a immaginare cosa succede commentando la riga contenente l’istruzione indice++.

Istruzione for

Un’alternativa al ciclo while, molto comoda se conosciamo già il numero di volte in cui un ciclo deve essere eseguito, è il ciclo for, la cui sintassi è la seguente:

for(<inizializzazione indice>; <condizione>; <incremento>)
{
    //codice eseguito ad ogni iterazione
}

dove <inizializzazione indice> contiene la dichiarazione e inizializzazione di una variabile indice analoga all’esempio fatto per il ciclo while; <condizione> è la condizione che deve essere vera per eseguire il corpo del ciclo e <incremento> è la condizione di incremento dell’indice.

Provate a scrivere un ciclo for sostituendo <inizializzazione indice>, <condizione>, e <incremento> con le corrispondenti istruzioni utilizzate nell’esempio del ciclo while.

Uno sketch di esempio del controllo del flusso di esecuzione in Arduino

Adesso siamo pronti per scrivere uno sketch di test, non prima però di aver appreso come acquisire un input dal monitor seriale per avere la possibilità di cambiare al volo il valore di una variabile

Acquisizione di input da monitor seriale in Arduino

Per acquisire un input da monitor seriale è possibile utilizzare l’istruzione:

Serial.read()

che restituisce il primo byte di dati disponibile. Tuttavia questo primo byte viene trattato da Arduino come un carattere ASCII, quindi se digitassimo ad esempio 5, verrebbe utilizzato il corrispondente numero del sistema decimale e cioè 53. Utilizzeremo quindi un’istruzione che, acquisito l’input da seriale, lo converte ad intero e cioè:

Serial.parseInt()

Qualora volessimo verificare che siano effettivamente disponibili dei dati in arrivo, potremo utilizzare la funzione Serial.available() in un test condizionale:

if(Serial.available())
{ 
    //codice da eseguire se è possibile leggere input da seriale
}

Ecco infine uno sketch riepilogativo di quanto detto finora:

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600); 
}

void loop() {
  // put your main code here, to run repeatedly:
  //eseguiamo il codice solo se arriva un input dalla seriale
  if(Serial.available() > 0)
  {
    int miaVariabile = Serial.parseInt();

    Serial.print("Hai digitato il numero ");
    Serial.println(miaVariabile);
    
    if(miaVariabile > 5)
    {
        Serial.println("miaVariabile è maggiore di 5");
    }
    delay(1000);
    
    if(miaVariabile > 5)
    {
        Serial.println("miaVariabile è maggiore di 5");
    }
    else
    {
        Serial.println("miaVariabile non è maggiore di 5");
    }
    delay(1000);
    
    if(miaVariabile >= 0 && miaVariabile <= 5)
    {
        //codice eseguito se miaVariabile assume un valore compreso tra 0 e 5
        Serial.println("miaVariabile è compresa tra 0 e 5");
    }
    else if(miaVariabile > 5 && miaVariabile <= 10)
    {
        //codice eseguito se miaVariabile assume un valore compreso tra 6 e 10
        Serial.println("miaVariabile è compresa tra 6 e 10");
    }
    else
    {
        //codice eseguito in tutti gli altri casi
        Serial.println("miaVariabile non è compresa tra 0 e 10");
    }
    delay(1000);

    switch(miaVariabile)
    {
        case 0:
            Serial.println("Hai premuto il tasto 0");
            break;
        case 1:
            Serial.println("Hai premuto il tasto 1");
            break;
        case 2:
            Serial.println("Hai premuto il tasto 2");
            break;
        case 3:
            Serial.println("Hai premuto il tasto 3");
            break;
        case 4:
            Serial.println("Hai premuto il tasto 4");
            break;
        case 5:
            Serial.println("Hai premuto il tasto 5");
            break;
        case 6:
            Serial.println("Hai premuto il tasto 6");
            break;
        case 7:
            Serial.println("Hai premuto il tasto 7");
            break;
        case 8:
            Serial.println("Hai premuto il tasto 8");
            break;
        case 9:
            Serial.println("Hai premuto il tasto 9");
            break;
        default:
            Serial.println("Tasto non riconosciuto");
            break;
    }
    delay(1000);

    Serial.print("Hai scelto di ripetere un ciclo while ");
    Serial.print(miaVariabile);
    Serial.println(" volte");
    
    int indice = 1;
    while(indice <= miaVariabile)
    {
        //stampiamo sul monitor seriale il numero di iterazioni
        Serial.print("Iterazione while numero ");
        Serial.println(indice);
        //incrementiamo la variabile di un'unità
        indice++;
    }
    delay(1000);
    
    Serial.print("Hai scelto di ripetere un ciclo for ");
    Serial.print(miaVariabile);
    Serial.println(" volte");
    
    for(int i = 1; i <= miaVariabile; i++)
    {
        //stampiamo sul monitor seriale il numero di iterazioni
        Serial.print("Iterazione for numero ");
        Serial.println(i);
    }
    delay(10000);
  }
  else
  {
    Serial.println("Digita un numero da 0 a 9");
    delay(3000);
  }
}

Da notare che, oltre ad aver inserito il codice nella funzione loop(), abbiamo richiamato la funzione delay() passandole in ingresso un certo numero di millisecondi, corrispondenti a quanto tempo vogliamo che il flusso di esecuzione si arresti, per evitare di intasare il monitor seriale.

Digitando ad esempio 4 nella casella di input del monitor seriale e premendo invio o il tasto “invia”, questo è l’output che otterremo:

input da monitor seriale in Arduino
input da monitor seriale in Arduino

Come è possibile notare nelle righe finali del monitor, l’istruzione loop() continua indefinitamente ad eseguire il suo corpo di istruzioni e, fin quando non sarà disponibile un nuovo input, il flusso di esecuzione finirà sempre nel secondo ramo else. Sbizzarritevi pure a testare vari input per verificarne l’output e modificate come preferite lo sketch.

Nota: le istruzioni if che si trovano nel corpo dell’if principale sono dette in gergo istruzioni if nidificate.

Conclusioni

Abbiamo visto come implementare nei nostri progetti Arduino qualunque logica condizionale o iterativa ci venga in mente grazie alle istruzioni di controllo del flusso di esecuzione. Siamo stati in grado di acquisire input da seriale e, per la prima volta, abbiamo inserito del codice all’interno della funzione loop() di Arduino.

Prossimamente vedremo come racchiudere blocchi di codice che vogliamo ripetere in più punti di un programma all’interno di funzioni.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.