Mod 18 –
Programmazione
Raspberry Pi
Comunicazione con Arduino
Raspberry Pi – Comunicazione Arduino
Comunicazione Seriale
TX – Transmitter • Tx e Rx delle GPIO della Raspberry Pi NON
RX - Receiver possono essere utilizzate per collegarlo
direttamente agli stessi pin di Arduino perché
c’è una differenza di potenziale (Raspberry
3.3V e Arduino 5V)
• Dovrei in ogni caso collegare il pin TX di una
scheda al pin RX dell’altra ma in mezzo dovrei
preparare un partitore di tensione
• In alternativa utilizziamo direttamente lo
stesso cavo con cui si collega arduino al pc, è
sempre una comunicazione seriale ed essendo
la Raspberry un microcomputer può
controllare direttamente arduino
Raspberry Pi – Comunicazione Arduino
Arduino IDE
• È possibile installare l’IDE tramite il comando:
sudo apt install arduino
• Tuttavia nelle repository del sistema operativo c’è una
versione datata, la 1.0.5 quindi è meglio scaricare
direttamente il programma dal sito di Arduino
• Scaricare l’ultima versione dal sito:
https://www.arduino.cc/en/software
• ATTENZIONE! Su Raspberry occorre scaricare la versione Linux
ARM 32 bits mentre su macchina virtuale o su un computer
dovrete scaricare la versione Linux 32 bit
• La parola chiave ARM infatti è specifica per i processori
installati sulle Raspberry e non possono funzionare con i
processi di computer normali
Raspberry Pi – Comunicazione Arduino
Arduino IDE
• Il file scaricato si troverà nei download ma è in formato
‘.tar.tx’, ovvero uno dei formati compressi esistenti sui sistemi
Linux
• Per estrarre i file compressi si utilizza il comando tar con la
seguente sintassi (bisogna essere nella stessa cartella in cui si
trova il file compresso):
sudo tar Jxvf arduino-1.8.13-linuxarm.tar.xz
• Il significato delle opzioni è:
J indica il formato del file da decomprimere (.xz)
x indica che vogliamo estrarre i file
v indica che vogliamo essere informati sui file che stanno
venendo estratti (altrimenti il programma non dà output)
f indica di salvare nella stessa posizione dell’archivio
Raspberry Pi – Comunicazione Arduino
Arduino IDE
• Se tutto si è svolto correttamente dovrebbe essere spuntata la cartella arduino-1.8.13
• Una volta estratta la cartella consiglio di spostarla nella directory home, di solito si trovano qui i
programmi dell’utente corrente:
mv arduino-1.8.13 ~
• Ora è possibile cancellare l’archivio compresso dalla cartella Download:
rm arduino-1.8.13-linuxarm.tar.xz
• Ora entriamo nella cartella con il comando cd ~/arduino-1.8.13
• All’interno della cartella si trovano i file di Arduino, quello che ci interessa è un file contenente le
istruzioni per la shell per eseguire l’installazione: install.sh. Eseguiamo questo programma come
superutenti:
sudo ./install.sh
• A questo punto l’IDE funziona e può essere avviato sia dal menù principale nella sezione
programming, sia dalla cartella appena creata eseguendo il comando ./arduino
Raspberry Pi – Comunicazione Arduino
È possibile comunicare con un Arduino anche utilizzando la macchina virtuale!
1. Per fare questo collegare l’Arduino al PC.
2. In seguito aprire VirtualBox e cliccando con tasto destro sull’icona della macchina virtuale del
Raspberry pi selezionare Impostazioni e poi USB
3. Dal menù aggiungere un nuovo dispositivo e selezionare Arduino Uno, poi premere Ok per
confermare.
Raspberry Pi – Comunicazione Arduino
Verificare il collegamento
• Per verificare se il dispositivo è stato correttamente riconosciuto (sia per il Raspberry sia per la
macchina virtuale) utilizzare il comando lsusb
Raspberry Pi – Comunicazione Arduino
Riconoscere la porta seriale
Per sapere a quale porta seriale è connesso l’Arduino utilizzare il comando
sudo dmesg | grep tty
Raspberry Pi – Comunicazione Arduino
Riconoscere la porta seriale
Per sapere a quale porta seriale è connesso l’Arduino utilizzare il comando
sudo dmesg | grep tty
dmesg serve a stampare tutti gli eventi recenti del sistema operativo
grep tty dice al sistema di cercare nell’output del comando precedente (dmesg) tutte le righe che
contengono la stringa ‘tty’
Nel caso in esempio vediamo che la porta seriale dell’Arduino è la porta ttyACM0 (sarebbe
l’equivalente di una COM1 su windows)
Attenzione! Se rimuovete il dispositivo mentre la comunicazione è attiva il sistema potrebbe
assegnargli una porta diversa alla connessione!
Raspberry Pi – Comunicazione Arduino
Comunicazione seriale con Python
• La comunicazione seriale si può gestire con python utilizzando la libreria pyserial
(https://pypi.org/project/pyserial/ )
• Per verificare il funzionamento della libreria pyserial realizzare uno script con il seguente
contenuto:
Raspberry Pi – Comunicazione Arduino
Comunicazione seriale con Python
• Il risultato dovrebbe essere come il seguente (non considerate la riga con ttyAMA0)
• Questo risultato significa che la libreria ha correttamente riconosciuto Arduino sulla porta seriale
Raspberry Pi – Comunicazione Arduino
Pyserial – breve guida
• Per inizializzare la comunicazione seriale la prima cosa da fare è creare un oggetto Serial tramite il
costruttore:
s_obj = serial.Serial(port_name, baudrate=9600)
• Port_name è il nome della porta seriale a cui è collegato arduino, quello che ci ha restituito il
file precedente (/dev/ttyACM0 nel mio caso)
• Baudrate deve essere lo stesso impostato su Arduino altrimenti la comunicazione seriale non
sarà corretta
• La porta verrà automaticamente aperta quando viene creata
• Per conoscere il numero di byte in attesa di essere letti e presenti sulla seriale si utilizza:
s_obj.in_waiting
• ATTENZIONE! Questo è un attributo (quindi una variabile) e non un metodo (quindi una
funzione), è corretto utilizzarlo senza parentesi alla fine!
Raspberry Pi – Comunicazione Arduino
Pyserial – breve guida
• Per leggere dalla porta seriale che è stata aperta un numero di byte pari alla size specificata si può
utilizzare:
s_obj.read(size)
• Questa chiamata attende fino a che non saranno presenti abbastanza byte da leggere sulla
seriale, a meno che non venga impostato un timeout. Fate attenzione perché stiamo parlando di
BYTE e non di CARATTERI
• Per scrivere sulla seriale invece si utilizza la funzione:
s_obj.write(data)
• ATTENZIONE! Data deve essere in formato bytes e NON una stringa
Raspberry Pi – Comunicazione Arduino
Esempio
• Creare un programma per Arduino che scriva «Hello there!» durante la funzione di setup
• Prova a leggere il messaggio con python da terminale
• Attenzione! Chiudere il monitor seriale di Arduino prima di provare altrimenti questo interferisce
con la lettura del seriale
Raspberry Pi – Comunicazione Arduino
Codifica ASCII
• Traduce in modo univoco i valori di byte da 0x20 a 0x7E (32 – 126)
Dec 72 101 108 108 111 32 119 111 114 108 100 33
Hex 0x48 0x65 0x6c 0x6c 0x6f 0x20 0x77 0x6f 0x72 0x6c 0x64 0x21
H e l l o w o r l d !
• Il problema è che questa codifica ha un numero limitato di caratteri e permette quindi di scrivere
solo le cifre arabe, i simboli di base e le lettere utilizzate in inglese (niente accenti e niente
caratteri non presenti nell’alfabeto inglese). Per questo, nel tempo sono state create altre
codifiche.
Raspberry Pi – Comunicazione Arduino
Codifica UTF-8
• È una delle codifiche più utilizzate. Questa codifica permette di scrivere tutti i caratteri in latino e
latino esteso, i simboli e i caratteri di tutti i linguaggi alfabetici (Greco, Cirillico, Arabo …) A
seconda del carattere rappresentato esso può occupare anche più di un byte.
Dec 99 105 195 178 32 195 168 32 117 116 102 45 56
Hex 0x63 0x69 0xc3b2 0x20 0xc3a8 0x20 0x75 0x74 0x66 0x2d 0x38
c i ò è u t f - 8
• Come si può vedere alcuni caratteri occupano 2 byte!
Raspberry Pi – Comunicazione Arduino
Byte in Python
• I byte si possono dichiarare mettendo una b davanti ad una stringa a patto che contenga solo
caratteri ascii:
my_bytes = b’ciao_ciao’ my_bytes = b’ciaoèciao’
• Oppure è possibile utilizzare il metodo encode() specificando il formato, per esempio utf-8
my_bytes = ’ciao_ciao’.encode(‘utf-8’)
my_bytes = ’ciaoèciao ’.encode(‘utf-8’)
• Per trasformare correttamente una sequenza di byte in una stringa utilizzare il metodo decode()
sempre specificando la codifica da utilizzare
print(my_bytes.decode(‘utf-8’)) #-> ciao è ciao
Raspberry Pi – Comunicazione Arduino
Da Byte a String
• Lungo le connessioni della seriale transitano quindi solo byte, per poterli trasmettere e ricevere
correttamente sarà necessario codificarli tramite encode prima dell’invio e poi decodificarli
appena dopo averli ricevuti
decode
Bytes String
encode
Raspberry Pi – Comunicazione Arduino
Scambio messaggi con Arduino
• Proviamo ora a scrivere un messaggio ad Arduino, facciamo un modo che Arduino legga il nostro
messaggio e risponda con lo stesso seguito da «ricevuto». Ovviamente non possiamo utilizzare il
monito seriale dell’IDE altrimenti sarà lui a leggere i dati e non il nostro script python!
Inizializzo una stringa vuota che riceve il messaggio
Inizializzo la stringa che contiene la scritta « ricevuto»
Inizializzo la seriale e abbasso il timeout
(di default è 1000 ms)
Se ci sono dati disponibili nel buffer di ricezione
leggo tutti i dati in arrivo e li salvo in inMessage,
poi invio la risposta
Raspberry Pi – Comunicazione Arduino
Scambio messaggi con Arduino
• Dopo avere chiuso il monitor seriale dell’IDE con l’interprete python testiamo la comunicazione
Appena la porta viene aperta
non ci sono dati in arrivo
Scrivo un messaggio dopo
averlo codificato in byte
Ora ci sono 15 byte da leggere,
Arduino ha risposto
Stampo i dati letti dalla seriale
dopo averli decodificati in
stringa
Raspberry Pi – Comunicazione Arduino
Esercizio 1
• Creare un sistema che legga i dati da una porta digitale ogni secondo
e li salvi in un file csv, inoltre deve mostrare a schermo la media delle
letture
• Collegare un potenziometro all’uscita analogica 0 dell’arduino come
si vede in figura
• Utilizzare la funzione analogRead(PORT) per leggere il dato del
potenziometro
• Utilizzare la funzione map per convertirlo da una lettura analogica a
10bit (da 0 a 1023) a un valore in millivolt (da 0 a 5000)
• Un file csv invece è un file di testo che contiene una determinata
formattazione nella prima riga e tutte le righe successive devono
seguire quella formattazione
Raspberry Pi – Comunicazione Arduino
File CSV
• CSV è l’acronimo di Comma Separated Values, è infatti un file di testo che utilizza le virgole per
separare i dati all’interno di celle diverse in una tabella
• La prima riga contiene l’intestazione, quindi nel nostro caso sarà:
• Mentre tutte le righe successive conterranno sulla prima colonna il tempo in secondi a cui è
avvenuta la lettura e sulla seconda colonna (separata da una virgola) il valore di millivolt letto
Raspberry Pi – Comunicazione Arduino
Parte Arduino Leggo i segnali sulla porta A0
Converto la lettura analogica in un segnale in millivolt
Invio il dato come stringa
Raspberry Pi – Comunicazione Arduino
Parte Raspberry Pi
Apro il file in scrittura, questo
significa che ogni volta che avvio il
programma sovrascrivo il file
precedente
Metto i titoli della tabella in csv
Raspberry Pi – Comunicazione Arduino
Se ci sono dati da leggere Leggo tutti i dati in arrivo e li converto in
attendo un po’ così intanto una stringa
Parte Raspberry Pi
arrivano tutti
Converto la stringa in un intero
Calcolo la media come somma di tutti
i dati ricevuti fratto il numero di dati
ricevuti
Calcolo il tempo come differenza
tra il tempo all’avvio del
programma e il tempo attuale
Scrivo tempo e
tensione nel file
Quando viene premuto ctrl+c csv
chiudo il file e la porta seriale
prima di uscire
Raspberry Pi – Comunicazione Arduino
File voltage_log.csv
Se il programma è stato fatto correttamente, una volta chiuso, nella stessa cartella è possibile
trovare il file voltage_log.csv che, se aperto con un editor di testo ha questa struttura:
Raspberry Pi – Comunicazione Arduino
File voltage_log.csv
La particolarità dei file csv è che possono essere aperti con un foglio di calcolo e quindi utilizzati per
calcoli e grafici (nell’immagine lo stesso file aperto con LibreOffice Calc e con un grafico generato):
Raspberry Pi – Comunicazione Arduino
Funzione millis()
• La funzione millis() di Arduino permette di conoscere esattamente quanto tempo (in
millisecondi) è trascorso da quando è stato acceso il microcontrollore
• Il valore restituito da millis() è da considerarsi un unsigned long e può quindi «contare» fino a
4.294.967.295 (232 -1, un long sono 4 byte), corrisponde a circa 50 giorni e quindi andrà bene se
non prevedete di utilizzare la funzione in un progetto che dovrà rimanere attivo per un periodo
così lungo
• Diversamente dalla funzione delay(ms) non sospendiamo il programma per un numero
determinato e fisso di millisecondi ma possiamo decidere quanto aspettare dall’ultimo evento
che si è verificato
Raspberry Pi – Comunicazione Arduino
Esercizio 2
• Modificare il programma dell’esercizio 1 e, utilizzando la funzione millis(), fare in modo che
l’utente, tramite la Raspberry Pi, possa comunicare all’arduino ogni quanti secondi effettuare la
lettura
Raspberry Pi – Comunicazione Arduino
Parte Arduino Riduco il timeout a 100ms perché devo leggere pochi
dati dalla seriale e quindi sono sufficienti
Visto che Arduino deve eseguire 2 task
contemporaneamente (leggere i dati, controllare i
messaggi in seriale) non si può più utilizzare il delay…
Passo alle funzioni temporizzate con millis()
Devo attendere che sia trascorso il tempo richiesto
prima di eseguire la lettura successiva
E devo poi salvarmi il tempo a cui è avvenuta la lettura
per misurare quanto trascorre prima della lettura
successiva
Se ci sono dati in arrivo li converto in interi e poi faccio
un controllo in modo che non vengano inseriti valori
nulli, l’intervallo di tempo minimo tra una lettura e la
successiva è di 1 secondo
Raspberry Pi – Comunicazione Arduino
Parte Raspberry Pi Anche nella parte di Raspberry Pi devono essere
eseguiti due task contemporaneamente:
1. Legge i valori dalla seriale, calcola la media e li
salva
2. Attende l’input dell’utente e invia i valori
Importo quindi la libreria threading
per gestire i due compiti
separatamente
Raspberry Pi – Comunicazione Arduino
Decido di eseguire in background la parte di lettura dalla seriale e scrittura
Parte Raspberry Pi su file. Per questo creo una funzione che sia eseguita da un thread separato.
Se in una funzione voglio SCRIVERE delle variabili globali devo
dichiarale global all’inizio della funzione. Se devo leggerle o
utilizzare dei metodi ad esse associati non c’è bisogno
Notate che non sono presenti
print in questa funzione
perché il compito di
comunicare con l’utente lo
lasciamo al thread principale
(se entrambi i thread
scrivessero sul terminale si
rischia di far confusione)
Raspberry Pi – Comunicazione Arduino
Definisco il thread e poi lo faccio partire
Parte Raspberry Pi
Se una stringa è molto lunga posso spezzarla su più righe
utilizzando un backslash (‘\’) prima dell’ «a capo»
Se l’utente preme invio senza scrivere nulla (quindi input
ritorna una stringa vuota) assumo che voglia vedere il
numero di misure e la media
Se l’utente scrive una stringa non nulla provo a
convertirla in un numero per vedere se è un valore
valido. In questo modo evito di mandare delle
stringhe sbagliate all’Arduino i cui errori sono sempre
difficili da rilevare
Raspberry Pi – Comunicazione Arduino
Parte Raspberry Pi
Il metodo .join() del thread dice alla funzione
principale di fermarsi ed attendere che il thread sia
finito. Se non uso questa istruzione rischio di chiudere
la porta seriale o il file mentre il thread le sta ancora
usando
Raspberry Pi – Comunicazione Arduino
Trasferire più valori
• Finora abbiamo visto come è possibile trasmettere un singolo valore tra due dispositivi. MA se
occorresse tramettere più di un valore come si può fare? Per esempio se devo trasmettere 1 int
(1234) 1 float (23.5) e 1 bool (True)
1. Utilizzare un delimitatore per i valori
Dec 49 50 51 52 36 50 51 46 53 36 49
Hex 0x31 0x32 0x33 0x34 0x24 0x32 0x33 0x2e 0x35 0x24 0x31
1 2 3 4 $ 2 3 . 5 $ 1
• Posso poi riottenere i valori che cercavo utilizzando una funzione di split sulla stringa con ‘$’
come separatore
Raspberry Pi – Comunicazione Arduino
2. Posso utilizzare un tipo di serializzazione standard, per esempio JSON
Dec 91 49 50 51 52 44 32 50 51 46 53 44 32 116 114 117 101 93
Hex 0x5b 0x31 0x32 0x33 0x34 0x2c 0x20 0x32 0x33 0x2e 0x35 0x2c 0x20 0x74 0x72 0x75 0x65 0x5d
[ 1 2 3 4 , 2 3 . 5 , t r u e ]
• Questa soluzione è molto più flessibile e il parsing può essere delegato a librerie specializzate.
L’unico lato negativo è che può aumentare il numero di byte trasmessi e il tempo di
codifica/decodifica.
3. Si può utilizzare una spaziatura fissa per i valori
Dec 48 49 50 51 52 50 51 46 53 48 48 49
Hex 0x30 0x31 0x32 0x33 0x34 0x32 0x33 0x2e 0x35 0x30 0x30 0x31
0 1 2 3 4 2 3 . 5 0 0 1
• Posso sapere in anticipo quanti caratteri utilizza ogni numero, il problema è che il limita molto i
valori numerici che posso tramettere
Raspberry Pi – Comunicazione Arduino
4. Utilizzare una spaziatura fissa ma trasmette valori binari
Dec 210 4 0 0 188 65 01
Hex 0xd2 0x04 0x00 0x00 0xbc 0x41 0x01
• Questa è sicuramene la soluzione più efficiente in quanto i valori occupano esattamente il
minimo numero di byte possibile.
• Il problema è che richiede la traduzione dei valori in linguaggio binario (su Python si può usare
https://docs.python.org/3.7/library/struct.html ) e che rende il messaggio non leggibile da un
umano, quindi risulta difficile il debug
Raspberry Pi – Comunicazione Arduino
Utilizzare Json per Arduino
• Per prima cosa installiamo
la libreria ArduinoJson
(https://arduinojson.org/ )
per serializzare in formato
JSON dal Library manager
Raspberry Pi – Comunicazione Arduino
Esercizio 3
• Modificare il programma precedente in modo che siano inviati dati di 2 porte analogiche.
• Le funzioni principali JSON che andranno utilizzate su Arduino:
1. DynamicJsonDocument var(size): crea un oggetto di tipo DynamicJsonDocument che
deve essere utilizzato dalle funzioni della libreria JSON ma di fatto si comporta come un
dizionario Python
2. serializeJson(var, Serial): var deve essere del tipo DynamicJsonDocument e tramite
questa funzione è possibile inviare i dati contenuti nel dizionario sulla seriale
• Le funzioni principali JSON che andranno utilizzate su Raspberry Pi:
1. json.loads(input_string): deserializza la stringa appena ricevuta dalla seriale utilizzando il
formato json e permette di ottenere un dizionario con i dati ricevuti
Raspberry Pi – Comunicazione Arduino
Parte Arduino Aggiungo le definizioni della seconda porta
Creo un oggetto DynamicJsonDocument
Creo un «dizionario» nell’oggetto doc
Lo serializzo in JSON e lo invio via seriale
Raspberry Pi – Comunicazione Arduino
Parte Raspberry Pi
Rispetto allo script precedente alcuni valori
sono diventati liste
Ho aggiunto una colonna relativa alla seconda porta
Raspberry Pi – Comunicazione Arduino
Parte Raspberry Pi
Deserializzo la stringa in formato JSON, ottengo un
dizionario
A seconda della key assegno i valori nella
posizione 0 o 1 della lista
Con questo ciclo aggiorno le variabili globali
Raspberry Pi – Comunicazione Arduino
File voltage_log.csv
Raspberry Pi – Comunicazione Arduino
Esercizio 4
• Creare un programma che permetta di attivare le porte dalla 2 alla 13 e una minima interfaccia
testuale per comandarlo.
• Le funzioni principali JSON che andranno utilizzate su Arduino:
1. DeserializeJson(var, Serial): è l’opposto di serializeJson quindi permette di riceve i dati
dalla seriale e salvarli nella variabile var di tipo DynamicJsonDocument
2. DeserializationError e: è la variabile che ritorna la funzione DeserializeJson e permette di
controllare se tutto è andato a buon fine, ovvero se e assume il valore
DeserializationError::Ok
• Le funzioni principali JSON che andranno utilizzate su Raspberry Pi:
1. json.dumps([data1,data2…], ): converte i dati in json, questo andrà poi codificato in utf-8
per inviarlo sulla seriale
Raspberry Pi – Comunicazione Arduino
Parte Arduino Creo un elenco di porte valide così evito di fare
dei danni per errore
Con questo comando acquisisco i dati da
seriale e li deserializzo
Se e ha un valore diverso dalla costante
DeserializationError::Ok vuol dire che non è
riuscita ad interpretare la stringa
Se il valore della porta fornito è tra quelli
validi posso accendere il led
Raspberry Pi – Comunicazione Arduino
Parte Raspberry Pi
Nella prima parte metto un programma di benvenuto, inizializzo la seriale e
attendo 2 s che Arduino si riavii
Il programma prosegue solo se il valore in input è intero (quindi solo
se la conversione non dà errore) e se è 1 o 0
Posso andare a capo all’interno di una parentesi
Se il vlaore ricevuto è valido si converte in
json, poi in bytes e infine si invia tramite
seriale
Raspberry Pi – Comunicazione Arduino
Parte Raspberry Pi
Nella prima parte metto un programma di benvenuto,
inizializzo la seriale e attendo 2 s che Arduino si riavii
Il programma prosegue solo se il valore in input è intero
(quindi solo se la conversione non dà errore) e se è 1 o 0
Posso andare a capo all’interno di una parentesi
Se il valore ricevuto è valido, lo si
converte prima in json, poi in bytes e
infine si invia tramite seriale grazie
alla funzione write