Arduino · 3 novembre 2013 6

Arduino, invio seriale di dati da e per il computer

Arduino UNO R3

Arduino UNO R3

Uno dei problemi che maggiormente assillano i nuovi utenti di Arduino è quello della comunicazione seriale, principalmente dello scambio di dati da e verso il computer. Questo è un argomento spesso non ben compreso da chi si avvicina per la prima volta al mondo dei microcontrollori, che incontra spesso molte difficoltà nel capire come i dati viaggiano sulla linea seriale.

Questo articolo, che è più un tutorial, cerca di spiegare come scambiare dati da e per il computer e come interpretare sull’Arduino ciò che arriva dall’esterno.

Prima di tutto ci serve un circuito di test ed un programma. Il circuito di test è molto semplice. Si compone di un LED con relativo resistore da 330/470 ohm (il valore di quest’ultimo non è fondamentale, a noi non interessa che faccia molta luce ma che faccia solo accendere il LED in sicurezza, per cui non scendete sotto ai 220 ohm per non rischiare una corrente eccessiva né salite sopra ai 680 ohm, per evitare invece che ne scorra troppo poca) collegato ad un pin PWM dell’Arduino e di un potenziometro collegato ad un ingresso analogico. In mancanza di un potenziometro, si può usare anche un altro resistore di basso valore (tipo 100 ohm) per fare un piccolo ponticello fra A0 ed il pin 5V oppure il pin 3V3. Disponete in componenti come in figura:

schematic_arduino_serial

Adesso collegate il vostro Arduino al computer e caricate il seguente sketch:

/* PROGRAMMA DI PROVA DI INTERFACCIA SERIALE BIDIREZIONALE
 Il programma riceve comandi dalla seriale e spedisce
 letture e stati sullo stesso canale.
 Per provare lo sketch, collegare un LED al pin 9
 con un resistore adeguato ed una tensione in ingresso
 al pin A0.
 Caricato lo sketch sull'Arduino, aprire il monitor seriale
 dell'IDE di Arduino con velocità di 19200 bps.
 I comandi accettati sono i seguenti:
 l - lettura: legge il valore della tensione in ingresso al pin analogico
 a - accedi LED: accende il LED
 s - spengi LED: spenge il LED
 q - richiede lo stato del LED. Verrà spedito lo stato del pin
 a cui è collegato il LED. Possibili risposte:
 LED acceso/spento/pilotato con PWM impostato a xxx
 w xxx - scrive un valore di PWM sul pin 9. Possibili
 valori per "xxx" vanno da 0 a 255. Gli spazi sono ignorati.
 I caratteri accettati sono solo quelli numerici (da 0 a 9).
 */

 //pin
 const byte inputPin = A0; //pin analogico di ingresso
 const byte outputPin = 9; //pin di output

 //variabili globali
 byte statoLed = 0; //contiene il valore sul pin 9

 void setup() {
   Serial.begin(19200); // apro la seriale
   delay(1000); //attendo che l'utente faccia altrettanto
   Serial.println(F("Pronto")); //sono pronto
 }

 //programma principale
 void loop() {
   int lettura; //contiene il valore di PWM letto dalla seriale
   unsigned long tempMillis; //serve per il timeout sulla ricezione del valore di PWM
   byte caratteri; //contiene il numero di caratteri ricevuti
   byte tempChar; //contiene il carattere letto

   //controllo se c'è qualcosa in arrivo sulla seriale
   if (Serial.available()) {
     byte command = Serial.read(); //leggo il primo byte
     switch (command) { //controllo che sia un comando valido
       case 'l': //lettura
         //invio la lettura dell'ingresso
         Serial.print(F("Lettura ingresso: "));
         Serial.println(analogRead(inputPin));
         break;
       case 'a': //accendo il LED
         statoLed = 255;
         analogWrite(outputPin, statoLed);
         Serial.println(F("LED acceso"));
         break;
       case 's': //spengo il led
         statoLed = 0;
         analogWrite(outputPin, statoLed);
         Serial.println(F("LED spento"));
         break;
       case 'q': //l'utente vuole lo stato del LED
         Serial.print(F("LED "));
         if (statoLed == 0) {
           Serial.println(F("spento"));
         } else if (statoLed == 255) {
           Serial.println(F("acceso"));
         } else {
           Serial.print(F("pilotato con PWM impostato a "));
           Serial.println(statoLed, DEC);
         }
         break;
       case 'w': //imposto il PWM sul LED
         lettura = 0;
         tempMillis = millis();
         caratteri = 0;
         //mi servono altri caratteri
         do {
           if (Serial.available()) {
             tempChar = Serial.read();
             caratteri++;
             if ((tempChar >= 48) && (tempChar <= 57)) { //è un numero? ok
               lettura = (lettura * 10) + (tempChar - 48);
             } else if ((tempChar == 10) || (tempChar == 13)) {
               //con invio e/o nuova linea esco
               break;
             }
           }
         //esco per timeout, per un valore di PWM maggiore di 255
         //oppure per troppi caratteri senza senso ricevuti
         } while((millis() - tempMillis < 500) && (lettura <= 255) && (caratteri < 10));
         //se il valore di PWM è valido, lo imposto sul pin
         if (lettura <= 255) {
           statoLed = lettura;
           analogWrite(outputPin, statoLed);
           Serial.print(F("LED acceso con PWM impostato a "));
           Serial.println(statoLed, DEC);
         }
         break;
     }
     //svuoto il buffer da eventuali altri caratteri che non mi servono più
     while (Serial.available()) {
       byte a = Serial.read();
     }
   }
 }

Adesso aprite il monitor seriale dell’IDE (cliccate sull’icona a forma di lente d’ingrandimento posta in alto a destra, sopra all’area di editing del codice) con una velocità di comunicazione di 19200 bps (la stessa impostata nello sketch). Dovreste ricevere il messaggio “Pronto”: questo significa che lo sketch è partito e che sta attendendo i vostri comandi.

Adesso interagiamo con l’Arduino usando i seguenti comand (i comandi vanno inseriti nel monitor e spediti all’Arduino premendo il tasto “invio”):

  • q – questo comando chiede all’Arduino lo stato del LED. L’Arduino risponderà con LED acceso oppure spento nel caso che il LED sia acceso con la massima intensità oppure spento, oppure risponderà con il valore corrente del segnale PWM (da 0 a 255).
  • a – questo comando accende il LED alla massima intensità.
  • s – questo comando spenge il LED.
  • l – questo comando fa leggere all’Arduino la tensione sul pin A0 e poi spedisce la lettura al PC: vedrete un messaggio del tipo “Lettura ingresso: xxx”, con xxx che rappresenta il valore sul pin convertito in formato digitale a 10 bit (da 0 a 1023).
  • w numero – con il comando w si spedisce all’Arduino un numero indicante il valore di PWM da assegnare al pin. Il numero può assumere un valore da 0 a 255: 0 equivale al comando “spento” mentre 255 equivale al comando “acceso”. Gli spazi sono ignorati (“w10” equivale a “w 10”) così come eventuali caratteri diversi dalle cifre numeriche.

Analizziamo il codice, per capire come funziona il tutto.

Sezioni “pin” e “variabili globali”: poco da dire, qui si dichiarano solo le costanti usate per indicare i pin ed alcune variabili usate più avanti.

Funzione “setup()”: apriamo la seriale, attendiamo un secondo per dare modo all’utente di aprire il terminale, poi spediamo il messaggio “Pronto”.

Funzione “loop()”: qui risiede il cuore di tutto. La prima cosa che facciamo è controllare se nel buffer di ricezione della seriale è presente qualcosa, ossia il codice vuol capire se dal computer all’Arduino è stato spedito qualche carattere. La comunicazione seriale è bidirezionale, quindi possiamo spedire dati dal computer all’Arduino e viceversa. L’Arduino ha 2 buffer seriali (ossia due array usati per salvare i dati in arrivo ed in uscita): la funzione Serial.available() resistuisce zero se nessun carattere è arrivato oppure il numero di caratteri ricevuti. Il test if (Serial.available()) serve quindi per capire se c’è almeno un carattere nel buffer perché qualsiasi valore maggiore di zero farà risultare vero il test ed il codice entrerà nel blocco di codice dell’if.

Una volta accertata la presenza di qualcosa nel buffer, preleviamo il primo byte ricevuto.

[notice]Attenzione, parliamo di “byte”, non di caratteri, valori, numeri, stringhe ecc… : la trasmissione seriale avviene spedendo singoli byte, uno dietro l’altro. Ogni byte viene interpretato per come esso viene spedito. Ad esempio, se noi scriviamo Serial.print(“A”) verrà inviato il carattere A, che corrisponde al valore ASCII 65. Questo significa che sulla seriale viaggerà il byte 65. In ricezione, se noi tratteremo quel byte come un carattere, esso corrisponderà alla lettera “A” mentre se noi tratteremo quel byte come valore, esso corrisponderà a 65. [/notice]

Analizziamo il byte per capire che cosa è arrivato usando uno switch..case.

Nel caso di comandi senza parametri (a, s, q, l) l’interpretazione è facile perché non necessitiamo di informazioni aggiuntive: quel comando va eseguito subito. Lo sketch compie quindi l’operazione richiesta ed informa l’utente di ciò che è avvenuto. Questo messaggio di risposta lo vedremo stampato direttamente sul monitor seriale.

Nel caso del comando “w”, invece, necessitiamo del parametro numerico con cui regolare il PWM sul pin 9 dell’Arduino. Come abbiamo detto precedentemente, l’informazione che viaggia sulla seriale è composta da singoli byte: se volessimo regolare il PWM a 100, potremmo semplicemente spedire un byte di valore 100, e l’Arduino non dovrebbe far altro che prendere quel byte e passarlo alla funzione analogWrite. Ma ciò creerebbe un pò di problemi all’utente: il valore ASCII di 100 è la lettera “d”, ci vuole una tabella ASCII per ritrovare il carattere corrispondente al valore 100. E cosa succederebbe se dovessimo spedire il valore 200? Siccome i caratteri ASCII sono standard solo fino al valore 127, da 128 a 255 ognuno riempie la tabella come vuole. Dovremmo trovare una combinazione di tasti sulla tastiera che nella mappatura usata dal nostro sistema crea il carattere con valore 200. Troppo complicato! L’alternativa? Spedire il numero come stringa, ossia inviare il carattere ASCII delle cifre che compongono il numero. 200 viene quindi scomposto come ‘2’, ‘0’, ‘0’ (notare gli apici, che in C indicano che il carattere alfanumerico racchiuso fra di essi deve essere trattato come carattere). E’ in questo modo che lavora il monitor seriale, fatto apposta per favorire l’interazione dell’uomo con la macchina. Quindi quando noi scriviamo “w 200” il monitor seriale invia all’Arduino 5 caratteri: ‘w’, ‘spazio’, ‘2’, ‘0’, ‘0’. Il nostro codice non deve fare altro che leggere i byte ricevuti e trattarli come caratteri. Per far ciò basta ricordarsi che un carattere ha un preciso codice ASCII, quindi lo spazio corrisponde al codice ASCII 32 per cui questo sarà il byte ricevuto, il carattere ‘2’ ha il codice ASCII 50 per cui questo sarà il byte ricevuto, ecc…

Il ciclo do…while esegue proprio il controllo in questa forma: sui valori ASCII dei caratteri ricevuti. Quindi per scartare lo spazio, basta controllare che il byte abbia valore 32. Per verificare che il carattere ricevuto sia numerico, basta controllare che sia compreso fra i valori di 48 e 57, che corrispondono ai codici ASCII rispettivamente di 0 e 9. Nel programma avremmo potuto sostituire i controlli basati sui numeri sui controlli basati sui caratteri. Quindi

if ((tempChar >= 48) && (tempChar <= 57))

può essere sostituito dal seguente codice:

if ((tempChar >= '0') && (tempChar <= '9'))

senza che il risultato cambi perché il compilatore crea lo stesso identico codice eseguibile. Nel do..while ho introdotto anche un timeout, per evitare che il codice possa bloccarsi per qualche ragione, come ad esempio un’interruzione della linea durante la trasmissione. Il codice accetta solo numeri ed esce non appena si verifica una delle seguenti condizioni: timeout; un valore numerico superiore a 255; la ricezione di più di 10 caratteri insignificanti. La conversione da carattere a valore numerico è fatta usando ancora i codici ASCII dei caratteri: sappiamo che 48 corrisponde a 0, 49 ad 1, 50 a 2 ecc… per cui per avere il valore numerico del carattere ricevuto basta sottrarre appunto 48, per cui: (48-48)=0, (49-48)=1, (50-48)=2 ecc…

Se il valore numerico è corretto (rientra nell’intervallo 0..255), lo scriviamo sul pin PWM con la funzione analogWrite e poi lo salviamo nella variabile che contiene lo stato del pin 9. Terminata quest’operazione, svuotiamo il buffer di ricezione da eventuali altri caratteri che potrebbero essere arrivati (prendiamo il caso di un utente “burlone” che spedisce “w fdsahjawknglkangw”). Sulle vecchie versioni dell’IDE c’era la funzione Serial.flush(), che svuotava il buffer. Con l’introduzione dell’IDE 1.0 questa funzione è stata modificata (adesso attende che i dati in uscita siano stati trasmessi tutti) per cui non resta che fare un ciclo che preleva dal buffer di ricezioni i caratteri uno ad uno, salvandoli in una variabile locale che verrà distrutta non appena usciti dal ciclo do..while.

Ecco un esempio di output:

test_serial_it

In un prossimo articolo vedremo come creare un protocollo di trasmissione per lo scambio di dati fra 2 Arduino (o 2 microcontrollori) collegati via seriale tra di loro.