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:
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:
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.
Buongiorno Leonardo, Mi chiamo Alessio e ho un quesito da proporti: Io sto lavorando con un progetto con due Arduino UNO: il primo lo uso con dei led a infrarossi con il quale invio il segnale (TX), il secondo arduino è collegato con un ricevitore TSOP che riceve il segnale e attraverso il monitor segnale mi tira fuori dei codici esadecimali (RX). ora il mio step successivo è quello di mandare dei messaggi testuali dall’arduino TX all’arduino RX. Hai qualche consiglio da darmi? Devo utilizzare la comunicazione seriale?
Ti ringrazio in anticipo
a presto
Alessio
Puoi spedire i dati sia sulla seriale che via I2C. Uno vale l’altro. Tieni conto che se su un Arduino usi la seriale per comunicare col PC, quel canale non puoi usarlo per comunicare con un altro dispositivo seriale per cui o usi la SoftwareSerial oppure, come detto, la I2C.
Ciao leonardo, ho controllato, sia per quanto riguarda la software Serial che la i2c per la comunicazione e mi permette effettivamente di comunicare con due arduino. Però ho notato che i due arduino devono essere collegati attraverso dei pin, quindi attraverso dei cavi che li collegano. Siccome io devo utilizzare questo progetto come comunicazione wireless e quindi stare in una distanza tra i 4-5 metri, risulta complicato collegarli in questo modo tramite dei cavi. C’è un modo, una libreria, un qualcosa che mi permette di comunicare dei dati ttra due arduino UNO senza collegarli ”fisicamente”? Spero di essere stato chiaro nell’esplicitare il mio problema 🙂 grazie per l’aiuto e a presto
Più che una libreria, ti serve un “mezzo”. Le schedine xbee sono fatte apposta per creare piccole reti wireless seriali.
Ciao Leonardo,
sono Giacomo, un dottorando in Agraria e vorrei fare un progetto per comandare un fermentatore con Arduino e un programma in Visual Basic e monitorare i processi, ma ho dei problemi di comunicazione tra Arduino e il computer. inizialmente ho provato con il protocollo Firmata (ho messo insieme simple analog e simple digital firmata perché firmata normale non mi funzionava) e funzionava bene ma dopo qualche minuto (a volte secondo) si interrompeva la comunicazione e si bloccava tutto (ho anche provato a cambiare il “sampling interval a 99, come precisato nel sito). Uso Arduino Mega 2560 e Visual Basic Express 2010.
Ho anche provato a fare un protocollo apposta, ma sia Arduino e Visual Basic sono molto lenti e non sempre rispondono ai comandi, forse per via della grande mole di dati che scambiano i due componenti (valori di pH, temperatura, flussi e stato dei relè, e sensori).
Avresti qualcosa da suggerirmi?
Sai di qualche altro protocollo che possa fare al caso mio?
Ti ringrazio fin da subito per la pazienza per leggere tutto questo e per il preziosissimo aiuto che eventualmente mi puoi dare.
P.S. la programmazione non è proprio il mio ambiente, ma mi attira molto e sto studiando per capire in tutti i modi come poter risolvere questo problema.