INTEGRATI: SHIFT REGISTER + MODULO 7-SEGMENTI  (versione 17/05/2017)

Per costruire gli esempi proposti è necessario procurarsi un display a 7 segmenti con un singolo elemento. Inoltre occorre uno shift register (esempio 74HC595 - Serial-In / Serial o Parallel Out).

Single Digit 7 Segment Display
  

I 16 piedini (le 16 porte) dello Shift Register hanno la seguente funzione:

>> Pin 1-7 e 15 (da Q1 a Q7 e Q0): porte di uscita, che vengono attivate o disattivate a secondo delle istruzioni ricevute da Arduino;
>> Pin 8 collegamento alla terra (GND);
>> Pin 9 (Q7S, serial out): porta di uscita seriale che puo’ essere collegata alla porta seriale di input DS (Pin 14) di eventuali chip 74HC595 collegati in cascata;
>> Pin 10 (MR, master reclear): porta di reset; se la si pone in stato LOW cancella il byte memorizzato nello shift register. Per evitare problemi di solito viene tenuto HIGH e quindi alimentata con una tensione di 5 volt;
>> Pin 11 (SHCP shift register clock pin): porta per l’attivazione della fase di trasferimento del bit da Arduino allo shif register;
>> Pin 12 (STCP storage register clock pin, detta anche latch pin) porta per l’attivazione della fase di trasferimento del byte dallo shift register allo storage register: Quando viene dichiarata LOW viene consentito lo spostamento, mentre quando e’ HIGH ne viene impedito. E’ una specie di interruttore, utilizzato per decidere il momento di spostamento del byte dal registro di entrata (shift register) al registro di utilizzo. (storage register). Se poi la porta 13 (la prossima porta OE,) e’ attiva (e cioè è LOW), il trasferimento dei dati nello shift register coincide con l’attivazione/disattivazione delle porte di uscita.
>> Pin 13 (OE output enable): porta che consente l’utilizzo del byte per attivare o disattivare le porte di uscita. Quando e’ LOW consente l’utilizzo del byte mentre quando e’ HIGH non ne consente l’utilizzo. Per limitare il numero di porte utilizzate da Arduino, questa porta viene normalmente lasciata attiva e cioe’ viene mantenuta in stato LOW e quindi collegata direttamente a terra

Funzionamento del 74HC595

Lo schema logico del 74HC595 è il seguente:

I dati vengono trasferiti nello shift register del 74HC595 attraverso i pin N°14 (DS) e 11 (SHCP). Per caricare l'intero byte sulla porta seriale si procede in questo modo:

A) Si imposta il livello dell'ingresso seriale DS a 0 o 1 in base al valore del bit da inserire. Si tenga presente che il primo bit immesso corrisponde al valore che assumerà l'ultima uscita.

B) Si porta la linea del clock SHCP da LOW a HIGH per trasferire il singolo dato (bit) nello Shift Register.

C) Una volta trasferiti tutti i dati (8 cicli di CLOCK), si porta la linea di LATCH  da LOW a HIGH e i dati vengono contemporaneamente scritti nello Storage Register (il LATCH).

D) Portando la linea Output Enable (OE) a livello basso, i dati vengono trasferiti sulle uscite corrispondenti. Normalmente la linea Output Enable viene collegata a massa per cui i dati passano direttamente dal LATCH alle uscite.

I bit verranno distribuiti sulle uscite in ordine inverso (il primo bit in Q7 mentre l'ultimo in Q0)

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+. Nell'immagine successiva si vede il comportamento dello shift register:

Codice Sorgente A

Obiettivo progetto: Utilizzando un display a 7 segmenti composto da un singolo elemento (digit) costruire un progetto che, sfruttando uno shift register consenta di visualizzare in sequenza i numeri esadecimali da 0 a F utilizzando solo 3 pin.

soluzione:

Costruiamo il seguente circuito

Scriviamo il seguente programma

/*
  Esempio di contatore con display a 7-Segmenti con multiplexing utilizzando uno 
  shift register a 8 bit come: 74HC595. Progetto originario di Mark Sweeting (2011):
  http://www.sweeting.org/mark/blog/2011/11/27/arduino-74hc595-shift-register-and-a-7-segment-led-display

  I collegamenti necessari sono:

    Arduino pin 5 => 74HC595 pin 11 - Clock pin
    Arduino pin 6 => 74HC595 pin 12 - Latch
    Arduino pin 7 => 74HC595 pin 14 - Serial input
    
    74HC595 pin 15 (Q0)   => LED Pin 9  (A)
    74HC595 pin 1  (Q1)   => LED Pin 10 (B)  :    PIN 7SEGMENT     PIN 74HC595
    74HC595 pin 2  (Q2)   => LED Pin 4  (C)  :       -9A-             -Q0-
    74HC595 pin 3  (Q3)   => LED Pin 2  (D)  :   7F |    | 10B     Q5|    |Q1
    74HC595 pin 4  (Q4)   => LED Pin 1  (E)  :      |-6G-|           |-Q6-|
    74HC595 pin 5  (Q5)   => LED Pin 7  (F)  :   1E |    | 4C      Q4|    |Q2
    74HC595 pin 6  (Q6)   => LED Pin 6  (G)  :       -2D-  .5DP       -Q3-  .Q7
    74HC595 pin 7  (Q7)   => LED Pin 5  (DP)
    74HC595 pin 8  (GND)  => Terra
    74HC595 pin 9  (Q7S)  => Non connesso (usabile per connettere un ulteriore Shift Register)
    74HC595 pin 10 (MR)   => Vcc (High)
    74HC595 pin 11 (SHCP) => Arduino pin 5 - Clock
    74HC595 pin 12 (STCP) => Arduino pin 6 - Latch
    74HC595 pin 13 (OE)   => Ground (Low)
    74HC595 pin 14 (DS)   => Arduino pin 7 - Serial In
    74HC595 pin 16 (Vcc)  => Vcc
    
    LED pin 3 o 8 => resistenza da 330 Ohm => Vcc
*/

const int clockPin = 5;  // Pin di arduino connesso al Pin 11 del chip 74HC595 (SH Clock)
const int latchPin = 6;  // Pin di arduino connesso al Pin 12 del chip 74HC595 (Latch)
const int dataPin  = 7;  // Pin di arduino connesso al Pin 14 del chip  74HC595 (Data Serial In)

unsigned long t1;
int i = 0;

/* ---------------------------------------------------------------------------------------
 Rappresentazione dei digit HEX sul display a 7 segmenti. I singoli bit sono associati 
 in sequenza rispettivamente ai segmenti A, B, C, D, E, F, G, Dp. Poichè la funzione ShiftIt()
 invia all'ingresso seriale del chip i bit partendo dal più significativo fino al meno significavo segue
 che i bit in ingresso si presentino con quest'ordine: Dp, G, F, E,  D, C, B, A. Sul registro in uscita
 del chip i bit vengono risistemati in ordine inverso rispetto all'ordine di ingresso per cui le
 uscite [Q0, Q1, ..., Q7] conterranno rispettivamente i bit associati a: A, B, C, D, E, F, G, Dp (quindi
 il byte originale!).  Controllando le connessioni sul ns circuito notiamo che rispettano tale sequenza.
 Infatti:
 
   il Pin 15 (Q0) del 74HC595 è connesso al pin del 7-Segmenti A 
   il Pin  1 (Q1) del 74HC595 è connesso al pin del 7-Segmenti B 
   il Pin  2 (Q2) del 74HC595 è connesso al pin del 7-Segmenti C 
   il Pin  3 (Q3) del 74HC595 è connesso al pin del 7-Segmenti D 
   il Pin  4 (Q4) del 74HC595 è connesso al pin del 7-Segmenti E 
   il Pin  5 (Q5) del 74HC595 è connesso al pin del 7-Segmenti F 
   il Pin  6 (Q6) del 74HC595 è connesso al pin del 7-Segmenti G 
   il Pin  7 (Q7) del 74HC595 è connesso al pin del 7-Segmenti Dp
   
---------------------------------------------------------------------------------------- */ 
const byte numbers[16] = {
                    0b11111100, // 0
                    0b01100000, // 1
                    0b11011010, // 2
                    0b11110010, // 3
                    0b01100110, // 4
                    0b10110110, // 5
                    0b10111110, // 6
                    0b11100000, // 7
                    0b11111110, // 8
                    0b11100110, // 9
                    0b11101110, // 10 A
                    0b00111110, // 11 B
                    0b10011100, // 12 C
                    0b01111010, // 13 D
                    0b10011110, // 14 E
                    0b10001110  // 15 F
};

void show(byte number)
{
    // In questo loop ad ogni iterazione viene generato un byte per ogni segmento. Se il segmento associato è acceso il bit corrispondente è
    // posto a 1 altrimenti a 0. I restanti bit del byte sono tutti posti a 0. Pertanto quando il segmento associato è spento il byte 
    // prodotto è composto da tutti zeri. I byte diversi da 0 (segmento acceso!) vengono spediti allo shift register (tramite ShiftIt()) mentre 
    // gli altri vengono scartati. I bit sono associati nell'ordine ai segmenti A, B, C, D, E, F, G, Dp. Sostanzialmente lo shiftIt() 
    // determina l'accensione di un segmento alla volta ma per effetto della velocità del microcontrollore e della "latenza" nella luminosità 
    // dei led i segmenti attivati appaiono tutti contemporaneamente accesi mostrando in chiaro il simbolo "number" impostato
    for(int j = 0; j <= 7; j++)
    {
        // AND a bit tra la rappresentazione in segmenti del simbolo "number" e il j-esimo bit
        byte toWrite = number & (0b10000000 >> j); // sono proposti partendo dal bit più significativo
        // Se l'AND a bit restituisce un valore pari a zero (false) allora il segmento associato è spento. In 
        // questo caso il loop si interrompe e passa alla prossima iterazione relativa al segmento successivo.
        if (!toWrite) 
           continue;
        // Mando allo Shift register l'intero byte composto da tutti zeri tranne 
        // quello associato al segmento analizzato e che vale 1
        shiftIt(toWrite);
    }
}

void shiftIt (byte data)
{
    // invio fisicamente il byte da Arduino allo Shift Register 
    for (int k=0; k <= 7; k++)
    {
        // Isolo ogni singolo bit, che corrispondente alla rispettiva uscita dello shift register, 
        // attraverso la maschera che cambia ad ogni ciclo. Scrivo il bit sull'ingresso seriale DS.
        // Poichè il modulo utilizzato è a CATODO COMUNE (i segmenti hanno in comune la stessa massa)
        // segue che per accendere un segmento devo agire sull'anodo il che implica che devo impostare
        // l’uscita del pin di arduino a HIGH, consentendo alla corrente di scorrere.
        // Se si utilizza un display a ANODO COMUNE devo invertire utilizzando LOW per accendere il LED.
        if ( data & (1 << k) ) 
            digitalWrite(dataPin, HIGH); // Accendo il segmento
        else
            digitalWrite(dataPin, LOW); // Spengo il segmento
        // Si porta il CLOCK a 1 per trasferire il singolo BIT e successivamente lo 
        // si riporta a 0.
        digitalWrite(clockPin, HIGH);
        digitalWrite(clockPin, LOW); 
    }
    // Si porta il LATCH a 1 per trasferire il contenuto dello SHIFT REGISTER nello STORAGE REGISTER.
    // L'aver connesso OUTPUT ENABLE alla massa (quindi è attivato!) fa si che i dati vengano trasferiti
    // sulle uscite corrispondenti. I bit verranno distribuiti sulle uscite in ordine inverso (il primo
    // bit in Q7 mentre l'ultimo in Q0)
    digitalWrite(latchPin, HIGH);
    digitalWrite(latchPin, LOW);
    // attivare il ritardo se si vuole vedere il multiplexing in azione
    // delay(100);
}

void setup()
{
    t1 = millis(); // memorizzo l'istante iniziale
    // imposto i pin di Output 
    pinMode(latchPin, OUTPUT);
    pinMode(clockPin, OUTPUT);
    pinMode(dataPin, OUTPUT);
}

void loop()
{
    if(millis() - t1 > 1000)  // Aggiorno il display ogni secondo
    {
      t1 = millis(); // memorizzo l'istante attuale per determinare il secondo successivo
      i = (i+1) % 16;
    }
    show(numbers[i]); // mostra il valore sul display
}

Riducendo ai minimi termini le spiegazioni sul funzionamento di questo programma, e’ possibile dire che Arduino invia al chip un byte contenente la configurazione binaria di 8 bit del numero da rappresentare sul display. Successivamente gli 8 bit vengono mandati nelle 8 porte di uscita del chip (da Q0 a Q7). Di fatto Arduino invia un byte al chip che lo memorizza in un registro di entrata (shift register). Il byte viene poi trasferito in un registro di utilizzo detto storage register e da qui utilizzato per attivare o disattivare, contemporaneamente, le 8 porte di uscita. I vari passaggi sono ovviamente tutti singolarmente pilotabili da Arduino.

Come gia’ detto, per pilotare il chip Arduino puo’ limitarsi ad utilizzare tre sole porte:

  1. una per attivare (o meglio consentire) l’invio del byte da Arduino al chip, e quindi da collegare alla porta numero 11 del chip (SHCP, shift register clock pin)

  2.  una per inviare fisicamente al chip il byte di configurazione e quindi da collegare alla porta 14 del chip (DS, Serial data input)

  3.  una per trasferire il byte dalla memoria di entrata alla memoria di utilizzo del chip. Deve essere collegata alla latch pin, e cioe’ allla porta 12 del chip.

La porta 13 (il cancello) va collegata terra per consentire l’attivazione delle porte di output nel momento in cui la latch pin viene dichiarata HIGH mentre la porta 10 va messa sotto tensione per evitare eventuali inaspettati reset.

Codice Sorgente B

Obiettivo progetto: Utilizzando un display a 7 segmenti composto da un singolo elemento (digit), uno shift register e un potenziometro costruire un progetto che consenta di visualizzare in sequenza, ruotando il potenziometro, i numeri esadecimali da 0 a F utilizzando solo 3 pin digitali e uno analogico.

soluzione:

Costruiamo il seguente circuito

Scriviamo il seguente programma

/*
  Esempio di contatore esadecimale con potenziometro e display a 7-Segmenti in multiplexing 
  mediante uno shift register a 8 bit (74HC595). 
  
  I collegamenti necessari sono:

    Arduino pin 5 => 74HC595 pin 11 - Clock pin
    Arduino pin 6 => 74HC595 pin 12 - Latch
    Arduino pin 7 => 74HC595 pin 14 - Serial input
    
    74HC595 pin 15 (Q0)   => LED Pin 9  (A)
    74HC595 pin 1  (Q1)   => LED Pin 10 (B)  :    PIN 7SEGMENT     PIN 74HC595
    74HC595 pin 2  (Q2)   => LED Pin 4  (C)  :       -9A-             -Q0-
    74HC595 pin 3  (Q3)   => LED Pin 2  (D)  :   7F |    | 10B     Q5|    |Q1
    74HC595 pin 4  (Q4)   => LED Pin 1  (E)  :      |-6G-|           |-Q6-|
    74HC595 pin 5  (Q5)   => LED Pin 7  (F)  :   1E |    | 4C      Q4|    |Q2
    74HC595 pin 6  (Q6)   => LED Pin 6  (G)  :       -2D-  .5DP       -Q3-  .Q7
    74HC595 pin 7  (Q7)   => LED Pin 5  (DP)
    74HC595 pin 8  (GND)  => Terra
    74HC595 pin 9  (Q7S)  => Non connesso (usabile per connettere un ulteriore Shift Register)
    74HC595 pin 10 (MR)   => Vcc (High)
    74HC595 pin 11 (SHCP) => Arduino pin 5 - Clock
    74HC595 pin 12 (STCP) => Arduino pin 6 - Latch
    74HC595 pin 13 (OE)   => Ground (Low)
    74HC595 pin 14 (DS)   => Arduino pin 7 - Serial In
    74HC595 pin 16 (Vcc)  => Vcc

    LED pin 3 or 8 => resistenza da 330 Ohm => Vcc

    Potenziometro: => pin analogico: A0
*/

const int clockPin = 5;  // Pin di arduino connesso al Pin 11 del chip 74HC595 (SH Clock)
const int latchPin = 6;  // Pin di arduino connesso al Pin 12 del chip 74HC595 (Latch)
const int dataPin  = 7;  // Pin di arduino connesso al Pin 14 del chip  74HC595 (Data Serial In)
const int PotPin = A0;   // Pin di arduino connesso al potenziometro

/* ---------------------------------------------------------------------------------------
 Rappresentazione dei digit HEX sul display a 7 segmenti. I singoli bit sono associati 
 in sequenza rispettivamente ai segmenti A, B, C, D, E, F, G, Dp. Poichè la funzione ShiftIt()
 invia all'ingresso seriale del chip i bit partendo dal più significativo fino al meno significavo segue
 che i bit in ingresso si presentino con quest'ordine: Dp, G, F, E,  D, C, B, A. Sul registro in uscita
 del chip i bit vengono risistemati in ordine inverso rispetto all'ordine di ingresso per cui le
 uscite [Q0, Q1, ..., Q7] conterranno rispettivamente i bit associati a: A, B, C, D, E, F, G, Dp (quindi
 il byte originale!).  Controllando le connessioni sul ns circuito notiamo che rispettano tale sequenza.
 Infatti:
 
   il Pin 15 (Q0) del 74HC595 è connesso al pin del 7-Segmenti A 
   il Pin  1 (Q1) del 74HC595 è connesso al pin del 7-Segmenti B 
   il Pin  2 (Q2) del 74HC595 è connesso al pin del 7-Segmenti C 
   il Pin  3 (Q3) del 74HC595 è connesso al pin del 7-Segmenti D 
   il Pin  4 (Q4) del 74HC595 è connesso al pin del 7-Segmenti E 
   il Pin  5 (Q5) del 74HC595 è connesso al pin del 7-Segmenti F 
   il Pin  6 (Q6) del 74HC595 è connesso al pin del 7-Segmenti G 
   il Pin  7 (Q7) del 74HC595 è connesso al pin del 7-Segmenti Dp
   
---------------------------------------------------------------------------------------- */ 
const byte numbers[16] = {
                    0b11111100, // 0
                    0b01100000, // 1
                    0b11011010, // 2
                    0b11110010, // 3
                    0b01100110, // 4
                    0b10110110, // 5
                    0b10111110, // 6
                    0b11100000, // 7
                    0b11111110, // 8
                    0b11100110, // 9
                    0b11101110, // 10 A
                    0b00111110, // 11 B
                    0b10011100, // 12 C
                    0b01111010, // 13 D
                    0b10011110, // 14 E
                    0b10001110  // 15 F
};

void show(byte number)
{
    // In questo loop ad ogni iterazione viene generato un byte per ogni segmento. Se il segmento associato è acceso il bit corrispondente è
    // posto a 1 altrimenti a 0. I restanti bit del byte sono tutti posti a 0. Pertanto quando il segmento associato è spento il byte 
    // prodotto è composto da tutti zeri. I byte diversi da 0 (segmento acceso!) vengono spediti allo shift register (tramite ShiftIt()) mentre 
    // gli altri vengono scartati. I bit sono associati nell'ordine ai segmenti A, B, C, D, E, F, G, Dp. Sostanzialmente lo shiftIt() 
    // determina l'accensione di un segmento alla volta ma per effetto della velocità del microcontrollore e della "latenza" nella luminosità 
    // dei led i segmenti attivati appaiono tutti contemporaneamente accesi mostrando in chiaro il simbolo "number" impostato
    for(int j = 0; j <= 7; j++)
    {
        // AND a bit tra la rappresentazione in segmenti del simbolo "number" e il j-esimo bit
        byte toWrite = number & (0b10000000 >> j); // sono proposti partendo dal bit più significativo
        // Se l'AND a bit restituisce un valore pari a zero (false) allora il segmento associato è spento. In 
        // questo caso il loop si interrompe e passa alla prossima iterazione relativa al segmento successivo.
        if (!toWrite) 
           continue;
        // Mando allo Shift register l'intero byte composto da tutti zeri tranne 
        // quello associato al segmento analizzato e che vale 1
        shiftIt(toWrite);
    }
}

void shiftIt (byte data)
{
    // invio fisicamente il byte da Arduino allo Shift Register 
    for (int k=0; k <= 7; k++)
    {
        // Isolo ogni singolo bit, che corrispondente alla rispettiva uscita dello shift register, 
        // attraverso la maschera che cambia ad ogni ciclo. Scrivo il bit sull'ingresso seriale DS.
        // Poichè il modulo utilizzato è a CATODO COMUNE (i segmenti hanno in comune la stessa massa)
        // segue che per accendere un segmento devo agire sull'anodo il che implica che devo impostare
        // l’uscita del pin di arduino a HIGH, consentendo alla corrente di scorrere.
        // Se si utilizza un display a ANODO COMUNE devo invertire utilizzando LOW per accendere il LED.
        if ( data & (1 << k) ) 
            digitalWrite(dataPin, HIGH); // Accendo il segmento
        else
            digitalWrite(dataPin, LOW); // Spengo il segmento
        // Si porta il CLOCK a 1 per trasferire il singolo BIT e successivamente lo riporta a 0.
        digitalWrite(clockPin, HIGH);
        digitalWrite(clockPin, LOW); 
    }
    // Si porta il LATCH a 1 per trasferire il contenuto dello SHIFT REGISTER nello STORAGE REGISTER.
    // L'aver connesso OUTPUT ENABLE alla massa (quindi è attivato!) fa si che i dati vengano trasferiti
    // sulle uscite corrispondenti. I bit verranno distribuiti sulle uscite in ordine inverso (il primo
    // bit in Q7 mentre l'ultimo in Q0)
    digitalWrite(latchPin, HIGH);
    digitalWrite(latchPin, LOW);
    // attivare il ritardo se si vuole vedere il multiplexing in azione
    // delay(100);
}

// Non uso la funzione map() originale poichè la lettura su A0 arriva a 1021/1022 
// per cui non riesco ad arrivare ad F (15) 
long ms_map(long x, long in_min, long in_max, long out_min, long out_max)
{
    // return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; // funzione originale in map()
    return round((float)(x - in_min) * (float)(out_max - out_min) /(float)(in_max - in_min) +(float)out_min);
}

void setup()
{
    // imposto i pin di Output 
    pinMode(latchPin, OUTPUT);
    pinMode(clockPin, OUTPUT);
    pinMode(dataPin, OUTPUT);
    // Per i pin analogici non è necessaria l'inizializzazione
    // pinMode(A0, INPUT);
    show(numbers[0]); // mostra il valore iniziale 0 sul display
}

void loop()
{
  int sensorValue = analogRead(PotPin);
  int conta = ms_map(sensorValue, 0, 1023, 0, 15);
  show(numbers[conta]); // mostra il valore sul display
}