Arduino/leggere una stringa numerica: differenze tra le versioni

Da PNLUG.
m (gli obiettivi)
(Completamento articolo)
Riga 1: Riga 1:
 +
{{TOC|align=right}}<br/>
 
Per gestire un dispositivo esterno con arduino, è necessario costruire alcune funzioni di controllo.
 
Per gestire un dispositivo esterno con arduino, è necessario costruire alcune funzioni di controllo.
  
 +
= Il progetto=
 
Una semplicissima, può essere quella di spedire un numero intero per richiedere la stampa di un numero uguale di righe di output, ciascuna corripondente a singola misura dell'accelerometro.<br/>
 
Una semplicissima, può essere quella di spedire un numero intero per richiedere la stampa di un numero uguale di righe di output, ciascuna corripondente a singola misura dell'accelerometro.<br/>
 
Il numero richiesto, tuttavia, deve essere digitato dall'utente, in formato decimale, ma utilizzato dal sistema in formato binario. Decidiamo di commissionare la conversione ad arduino. 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:
 
Il numero richiesto, tuttavia, deve essere digitato dall'utente, in formato decimale, ma utilizzato dal sistema in formato binario. Decidiamo di commissionare la conversione ad arduino. 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:
Riga 8: Riga 10:
 
* Separare le funzioni dal corpo principale del programma, come se fossero delle vere e proprie librerie indipendenti.
 
* Separare le funzioni dal corpo principale del programma, come se fossero delle vere e proprie librerie indipendenti.
 
* Adeguare l'algoritmo in funzione delle caratteristiche concrete di funzionamento del dispositivo reale (cioè la porta seriale).
 
* Adeguare l'algoritmo in funzione delle caratteristiche concrete 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.<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/>
 +
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.<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.
 +
 +
  * 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.<br/>
 +
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 legare le funzioni tra loro.<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. È 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>
 +
 
 +
    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?<br/>
 +
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.<br/>
 +
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.<br/>
 +
Oppure lo invitiamo a cercarla [https://github.com/cloc3/arduGiochi/archive/letturaSeriale.zip qui]

Versione delle 01:55, 1 gen 2014


Per gestire un dispositivo esterno con arduino, è necessario costruire alcune funzioni di controllo.

Il progetto

Una semplicissima, può essere quella di spedire un numero intero per richiedere la stampa di un numero uguale di righe di output, ciascuna corripondente a singola misura dell'accelerometro.
Il numero richiesto, tuttavia, deve essere digitato dall'utente, in formato decimale, ma utilizzato dal sistema in formato binario. Decidiamo di commissionare la conversione ad arduino. 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 funzione delle caratteristiche concrete 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.
Prendere una singola cifra, trasformarla in formato digitale e integrarla come cifra meno significativa del valore 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 riprende con poche modifiche il 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 legare le funzioni tra loro.
Contiene la definizione delle variabili convise.
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>
 
   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