Corso programmazione PICmicro in C – Approfondimenti – Come aumentare il numero di uscite a disposizione. Gli Shift Register SIPO, cosa sono e come si usano

Succede spesso che, quando incominciamo a prendere la mano con i microcontrollori PIC, le esigenze aumentano sempre più e ci troviamo così a voler disporre di un numero sempre crescente di periferiche e di I/O. Ovviamente questo è un problema sentito anche quando, ad esempio, si vorrebbe avere quel “qualcosa in più”, ma senza necessariamente dover ricorrere ad un pic più grande oppure quando le reali necessità non giustificano l’acquisto di un controllore più potente.

In questo approfondimento vedremo quindi come aumentare il numero delle uscite (parlo di sole uscite e non anche di ingressi) di un microcontrollore con una spesa davvero irrisoria e in maniera semplice, senza complicarci troppo le cose. Difatti non dovremo far affidamento a nessuna periferica integrata nei microcontrollori: bastano soltanto due normalissimi I/O per pilotare un minimo 8 uscite.

Per realizzare questo concetto utilizzeremo quei circuiti integrati meglio noti come SIPO (Serial In – Parallel Out) detti anche comunemente Shift Register. Come suggerisce il nome stesso, tali integrati accettano in ingresso un dato seriale e forniscono in uscita il dato in formato parallelo. Sappiamo già qualcosa di come è fatto un segnale seriale, in quanto ne abbiamo discusso nella lezione riguardante la RS232: prendiamo un byte (8bit) e inviamo i bit uno dietro l’altro su un’unica linea. L’integrato SIPO riceverà questi 8 bit in serie e li trasferirà su 8 uscite, realizzando così la conversione seriale/parallelo.

In realtà per aumentare il numero di uscite di un picmicro esistono anche altri sistemi: si può ricorrere a circuiti integrati chiamati I/O expander (che permettono anche di avere degli ingressi aggiuntivi) come il PCF8575. Tali integrati vanno però controllati utilizzando il bus I2C che non tutti i picmicro hanno a bordo (anche se può essere emulato via software) e richiede un po’ di esperienza, codice e costi in più. In questo articolo, però, non parleremo degli I/O expander.

Tutto ciò è possibile tramite un circuito elettronico, contenuto nei SIPO, che prende il nome di Shift Register, o Registro a scorrimento.  Il dato seriale viene fatto passare attraverso 8 celle di memoria a 1 bit (flip/flop) contigue: il primo bit andrà nella cella numero zero, il secondo bit sposterà il primo, il quale andrà a finire nella cella numero 1, e lui stesso andrà a finire nella cella zero e così via: ecco perchè “registro a scorrimento”: il dato scorre attraverso queste “celle”, di cella in cella.

Le celle a loro volta, a seconda del SIPO utilizzato, possono essere collegate ad un latch (una cella di memoria) in cui il singolo bit viene memorizzato e quindi solo successivamente trasferito al relativo pin di uscita tramite un segnale di abilitazione. In altri SIPO, quelli privi di latch, il bit viene trasferito direttamente all’uscita.

Appartengono al primo tipo (latched shift register) gli integrati 74xx595  e 74xx4094, appariene invece al secondo tipo (non-latched shift register) il 74xx164. Al posto delle xx ci saranno le sigle che contraddistinguono la tecnologia utilizzata per la costruzione del circuito integrato.

Il 74/164 lo troveremo più comunemente venduto come 74LS164 (LS sta per Low-power Shottky) ed è un integrato TTL. I 74/4094 e 74/565 li troveremo (più comunemente) come 74HC4094, 74HCT4094 e 74HC595,74HCT595, dove la C  sta ad indicare che questi sono integrati CMOS (anche se la sigla inizia per 74) e sono compatibili come piedinatura ai circuiti integrati corrispondenti della famiglia logica TTL.

Ovviamente esistono altri integrati che svolgono tale funzione ma ho focalizzato qui l’attenzione solo su quelli che ritengo personalmente più comuni.

Tutti i tipi di SIPO ricevono il dato seriale su un pin ed effettuano il trasferimento dei bit attraverso le celle dello shift register tramite un segnale di clock (stiamo quindi parlando di una trasmissione seriale sincrona). Lo schema di funzionamento è il seguente:

Shift Register SIPO a 4 bit – Immagine tratta da Wikipedia

In “Data In” entra il dato seriale, ogni bit è trasportato attraverso le celle (i quadratini nello schema) tramite la transizione del segnale di  Clock da livello logico basso a livello logico alto.

Abbiamo detto che i SIPO con latch (74xx4094 e 74xx595) memorizzano il dato e rendono il byte disponibile sulle uscite solo dopo un comando: tali integrati hanno difatti un pin di enable (generalmente indicato con OE : Output Enable) che in pratica effettua questa operazione. Il pin di enable in questi SIPO può essere tenuto al livello attivo (alto per il 4094 e basso per il 595) in maniera tale che il dato venga trasferito direttamente alle uscite man mano (come con quelli non latched) senza il segnale di abilitazione.

Il 74xx595 ha anche bisogno di un secondo clock per poter trasferire i bit dalle celle dello shift register ai latch di memorizzazione.

Il 74xx4094 e il 74xx595 hanno inoltre un’uscita che permette di collegare più integrati in cascata in maniera tale da poterli pilotare tutti con le stesse linee clock e dati e quindi rendere più semplici ulteriori “espansioni”.

I SIPO senza latch (74xx164) rendono il bit disponibile sulle uscite man mano che viene caricato: questo a volte può essere un problema se dobbiamo pilotare circuiterie che richiedono l’invio perfettamente contemporaneo dei dati. Difatti inviato il primo bit questo è già disponibile sulle uscite (supponendo che il primo bit valga 1, avremo le linee di uscita nello stato 10000000), arrivato il secondo bit (se questo ad esempio vale zero) lo stato sarà  aggiornato nuovamente (01000000) ecc, con una transizione continua fino a che tutto il byte non è stato inviato: questo causa sicuramente malfunzionamenti in quei dispositivi che devono ricevere tutto il dato insieme, in un sol colpo.

Questo non è un problema quando lo si deve utilizzare per pilotare un qualcosa che già di per sè ha un proprio meccanismo di abilitazione prima di dover effettuare la lettura dei bit sul bus parallelo; i display LCD basati sul controller HD44780, ad esempio, hanno un pin di enable: quando tutto il dato è stato inviato diamo l’abilitazione e siamo sicuri che il dato prelevato sia quello corretto. Oppure ancora un display a 7 segmenti: possiamo inviare i dati per accendere i segmenti e solo successivamente attiviamo il comune; se rimanessimo il comune acceso, vedremo i led “sfarfallare” fino a che il dato non è stato ricevuto. Spero sia chiaro il concetto, che è da capire bene altrimenti non riuscirete a dare spiegazioni ad eventuali malfunzionamenti dei circuiti che avrete intenzione di ideare con questo sistema.

In questo articolo utilizzerò il 74LS164 (datasheet come sempre in fondo all’articolo), che è il tipo più economico non avendo i latch connessi alle uscite (e poi è anche l’unico che sono riuscito a recuperare in breve tempo per poter fare delle prove!).

Spero di poter inserire prossimamente anche degli esempi per i 74xx4094 e 74xx595.

Vediamo qui la struttura del 74xx164:

I pin indicati con Q0…Q7 sono le 8 uscite. A differenza degli altri SIPO, questo ha due ingressi: A e B, in realtà l’integrato in questione effettua un AND su questi due ingressi e prende quindi il risultato, noi non volendo sfruttare questa funzione li collegheremo insieme per essere sicuri di non combinare guai (oppure ne colleghiamo uno soltanto e l’altro lo teniamo alto con una resistenza di pull-up).

CP è il pin su cui si invia il segnale di clock: il dato sarà trasportato attraverso lo  shift register sulla transizione da livello logico basso a livello logico alto.

MR è il master reset, attivo basso, il che significa che tale pin esplica la sua funzione (azzerare tutte le uscite) quando viene posto a livello logico basso; non volendo sfruttare tale funzione (abbiamo detto che vogliamo sfruttare poche risorse del picmicro) lo terremo “buono” con una resistenza di pullup: se proprio vogliamo azzerare tutte le uscite invieremo uno zero, no?!

La logica di funzionamento è la seguente:


Ci sono gli 8 flip/flop dello shift register in cui vengono fatti scorrere i bit che compongono la parola seriale in ingresso. La prima cella è quella connessa all’uscita Q0 (pin3 del circuito integrato). Faremo un esempio di funzionamento collegando dei led alle uscite.

Le uscite forniscono 8mA, per cui sono inadatte per pilotare qualsiasi cosa se non un’altra logica o un transistor, io comunque ho “osato” collegando su  un 74LS164N dei led alle uscite tramite resistenze da 330Ω senza che nulla si sia guastato… però… non andrebbe fatto! Per cui non ve la prendete con me se l’integrato vi si guasta! Vi consiglio pertanto di collegare una resistenza più alta (tipo 620Ω) in maniera che non si prelevino più di 8mA dalle uscite: i led si accenderanno molto, molto poco, ma almeno state tranquilli e non rischiate che vi si rompa l’integrato! Uomo avvisato…

Realizzeremo quindi il seguente, semplice schemino (se lo realizzate, state attenti ai numeri di pin!):

Di tale schema ho anche realizzato il master, non ho purtroppo avuto il tempo di testarlo, ma lo do per buono al 99%, (in ogni caso è possibile scaricare in fondo all’articolo anche i sorgenti per Eagle):

Andremo quindi ad applicare il byte “serializzato” sull’ingresso che ho denominato “serial in” (che va ai pin 1 e 2 dell’integrato contemporaneamente) e quindi il clock sul pin 8.

Bene. Passiamo al programma sul picmicro. Serializzare un dato non è così difficile, basta semplicemente sfruttare le cose che abbiamo imparato in questo articolo e in quest’altro, e applicarle opportunamente, utilizzando un ciclo: non è difficile.

Dobbiamo focalizzarci su un bit alla volta spostandoci nel byte che vogliamo trasferire, controllare quindi se vale zero o uno e infine trasferire questo zero o questo uno sul pin (qualsiasi) che abbiamo scelto per trasferire il dato.

Facciamo quindi un rapido esempio in C, un passo alla volta, di come può essere effettuata questa operazione:

unsigned char shift;
for (shift=0; shift<8; shift++)
   {
   value = dat >> shift;
   if (value & 1)
      {
      DATA=1;
      }
   else
      {
      DATA=0;
      }
   }

Value è il valore che voglio serializzare (valore di tipo CHAR, quindi un byte). Come vedete faccio un ciclo costituito da 8 iterazioni (quanti sono i bit da trasferire), effettuo quindi uno shift verso destra di un numero di posizioni pari al valore “shift” (zero, e quindi nessuno spostamento, la prima volta che viene eseguito il ciclo, 1 la seconda volta, 2 la terza e via di seguito): in questo modo il bit “shift-esimo” si viene a trovare sempre nella posizione zero del mio byte risultante.

Effettuo quindi un AND del byte risultante con il valore 1 (oppure, per farvi rendere conto: 0b00000001): in questo modo i 7 bit “alti” vengono azzerati e se il bit shift-esimo vale 1, DATA (che nella fattispecie è il nostro pin che trasmetterà il dato seriale) varrà esattamente uno, altrimenti varrà zero. Semplice no? Ovviamente qui non stiamo tenendo conto della velocità di trasmissione: come vedete tra due stati successivi del pin DATA c’è soltanto il ritardo legato alle istruzioni precedenti.

E perchè non stiamo tenendo conto della velocità?

Primo perchè il 74LS164 regge frequenze fino a 35MHz, quindi usando il quarzo da 20MHz la transizione tra uno stato e l’altro di DATA rientra nella capacità del SIPO utilizzato, secondo… abbiamo detto che la trasmissione seriale che dobbiamo realizzare con il SIPO deve essere sincrona quindi c’è il clock che regola il trasferimento.

Già… il Clock!

Dopo queste istruzioni, supponendo che CK sia il pin che trasmette il clock (e precedentemente inizializzato a zero) basta fare semplicemente:

CK=1;
CK=0;

Abbiamo difatti detto che il dato viene trasferito sulla transizione da basso ad alto del clock. Il pin CK era già basso dall’inizio, lo mettiamo a uno in maniera da spostare il bit presente sugli ingressi (quello che abbiamo “messo” sul pin DATA) e quindi lo rimettiamo a zero in maniera che sia pronto per il ciclo successivo. Abbiamo appena realizzato una trasmissione seriale sincrona con delle banali istruzioni!

Nel caso in cui tutto ciò avesse bisogno di rispettare una determinata velocità di trasmissione, basterebbe includere dei ritardi tra l’invio di un bit e il successivo (tra un clock alto e un clock basso). Questa è la tecnica che si utilizza quando, ad esempio, si vogliono implementare le comunicazioni RS232 o I2C anche su pic che non hanno le relative periferiche a bordo.

Vediamo quindi il programma completo come è strutturato. Definiamo i nnanzitutto i due pin necessari alla comunicazione seriale:

#define DATA RC7 // pin utilizzato per inviare il dato seriale
#define CK&nbsp;&nbsp; RC6 // pin utilizzato per il clock

(sto usando il 16F877A, se usate altri pic, cambiate voi le porte, state attenti solo se utilizzate RA4 che è a collettore aperto e vuole quindi una resistenza di pullup, e se utilizzate porte che hanno pure funzione analogica ricordatevi di settarle come digitali…).

Dovrò avere cura di inizializzare a zero tali pin:

DATA=0;
CK=0;

e quindi di definirli come uscite:

TRISC=0;

Per comodità ci definiamo una funzione che effettua la trasmissione seriale verso il SIPO, funzione alla quale passeremo come argomento il dato da serializzare e trasmettere:

// Questa funzione serializza il byte DAT per inviarlo al 74LS164
void sipo_out(unsigned char dat)
   {
   static unsigned char value=0; // valore dopo lo shift
   static unsigned char shift=0; // shift
 
   for (shift=0; shift<8; shift++)
      {
      value = dat >> shift;
      if (value & 1)
         {
         DATA=1;
         }
      else
         {
         DATA=0;
         }
      // Segnale di clock:
      CK=1;
      CK=0;
      // La transizione da basso ad alto sposta il dato nello shift register
      }
   }

Volendo infine trasferire un byte al SIPO basterà richiamare la funzione passandole il dato da trasferire, ad esempio:

sipo_out(255); // così si accenderanno tutti i led

Realizziamo ora un semplicissimo contatore:

for (int a=0; a<256; a++)
   {
   sipo_out(a);
   DelayMs(100);
   }

Tale ciclo conta da 0 a 255 e mi trasferisce il valore sul SIPO, vedremo quindi accendersi i led in sequenza binaria, il ritardo serve per farci vedere che i led sono accesi nella maniera corretta, altrimenti tutto scorrerebbe via molto velocemente.  Questo esempio è scaricabile in fondo all’articolo.

Per i più attenti: perchè ho definito “a” come int (valore a 16bit) se deve contare solo fino a 255 (valore a 8 bit)?

Arrivati a questo punto, sempre i più attenti spero noteranno due cose molto, molto importanti:

1- I led che devono rimanere spenti si accenderanno in maniera quasi impercettibile fino a che il programma non termina. Questo è appunto causato dal fatto che, come dicevo prima, i bit su questo SIPO vengono trasferiti man mano che arrivano, quindi quel leggero lampeggìo (che potrebbe anche non notarsi affatto) è dovuto al fatto che in quel microsecondo sta “scorrendo” un bit a 1 attraverso il registro.

2- Il bit zero del mio dato mi va a finire nell’uscita Q7 e non nell’uscita Q0 come verrebbe logico da pensare!

Giustissimo. Ma non è un vero e proprio errore, è solo questione di come vogliamo ragionare. Con la nostra funzione sipo_out stiamo scansionando il byte da sinistra verso destra, mentre nello shift register, se ridiamo un’occhiatina allo schema che ne illustra il funzionamento logico, la prima cella è quella connessa a Q0 e l’ultima è Q7.

Alla fine della mia funzione, il primo bit (il bit zero) del mio dato finirà in Q7 e l’ultimo in Q0. Se vogliamo fare in modo che il primo bit vada in Q0… Basta ragionare al contrario: non effettueremo più lo shift del nostro dato e l’and con la maschera (1), bensì effettueremo lo shift della maschera ( però 0b10000000 questa volta) e quindi l’AND con il dato. Se avete letto bene gli articoli precedenti (questo e questo) che parlano di queste operazioni dovreste dovrebbe risultarvi più semplice afferrare il concetto. Alla fine si può strutturare il programma nell’uno o nell’altro modo: l’importante, quando si progetta il circuito, è sapere esattamente cosa succederà.

Downloads

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