RaspberryPI: GPIO, I2C, SPI ed UART!

Presentazione

Le periferiche HDMI, USB ed Ethernet sono solo tre delle tante periferiche messe a disposizione da Rpi per poterlo interfacciare con l’hardware necessario ad un normale utilizzo. Ma Rpi non si ferma qua, infatti, integrato nel pcb, vi è un connettore il quale mette a disposizione SPI, USART, I2C, e GPIO per potervi collegare sensori, eprom, microprocessori e molo altro! Vediamo come poterli gestire con apposite librerie in linguaggio C!

 

Panoramica

Sul PCB di Rpi, è presente un connettore a 26pin il quale mette a disposizione dell’utente diverse funzionalità per aumentare l’interazione tra Rpi ed il “mondo esterno”. Dando uno sguardo al pinout, possiamo subito notare che oltre ai normali pin di I/O chiamati GPIO (General Purpose Input/Output) sono presenti dei pin dedicati a delle funzioni speciali, quali USART, SPI ed I2C.

Sul connettore possiamo quindi trovare 17pin di I/O alcuni dei quali possono essere configurati per operare in modo diverso da normali input/output. Oltre a tali pin, ci vengono messi a disposizione 9 pin per fornire ai dispositivi che andremo a collegarvi, un alimentazione di +5v o +3.3v. Nonostante siano disponibili i +5v, i livelli logici dei pin di I/O sono pari a 3.3v, quindi ogni eventuale errore o tentativo di fornire in ingresso una tensione superiore ai 3.3v danneggerà il nostro Rpi. Non vi è infatti nessun tipo di protezione per un eventuale sovra-tensione in Rpi, questo perché si presuppone che l’utente finale utilizzi un adeguato buffer o adattatore di livelli nel caso in cui si debba operare con segnali con standard diversi. Se utilizzati come normali uscite, tali pin possono fornire una corrente massima pari a 16mA per pin, con una corrente massima sul connettore di 51mA. Ciò vuol dire che, nel caso in cui venissero utilizzati tutti e 17 i pin come uscite, la corrente prelevabile da ogni singolo pin sarà di 51 : 17 = 3mA per pin. La corrente messa a disposizione su tali pin è comunque abbastanza per iniziare con i primi test per accendere qualche led e fare quindi pratica con le librerie che andremo ad utilizzare. Sui pin di alimentazione invece, possiamo prelevare una corrente non superiore ai 50mA nel caso dei 3.3v; i +5v invece, non essendo stabilizzati da nessun regolatore, arrivano direttamente dal connettore Micro-USB, ed avremo quindi teoricamente a disposizione tutta la corrente fornita dal nostro alimentatore, esclusa quella utilizzata da Rpi che si aggira attorno ai 700mA.

 

Linguaggio e librerie

Per operare con le periferiche appena menzionate  via software, dobbiamo utilizzare apposite librerie le quali mettono a nostra disposizione diverse funzioni per definire l’uso dei vari pin e poter poi accedere alle funzioni speciali. Navigando in internet possiamo trovare diversi tipi di librerie che, oltre a differire nel linguaggio di programmazione, mettono a disposizione un numero maggiore o minore di funzionalità. WiringPI è un set di librerie che permette all’utente di lavorare sui pin di GPIO in linguaggio C. Utilizzeremo tali librerie negli esempi riportati in seguito poiché, oltre ad avere funzioni molto semplici che ci permettono di operare con tutte le periferiche, permettono di numerare in modo diverso i pin sul connettore, rendendo la programmazione più intuitiva.

 

Installazione WiringPI

Ovviamente, prima di poter scrivere i nostri programmi in C, dobbiamo necessariamente installare il set di librerie.
Procediamo quindi da terminale con i comandi:

Installiamo il pacchetto git-core:

sudo apt-get install git-core 

Scarichiamo le librerie nella home del nostro Rpi:

git clone git://git.drogon.net/wiringPi 

Ci spostiamo nella cartella appena scaricata:

cd wiringPi 

Verifichiamo che siano aggiornate all”ultima versione:

git pull origin

ed infine installiamo le librerie:

./build 

Per verificare che tutto sia andato correttamente, possiamo utilizzare il comando: gpio readall, il quale riporterà una tabella con lo stato logico dei vari pin.

 

Pinout

Prima di procedere con una breve descrizione seguita da esempi pratici sulle funzionalità appena descritte, dobbiamo fare una precisazione sul pinout del connettore di Rpi: esso infatti è stato commercializzato in due versioni (Rev1, Rev2), le quali differiscono nella disposizione dei vari pin sul connettore. Da settembre 2012 l’unica versione commercializzata risulta essere la Rev2. Se avete, quindi, acquistato Rpi oltre questa data molto probabilmente avrete una Rev2 e dovrete quindi fare riferimento al pinout di tale versione. A prescindere dal tipo di Rpi in uso, le librerie wiringPi permettono una diversa numerazione dei pin, selezionabile utilizzando due diverse funzioni di inizializzazione:

wiringPiSetup()
Con questa funzione inizializziamo le porte con la numerazione WiringPi. Tale numerazione differisce dalla piedinatura originale rendendola molto più intuitiva.

wiringPiSetupGpio()
Utilizzando questa funzione invece, inizializzeremo le porte utilizzando la normale numerazione Broadcom senza nessuna ri-mappatura da parte della libreria. Spetta all’utente finale scegliere quale numerazione trovi più intuitiva e procedere utilizzando l’adeguata funzione.

 

GPIO

Come già detto, 17 pin possono essere utilizzati come semplici Ingressi od Uscite, a tali pin possiamo collegarvi led, pulsanti, relè…e molto altro! Un esempio molto semplice ma allo stesso tempo molto pratico per poter iniziare ad interagire con i GPIO è il classico lampeggio di un led al quale andremo ad aggiungervi un pulsante per rendere l’esempio ancora più completo. Il programma che andremo a scrivere, utilizzerà un pin come ingresso per verificare che il pulsante ad esso collegato sia premuto o a riposo e se premuto faccia lampeggiare un led ad intervalli regolari realizzati con una semplice Delay. Seguendo lo schema possiamo facilmente collegare led e pulsante a Rpi, dopo di che possiamo iniziare a scrivere il nostro primo programma!

 

Sul desktop di Raspbian, creiamo un nuovo file di testo, che andremo poi a rinominare “led.c”, e scriviamo queste semplici righe:

#include<wiringPi.h> void main (void) { wiringPiSetup(); pinMode(0,OUTPUT); pinMode(1,INPUT); for(;;) { while(digiralRead(1)) { digitalWrite(0,HIGH); Delay(100); digitalWrite(0,LOW); Delay(100); } } }

Analizziamo il codice appena scritto.

Nella prima riga troviamo un #include, per chi è a digiuno di C tale comando indica al compilatore che deve includere il file wiringPi.h al programma. Tale file include tutte le funzioni base che andremo poi a richiamare nel programma comprese le funzioni d’inizializzazione delle porte. Come tutti i programmi in C, la funzione principale è composta da un Main il quale contiene il cuore del programma. Subito dopo il main troviamo una delle funzioni precedentemente descritta: wiringPiSetup(), per comodità inizializziamo le porte utilizzando la piedinatura WiringPi e successivamente indichiamo la funzione che devono avere due dei pin da noi scelti, in questo caso il pin 0 ed il pin 1 di WiringPi corrispondenti al N°11 e 12 del connettore. Ed ora una funzione probabilmente già nota a chi ha avuto a che fare con Arduino: pinMode(0,OUTPUT). Come da schema, il pin 0 (11 sul connettore) verrà utilizzato come OUTPUT per accendere e spegnere il led mentre il pin 0 (12 sul connettore) verrà utilizzato come INPUT per leggere lo stato del pulsante, per indicare a Rpi la funzione che devono avere questi due pin, utilizziamo pinMode(pin, Tipo) dove in pin indichiamo il numero del pin secondo lo standar richiesto dalla funzione d’inizializzazione precedentemente utilizzata, e per tipo indichiamo se ingresso od uscita. Ora che i pin sono correttamente inizializzati, si passa alla funzione che controllerà lo stato del led ed eventualmente lo farà lampeggiare. Un for privo di argomenti ci assicura un ciclo infinito nel quale il while(digiralRead(1)) permette una chiamata pressoché continua della funzione digiralRead(1) la quale, ad ogni chiamata, restituisce lo stato del pin indicato tra le parentesi, in questo caso il numero 1. Se la funzione restituisce un 1, e quindi l’ingresso si trova a livello alto, verrà eseguito ciò che si trova all’interno del while che consiste in due settaggi del pin 0, la prima a livello alto e la successiva a livello basso, intervallati da un semplice ritardo.
Chi conosce Arduino e ha avuto modo di realizzare anche solo dei piccoli programmi, troverà nelle librerie WiringPI molte delle funzioni utilizzate nella programmazione di Arduino e risulterà quindi molto facile il passaggio da Arduino a Rpi.

Salviamo il file, ed apriamo il terminale per compilare ed eseguire il programma:

Ci spostiamo sul Desktop o Scrivania;

cd Desktop

Compiliamo il file led.c con in compilatore gcc, presente tra i programmi base di Rpi:

gcc – o led led.c -lwiringPi

ed eseguiamo il file appena compilato:

sudo ./led

Se tutto è andato bene, alla pressione del pulsante il led inizierà a lampeggiare, per fermarsi poi non appena il pulsante verrà rilasciato.
Per terminare l’esecuzione basterà la combinazione Ctrl + C.

 

 

 

UART

Ora vediamo come utilizzare la porta UART per comunicare con altri microcontrollori, con il pc, o magari un modulo Xbee!
Con le librerie WiringPi possiamo gestire con apposite funzioni la comunicazione seriale sfruttando i due pin TxD(8) e RxD(10) di Rpi od un convertitore USB-> seriale. Per poter inviare o ricevere dati via UART, dobbiamo includere l’apposita libreria nel nostro programma:

#include <wiringSerial.h>

vediamo subito un semplice esempio su come inviare e ricevere un dato via seriale ponticellando il pin RxD con TxD per far si che il dato inviato venga subito ricevuto da Rpi.
Come prima, creiamo un nuovo file di testo e scriviamo il seguente codice:

#include <wiringPi.h>
#include <stdio.h>
#include zwiringSerial.h>

 int fd, ric;

void main (void)
{
fd = serialOpen("/dev/ttyAMA0",9600);
serialPutchar(fd, “A”);
ric = serialGetchar(fd);
printf(“%d\n”, ric);
}

Come nel programma precedente, dentro al main troviamo innanzitutto l’inizializzazione delle porte con il richiamo della funzione serialOpen alla quale viene passato il percorso della porta seriale presente sul connettore di Rpi e successivamente il baud rate della comunicazione. La funzione ritorna un identificatore che verrà poi sfruttato dalle successive funzioni, tuttavia sarebbe opportuno verificare che tale valore non sia negativo, infatti un valore negativo indica un errore nell’inizializzazione.
A configurazione effettuata, viene inviato il carattere A il quale viene immediatamente ricevuto dalla funzione serialGetchar, poichè TxD e Rxd sono ponticellati, e provvede a scriverlo in ric per poi essere riportato nel terminale.

Nelle varie prove effettuate, ho notato una certa instabilità delle funzioni nell’utilizzo della periferica UART. Talvolta può capitare che la funzione di inizializzazione non venga eseguita correttamente ed il programma si blocchi fino a nuovo ordine. Al successivo riavvio di Rpi tutto riprende a funzionare correttamente, mi è stato infatti possibile inviare e ricevere dati tra Rpi ed un Pc utilizzando due moduli Xbee, seppur con diverse interruzioni del programma su Rpi.

I2C

Il protocollo I2C è un protocollo che prevede l’utilizzo di due pin, SDA ed SCL per la comunicazione tra due o più dispositivi i quali dispongono di un indirizzo per l’identificazione in rete. Con Rpi possiamo interagire con tali dispositivi, come accelerometri, sensori di temperatura ed RTC con le librerie WiringPi.
In WiringPi sono già presenti le librerie che ci permettono di leggere e scrivere nel protocollo I2C, tuttavia non sempre sono presenti alcuni componenti aggiuntivi che ne permettono il corretto funzionamento, procediamo quindi all’installazione, od eventualmente alla re-installazione di tali componenti:

sudo apt-get install libi2c-dev 

Successivamente, rieseguiamo la procedura di installazione di WiringPi con i comandi:

cd wiringPi
./build 

Prima di poter utilizzare i due pin dedicati all’I2C, dobbiamo avviare i rispettivi driver nel kernel:

gpio load i2c

In seguito, se necessario, modifichiamo il baud rate che di default è pari a 100Kbps:

gpio load spi 400 

Ora siamo pronti per dedicarci alla scrittura del programma, prima però dobbiamo conoscere l’indirizzo del dispositivo con il quale vogliamo comunicare. Nel mio caso ho scelto un RTC DS1307, del quale conoscevo già l’indirizzo perché presente sul datasheet fornito dal produttore, tuttavia è possibile ricercare l’indirizzo della periferica con il comando:

i2cdetect -y 0 //se Rpi Rev1
i2cdetect -y 1 //se Rpi Rev2 

L’indirizzo ci servirà nel momento in cui dovremo comunicarlo alla funzione che si occuperà di inizializzare correttamente l’I2C in Rpi.

 

Vediamo alcune delle funzioni disponibili:

fd = wiringPiI2CSetup(Indirizzo Periferica);
Al richiamo di questa funzione, verrà inizializzato il protocollo I2C per comunicare con la periferica corrispondente all’indirizzo fornito tra le parentesi. Se l’inizializzazione è avvenuta correttamente, la funzione restituirà un valore positivo il quale verrà poi utilizzato dalle successive funzioni come identificatore, in alternativa ritornerà un -1 il quale indica un errore.

wiringPiI2CWriteReg8 (fd, Registro, Dato) ;
wiringPiI2cReadReg8(fd, Registro);

Solitamente le periferiche I2C richiedono per la lettura o la scrittura di un dato, l’invio dell’indirizzo dal quale leggere o scrivere. Per fare ciò utilizziamo queste due funzioni in grado di leggere o scrivere un dato a 8 bit in un determinato registro. La funzione di scrittura ritornerà un valore positivo se la scrittura è andata a buon fine, negativo se presente qualche errore. La funzione di lettura invece, ritornerà il valore letto dall’indirizzo indicato. Con fd si intende il valore restituito dalla funzione di inizializzazione il quale va passato a tutte le funzioni che operano sull’I2C.

Vediamo un semplice esempio per scrivere e leggere un dato su un RTC DS1307, per poi comunicare il dato letto sul terminale.

 

Come prima, apriamo un nuovo file di testo e scriviamo:

#include <wiringPi.h>
 #include <stdio.h>
 #include <wiringPiI2C.h>
 
int id, ric;

void main(void)
{
id = wiringPiI2CSetup(0x68);  //Inizializzo l'I2C con l'indirizzo della periferica in uso
wiringPiI2CWriteReg8(id, 0x07, 0x90);   //Invio il valore 0x90 nel registro 0x07
ric = wiringPiI2CReadReg8(id, 0x07);  //Leggo il valore presente nel registro 0x07
printf(“%d\n”, ric);   //Stampo sul terminale il valore letto
}

Se tutto è corretto, una volta compilato ed eseguito il programma, vedremo apparire sul terminale il valore 0x90, valore che è stato appena scritto sul registro 0x07.

SPI
Per la gestione della comunicazione via SPI, abbiamo a disposizione due sole funzioni, una per l’inizializzazione delle porte, e la successiva per la lettura e successivamente la scrittura di un determinato dato.

rit = wiringPiSPISetup (canale, velocità del clock);
Questa funzione permette l’inizializzazione di uno dei due moduli SPI a disposizione su Rpi; alla voce canale infatti, indicheremo se vogliamo utilizzare il dispositivo che andrà collegato sul canale 0 (pin 24) o 1(pin 26), mentre dopo la virgola verrà indicata la frequenza di clock della comunicazione esptressa in Hz disponibile dai 500KHz ai 32MHz. La funzione restituisce un valore positivo se l’inizializzazione è andata a buon fine, un numero negativo se è presente qualche errore.

rit = wiringPiSPIDataRW (canale, dato in char, lunghezza in byte) ;
Esegue una lettura e successivamente scrive il dato espresso in char della lunghezza indicata nel canale 1 o 0.

Prima di avviare un programma che utilizza la comunicazione SPI però, come per il protocollo I2C dobbiamo caricare i driver SPI nel kernel:

gpio load spi

e se necessario cambiare la dimensione del buffer dedicato alla comunicazione:

gpio load spi 100 

In questo caso verranno dedicati 100 Kb al buffer.
Infine dovremo includere la libreria dedicata all’SPI nel programma che scriveremo:

#include <wiringPiI2C.h>

LCD
Non ci rimane che far parlare Rpi!
WiringPi infatti, ci mette a disposizione delle funzioni per la gestione di un LCD HD44780 compatibile con la semplice agiunta della riga:

#include <lcd.h>

Grazie alle librerie WiringPi possiamo utilizzare fino a due display contemporaneamente potendoli gestire separatamente per visualizzare quindi dati differenti. Possiamo connettere il display a Rpi sia in modalità 4bit che 8bit, ovviamente la prima a parità di risultati ci permette di avere più pin disponibili per ulteriori connessioni, scegliamo quindi 6 pin a nostro piacimento e prepariamoci ad inizializzare l’LCD.

 

fd = lcdInit (righe, colonne, bits, RS, E, D0, D1, D2, D3, D4, D5, D6, D7) ;
Sembra un po’ complessa, ma si spiega in breve. Per l’inizializzazione dell’LCD dobbiamo fornire alla funzione qualche dato: oltre al numero di righe e di colonne del nostro LCD, indichiamo il tipo di comunicazione che vogliamo utilizzare, 4 o 8 bit. Successivamente dovremo indicare i pin da utilizzare: se utilizziamo la modalità a 4 bit saranno necessari i soli pin da D0 a D3, i restanti saranno posti a zero.

Cos’è un display LCD senza il classico Hello World?

#include <wiringPi.h>
#include <lcd.h>

 int fd;
void main (void)
{
wiringPiSetup();
fd = lcdInit (2,16,4,11,10,0,1,2,3,0,0,0,0);
lcdClear(fd);
lcdHome(fd);
lcdPrintf(fd,"Hello World");
} 

Oltre alle due funzioni per cancellare l’LCD e riportare il cursore alla Home, troviamo lcdPrintf che altro non è che l’equivalente del classico printf. In alternativa, possiamo utilizzare lcdPutchar(fd, ‘X’) per la scrittura di un solo carattere ASCII, o lcdPuts(fd, “string”) per la scrittura di una stringa.
Per comodità riporto lo schema delle connessioni Rpi → LCD ricordando che il pin RW dell’LCD va collegato a massa per evitare che l’LCD entri in modalità lettura ed invii quindi dei dati sulle porte di Rpi.
L’LCD infatti, opera a 5v mentre Rpi a 3.3, se utilizzato in lettura quindi danneggerebbe la nostra schedina.

 

 

Tips
Infine qualche trucchetto o raccomandazione. Dall’inizio di scrittura di tale articolo, WiringPi è passato alla versione 2, talvolta quindi possono verificarsi degli errori durante la compilazione. Questo avviene perché nella V2 il programma può richiedere la compilazione con:

gcc -o prova prova.c -lwiringPiDev -lwiringPi 

Ancora una volta, ci tengo a ricordare che gli stati logici delle porte di Rpi sono pari a 3.3V, quindi in nessun caso va fornita una tensione superiore su uno dei pin utilizzati come ingressi, o Rx dei vari protocolli di comunicazione.

Ulteriori informazioni sulle funzioni disponilbi in WiringPi, le trovate sul sito dello sviluppatore :)

Per concludere
In conclusione, in rete possiamo trovare diverse librerie, alcune più compete ed altre più scarne, WiringPi è un buon compromesso tra comodità e praticità, sopratutto per chi arriva da Arduino, dato che ha a disposizione molte funzioni pressochè identiche, rendendo quindi la programmazione Arduino-Friendly.
Più utilizzo Rpi più rimango soddisfatto dalle potenzialità di questa schedina, poter lavorare e programmare in un ambiente grafico, e subito dopo una compilazione vedere Rpi che interagisce con sensori, relè, lcd…ti fa pensare sulle grandi potenzialità di questa piccola ma grande scheda.

 

Quello che hai appena letto è un Articolo Premium reso disponibile affinché potessi valutare la qualità dei nostri contenuti!

 

Gli Articoli Tecnici Premium sono infatti riservati agli abbonati e vengono raccolti mensilmente nella nostra rivista digitale EOS-Book in PDF, ePub e mobi.
volantino eos-book1
Vorresti accedere a tutti gli altri Articoli Premium e fare il download degli EOS-Book? Allora valuta la possibilità di sottoscrivere un abbonamento a partire da € 2,95!
Scopri di più

13 Comments

  1. supermario87 2 luglio 2013
  2. Giorgio B. Giorgio B. 2 luglio 2013
  3. Giorgio B. Giorgio B. 2 luglio 2013
  4. Piero Boccadoro Piero Boccadoro 2 luglio 2013
  5. Antonello Antonello 3 luglio 2013
  6. Lucasss 4 luglio 2013
  7. davidessss 11 luglio 2013
  8. IvanScordato Ivan Scordato 24 luglio 2013
  9. Piero Boccadoro Piero Boccadoro 24 luglio 2013
  10. IvanScordato Ivan Scordato 24 luglio 2013
  11. davidessss 25 luglio 2013
  12. Piero Boccadoro Piero Boccadoro 9 settembre 2013
  13. Antonio.La Mura Antonio.La Mura 3 marzo 2015

Leave a Reply