Utilizzare l’ IO Expander PCF8574

A volte può ritenersi necessario, quando si è a corto di pin sul proprio microcontrollore, ricorrere a quei circuiti integrati chiamati IO Expander la cui funzione è palese: aumentare il numero di IO a disposizione. Abbiamo visto come è possibile aumentare il numero di uscite (nota: sole uscite) ricorrendo agli shift register (qui e qui). Un IO Expander permette di aggiungere sia ingressi che uscite, un unico IO Expander è in grado di aggiungere fino a 16 IO (es.: il PCA9555), tuttavia in questo articolo mi soffermo unicamente sul PCF8574, IO expander ad 8 bit, che pare essere quello più diffuso.

Il PCF8574 viene pilotato in I2C ed il suo utilizzo, avendo a disposizione delle semplici librerie per gestire la comunicazione I2C è davvero molto semplice. Il PCF8574 esiste in versione con o senza la lettera A finale: l’unica differenza tra i due è l’indirizzo I2C. Gli integrati hanno una parte di indirizzo fissa  più 3 bit variabili, che seguono il livello logico impostato sui pin contrassegnati con A0, A1 e A2:

Per cui, supponendo di impostare A0 e A1 a GND e A2 a +Vcc, l’indirizzo a 7 bit del PCF8574 sarà 0100100, al quale seguirà uno zero se vogliamo accedervi in modalità scrittura o un 1 se in modalità lettura. Gli IO sono i pin contrassegnati da P0 a P7. E’ anche presente un pin di interrupt (INT) a collettore aperto che genera un livello basso quando un qualsiasi pin configurato come ingresso ha una transizione.

Il PCF8574 può funzionare con una tensione di alimentazione da 2.5 a 6V.

Il funzionamento del PCF8574 è abbastanza particolare dal momento che gli IO sono QUASI-bidirezionali. Cosa significa? Quando gli IO vengono utilizzati come uscite, basta inviare unicamente un 1 o uno zero sul bit relativo per impostare l’uscita a livello alto o basso. Quando invece uno o più IO devono essere utilizzati come ingressi è necessario impostare il relativo IO a livello logico alto; un utilizzatore esterno può poi forzare il pin a livello alto o basso e quindi possiamo leggerlo. Il PCF8574 difatti non ha un registro di lettura/scrittura/impostazione dei pin. I pin vengono semplicemente impostati inviando un byte sul bus. Se inviamo, ad esempio, 00001111, impostiamo i pin P7-P4 come uscite a livello basso e i pin P3-P0 come uscite a livello alto ma anche come ingressi: sarà l’utilizzatore posto sui pin che se “legge” il pin, vedrà un livello logico alto mentre se “scrive” il pin lo può forzare a livello alto o basso inviandogli un segnale, senza pericolo di cortocircuiti se invia un livello basso. Sul datasheet difatti si legge chiaramente:

The device features an 8-bit quasi-bidirectional I/O port (P0–P7), including latched outputs with high-current drive capability for directly driving LEDs. Each quasi-bidirectional I/O can be used as an input or output without the use of a data-direction control signal. At power on, the I/Os are high. In this mode, only a current source to VCC is active. An additional strong pullup to VCC allows fast rising edges into heavily loaded outputs. This device turns on when an output is written high and is switched off by the negative edge of SCL. The I/Os should be high before being used as inputs.

Altri IO Expander, come il PCA9555 ad esempio, hanno invece un registro per impostare la direzione dei pin. Nel momento in cui vogliamo utilizzare alcuni IO come ingressi, quindi, dobbiamo ricordarci di impostarli a livello alto e successivamente eseguire la lettura.

Per scrivere sul PCF8574 utilizzeremo quindi la sequenza:

  1. Sequenza di Start
  2. Invio indirizzo PCF8574 in modalità scrittura
  3. Invio valore da impostare sulle 8 uscite
  4. Sequenza di Stop

Per leggere gli IO dal PCF8574, dopo esserci ricordati di impostarli a livello alto:

  1. Sequenza di Start
  2. Invio indirizzo PCF8574 in modalità scrittura
  3. Sequenza di Start ripetuto
  4. Invio indirizzo PCF8574 in modalità lettura
  5. Lettura
  6. Sequenza di stop

Dal valore ottenuto dalla lettura dovremmo ovviamente scartare tutti i bit relativi gli IO che stiamo utilizzando come uscite. La libreria inclusa nel download necessita delle routine I2C che potete scaricare in questa pagina e fa uso esclusivamente di due funzioni:

void ioexp_write(unsigned char address, unsigned char value);
unsigned char ioexp_read(unsigned char address);

La prima funzione accetta come primo parametro l’indirizzo a 7 bit del PCF8574 e come secondo il valore da assegnare agli IO. La seconda funzione esegue la lettura degli IO e la restituisce in formato unsigned char. Va sempre passato l’indirizzo a 7 bit dell’ IO expander.

Per poter utilizzare questa libreria, nel main dovete includere le librerie I2C di settorezero e le librerie pcf8574:

#include "i2c.c"
#include "ioexp.c"

Dopodichè vi conviene definire con una costante l’indirizzo del vostro PCF8574, esempio:

#define IOEXP_ADDR 0x20	// indirizzo a 7 bit del PCF8574 con A0,A1 e A2 collegati a GND

Ricordatevi quindi di inizializzare il modulo I2C nel main:

I2cInitMaster(); // avvio il modulo MSSP in modalità I2C master

Dopodichè qui ci sono alcuni esempi di utilizzo:

Impostazione P7-P0 come uscite a livello basso

ioexp_write(IOEXP_ADDR, 0b00000000);

Impostazione P7-P4 come uscite a livello basso e P3-P0 come uscite a livello alto/ingressi

ioexp_write(IOEXP_ADDR, 0b00001111);

Lettura di P0 e P1 impostati come ingressi

ioexp_write(IOEXP_ADDR, 0b00000011); // imposto P0 e P1 come ingressi
unsigned char value; // questa variabile conterrà il valore letto dall' IO expander
value=ioexp_read(IOEXP_ADDR);
value &= 0b00000011; // azzero i bit P7-P2 per sicurezza. Ora value contiene solo il valore di P0+P1

Nel caso in cui la scrittura potrebbe influenzare lo stato di altre uscite che non vogliamo toccare, è bene utilizzare una variabile che contenga lo stato degli IO ed una maschera. Ad esempio:

unsigned char iovalue=0b00110011; // P7,P6,P3 e P2 come uscite a livello basso, P5,P4,P1,P0 come uscite a livello alto o ingressi
ioexp_write(IOEXP_ADDR, iovalue);
// a questo punto voglio utilizzare anche P7 e P6 come ingressi ma non voglio spostare gli altri IO:
unsigned char newvalue = iovalue | (0b11000000); // newvalue ora vale 0b11110011
ioexp_write(IOEXP_ADDR, newvalue);
unsigned char value;
value=ioexp_read(IOEXP_ADDR); // leggo lo stato delle porte del PCF8574
value &= 0b11000000; // value ora contiene il valore di P6+P7
ioexp_write(IOEXP_ADDR, iovalue); // riporto gli IO com'erano prima

In tutto questo ci dobbiamo assicurare che gli utilizzatori posti sulle porte configurati come ingressi (uscite a livello alto) poi non corrano il rischio di mandare tutto in corto circuito se impostiamo quegli IO come uscite a livello basso.

Download

Attenzione: queste routine fanno uso delle vecchie routine I2C presentate qui, e che oggi, su MPLAB X IDE e compilatore XC, non vanno più bene (a patto di piccole modifiche). Ad ogni modo ho scritto un nuovo articolo in cui illustro come utilizzare le nuove routine I2C create dall’MPLAB Code Configurator e dovrebbe essere molto semplice adattare queste funzioni con le nuove routine.

Links utili

Se questo articolo ti è piaciuto, condividilo su un social:
Se l'articolo ti è piaciuto o ti è stato utile, potresti dedicare un minuto a leggere questa pagina, dove ho elencato alcune cose che potrebbero farmi contento? Grazie :)