Arduino/leggere una stringa numerica: differenze tra le versioni

Da PNLUG.
m (gli obiettivi)
m (formattazione)
 
(5 versioni intermedie di uno stesso utente non sono mostrate)
Riga 1: Riga 1:
Per gestire un dispositivo esterno con arduino, è necessario costruire alcune funzioni di controllo.
+
{{TOC|align=right}}<br/>
 +
<div style="text-align:justify">
 +
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'''.
  
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 progetto=
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:
+
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.<br/>
 +
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'''.<br/>
 +
Possiamo anche porci qualche piccolo obiettivo educativo in più, come ad esempio:
  
* Suddividere il programma in piccole subroutine elementari, ciascuna di poche righe.
+
* '''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.
+
* 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.
+
* 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 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.<br/>
 +
La prima può occuparsi dei puri '''calcoli algebrici'''.<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.
 +
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 richiama nella struttura [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 definire il database condiviso.<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.
 +
 
 +
  * 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 accade?<br/>
 +
In realtà, la spiegazione di questo comportamento risiede in una '''causa fisica''', anzichè virtuale, perché non è dovuta a un errore di programmazione.
 +
Infatti, l''''algoritmo che abbiamo scritto è corretto''', ma viene eseguito dal processore in tempi troppo rapidi rispetto alla porta seriale. A volte, il programma elabora così velocemente la singola cifra, che effettua la lettura di quella successiva molto prima che la porta seriale abbia reso disponibile il nuovo carattere.<br/>
 +
Siccome la versione attuale del programma controlla lo stato di esecuzione del programma solo in base alla funzione Serial.availabe(), il programma assume dei comportamenti impropri, spezzando in modo arbitrario 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]
 +
</div>

Versione attuale delle 13:46, 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 accade?
In realtà, la spiegazione di questo comportamento risiede in una causa fisica, anzichè virtuale, perché non è dovuta a un errore di programmazione. Infatti, l'algoritmo che abbiamo scritto è corretto, ma viene eseguito dal processore in tempi troppo rapidi rispetto alla porta seriale. A volte, il programma elabora così velocemente la singola cifra, che effettua la lettura di quella successiva molto prima che la porta seriale abbia reso disponibile il nuovo carattere.
Siccome la versione attuale del programma controlla lo stato di esecuzione del programma solo in base alla funzione Serial.availabe(), il programma assume dei comportamenti impropri, spezzando in modo arbitrario 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