Edoardo Vignali
Tutorial GPIO port expander e Arduino
Tutorials -
Lunedì 26 Aprile 2010 10:48
Scritto da Edoardo Vignali

Cos'è un GPIO Port Expander?


Un Gpio port expander, per esteso general purpose input output port expander ovvero espansore di port input output per uso generico, è un dispositivo che ci permette di aumentare il numero di pin di I/O indirizzabili da un microcontrollore utilizzando un protocollo I2C. I GPIO di solito hanno dei port di 8bit al loro interno e vengono venduti in formati che permettono di espandere il port di 8bit, 16bit , 24bit ecc ecc fino ad arrivare a 64bit.
I port expander I2C più utilizzati sono quelli dell NXP, ne troverete un elenco in questa pagina.
port expander generico
In figura vediamo l'esempio di un port expander generico a 8 bit, che in questo caso è collegato ad altri dispositivi. Infatti abbiamo in figura un sensore di temperatura può alzare un bit quando si supera una soglia critica, lo stesso vale per il circuito di carica/scarica di una batteria e così via dicendo.

Come funziona un Port Expander?

Mi è capitato di utilizzare in un mio progetto il PCA9555 e prenderò questo come esempio dilungandomi però su alcune parti non comuni in tutti i port expander. La prima cosa che appare quando apriamo il datasheet è il diagramma a blocchi in figura :

pca9555 schema a blocchi
Il blocco centrale, ovvero I2C-Bus/SMBus Control è il "cervello" del nostro integrato. Sulla sinistra possiamo vedere 3 bit A0, A1, A2, i quali serviranno per definire l'indirizzo dell'integraro. In figura il primo byte da trasmettere al PCA9555 e in generale ai port expander della NXP.

indirizzo pca

SDA e SCL sono gli ingressi del bus I2C ed entrano in un filtro prima di arrivare al nostro blocco centrale, questo servirà a ripulire il segnale dal rumore. VDD è la tensione di alimentazione che può variare dai 2.3 ai 5.5 V, il blocco a cui è collegata serve a far capire che quando il chip viene alimentato avremo un Reset, quindi tutte le informazioni scritte in un precedente uso andranno perse. Sulla detra vediamo  i 2 blocchi del I/O port collegati all'INT, ovvero il bit attivo basso che gestisce gli interrupt dei port. Quando su INT avremo uno 0 i port saranno disabilitati.

Il Command Byte


Il PCA9555 come tutti gli altri port expander dopo il byte contenente lo slave address e il bit R/W posto a zero, si aspetta un ack e dopo il command byte. Questo byte è importantissimo perchè deciderà la funzione dei port, in figura vediamo il codice da inviare (naturalmente in esadecimale) con il relativo utilizzo.


tabella command byte


Se il command register è impostato a 0, il port0 è un'ingresso. Se invece il command register è settato a 1, il port1 è settato come ingresso.
Il valore X varia a seconda del valore di tensione applicato al pin, sarà 0 se posto a GND oppure 1 se posto a VDD. Nel pca9555 in particolare quando non sono applicati segnali in ingresso il valore sarà sempre 1 a causa delle resistenze di pull up sui port. In generale il pca9555 è preferibile utilizzarlo quando si devono acquisire segnali da tanti interruttori.
port01
Su questo registro scriverete i valori che verranno riflessi dai pin del port, impostati come uscite tramite i registri 6 e 7. i bit in questo registro non avranno effetto sui pin del port se questi sono stati in precedenza definiti come ingressi. Viceversa se andremo a leggere un pin settato come uscita, prenderemo il valore che si trova nel flip-flop di controllo, non quello che si trova effettivamente sul pin.
port23
Il registro di polarity inversion interviene solamente quando un port è definito come ingresso. Questo registro trasmesso come command byte farà un not bit a bit degli ingressi del port. Ad esempio se impostiamo il port0 come ingresso e scriviamo 0xAA nel registro 4, leggeremo i segnali di ingresso negati per i pin pari e non negati per i bit dispari. In parole povere con questo registro si "mettono" delle porte not davanti ai pin del gpio quando un dato port è settato come ingresso.
port45
Questo registro imposta i vari pin del port in ingresso o in uscita. Se in un bit scriviamo 1 il corrispondente pin del port è abilitato come ingresso ad alta impedenza. Se invece il bit sarà posto a 0 il rispettivo pin del port sarà abilitato come uscita. Al reset (e quindi di default) i pin del port sono abilitati come ingressi con una resistenza di pull-up connessa a VDD.
port67

Istruzioni Arduino

Ho modificato un paio di istruzioni che ho trovato sulla rete rendendole molto più efficaci, in particolare queste istruzioni serviranno a settare il registro Dir, scrivere determinati valori sugli output, leggere bit sui port.

Per prima cosa ho definito nel mio codice l'oggetto COMMAND REGISTER in questo modo

  • byte COMMAND_REGISTER[]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};

in modo che sia un array contenente tutti gli indirizzi dei registri.

Settare il registro Dir

//funzione che setta il chip di indirizzo address nel modo dir (dir è il control register)
//funzione che setta il chip di indirizzo address nel modo dir (dir è il control register)

void gpio_dir(int address, int dir, int port) {
switch(port){
case 0:
// Trasmetto il registro di configurazione per il port 0
Wire.beginTransmission(address);
Wire.send(COMMAND_REGISTER [6]);
//  Mi connetto alla periferica e mando 2 byte contenenti la direzione
Wire.send(0xff & dir); //  low byte
Wire.send(dir >> 8); //  high byte
Wire.endTransmission();
break;
case 1:
//  Trasmetto il registro di configurazione per il port 1
Wire.beginTransmission(address);
Wire.send(COMMAND_REGISTER [7]);
//  Mi connetto alla periferica e mando 2 byte contenenti la direzione
Wire.send(0xff & dir); //  low byte
Wire.send(dir >> 8); //  high byte
Wire.endTransmission();
break;
}
}

 

Scrivere Dati su un Port

void gpio_write(int address, int data, int port) {
switch(port){
case 0:
//  Trasmetto l'indirizzo del Output Port0
Wire.beginTransmission(address);
Wire.send(COMMAND_REGISTER[2]);
//  Mi connetto alla periferica e mando 2 byte
Wire.send(0xff & data); //  low byte
Wire.send(data >> 8); //  high byte
Wire.endTransmission();
break;
case 1:
//  Trasmetto l'indirizzo del Output Port0
Wire.beginTransmission(address);
Wire.send(COMMAND_REGISTER[3]);
//  Mi connetto alla periferica e mando 2 byte
Wire.send(0xff & data); //  low byte
Wire.send(data >> 8); //  high byte
Wire.endTransmission();
break;
}
}

Quello che fanno queste funzioni è illustrato in questa foto:
dir e output

Leggere Dati su un port

 

Saranno letti dei bit a scelta sui pin che precedentemente settati come ingressi con la funzione gpio_dir

byte gpio_read(int address, int port) {
switch(port){
case 0:
int data = 0;
//  Trasmetto l'indirizzo dell'input register del port0
Wire.beginTransmission(address);
Wire.send(COMMAND_REGISTER[0]);
Wire.endTransmission();
// mi connetto al gpio e richiedo 2 byte
Wire.beginTransmission(address);
Wire.requestFrom(address, 2);
if (Wire.available()) {
data = Wire.receive();
}
if (Wire.available()) {
data |= Wire.receive() << 8;
}
Wire.endTransmission();
return data;
break;
case 1:
//  Trasmetto l'indirizzo dell'input register del port1
Wire.beginTransmission(address);
Wire.send(COMMAND_REGISTER[1]);
Wire.endTransmission();
//  mi connetto al gpio e richiedo 2 byte
Wire.beginTransmission(address);
Wire.requestFrom(address, 2);
if (Wire.available()) {
data = Wire.receive();
}
if (Wire.available()) {
data |= Wire.receive() << 8;
}
Wire.endTransmission();
return data;
break;
}
}

Quello che fa questa istruzione si capisce bene in questa foto:
leggere bit sui pin

Immagini tratte dal datasheet dell'integrato per gentile concessione della Philips.

CC

Tutorial GPIO port expander e Arduino PDF Stampa E-mail
Scritto da Calamaro
Lunedì 26 Aprile 2010 09:48

Tutorial per interfacciare GPIO Port Expander I2C e Arduino

Cos'è un GPIO Port Expander?

Un Gpio port expander, per esteso general purpose input output port expander ovvero espansore di port input output per uso generico, è un dispositivo che ci permette di aumentare il numero di pin di I/O indirizzabili da un microcontrollore utilizzando un protocollo I2C. I GPIO di solito hanno dei port di 8bit al loro interno e vengono venduti in formati che permettono di espandere il port di 8bit, 16bit , 24bit ecc ecc fino ad arrivare a 64bit.

I port expander I2C più utilizzati sono quelli dell NXP, ne troverete un elenco in questa pagina.

pca

In figura vediamo l'esempio di un port expander generico a 8 bit, che in questo caso è collegato ad altri dispositivi. Infatti abbiamo in figura un sensore di temperatura può alzare un bit quando si supera una soglia critica, lo stesso vale per il circuito di carica/scarica di una batteria e così via dicendo.

Come funziona un Port Expander?

Mi è capitato di utilizzare in un mio progetto il PCA9555 e prenderò questo come esempio dilungandomi però su alcune parti non comuni in tutti i port expander. La prima cosa che appare quando apriamo il datasheet è il diagramma a blocchi in figura :

schema a blocchi

Il blocco centrale, ovvero I2C-Bus/SMBus Control è il "cervello" del nostro integrato. Sulla sinistra possiamo vedere 3 bit A0, A1, A2, i quali serviranno per definire l'indirizzo dell'integraro. In figura il primo byte da trasmettere al PCA9555 e in generale ai port expander della NXP.

indirizzo pca

SDA e SCL sono gli ingressi del bus I2C ed entrano in un filtro prima di arrivare al nostro blocco centrale, questo servirà a ripulire il segnale dal rumore. VDD è la tensione di alimentazione che può variare dai 2.3 ai 5.5 V, il blocco a cui è collegata serve a far capire che quando il chip viene alimentato avremo un Reset, quindi tutte le informazioni scritte in un precedente uso andranno perse. Sulla detra vediamo  i 2 blocchi del I/O port collegati all'INT, ovvero il bit attivo basso che gestisce gli interrupt dei port. Quando su INT avremo uno 0 i port saranno disabilitati.


Il Command Byte

Il PCA9555 come tutti gli altri port expander dopo il byte contenente lo slave address e il bit R/W posto a zero, si aspetta un ack e dopo il command byte. Questo byte è importantissimo perchè deciderà la funzione dei port, in figura vediamo il codice da inviare (naturalmente in esadecimale) con il relativo utilizzo.

tabella command byte

Se il command register è impostato a 0, il port0 è un'ingresso. Se invece il command register è settato a 1, il port1 è settato come ingresso.

Il valore X varia a seconda del valore di tensione applicato al pin, sarà 0 se posto a GND oppure 1 se posto a VDD. Nel pca9555 in particolare quando non sono applicati segnali in ingresso il valore sarà sempre 1 a causa delle resistenze di pull up sui port. In generale il pca9555 è preferibile utilizzarlo quando si devono acquisire segnali da tanti interruttori.

port01

Su questo registro scriverete i valori che verranno riflessi dai pin del port, impostati come uscite tramite i registri 6 e 7. i bit in questo registro non avranno effetto sui pin del port se questi sono stati in precedenza definiti come ingressi. Viceversa se andremo a leggere un pin settato come uscita, prenderemo il valore che si trova nel flip-flop di controllo, non quello che si trova effettivamente sul pin.

output  port

Il registro di polarity inversion interviene solamente quando un port è definito come ingresso. Questo registro trasmesso come command byte farà un not bit a bit degli ingressi del port. Ad esempio se impostiamo il port0 come ingresso e scriviamo 0xAA nel registro 4, leggeremo i segnali di ingresso negati per i pin pari e non negati per i bit dispari. In parole povere con questo registro si "mettono" delle porte not davanti ai pin del gpio quando un dato port è settato come ingresso.

polarity inversion

Questo registro imposta i vari pin del port in ingresso o in uscita. Se in un bit scriviamo 1 il corrispondente pin del port è abilitato come ingresso ad alta impedenza. Se invece il bit sarà posto a 0 il rispettivo pin del port sarà abilitato come uscita. Al reset (e quindi di default) i pin del port sono abilitati come ingressi con una resistenza di pull-up connessa a VDD.

config  port

Istruzioni Arduino

Ho modificato un paio di istruzioni che ho trovato sulla rete rendendole molto più efficaci, in particolare queste istruzioni serviranno a settare il registro Dir, scrivere determinati valori sugli output, leggere bit sui port.

Per prima cosa ho definito nel mio codice l'oggetto COMMAND REGISTER in questo modo

byte COMMAND_REGISTER[]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};

in modo che sia un array contenente tutti gli indirizzi dei registri.

  • Settare il registro Dir
  1. //funzione che setta il chip di indirizzo address nel modo dir (dir è il control register)
  2. void gpio_dir(int address, int dir, int port) {
  3. switch(port){
  4. case 0:
  5. // Trasmetto il registro di configurazione per il port 0
  6. Wire.beginTransmission(address);
  7. Wire.send(COMMAND_REGISTER [6]);
  8. //  Mi connetto alla periferica e mando 2 byte contenenti la direzione
  9. Wire.send(0xff & dir); //  low byte
  10. Wire.send(dir >> 8); //  high byte
  11. Wire.endTransmission();
  12. break;
  13. case 1:
  14. //  Trasmetto il registro di configurazione per il port 1
  15. Wire.beginTransmission(address);
  16. Wire.send(COMMAND_REGISTER [7]);
  17. //  Mi connetto alla periferica e mando 2 byte contenenti la direzione
  18. Wire.send(0xff & dir); //  low byte
  19. Wire.send(dir >> 8); //  high byte
  20. Wire.endTransmission();
  21. break;
  22. }
  23. }
  • Scrivere Dati su un Port

  1. void gpio_write(int address, int data, int port) {
  2. switch(port){
  3. case 0:
  4. //  Trasmetto l'indirizzo del Output Port0
  5. Wire.beginTransmission(address);
  6. Wire.send(COMMAND_REGISTER[2]);
  7. //  Mi connetto alla periferica e mando 2 byte
  8. Wire.send(0xff & data); //  low byte
  9. Wire.send(data >> 8); //  high byte
  10. Wire.endTransmission();
  11. break;
  12. case 1:
  13. //  Trasmetto l'indirizzo del Output Port0
  14. Wire.beginTransmission(address);
  15. Wire.send(COMMAND_REGISTER[3]);
  16. //  Mi connetto alla periferica e mando 2 byte
  17. Wire.send(0xff & data); //  low byte
  18. Wire.send(data >> 8); //  high byte
  19. Wire.endTransmission();
  20. break;
  21. }
  22. }

Quello che fanno queste funzioni è illustrato in questa foto:

dir e  output

  • Leggere Dati su un port a scelta sui bit che sono stati settati come ingressi con la funzione gpio_dir

  1. byte gpio_read(int address, int port) {
  2. switch(port){
  3. case 0:
  4. int data = 0;
  5. //  Trasmetto l'indirizzo dell'input register del port0
  6. Wire.beginTransmission(address);
  7. Wire.send(COMMAND_REGISTER[0]);
  8. Wire.endTransmission();
  9. // mi connetto al gpio e richiedo 2 byte
  10. Wire.beginTransmission(address);
  11. Wire.requestFrom(address, 2);
  12. if (Wire.available()) {
  13. data = Wire.receive();
  14. }
  15. if (Wire.available()) {
  16. data |= Wire.receive() << 8;
  17. }
  18. Wire.endTransmission();
  19. return data;
  20. break;
  21. case 1:
  22. //  Trasmetto l'indirizzo dell'input register del port1
  23. Wire.beginTransmission(address);
  24. Wire.send(COMMAND_REGISTER[1]);
  25. Wire.endTransmission();
  26. //  mi connetto al gpio e richiedo 2 byte
  27. Wire.beginTransmission(address);
  28. Wire.requestFrom(address, 2);
  29. if (Wire.available()) {
  30. data = Wire.receive();
  31. }
  32. if (Wire.available()) {
  33. data |= Wire.receive() << 8;
  34. }
  35. Wire.endTransmission();
  36. return data;
  37. break;
  38. }
  39. }

Quello che fa questa istruzione si capisce bene in questa foto:

leggere


 

Gioblu Robotics © 2010 - 2012 · Sitemap · privacy

gioscarab@gmail.com

Gioblu BOTServer è online dal 10 Aprile 2010 - 319.232 Visite - 1.027.175 Pagine visualizzate - 182.309 Visitatori unici - 536 utenti attivi