Corso programmazione PICMicro in C – Approfondimenti – Comandare 16 led con 3 I/O utilizzando un 74HC595

In una lezione precedente abbiamo visto cosa sono i SIPO (Serial In Parallel Out) e come si utilizzano, sappiamo quindi bene cos’è e come funziona uno shift register. In questa lezione utilizzeremo un 74HC595 per accendere 16 led utilizzando solo 3 I/O del picmicro.

I concetti che esporrò possono essere usati anche con altri microcontrollori. Difatti sulla schedina che presento ho scelto di montare nessuna mcu.

Il SIPO in questione ha 8 uscite quindi per comandare 16 led lavoreremo in multiplex per cui questa applicazione è utilizzabile unicamente con i led, non pensate assolutamente di collegare 16 relè a questo circuito! Se non sapete cos’è il multiplex leggete questo nostro articolo.

Bando alle ciance, passiamo subito a dare uno sguardo al circuito:

Matrice con 16 led e 74HC595. Clicca per ingrandire

Il 74HC595 ha 8 uscite, ne useremo 4 per pilotare le righe e 4 per pilotare le colonne tramite transistor di tipo BC337 o NPN equivalenti (basta che siano in grado di sopportare la corrente di 4 led). Come abbiamo già visto nella lezione precedente sui SIPO, abbiamo una linea dati e una linea clock. In più su questo integrato, rispetto al 74LS164, abbiamo un segnale di latch. Cosa significa?

Sul 74LS164 i dati li ritroviamo sulle uscite man mano che vengono inviati. Il 74HC595 è dotato di un latch: i dati vengono memorizzati prima tutti nello shift register, dopodichè abilitando il pin di latch (in realtà si tratta più propriamente di un clock) i dati vengono trasferiti appunto nel latch e solo ora li ritroviamo tutti contemporaneamente sulle uscite (se le uscite sono abilitate). Questo funzionamento è molto comodo in quanto non ci crea fluttuazioni sulle uscite del SIPO e lavorando in multiplex non è necessario adottare tecniche di programmazione per evitare l’effetto ghosting.

Funzionamento del 74HC595

I dati vengono trasferiti nello shift register del 74HC595 attraverso i pin  N°14 (Serial data in) e 11 (Shift register Clock). Una volta finito l’invio dei dati, basta portare a livello alto il pin 12 (Storage Clock o Latch Clock) per trasferire i dati dallo shift register al latch. Il pin 13 (Output Enable – attivo basso) consente il trasferimento dei dati dal latch alle porte di uscita quando viene portato a livello basso: avendo noi collegato tale pin fisso a massa, i dati li ritroveremo sulle uscite non appena diamo il colpo di clock sul latch. Il pin 10 è il master reset: ponendolo a livello basso resetta il contenuto dello shift register, per cui lo teniamo alto per sicurezza. Il pin 9 lo teniamo scollegato in quanto non viene utilizzato in questa applicazione ma è comodo qualora volessimo pilotare matrici di led più grandi dato che serve per trasferire i dati in cascata verso altri 74HC595.

Nelle applicazioni dove si fa uso di matrici di led più grandi, i 74HC595 vengono collegati in cascata utilizzando appunto l’uscita del primo sipo collegata all’ingresso del secondo e così via. Questa configurazione consente di utilizzare sempre e solo 3 pin: basta difatti mettere in comune lo shift register clock e il latch clock. Il serial data in sarà usato solo per il primo SIPO, che trasferirà tutti i dati in cascata verso gli altri shift register. Dando il colpo di clock sul latch, ogni SIPO avrà il suo dato sulle uscite.

Il dato serializzato viene trasferito durante la transizione del clock da livello alto a livello basso. I bit vengono inviati dal più significativo verso il meno significativo, per cui dobbiamo tenere conto di questo nella routine di serializzazione per fare in modo che i bit si ritrovino sulle uscite nel giusto ordine (o meglio… come abbiamo previsto).

Demoboard con 16 led per 74HC595

Vedete che nella parte sottostante dello schema ho incluso un piccolo circuito di alimentazione. Se avete una demoboard che può fornire i 5V, questa parte non è necessaria. Io in realtà l’ho inclusa perchè mi sono fatto una schedina di sviluppo costituita per metà dal circuito illustrato e per l’altra metà da una zona millefori in quanto devo utilizzare tale circuito per la costruzione  di un giocattolo per mia figlia e quindi monterò nella zona millefori anche il picmicro che deciderò di utilizzare (probabilmente userò un pic della serie 12F).

Vi illustro la scheda realizzata e un contenitore standard che ho cominciato a predisporre per il giochino:

Vedete che c’è un’area millefori grande abbastanza per montare un picmicro e altra circuiteria ma in questo articolo non la userò e faccio riferimento al collegamento di tale scheda con un 16F877A montato sulla Freedom2. Ci sono 6 fori di fissaggio che dovrebbero andar bene per un po’ tutte le scatole. Il master del circuito si può scaricare in fondo all’articolo.

In realtà questa scheda la si potrebbe utilizzare per fare anche altre cose: un orologio binario (come suggerito dal sig. R. Patron), un gioco del tipo “Whac-a-mole“, un gioco di luci per presepe (e siamo pure in periodo!), laddove abbiamo bisogno di molte spie a led e abbiamo pochi I/O a disposizione ecc.

Durante il montaggio bisogna stare molto attenti al giusto orientamento dei led in quanto per fare in modo che il circuito venisse fuori a singola faccia, ho dovuto girare in un certo modo ogni led in maniera che non fosse d’intralcio alle piste.  La serigrafia si può scaricare in fondo all’articolo.

In particolare se utilizzate la scheda come test e cioè per collegarla ad una demoboard, sul connettore CN1 potete montare uno strip maschio da 5 contatti e collegare quindi tra loro le schede utilizzando dei cavetti jumper. Se invece volete montarvi un circuito con un picmicro o altro microcontrollore su tale scheda, il connettore CN1 non va montato e l’alimentazione verrà fornita dall’alimentatore incluso sulla scheda ponticellando come mostrato sulla serigrafia dalle due lineette rosse:

Farete quindi arrivare i segnali data, latch e clock come più ritenete opportuno. Le due file centrali di pad portano l’alimentazione mentre tutti i pad perimetrici sono collegati al segnale di massa… quindi dovrebbe essere abbastanza agevole realizzarsi da sè un giochino personalizzato.

Se lo desiderate, la scheda ve la posso anche realizzare io (ovviamente solo il circuito stampato), mandatemi una email dalla sezione contatti per maggiori informazioni.

Struttura del software

Analizziamo il funzionamento del programma. I led li ho numerati da 0 a 15; per determinare la loro accensione ho predisposto un array composto da 2 variabili di tipo char (che ho chiamato scan[0] e scan[1]). La prima variabile, avente indice 0, controlla i singoli led della prima e della seconda colonna (il nibble basso controlla i led della prima colonna e quello alto controlla i led della seconda). Un’immagine dovrebbe chiarire ogni dubbio:

Abbiamo detto che accenderemo i led in multiplexing: sfrutteremo ovviamente l’interrupt sul Timer0 come già fatto nella lezione precedente sui display in multiplex. Questa volta però la cosa forse è leggermente più complicata perchè dovremo controllare 4 colonne e successivamente elaborare opportunamente i dati per inviare la giusta sequenza di bit al 74HC595: non stiamo pilotando i led direttamente ma tramite un circuito integrato di interfaccia al quale dobbiamo inviare dei dati seriali!

Diamo un’occhio allo schema elettrico: sappiamo che al SIPO  dobbiamo inviare un byte, di questo byte i primi 4 bit controllano le righe e gli ultimi 4  (i bit alti) la colonna da “attivare”. Per cui se vogliamo, ad esempio, accendere il led n°2 (che si trova sulla 3a riga, 1a colonna) il dato da inviare al SIPO sarà:

0001 0100

Il nibble basso (0100 in questo esempio) controlla le righe, mettendo un 1 in terza posizione accendiamo la terza riga. Il nibble alto (0001) controlla la colonna: mettendo a 1 il primo bit del nibble alto (o, in altre parole, il bit 4 dell’intero dato), mandiamo in saturazione il transistor T1 il quale farà arrivare il segnale di massa alla 1a colonna consentendo al led di accendersi.

La routine di multiplex scansionerà una colonna alla volta ogni 4 millisecondi (già a 5 un occhio attento nota lo sfarfallìo), la presenza del latch ci consentirà di non dover eseguire lo spegnimento della colonna prima di passare alla successiva in quanto il dato nuovo è già presente nel latch e abilitando il latch otteniamo automaticamente lo spegnimento della colonna precedente e l’accensione della successiva con i dati giusti (il valore di colonna che si deve accendere è contenuto nel latch insieme a tutto il dato da visualizzare).

Nella routine di multiplex, gestita dall’Interrupt Service Routine, abbiamo quindi 4 cicli (uno per ogni colonna). A seconda del ciclo dobbiamo elaborare opportunamente il dato per poterlo inviare al SIPO in maniera da far accendere i led correttamente.

Abbiamo detto che ogni singolo led ha memorizzato il suo stato (acceso/spento) in una delle due variabili scan[0] / scan[1]. I led delle prime due colonne hanno memorizzato il loro stato in scan[0] e quelli delle colonne 3 e 4 in scan[1].

Per seguire bene i concetti che seguiranno, oltre ad aver letto le due lezioni già menzionate, è necessario capire come funzionano gli operatori logici e quelli di scorrimento. Potete leggere questo e quest’altro articolo.

Analizziamo il primo ciclo di multiplex: quello che  gestisce la prima colonna (cioè  quella più a sinistra e che contiene i led 0,1,2 e 3). Lo stato dei led della prima colonna è contenuto nel nibble basso di scan[0], per cui ci dobbiamo prelevare solo i 4 bit bassi di tale variabile. Lo facciamo semplicemente con un AND ed una maschera che ci servirà a portare a zero il solo nibble alto preservando quello basso. Ovviamente non memorizziamo il risultato nuovamente in scan[0] altrimenti combiniamo un pasticcio, per cui utilizzeremo una variabile di appoggio che ho chiamato tempscan:

tempscan = scan[0] & 0b00001111;

dopodichè dobbiamo comporre il dato da inviare al SIPO. I primi 4 bit del dato da inviare al SIPO identificano le righe da attivare (cioè i singoli led da accendere nella colonna attuale), per cui i nostri 4 bit elaborati in precedenza si trovano già nella posizione corretta. Dobbiamo aggiungere soltanto i 4 bit alti per attivare la colonna. Dovendo attivare la prima colonna , i 4 bit alti devono trovarsi nello stato:

0001

per cui possiamo sommare tale valore con quello di riga e ottenere quindi il dato da inviare al SIPO (che ho chiamato latchOut):

latchOut = 0b00010000 | tempscan;

Per la seconda colonna, invece, per attivare le righe dobbiamo prendere i 4 bit alti di scan[0] e portarli in posizione bassa, per cui facciamo semplicemente uno shift a destra di 4 posizioni:

tempscan=scan[0] >> 4;

dopodichè, dovendo attivare la seconda colonna, i 4 bit alti da inviare al SIPO dovranno trovarsi nello stato:

0010

per cui il dato completo sarà:

latchOut = 0b00100000 | tempscan;

Passo ad evidenziare tutto questo con un disegno:

Le celle colorate in nero rappresentano il valore da fornire per l'accensione della colonna relativa, col bordino verde sono rappresentati i risultati delle operazioni ovvero il dato da passare al SIPO per visualizzare la colonna attualmente scansionata

Quindi la colonna 3 segue lo stesso andamento della 1 ma utilizzando scan[1] al posto di scan[0], la colonna 4 segue lo stesso andamento della 2 ma sempre utilizzando scan[1].

Per rendere le cose più fluide, il valore con cui effettuare l’OR anzichè impostarlo ogni volta, me lo calcolo ad ogni ciclo sfruttando la variabile multiplex_column che tiene appunto conto del numero di ciclo in corso (0,1,2,3 = uno per ogni colonna):

// sposto l'1 nella posizione della colonna desiderata
latchOut=0b00010000 << multiplex_column;
// sommo il valore della colonna da visualizzare
latchOut |= tempscan;

Il dato va inviato dal MSB al LSB ed inoltre abbiamo il segnale di latch, per cui il codice per trasferire il byte al SIPO può essere il seguente: (i simboli DATA, CLOCK e LATCH rappresentano i pin del picmicro relativi ai 3 segnali)

for (shift=0; shift<8; shift++)
    {
    value = latchOut & (0b10000000 >> shift);
    if (value)
        {
        DATA=1;
        }
    else
        {
       DATA=0;
       }
    // la transizione del clock dello shift register da alto a basso
    // trasferisce il dato in ingresso nello shift register
    CLOCK=1;
    CLOCK=0;
    }
// la transizione del clock del latch da alto a basso
// trasferisce tutto il byte dallo shift register al latch
LATCH=1;
LATCH=0;

il bit da trasferire sulla linea seriale me lo calcolo facendo l’AND con una maschera. La maschera la ottengo spostando il valore 0b10000000 verso destra di un numero di posizioni pari alla posizione del bit da trasferire: in questo modo trasferiamo i bit dal più alto (0b10000000) al più basso (0b00000001), proprio come vuole il 74HC595.

Per ogni bit abbiamo quindi la transizione del clock dello shift register, necessaria per memorizzare il dato. Alla fine del trasferimento dobbiamo dare un colpo di clock sul pin del clock latch, in questo modo tutto il dato dallo shift register viene trasferito nel latch e ce lo ritroviamo direttamente sulle uscite in quanto abbiamo messo il pin Output Enable fisso a massa.

Una volta strutturata tutta questa routine, che lavora tramite interrupt ed è quindi indipendente da tutto il resto, potete capire come il vostro programma può essere il più semplice possibile. Nel main, difatti, dovremo solo preoccuparci di impostare quali led devono accendersi modificando i valori delle variabili scan[0] e scan[1].

La routine di multiplex farà quindi tutto il lavoro “sporco” sollevandoci da un bel peso e rendendo la visualizzazione dei led nella maniera più efficiente possibile. Potete anche capire come una cosa del genere sia irrealizzabile tramite polling. Per questo motivo io insisto sempre con l’utilizzo degli interrupt: è l’unico modo a disposizione per avere la massima efficienza nei programmi.

Il programma di esempio l’ho scritto facendo un semplice gioco di luci che potete vedere qui:

Non è niente di eccezionale ma serve solo per far capire come utilizzare i due valori scan[0] e scan[1] per accendere i led. La routine di interrupt in pratica non la dovete modificare e nei vostri programmi, nel main, vi dovete solo preoccupare di quale led accendere magari utilizzando maschere e bitshift sui valori scan[0] e scan[1].

Le porte da utilizzare per il collegamento alla scheda le ho definite in settings.h come mio solito e ovviamente nel rispettivo registro tristato vanno impostate come uscite:

15
16
17
#define DATA	RC1 // linea dati su RC1
#define CLOCK	RC3 // linea clock shift register su RC3
#define LATCH	RC5 // linea clock latch su RC5

Il programma, come già detto, l’ho testato su un 16F877A a 20MHz. Tenete conto che la routine di serializzazione, in queste condizioni, dura circa 88μS e tutta la routine di interrupt sul timer0 nel peggiore dei casi dura circa 120μS, rientriamo quindi abbondantemente nel millisecondo di interrupt impostato e c’è ancora tanto spazio per fare altre cose!

Utilizzando un quarzo a 4MHz la routine di serializzazione dura 439μS e tutta la routine di interrupt nel peggiore dei casi dura circa 560μS, per cui è possibile realizzare questo meccanismo anche usando un quarzo da 4MHz, altrimenti se avete bisogno ancora di più spazio nell’interrupt sul timer0 è possibile portare l’interrupt direttamente a 4mS ed eliminare i conteggi per il multiplexing: questa in effetti è un’altra strada ma in genere io metto sempre un tempo di interrupt più piccolo perchè non è detto che il programma che sto usando serva solo ad accendere dei led…

Downloads

Datasheet 74HC595 e 74HCT595 (2521 download)

Comandare una matrice a led 4x4 con 3 I/O usando un 74HC595 (698 download)

Schema e PCB matrice a led 4x4 con 74HC595 (643 download)

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 :)