Arduino/leggere una stringa numerica: differenze tra le versioni

Da PNLUG.
m (Il progetto: formattazione)
m (Le routine: formattazione)
Riga 13: Riga 13:
  
 
=Le routine=
 
=Le routine=
Cominciamo dal punto uno. Per un programma così piccolo, due subroutine sono già molto.<br/>
+
Cominciamo dal punto uno. Per un programma così piccolo, ''due'' subroutine sono già molto.<br/>
La prima può occuparsi dei puri calcoli algebrici.<br/>
+
La prima può occuparsi dei puri '''calcoli algebrici'''.<br/>
Prendere una singola cifra, trasformarla in formato digitale e integrarla come cifra meno significativa del valore in elaborazione.<br/>
+
''Acquisire'' una cifra singola, ''trasformarla in formato digitale'' e integrarla come cifra meno significativa nel dato in elaborazione.<br/>
Restituire, se necessario, una condizione di errore.
+
''Restituire'', se necessario, una condizione di errore.
 
Ecco il codice:
 
Ecco il codice:
  
Riga 28: Riga 28:
 
     }
 
     }
  
Una seconda funzione può essere dedicata alla lettura della porta seriale.<br/>
+
Una seconda funzione può essere dedicata alla '''lettura della porta seriale'''.<br/>
Gestire lo stato della lettura (inizioLettura, corsoLettura, datoValido), riconoscere il carattere di fine stringa e passare ogni singola cifra alla funzione precedente. Questa funzione riprende con poche modifiche il [http://arduino.cc/en/Tutorial/SerialEvent tutorial] ufficiale.
+
''Gestire lo stato'' della lettura (inizioLettura, corsoLettura, datoValido), riconoscere il carattere di ''fine stringa'' e passare ogni singola cifra alla funzione precedente. Questa funzione richiama nella struttura [http://arduino.cc/en/Tutorial/SerialEvent tutorial] ufficiale.
  
 
   * boolean attendiStringaNumerica(int *numeroCicliRichiesti, boolean *corsoLettura) {
 
   * boolean attendiStringaNumerica(int *numeroCicliRichiesti, boolean *corsoLettura) {
Riga 51: Riga 51:
 
   }
 
   }
  
Poi il programma principale.<br/>
+
Poi il '''programma principale'''.<br/>
 
Minimale. Deve soltanto chiamare le routine precedenti, stampare il risultato e ripristinare le condizioni iniziali di lavoro.
 
Minimale. Deve soltanto chiamare le routine precedenti, stampare il risultato e ripristinare le condizioni iniziali di lavoro.
  
Riga 69: Riga 69:
 
     }
 
     }
  
In ultimo, un file di header, per legare le funzioni tra loro.<br/>
+
In ultimo, un '''file di header''', per definire il database condiviso.<br/>
Contiene la definizione delle variabili convise.<br/>
+
Siccome abbiamo voluto passare le varibili attraverso i puntatori, ognuna di esse è definita e inizializzata due volte, per il contenuto e per l'indirizzo. È estremamente importante, nel linguaggio C, fare attenzione a inizializzare ''tutte'' le varibili in fase di precompilazione, altrimenti i programmi possono assumere comportamenti indesiderati.
Siccome abbiamo voluto passare le varibili attraverso i puntatori, ognuna di esse è definita e inizializzata due volte. È estremamente importante, nel linguaggio C, fare attenzione ad inizializzare tutte le varibili in fase di precompilazione, altrimenti i programmi possono assumere comportamenti indesiderati.
+
  
 
   * include <Arduino.h>
 
   * include <Arduino.h>

Versione delle 13:31, 1 gen 2014


Quando arduino viene messo in comunicazione con una dispostivo esterno, come può essere un computer conesso attraverso la porta seriale, è necessario scrivere alcune funzioni dedicate per controllare la comunicazione.

Il progetto

Una idea minimimale, può essere quella di spedire verso arduino un numero intero per richiedere la stampa di un numero uguale di righe di output, contenti il risultato di una singola misura dell'accelerometro.
Naturalmente,l'utente digita l'input in formato decimale, ma il sistema ha bisogno di operare su un numero in formato binario. Decidiamo di affidare ad arduino il compito di eseguire la conversione. In questo caso, non possediamo funzioni pronte, ma siamo costretti ad affrontare un piccolo esercizio di programmazione in linguaggio C.
Possiamo anche porci qualche piccolo obiettivo educativo in più, come ad esempio:

  • Suddividere il programma in piccole subroutine elementari, ciascuna di poche righe.
  • Gestire la trasmissione dei dati tra le subroutine attraverso puntatori, piuttosto che scambiando il contenuto delle variabili.
  • Separare le funzioni dal corpo principale del programma, come se fossero delle vere e proprie librerie indipendenti.
  • Adeguare l'algoritmo in base al funzionamento concreto di funzionamento del dispositivo reale (cioè la porta seriale).

Le routine

Cominciamo dal punto uno. Per un programma così piccolo, due subroutine sono già molto.
La prima può occuparsi dei puri calcoli algebrici.
Acquisire una cifra singola, trasformarla in formato digitale e integrarla come cifra meno significativa nel dato in elaborazione.
Restituire, se necessario, una condizione di errore. Ecco il codice:

 * boolean digit2int( int* cifra, int* intero ) {
       if ((*cifra >= '0') && (*cifra <= '9')) {
               *intero = (*intero)*10 + *cifra -'0';
       } else {
               return false;
       }
       return true;
    }

Una seconda funzione può essere dedicata alla lettura della porta seriale.
Gestire lo stato della lettura (inizioLettura, corsoLettura, datoValido), riconoscere il carattere di fine stringa e passare ogni singola cifra alla funzione precedente. Questa funzione richiama nella struttura tutorial ufficiale.

 * boolean attendiStringaNumerica(int *numeroCicliRichiesti, boolean *corsoLettura) {
       static boolean inizioLettura,datoValido;
       static int *cifra;
 
       inizioLettura=false;
       *corsoLettura=true;
       datoValido=true;
 
 while (Serial.available()) {
               inizioLettura=true;
               *cifra = Serial.read();
               if (*corsoLettura) {
                       if (*cifra == '\n') *corsoLettura=false;
                       else if (datoValido == true)
                               datoValido=digit2int(cifra,numeroCicliRichiesti);
               }
       }
       return inizioLettura && datoValido;
  }

Poi il programma principale.
Minimale. Deve soltanto chiamare le routine precedenti, stampare il risultato e ripristinare le condizioni iniziali di lavoro.

 * #include <letturaIntero.h>
 
   void setup() {
     Serial.begin(115200);
   }
 
   void loop() {
     static char str[40];
     if (attendiStringaNumerica(numeroCicliRichiesti,corsoLettura)) {
       sprintf(str,"intero = %u\n",*numeroCicliRichiesti);
       Serial.print(str);
       *numeroCicliRichiesti=0;
     }
   }

In ultimo, un file di header, per definire il database condiviso.
Siccome abbiamo voluto passare le varibili attraverso i puntatori, ognuna di esse è definita e inizializzata due volte, per il contenuto e per l'indirizzo. È estremamente importante, nel linguaggio C, fare attenzione a inizializzare tutte le varibili in fase di precompilazione, altrimenti i programmi possono assumere comportamenti indesiderati.

 * include <Arduino.h>
 
   int _numeroCicliRichiesti=0;
   int *numeroCicliRichiesti=&_numeroCicliRichiesti;
 
   boolean _corsoLettura=false;
   boolean *corsoLettura=&_corsoLettura;
 
   boolean attendiStringaNumerica(int *,boolean *);
   boolean digit2int(int *,int *);

Il test sul dispositivo reale

Proviamo a verificare il funzionamento del programma:

 * cloc3@dell ~/arduGiochi/letturaIntero $ cat </dev/arduino & echo 61 >/dev/arduino
 [7] 5587
 cloc3@dell ~/arduGiochi/letturaIntero $ intero = 6
 intero = 1
 
 cloc3@dell ~/arduGiochi/letturaIntero $ cat </dev/arduino & echo 617 >/dev/arduino
 [8] 5588
 cloc3@dell ~/arduGiochi/letturaIntero $ intero = 6
 intero = 17

Delusione: qualcosa non funziona. Le singole cifre vengono lette correttamente, ma spesso il numero in ingresso viene spezzato in più letture successive. Come mai è accaduto?
In realtà, l'algoritmo che abbiamo scritto è corretto, ma è troppo veloce rispetto alla porta seriale. A volte, il programma effettua una elaborazione talmente rapida di ogni singola cifra, che si rimette in lettura per il ciclo successivo prima ancora che la porta seriale abbia inoltrato il carattere atteso.
In queste condizioni, il programma riprende il calcolo spezzando la stringa numerica in ingresso.

Grazie Arduino,ci stai insegnando che un algoritmo tecnicamente corretto, può assumere comportamenti inattesi nelle situazioni reali. In questi casi serve un controllo ulteriore, che ad un'analisi astratta del codice sembrerebbe superflua.

Lasciamo al lettore il piacere di applicarla.
Oppure lo invitiamo a cercarla qui