Foto progetto


DISPLAY CON

SHIFT REGISTER

Foto Micro GT


Identificazione progetto

progetto

autore

email

note

  • Controllo di sei display a 7 segmenti con due shift  register:
    • Contatore Up/Down
    • Orologio digitale

Mirco  Battistella

btmpicproject@gmail.com

Piattaforma: MPLAB X  v1.80
Compilatore: Hitec C 9.83

Premessa

Il presente progetto è nato per illustrare le innumerevoli possibilità offerte dagli integrati SIPO 74HC595 (Serial In – Parallel Out), in particolare, per la loro capacità di espandere il numero di porte dei microcontrollori e per la compatibilità con i loro livelli di segnale e alimentazione. Questi dispositivi, in gran parte sottovalutati e poco conosciuti, permettono di ricevere serialmente la combinazione di 8 uscite indipendenti, con la possibilità di mettere in cascata più elementi, estendendo così in modo considerevole il numero di uscite controllabili. C'è da dire che esistono integrati specializzati per questo scopo, come ad es. il PCF8574, che hanno fra l'altro la possibilità di impostare indipendentemente i pin sia come ingressi che come uscite. Normalmente però, questi sono pilotabili unicamente con un protocollo specifico, come l'I²C, e hanno un costo decisamente superiore agli shift register.

Verrà spiegato brevemente il funzionamento e il pilotaggio dei 74HC595 e successivamente verranno presentate le librerie di interfacciamento e due esempi di utilizzo in soluzioni dove normalmente si utilizzerebbero un gran numero di pin del microcontrollore.

Chi fosse interessato alla piattaforma Micro-GT mini potrà richiederla assemblata, oppure kit di montaggio, o solo PCB all'indirizzo ad.noctis@gmail.com

Il 74HC595

Come già anticipato, il 74HC595 fa parte della famiglia degli shift register di tipo SIPO, permette cioè, attraverso l’invio seriale di un byte, di controllare indipendentemente 8 uscite digitali. E' composto da cinque ingressi e nove uscite, più i due pin di alimentazione. Gli ingressi si compongono di un pin seriale DATA, dove viene impostato il dato da trasferire (0 o 1), un pin di CLOCK, che portato dal livello logico 0 a 1, consente di acquisire il valore dell'ingresso DATA e di far scorrere i dati all'interno dello Shift Register, un pin di LATCH che, portato dal livello logico 0 a 1, trasferisce gli otto bit contenuti nello Shift Register allo Storage Register, ovvero il LATCH, un pin di Output Enable, che trasferisce gli otto bit dal LATCH alle uscite, e un pin di RESET, che azzera il contenuto dello Shift Register (non delle uscite!). Da notare che l'Output Enable e il RESET sono negati. Le uscite si compongono di otto pin digitali QA-QH, che assumono i valori caricati nello Shift Register (e di conseguenza nello Storage Register) e di un'uscita digitale QH', che, all'ottavo colpo di CLOCK, assume il valore di QH. Restano i due pin Vcc e GND, ai quali va applicata una tensione tipicamente compresa fra 2V e 6V.

La piedinatura standard è quella riportata nella figura sottostante. 

Pinout 74HC595

In pratica, la sequenza per trasferire il byte e attivare le uscite è la seguente:

      Shift Register Schematic
  1. Si imposta il livello dell'ingresso seriale DATA (0 o 1) in base al valore da assegnare alla relativa uscita, considerando che il primo dato (bit) immesso corrisponde al valore che assumerà l'ultima uscita.
  1. Si porta la linea di CLOCK da livello basso a quello alto (da 0 a 1) per trasferire il singolo dato (bit) allo Shift Register.
  1. Una volta trasferiti tutti i dati (8 cicli di CLOCK), si porta la linea di LATCH dal livello basso a quello alto (da 0 a 1) e i dati vengono contemporaneamente scritti sullo Storage Register (il LATCH).
  1. Portando la linea Output enable a livello basso, i dati passano contemporaneamente alle uscite corrispondenti. Normalmente la linea Output enable viene collegata a massa e i dati passano direttamente dal LATCH alle uscite.
  1. Portando a livello basso la linea di RESET, si cancellano i dati contenuti nello Shift Register. Normalmente la linea di RESET viene collegata a Vcc+.





Ecco, a titolo di esempio, quello che si ottiene trasferendo due byte (11010101 - 11001101) ad altrettanti shift register (1 = uscita in ON):
Trasferimento dati shift register

.

Per collegare in cascata più shift register, si mettono in comune le linee di CLOCK e di LATCH e si collega l'uscita seriale QH' del primo (pin 9) con l'ingresso seriale DATA del secondo (pin 14). Al nono colpo di CLOCK, i dati iniziano a passare dal primo shift register al secondo. Ovviamente, sono necessari Nx8 colpi di CLOCK per trasferire tutti i dati (dove N è il numero di shift register collegati).


Come detto, i dati vengono trasferiti considerando che il primo bit trasmesso corrisponde all'ultima uscita dell'ultimo shift register collegato (o all'uscita 8 in caso di un unico shift register).

Se si considera di collegare l’Output enable a massa e il RESET a Vcc+, sono in pratica necessari solo 3 pin del microcontrollore per gestire uno o più 74HC595. La cosa diventa interessante quando si devono comandare un numero elevato di uscite, siano esse un display a matrice di punti o delle uscite a relè (es. realizzazione di un PLC modulare).

Librerie per l’interfacciamento

Per il controllo software, ho previsto delle librerie in C che consentono di interagire in modo semplice con gli integrati, avendo, in primis, l’accortezza di settare correttamente i 3 pin che si intendono utilizzare per i comandi e il numero di shift register utilizzati. Nulla vieta di modificare le librerie per poter controllare anche le linee Output enable e RESET (non implementate).

Per settare i pin di comando e le costanti utilizzate creiamo, per comodità, degli opportuni #define, in particolare:

#define DATA RB0

#define CLOCK RB1

#define LATCH RB2

#define NUM_SHIFT_REG 3

dove ovviamente al posto di RB0, RB1, RB2, vanno inseriti i pin che intendete utilizzare e al posto del numero 3 va inserito il numero di shift register che state comandando. 

Funzioni:


void
ImpostaShift (unsigned char valorePorte)
{
       
unsigned char shift;
       
unsigned char value;

        for (shift=0; shift<8;shift++)
        {
                 value=valorePorte & (0b00000001 << shift);
                 if (value)
                 {
                         DATA=1;
                 }
                 else
                 {
                         DATA=0;
                 }
                 CLOCK=1;
                 CLOCK=0;
         }
        LATCH=1;
        LATCH=0;
}


Partiamo con la prima funzione che serve per trasferire un byte ad uno shift register e successivamente settarne le uscite. 
Dapprima si isola il valore di ogni singolo bit, che corrispondente alla rispettiva uscita dello shift register, attraverso la maschera che cambia ad ogni ciclo:
 
           value = valorePorte & (0b00000001 << shift);
 
           es. 1 –        value = 0b00101101 & (0b00000001<<3) =
   0b00101101 & (0b00001000) =
   0b00001000
   
In questo caso value è diverso da 0, quindi DATA=1

           es. 2 –       value = 0b00101101 & (0b00000001<<4) =
   0b00101101 & (0b00010000) =
   0b00000000

   In questo caso value è uguale a 0, quindi DATA=0
 
In base al valore di value, si setta la linea DATA. Si noti che all'interno di if (value), quando value è uguale a 0, DATA è uguale a 0, mentre, in tutti gli altri casi, DATA è uguale a 1:
 
            if (value)
            {
                        DATA=1;
            }
            else
            {
                        DATA=0;
            }
 
 
si porta il CLOCK a 1 per trasferire il dato e successivamente lo si riporta a 0 per non creare problemi nelle successive operazioni:
 
                CLOCK=1;
                CLOCK=0;
 
 
Tutto questo viene ripetuto  per 8 volte grazie al ciclo for. Una volta trasferiti tutti gli 8 bit si porta il LATCH a ,1 per settare le uscite e ritrovarci il nostro byte sulle porte del 74HC595. Si riporta infine il LATCH a 0.
 


void
CaricaShift (unsigned char valorePorte)
{
        unsigned char shift;
        unsigned char value;

        for (shift=0; shift<8;shift++)
        {
                 value=valorePorte & (0b00000001 << shift);
                 if (value)
                 {
                         DATA=1;
                 }
                 else
                 {
                         DATA=0;
                 }
                 CLOCK=1;
                 CLOCK=0;
         }
}


La seconda funzione, molto simile alla prima, è la base per una funzione più complessa, che serve per settare più integrati in cascata. Infatti, in questo caso, è opportuno attivare il LATCH solo quando sono stati trasferiti i dati di tutti gli elementi connessi. Per far ciò, è stato tolto il comando del LATCH che trasferisce i dati alle porte. Questa funzione, ripetuta N volte tanti quanti sono gli integrati in cascata, permette di caricare un byte per ciascun elemento.

Ricordo che per ogni colpo di CLOCK, i dati all’interno dello shift register vengono traslati di una posizione, quindi all'ottavo CLOCK, il bit n. 8 viene presentato all’uscita seriale (pin 9) e, di conseguenza, all’ingresso seriale del successivo integrato, il quale, al nono colpo di 
CLOCK, acquisirà il segnale ricevuto.
 
          


void
ImpostaMultiShift (unsigned char valorePorte[], 
unsigned char numShiftReg )
{
        unsigned char numShift;

        for (numShift=0; numShift<=numShiftReg; numShift++)
        {
                 CaricaShift(valorePorte[numShift]);
         }
LATCH=1;
LATCH=0;
}

La terza funzione riceve una matrice di char come parametro, in cui ogni elemento della matrice corrisponde al byte da inviare a ciascuno degli shift register in cascataOvvio dire che il numero massimo di elementi che accetta sono 255 (char!) e che per avere un’estensione maggiore basta cambiare il tipo di dato della variabile passata. Come prima cosa, viene attivato il ciclo for che trasferisce, elemento per elemento, tutti i dati della matrice, grazie appunto all’utilizzo della precedente funzione CaricaShift:

for (numShift=0; numShift<=lunghezzaMatrice; numShift++)
{
                 CaricaShift(valorePorte[numShift]);
}



Quando tutti i byte sono stati caricati, viene portato il LATCH a 1 (e poi di nuovo a 0) e vengono settate le uscite di tutti gli shift register contemporaneamente.


N.B. - Ho preferito passare il numero di shift register come parametro in quanto funziona (ovviamente) su tutti i compilatori. In realtà, non permette di evitare tutti gli errori che possono verificarsi, specialmente nel caso in cui il numero passato non coincida con la lunghezza della matrice. Un metodo più efficace, che però ho testato esclusivamente sul C18 (qui funziona) e sul Hitec C 9.83 (qui non funziona!), consiste nel creare una variabile di tipo unsigned char all'interno della funzione, togliendo il parametro in ingresso con il numero di shift register. Successivamente si adotta la seguente funzione:

           
lunghezzaMatrice=sizeof(valorePorte/sizeof(int);

 
che permette di stabilire la lunghezza della matrice, e quindi il numero di shift register. In seguito si imposta il limite del ciclo for sulla variabile ottenuta:

for (numShift=0; numShift<= lunghezzaMatrice; numShift++)
          

Oltre a queste tre funzioni, ne sono state realizzate altre due, in particolare una di test, che porta a 1 tutte le uscite di tutti gli integrati, e una di reset software, che le porta a zero.

 


 

void ShiftTest (unsigned char numShiftReg)
{
        unsigned char shift;
        unsigned char numShift;

DATA=1;

        for (numShift=0; numShift<numShiftReg;numShift++)
        {

        for (shift=0; shift<8;shift++)
        {
                 CLOCK=1;
                 CLOCK=0;
         }
}
LATCH=1;
LATCH=0;

DATA=0;

}




La funzione di test accetta un parametro contenente il numero di elementi in cascata (max 255), porta a 1 la linea DATA e attiva due cicli for, il primo trasferisce 8 bit a 1 per ogni singolo integrato, l’altro lo ripete per tutti gli N integrati:

for (numShift=0; numShift<numShiftReg;numShift++)
{
        for (shift=0; shift<8;shift++)
        {
                 CLOCK=1;
                 CLOCK=0;
         }
}



 

Successivamente viene portato il LATCH a 1 e vengono contemporaneamente settate a 1 le uscite di tutti gli shift register.
 Segue il ritorno a 0 delle linee  DATA e LATCH.

 


void
 ShiftReset (unsigned char numShiftReg)
{
        unsigned char shift;
        unsigned char numShift;

DATA=0;

        for (numShift=0; numShift<numShiftReg;numShift++)
        {

        for (shift=0; shift<8;shift++)
        {
                 CLOCK=1;
                 CLOCK=0;
         }
}
LATCH=1;
LATCH=0;
}

La funzione di reset è tale e quale a quella di test, fatta eccezione al fatto che la linea DATA viene anticipatamente impostata a 0, con la conseguenza che, dopo il colpo di LATCH, tutte le uscite vengono settate a 0.

Bene, veniamo ora a due esempi pratici di quanto detto fin’ora.

Controllo di 6 display a 7 segmenti

Nei due esempi che vedremo a breve, verranno utilizzati 2 shift register in cascata per la gestione in multiplexing di 6 display a 7 segmenti. Il primo shift register viene utilizzato per l’accensione dei segmenti, mentre il secondo viene utilizzato per accendere in sequenza i vari display. Nel mio caso ho utilizzato display a catodo comune con l’utilizzo di 6 transistor NPN BC337 di pilotaggio. Anche qui, ciascuno può adattare lo schema in base ai display che ha, avendo cura di modificare i transistor in PNP qualora si comandi display ad anodo comune.
La parte hardware è comune per entrambi gli esempi ed è composta da una scheda display e da una scheda di interfaccia per la Micro GT Mini. La prima è stata "impropriamente" sviluppata su millefori ( nota personale: sconsiglio vivamente di provarci a meno che non siate veramente degli autolesionisti!), mentre la seconda è stata sviluppata con il metodo della fotoincisione su due layer.


Display lato componenti Display lato saldature
Interfaccia layer 1 Interfaccia layer 2


Schemi elettrici con descrizioni:

Schema orologio

Come si può vedere dallo schema del display, ciascuna uscita del primo shift register è collegata, attraverso una resistenza di limitazione da 330ohm, ai segmenti a, b, c, d, e, f, g, dp di tutti i display, mediante il collegamento parallelo fra questi. Il secondo shift register e collegato ai transistor che comandano i catodi dei display, sempre attraverso la propria resistenza di limitazione da 1k. Come detto, le linee di CLOCK e LATCH dei due integrati sono messe in parallelo, mentre i pin del Output Enable sono collegati a massa e quelli di RESET sono collegati a +5V. Il segnale DATA entra sul primo shift register e, dall'uscita QH', entra nell'ingresso seriale del secondo. Per finire, ho previsto i soliti condensatori di disaccoppiamento nei pin di alimentazione. La routine che si andrà a realizzare, deve tenere conto dei segmenti da accendere in base al display attivo. Tutto ciò, ripetuto in sequenza ciclica e a velocità opportuna, crea l’illusione che tutti i display siano accesi contemporaneamente visualizzando le varie cifre (multiplexing).

Il connettore del display ha 5 pin, di cui:

Riguardo alle resistenze di limitazione dei display a 7 segmenti c'è da fare una precisazione: sul datasheet del 74HC595 è riportato che la massima corrente erogabile dalle singole uscite è di ±35mA, ma che la corrente massima prelevabile contemporaneamente (Icc o IGND) è di soli ±70mA. Questo vuol dire che bisogna calcolare correttamente le resistenze in base al display che si usa ed eventualmente aumentarne il valore (es. 470ohm), a discapito di una minore luminosità. Si consideri comunque che, grazie al multiplexing, è acceso un solo display alla volta e che, normalmente, non vengono mai attivati tutti i segmenti contemporaneamente. Quest'ultima affermazione fa eccezione solo quando viene eseguito il test del display, quindi, se il calcolo delle resistenze che avete fatto è al limite, evitate di eseguirlo.

Interfaccia Micro GT Mini

Nel secondo schema relativo all'interfaccia, ho previsto il collegamento di n. 3 pulsanti sui pin RB4, RB5 e RB6. Questa scelta non è stata fatta a caso: infatti sul PORTB è possibile attivare le resistenze di pull-up (RBPU=0), evitando di montarle fisicamente sul circuito. Inoltre, i pin RB4-RB7 hanno la possibilità di generare interrupt nei cambi di stato, quindi sono decisamente indicati per l'acquisizione di pulsanti.

L'interfaccia ha il inoltre il compito di portare i segnali di pilotaggio e alimentazione alla scheda display, prelevandoli dai rispettivi strip predisposti sulla Micro GT Mini, nonchè di collegare il led n. 1 all'uscita RB3 del pic, per visualizzare il segnale di clock nell'applicazione del esempio n. 2.

Per quanto riguarda le impostazioni della Micro GT Mini, è necessario portare l'alimentazione a +5V sugli strip centrali con un ponticello in morsettiera.

Impostazioni Micro Gt Mini

Esempio n. 1: Contatore UP/DOWN

Il primo esempio, molto banale,  riguarda un contatore UP/DOWN con un range che va da -60000 a +60000 (i due limiti si possono impostare a piacere). Questo tipo di contatore, con le dovute modifiche, può essere utilizzato in mille applicazioni, dal contapezzi al conta persone, dal contaore al conta impulsi, ecc. All'accensione viene eseguito un ciclo di test del display, attivando per 1 secondo tutti i segmenti. Successivamente, viene azzerato il display e visualizzato il conteggio, che di default è impostato a 0 (posizione centrale nel range). Il pulsante UP incrementa il conteggio, mentre il pulsante DOWN lo decrementa. Quando il numero visualizzato è negativo, i ruoli dei due pulsanti nelle funzioni si invertono: infatti per passare da 0 a -1devo premere il pulsante DOWN, ma in realtà questo internamente incrementa la variabile conteggio da 0 a 1, mentre la variabile sign viene posta a 1 per tenere traccia del segno del conteggio (0=positivo / 1=negativo). Per finire, il pulsante SET mi permette di riportare il contatore a 0. Demando la spiegazione dettagliata ai relativi commenti che ho inserito nel codice.

Esempio n. 2: Orologio digitale 

Il secondo esempio riguarda la realizzazione di un orologio digitale. Premetto fin da subito che si tratta di un progetto a scopo didattico basato sul TIMER0, con il solo fine di dimostrare l'utilità degli shift register. Infatti, per la realizzazione di un orologio degno di nota, è consigliato l'uso del TIMER 1 con un clock esterno dato da un quarzetto da 32.768kHz, oppure l'utilizzo di un RTC, che permette di avere, oltre alla buona precisione, anche molte funzioni aggiuntive, quali la data, il giorno della settimana, la sveglia, ecc. In questo caso si vuole dimostrare come, modificando il software, sia possibile utilizzare lo stesso hardware per diverse applicazioni.

Come detto, il clock dell'orologio viene scandito dal TIMER0 che incrementa una variabile clockMs ogni millisecondo. E qui ci sono da dire un'infinità di cose: nella Micro GT Mini in mio possesso è montato un quarzo da 4MHz che non permette di avere l'interrupt richiesto di 1 secondo. Per avere una combinazione sufficientemente precisa, ho previsto di settare il prescaler e il preset del TIMER0 in modo da ottenere l'overflow ogni millisecondo, per poi incrementare la variabile clockMs e aggiornare i secondi dopo 1000 cicli (1000ms=1s). Messa così, la storia funziona, ma è soggetta a grosse imprecisioni. Per questo infatti ho previsto 3 variabili aggiuntive, clockCalib, clockCalibDec e clockCalibMil, che mi permettono di calibrare il conteggio con una risoluzione fino al centesimillesimo di millisecondo. La variabile clockCalib, con range da -100ms a +100ms, mi permette di anticipare o posticipare lo scoccare dei secondi, mentre le due variabili clockCalibDec e clockCalibMil, con range da 0 a 99, aggiungono un ulteriore affinamento della calibrazione lavorando sui centesimi e centesimillesimi di millisecondo. In realtà, le tre variabili potevano essere sostituite da un'unica variabile di tipo float, ma questo complicava di molto sia la parte di correzione dei millisecondi che la parte di salvataggio in memoria. La formula finale che gestisce il clock è:

clock(interrupt)=1000ms±(clockCalib+clockCalibDec+clockCalibMil)

La funzione di interrupt si completa con la gestione dell'incremento dei minuti, delle ore, del lampeggio del led di clock e, in fase di programmazione, del lampeggio dei punti decimali del display.

La programmazione dell'orologio viene effettuata mediante i soliti tre pulsanti SET, UP e DOWN: il pulsante SET mi permette di entrare nella programmazione dell'ora (pressione per più di 1 secondo assieme al pulsante UP), nella programmazione della calibrazione (pressione per più di 1 secondo assieme al pulsante DOWN) e di confermare i parametri visualizzati (pressione singola). Il pulsante UP incrementa il valore dei parametri, mentre il pulsante DOWN lo decrementa. Anche qui, come per il contatore, quando operiamo sulla variabile della calibrazione principale, clockCalib, visto che questa può essere negativa, ho preferito considerare la variabile solo positiva e demandare il segno (positivo o negativo) alla variabile calibSign, con la conseguenza che, in fase di programmazione, le funzioni interne dei due pulsanti si invertono. Da notare che, mentre l'ora e i minuti si azzerano al mancare della tensione di alimentazione o a seguito di reset, le variabili di calibrazione vengono salvate nella memoria eeprom e non si cancellano mai, salvo riprogrammazione del pic.

Per la calibrazione del clock esistono due possibilità: la prima, piuttosto spartana, prevede di controllare "a occhio" la differenza fra il clock del nostro orologio e quello di un orologio campione. In questo caso si verifica nell'immediato di quanto sgarra il clock partendo con le tre calibrazioni a 0. Se il nostro orologio si prende indietro, si setta la calibrazione principale, clockCalib, in negativo, viceversa, se si prende avanti, si setta la variabile in positivo. Trovato il punto in cui il clock dell'orologio si avvicina a quello campione, si riduce la differenza tarando dapprima clockCalibDec e poi clockCalibMil. Ovvio dire che, via via che si affina la calibrazione, è necessario attendere tempi sempre maggiori per poter pretendere di effettuare una correzione precisa che, per la variabile clockCalibMil, possono arrivare anche al mese. La seconda possibilità prevede l'uso di un oscilloscopio (o un frequenzimetro) per verificare il clock sul pin RB3 (o sul led di clock), ponendo anticipatamente le tre variabili di calibrazione a 0. Da qui si vede il reale periodo del nostro segnale e quindi la differenza rispetto al secondo, permettendoci di settare perfettamente le nostre tre variabili. Nel mio caso i valori corrispondenti sono: clockCalib=-8, clockCalibDec=96 e clockCalibMil=5. Come sopra, demando la spiegazione dettagliata ai relativi commenti che ho inserito nel codice.

Esempio di calibrazione "a occhio":

Video

 Note finali

Questo articolo è solo la base di partenza per capire le potenzialità degli shift register nell'uso combinato con i microcontrollori. Prossimamente, spero di riuscire ad integrare interfaccie più complesse, quali schede a relè modulari e display a matrice di punti, staremo a vedere. Alla prossima!

File correlati

N.B - I due progetti sono compilati per un pic 16F876A con quarzo a 4MHz, entrambi integrati nella mia versione di Micro GT Mini . Per un corretto trasferimento dei file dall'MPLAB X alla Micro GT Mini, chi utilizza il PicKit 3 deve effettuare il seguente passaggio: click su File - Project Properties - PICkit 3 - selezionare Power nella casella a discesa Option categories e abilitare l'opzione Power target circuit from PICkit3.

Questo progetto è ridistribuibile secondo i termini di licenzaCreative Commons Attribuzione-Condividi allo stesso modo 3.0 Italia

 


  Pagina precedente