Moltiplicare le linee I/O dei micro con i Port Expander

Quando si affronta la progettazione di un sistema embedded, uno dei limiti è quello di non riuscire a prevedere in maniera precisa il numero di pin del micro che saranno necessari. Ecco come risolvere questo problema ricorrendo all’uso dei port expander.

Nella progettazione di sistemi embedded un vincolo importante da considerare è il numero di pin disponibili del sistema a microcontrollore scelto. Spesso il fallimento di un progetto può dipendere proprio dalla scelta errata del modello di micro, dotato di un numero di pin inferiore rispetto a quello necessario a pilotare tutte le periferiche. D’altro canto anche una pianificazione attenta e l’adozione di margini di sicurezza non sempre permettono di ottenere il risultato migliore. La soluzione più corretta in questi casi è quindi quella di selezionare il micro sulla base delle altre specifiche (come consumo di potenza, interfacce, capacità di elaborazione) e poi aumentare il numero di linee mediante dispositivi di espansione, detti: port expander (PE). I PE consentono di disporre di un numero di pin molto elevato a fronte di un occupazione di soli 2 o 3 pin del micro. Si tratta di una particolare classe di dispositivi, generalmente di tipo slave, collegati su un unico bus  di  comunicazione  seriale. Il micro,  tramite 2 interfaccia I2C o SPI, seleziona la linea desiderata ed effettua l’operazione di lettura o scrittura. La Figura 1 mostra una tipica configurazione del bus con i port expander.

Figura 1. I port expander garantiscono una grande flessibilità in fase di progettazione, poiché consentono di aggiungere un numero elevato di linee al microcontrollore. Essi sono dispositivi slave che si interfacciano con il master (micro) su di un bus di tipo I2C.

Figura 1. I port expander garantiscono una grande flessibilità in fase di progettazione, poiché consentono di aggiungere un numero elevato di linee al microcontrollore. Essi sono dispositivi slave che si interfacciano con il master (micro) su di un bus di tipo I2C.

L’indirizzo di ciascuno slave è 7 determinato da 7 bit, ossia 128 dispositivi (= 27); in realtà nella maggior parte dei casi il programmatore potrà selezionare solo un sotto gruppo di tali 7 bit. Ad esempio, nel caso del port expander MCP23008 della Microchip (Figura 2) solo tre bit saranno selezionabili tramite i pin A2, A1 e A0; questo  consente  di  collegare sul bus  fino  a 8 moduli ( = 23).

Figura 2. L’indirizzo del dispositivo è selezionato tramite i relativi pin presenti sul chip

Figura 2. L’indirizzo del dispositivo è selezionato tramite i relativi pin presenti sul chip

Considerato che il chip fornisce 8 linee di ingresso/uscita, si avranno ben 64 linee complessivamente che potranno essere aggiunge al proprio progetto. Tutto questo impegnando solo 2 le due linee I2C del micro! Oltre al numero di linee del PE, che rappresenta la caratteristica fondamentale, esistono delle varianti che consentono di gestire uscite con PWM. Queste possono essere utili per modificare la luminosità dei LED o per il controllo di motori brushless DC. Un altro aspetto da valutare nella scelta di un port expander è la sua modalità di configurazione. I modelli più avanzati dispongono di una memoria non volatile che consente di salvare la configurazione dei pin (direzione, stato dell’uscita). Nella maggior parte dei casi, comunque, tale setup deve essere effettuato ogni volta all’accensione del dispositivo. Quando si programma un micro, una delle caratteristiche molto sfruttate sono i pin di interrupt, che consento di gestire eventi esterni asincroni; moti PE forniscono delle linee con tale funzione, permettendo di moltiplicare quelle già presenti di default nel micro.

LA  PROGETTAZIONE CON UN I/O  EXPANDER

Come utilizzarli?

Dopo aver descritto le caratteristiche dei port expander ed evidenziato i vantaggi offerti nella progettazione, si esamineranno due casi pratici. Si vedrà in particolare come interfacciarli con un microcontrollore (nel caso specifico un PIC) e come scrivere il relativo codice (sarà presentato un esempio in CCS e in MikroC).

MCP23008 con I2C

L’MCP23008 è uno dei port expander proposti da 2 Microchip,  dotato  di  interfaccia  I C.  Il  relativo pinout è mostrato in Figura 3.

Figura 3. MCP23008 è uno modelli di port expander proposti da Microchip, con 8 linee di uscita e 3 di indirizzamento. Per l’interfacciamento con il micro è utilizzala la I2C

Figura 3. MCP23008 è uno modelli di port expander proposti da Microchip, con 8 linee di uscita e 3 di indirizzamento. Per l’interfacciamento con il micro è utilizzala la I2C

Come si nota i pin A2, A1 e A0 consento di selezionare l’indirizzo del chip sul bus, portandoli ad “1” o a “0”. La presenza del pin INT consente di programmare un interrupt su un determinato evento di ciascuna linea. Tali eventi possono essere:

  • Variazione di una qualunque linea, rispetto  al valore precedente;
  • Variazione di una qualunque linea, rispetto al valore di default (impostato nel registro DEFVAL).

Infine, con GPx sono indicate le porte di espansione, configurabili sia come ingressi che come uscite. In Figura 4 è mostrato lo schema a blocchi del dispositivo, in cui sono riportati anche i registri di configurazione.

Figura 1. I port expander garantiscono una grande flessibilità in fase di progettazione, poiché consentono di aggiungere un numero elevato di linee al microcontrollore. Essi sono dispositivi slave che si interfacciano con il master (micro) su di un bus di tipo I2C.

Figura 1. I port expander garantiscono una grande flessibilità in fase di progettazione, poiché consentono di aggiungere un numero elevato di linee al microcontrollore. Essi sono dispositivi slave che si interfacciano con il master (micro) su di un bus di tipo I2C.

Figura 4. Schema a blocchi dell’MCP23008

Figura 4. Schema a blocchi dell’MCP23008

Essi consento di impostare il comportamento  del port expander. La lista completa dei relativi indirizzi è riportata in Tabella 1.

Tabella 1. Il funzionamento dell’MCP23008 è deciso tramite i registri di configurazione

Tabella 1. Il funzionamento dell’MCP23008 è deciso tramite i registri di configurazione

Per impostare la direzione delle linee di uscita si utilizza il registro IODIR, mentre per leggere o scrivere la linea è utilizzato il registro GPIO. Se si vuole, ad esempio, che tutte le porte siano configurate come output e che le prime quattro linee (GP0…GP3) siano a “0”  e restanti ad “1” (GP4…GP7), allora si scriverà quanto segue:

  • IODIR = 00h
  • GPIO = F0h

Per il controllo del dispositivo è necessario seguire il protocollo riportato nel datasheet. Per semplicità nel Listato 1 sono riportate le funzioni a basso livello per il controllo di tale dispositivo.

1. // Byte di controllo
2. #define CTRL_BYTE_W 0b01000000
3. #define CTRL_BYTE_R 0b01000001
4. // Linee di uscita
5. #define GP7 0x07
6. #define GP6 0x06
7. #define GP5 0x05
8. #define GP4 0x04
9. #define GP3 0x03
10. #define GP2 0x02
11. #define GP1 0x01
12. #define GP0 0x00
// —————————————————————————————-
// La funzione effettua la lettura di
// un registro del MCP23008
// input: ind_reg -> registro da
// leggere
// output: valore letto
// —————————————————————————————-
18. int i2c_lettura(int ind_reg){
19. unsigned int temp_dato;
20. i2c_start();
21. i2c_write(CTRL_BYTE_W);
22. i2c_write(ind_reg);
23. i2c_start();
24. i2c_write(CTRL_BYTE_R);
25. temp_dato = i2c_read(0);
26. i2c_stop();
27. return temp_dato;
28. }
// —————————————————————————————-
// La funzione effettua la scrittura
// di un registro del MCP23008
// input: ind_reg -> registro da
// scrivere
// input: dato -> valore da
// scrivere
// —————————————————————————————-
34. void i2c_scrittura(int ind_reg,
int dato){
35. i2c_start();
36. i2c_write(CTRL_BYTE_W);
37. i2c_write(ind_reg);
38. i2c_write(dato);
39. i2c_stop();
40. delay_ms(50);
41. }
// —————————————————————————————-
// La procedura restituisce il valore
// di tutti i registri
// —————————————————————————————-
45. void i2c_mostra_reg(void){
46. int i,dato;
47. for(i=0;i<0x0B;i++){
48. dato = i2c_lettura(i);
49. printf(“\n\rRegistro %i:
%u”,i,dato);
50. delay_ms(100);
51. }
52. }
Listato 1

È stato utilizzato il compilatore CCS versione 3.249 e il microcontrollore PIC (qualsiasi modello dotato di interfaccia I2C è perfetto). Queste funzioni sono:

  • i2c_lettura(). Passando come parametro di input l’indirizzo di uno dei registri riportati in Tabella 1, restituisce il valore corrispondente.
  • i2c_scrittura(). Effettua la scrittura di un registro passando come parametri di input, rispettivamente, l’indirizzo del  registro  ed  il  valore  da assegnarli.
  • i2c_mostra_reg(). Può essere utilizzata in fase di debug per visualizzare lo stato di tutti i registri tramite interfaccia seriale, configurata a 9600 baud.

Le righe 2 e 3 del Listato 1 riportano le define del codice che serve per eseguire l’operazione di lettura e scrittura nel caso di un solo port expander. L’indirizzo utilizzato è infatti A2=0, A1=0 e A0=0 (secondo quanto riportato nella Figura 2); qualora si volesse collegare un secondo dispositivo sul bus è necessario utilizzare un indirizzo diverso dal precedente, per esempio A2=0, A1=0 e A0=1. Sulla base delle suddette funzioni se ne possono facilmente costruire altre di alto livello, come quelle riportate nel Listato 2:

  • MCP23008_output_high(). È la funzione equivalente ad output_high() del compilatore CCS. Passando come parametro di input una delle linee dell’MCP23008, la linea sarà configurata come uscita al valore logico alto (ad esempio, MCP23008_output_high(0) porta la linea GP0 al valore “1”).
  • MCP23008_output_low(). È la funzione equivalente ad output_low() del compilatore CCS. Passando come parametro di input una delle linee dell’MCP23008, la linea sarà configurata come uscita al valore logico basso (ad esempio, MCP23008_output_high(6) porta la linea GP6 al valore “0”).
  • MCP23008_input_state(). È la funzione  corrispondente  a input_state() del CCS. Passando come paramentro di ingresso uno dei pin dell’MCP23008,  sarà restituito  lo stato  attuale del pin stesso.
// —————————————————————————————-
// La funzione porta al valore alto
// una delle linee di uscita
// input: pin -> pin da asserire
// (PIN_GPx con x = 0...7)
// —————————————————————————————-
5. void MCP23008_output_high(int pin){
6. int _iodir, _gpio;
7. int mask = 0;
8. _iodir = i2c_lettura(IODIR);
9. _gpio = i2c_lettura(GPIO);
10. // Configura il pin come uscita
11. mask = ~(1 << pin);
12. _iodir = _iodir & mask;
13. i2c_scrittura(IODIR,_iodir);
14. // Configura lo stato 1
15. mask = 1 << pin;
16. _gpio = _gpio | mask;
17. i2c_scrittura(GPIO,_gpio);
18. }
// —————————————————————————————-
// La funzione porta al valore basso
// una delle linee di uscita
// input: pin -> pin da asserire
// (PIN_GPx con x = 0...7)
// —————————————————————————————-
23. void MCP23008_output_low(int pin){
24. int _iodir, _gpio;
25. int mask = 0;
26. _iodir = i2c_lettura(IODIR);
27. _gpio = i2c_lettura(GPIO);
28. // Configura il pin come uscita
29. mask = 1 << pin;
30. _iodir = _iodir | mask;
31. i2c_scrittura(IODIR,_iodir);
32. // Configura lo stato 0
33. mask = ~(1 << pin);
34. _gpio = _gpio & mask;
35. i2c_scrittura(GPIO,_gpio);
36. }
// —————————————————————————————-
// La funzione legge una delle linee
// di ingresso
// input: pin -> pin da asserire
// (PIN_GPx con x = 0...7)
// output: stato della linea
// —————————————————————————————-
42. int MCP23008_input_state(int pin){
43. int _iodir, _gpio;
44. int mask = 0;
45. _iodir = i2c_lettura(IODIR);
46. // Configura il pin come ingresso
47. mask = 1 << pin;
48. _iodir = _iodir | mask;
49. i2c_scrittura(IODIR,_iodir);
50. // Leggo lo stato
51. _gpio = i2c_lettura(GPIO);
52. _gpio = _gpio & mask;
53. return (_gpio >> pin);
54. }
Listato 2

Inserendo  queste  funzioni  all’interno  del  proprio codice è possibile utilizzare le linee del PE come fossero quelle del microcontrollore. Anche se le funzioni proposte sono state scritte per un particolare tipo di micro e con uno specifico compilatore, la trattazione non perde di generalità. Il porting del codice su un altro modello e con un altro compilatore non risulta particolarmente complesso. L’importante  è che si disponga del 2 l’interfaccia  I2C.  È anche possibile  utilizzare la scheda di test fornita da Microchip per testare le funzionalità di questo chip.

MCP23S17 CON SPI

L’interfaccia  I2C  non  è  l’unica  comunicazione seriale che è possibile utilizzare con i port expander. Esistono anche modelli interfacciabili con la periferica SPI. Un esempio è rappresentato  dal dispositivo MCP23S17, il quale è dotato di ben 2 porte di comunicazione (PORTA e PORTB), per un totale di 16 linee aggiuntive (vedere Figura 5).

Figura 5. I port expander sono interfacciabili, oltre che tramite I2C, anche con SPI. Il modello MCP23S17 è dotato di ben 16 linee e 3 di indirizzamento

Figura 5. I port expander sono interfacciabili, oltre che tramite I2C, anche con SPI. Il modello MCP23S17 è dotato di ben 16 linee e 3 di indirizzamento

Figura 6. La scheda di test per MCP23S17 proposta da Mikroelektronika

Figura 6. La scheda di test per MCP23S17 proposta da Mikroelektronika

Le linee di indirizzamento sono sempre pari a 3. Questo significa che a fronte di sole 3 linee utilizzate dal micro per controllarlo, si guadagnano ben:

23 (dispositivi indirizzabili)×16 (linee/dispositivo) = 128

Una possibilità per controllare questo chip, oltre che a sviluppare dei driver analoghi a quelli visti in precedenza, è rappresentata dai compilatori MikroC, MikroBasic o MikroPascal. Essi mettono a disposizione una libreria già pronta di funzioni per pilotare l’MCP23S17. Tra le altre si ricordano:

  • Expander_Init, deve essere richiamata prima di tutte le altre funzioni e serve per inizializzare il dispositivo.
  • Expander_Read_PortA, legge le linee della porta A.
  • Expander_Read_PortB, legge  le  linee  della porta B.
  • Expander_Write_PortA, utilizza  le  linee  della porta A come uscita.
  • Expander_Write_PortB, utilizza le linee  della porta B come uscita.
  • Expander_Set_DirectionPortA, imposta la direzione delle linee della porta A (ingresso o uscita).
  • Expander_Set_DirectionPortB, imposta la direzione delle linee della porta B (ingresso o uscita).

Il Listato 3 presenta un semplice esempio di codice C in cui sono utilizzate le funzioni di libreria dell’MCP23S17, usando il compilatore MikroC v6 ed il PIC16F877A. La scheda può essere testata tramite l’add-on fornito da Mikroelektronika e la EasyPIC.

1. // Progetto Port Expander MCP23S17
2. // Autore: S.Giusto
3. // Compilatore: MikroC v6
4. // PIC16F877A
5. void main(){
6. TRISB = 0;
7. TRISD = 0;
8. Spi_Init(); // inizializzazione interfaccia SPI
9. Expander_Init(0, &PORTC, 0, &PORTC, 1); // inizializzazione del port expander
10. Expander_Set_DirectionPortA(0, 0xFF); // configurazione portA come ingresso
11. Expander_Set_PullUpsPortA(0, 0xFF); // configurazione pull-up di portA
12. Expander_Set_DirectionPortB(0, 0xFF); // configurazione portB come ingresso
13. Expander_Set_PullUpsPortB(0, 0xFF); // configurazione pull-up di portB
14. while(1) {
15. PORTB = Expander_Read_PortA(0); // lettura portA e visualizzazione su portB
16. PORTD = Expander_Read_PortB(0); // lettura portB e visualizzazione su portD
17. Delay_100ms();
18. }
19. }
Listato 3

 

CONCLUSIONI

L’utilizzo di un port expander può rivelarsi utile in varie situazioni, soprattutto in fase di progettazione quando il numero di linee non è ancora ben precisato. Esistono tre possibili modi di utilizzare un port expander, per sfruttare al massimo le sue capacità:

  1. Aggiungere un PE ad un progetto per poter eseguire la prototipazione in tempi rapidi, come supporto nelle fasi di test e debug con l’obiettivo di escluderlo poi dal progetto.
  2. Aggiungere un PE ad un progetto senza una ben precisa funzione, per avere sempre un margine di sicurezza in futuro.
  3. Ridurre le esigenze in termini di pin del processore utilizzando i PE laddove possibile.

Quelli presentati in quest’articolo sono solo alcuni dei modelli disponibili; ne esistono di varie tipologie, per gli usi più disparati. Il loro funzionamento, tuttavia, si discosta di poco rispetto a quello descritto in questo articolo.

 

 

Scrivi un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *