Ago 062018
 

Dato che sto per realizzare l’aggiornamento di un mio progettino, ho pensato di scrivere un articolo sull’uso della mia McMajan Library in quanto in molti mi fanno più o meno sempre le stesse domande. Vi ricordo che la peculiarità della la mia libreria è permette di gestire una catena di shift register 74HC595 ed utilizzarla per controllare in contemporanea multipli display lcd HD44780 compatibili ed uscite digitali. Il limite è quello dei 256 shift registers, quindi potremmo ad esempio usare 256 display in contemporanea, oppure 2048 uscite digitali, o mescolare le cose e mettere ad esempio 10 display e 1968 uscite digitali. Questo ovviamente in via teorica dato che l’aumento degli shift register comporta dei progressivi decadimenti prestazionali che potrebbero diventare rilevanti a seconda del tipo di applicazione. Non dimentichiamoci inoltre che tutto ciò che aggiungiamo consuma corrente e quindi dobbiamo accertarci di avere una fonte di alimentazione adeguata. Per quanto riguarda la velocità, si tenga conto che su Arduino UNO è possibile configurare la libreria perchè vada ad operare in maniera diretta sui registri del microcontrollore andando ad aumentare notevolmente la velocità del sistema.

Visto che risulta sempre molto noioso fare tutti i collegamenti, mi sono stampato un paio di pcb di prova, come potete vedere nella foto qui vicino. Grazie ad essi ho potuto creare tutti i collegamenti necessari nel giro di mezz’ora anzichè perderci delle ore come nei prototipi precedenti. Ad essere onesto la verità è ben diversa da quanto vi ho detto in quanto un piccolo errore nello schema mi ha fatto lavorare due giorni interi per capire come mai il circuito non funzionasse, in realtà un errore causato dal software, tutto era infatti dovuto ad un nodo che visivamente sembrava a posto ma che il software non aveva collegato cosa che ho dovuto poi fare con lo stagno sul pcb.

Lo schema comunque è sempre lo stesso e ve lo ripropongo anche qui per una più agevole visione. Faccio notare che nello schema sono stati usati due 74HC595 (per mostrare il collegamento in serie) di cui il primo è utilizzato per pilotare un display LCD, il secondo è collegato ad un ULN2803A per permettere di connetterci direttamente dei relè o altri carichi compatibili con quell’integrato. Quindi dallo schema potete fare da voi tutti i collegamenti necessari, io userò le piccole schedine che ho stampato, magari ora che le ho provate faccio alcuni aggiustamenti e poi potrei metterle in produzione per la vendita anche se i volumi non mi permettono certo di fare dei prezzi concorrenziali, ma magari può essere una cosa utile per chi non è in grado di fare da se e necessita di pochi esemplari per qualche particolare progetto.  

Prima di cominciare con alcuni esempi pratici vi ricordo che qui potete trovare la documentazione ufficiale che nel tempo verrà aggiornata ed ampliata.

Esempio 1: una semplice scritta

Partiamo dalle cose semplici per cui in questo primo esempio vediamo il collegamento di un semplice display 16×2. Nulla di particolare perciò, effettuiamo i collegamenti del caso e vediamo lo sketch d’esempio.

#include <Ss_McMajan_Config.h>

hc595 My595(12,11,13,1); // latch,clock,data,number of 74hc595
void setup()
{
 My595.DisplayReset(LCD595_BASIC_DISPLAY_INIT | LCD595_MORELINES ,0); 
 My595.SetCursor(0,0,1,0); // cursor at 0,0, display type 1 on first 595 
 My595.DisplayWrite("Setup OK...",0); // Write string
}

void loop()
{
}

Questo è l’esempio più basilare che si possa pensare di scrivere, infatti non facciamo altro che inizializzare il display e stampare sulla prima riga “Setup OK…”. Ma andiamo per passi ed analizziamo il listato. Per prima cosa vediamo che per includere la mia libreria occorre usare:

#include <Ss_McMajan_Config.h>

La seconda riga permette invece l’inizializzazione della catena di shift register sulla quale, vi ricordo, possiamo mescolare display lcd ed uscite digitali.

hc595 My595(12,11,13,1); // latch,clock,data,number of 74hc595

Direi che il commento già dice tutto, infatti i vari parametri da passare sono latch (ST: Storage register clock ), clock (SH: Shift register clock), data (DS: Serial Data input ) ed il numero di shift registers collegati alla catena che nell’esempio è uno solo. Fate però attenzione che se volete abilitare l’uso diretto dei registri su Arduino UNO, siete obbligati ad usare rispettivamente i pin digitali 2, 4 e 7. Ovviamente non sono valori casuali ma sono dettati da tutta una serie di motivazioni legate alla compatibilità con altre librerie, alla necessità di lasciare libere alcune risorse “peculiari”, etc. Tale limitazione è comunque imposta solo se volete utilizzare la programmazione diretta delle porte da parte della mia libreria, cosa che comunque non risulta necessaria nella stragrande maggioranza dei casi. Vedremo alla fine come fare ad utilizzare questa opzione.

My595.DisplayReset(LCD595_BASIC_DISPLAY_INIT,0);

Questa funzione inizializza il display. L’ultimo parametro indica il numero di shift register da controllare, in questo caso lo 0 che è il primo della catena (la numerazione parte da 0 ed arriva a 255). Il primo parametro permette di selezionare alcune opzioni:

  • LCD595_BASIC_DISPLAY_INIT:  Inizializzazione standard del display
  • LCD595_USEFONT_5X10: Utilizza il font 5×10 anzichè lo standard 5×8
  • LCD595_MORELINES: Indica che il display dispone di più di una linea, cosa comune a pressochè tutti i comuni display
 My595.SetCursor(0,0,1,0);

La SetCursor serve a spostare il cursore sul display. I primi due parametri sono le coordinate X ed Y, quindi colonna e riga. L’ultimo parametro come sempre è il numero di shift register interessato. Il terzo parametro è molto tecnico e diciamo che è dipendente dalla rappresentazione in memoria delle varie righe e si riferisce unicamente a display 4 righe e non a quelli a 2. I valori ammessi sono 1 e 2. Diciamo che se notate una incongruenza fra la posizione del cursore e quanto avete selezionato nello sketch, allora è il caso di cambiare questo parametro. Se nemmeno cambiandolo ottenete il giusto posizionamento, scrivetemi perchè molto probabilmente avete trovato un tipo di display a me ancora sconosciuto. Nell’eventualità che nessuno dei due parametri permetta un utilizzo corretto della funzione SetCursor, è possibile settare il cursore con un accesso diretto alla memoria tramite la funzione:

My595.SetDDRAM_Address(40,0);

In questo caso il secondo parametro è sempre il numero di shift register da comandare. Il primo è l’indirizzo di memoria della cella a cui vogliamo accedere. Giusto per completezza vi dico che nella funzione vista sopra l’1 corrisponde ai valori 0x00,0x40,0x14,0x54  mentre il 2 corrisponde a 0x00,0x40,0x10,0x50.

Per ultima troviamo la DisplayWrite. Anche per essa l’ultimo parametro rappresenta il numero di shift register da controllare. Il primo parametro è la stringa da visualizzare. Ripeto il fatto che è una stringa (array di caratteri) per cui non potete stampare ad esempio un valore numerico senza prima convertirlo in stringa. Se avete l’esigenza di scrivere un singolo carattere, potete usare la più veloce My595.DisplayChar(‘Z’,0); dove verrà stampata una Z nella posizione prima settata del display collegato allo shift register numero 0. Mi rendo conto di essere un po’ prolisso, forse anche un po’ ripetitivo, ma vista la quantità di domande che mi vengono fatte sull’uso della libreria preferisco fare un articolo un po’ più lungo del solito.

Esempio 2: scritta composta….

In questo secondo esempio stampiamo un primo messaggio di benvenuto e poi nel ciclo principale scriviamo il valore di tensione utilizzata internamente da Arduino come riferimento. Ho usato questo valore in quanto possiamo prelevarlo senza dover aggiungere nuovo hardware, ma l’esempio andrebbe benissimo per stampare il valore di temperatura letto da un sesnore esterno o qualunque altro dato numerico. Non ha nessuna impotanza la funzione che legge la tensione interna, ciò che realmente importa è che viene restituito un valore di tipo “long” che come tale deve essere convertito in stringa prima di essere stampato. Vediamo ora due vie alernative per ottenere lo stesso risultato. In un primo listato vi mostrerò l’utilizzo delle classiche funzioni del C, nel secondo utilizzerò la classe String così metterò a confronto anche la velocità del codice e l’occupazione in memoria. Il display perciò riporterà una scritta del tipo “Tensione : 5010 mV”. Ovviamente potrei ottenere il medesimo risultato semplicemente spostando il cursore e stampando i vari pezzi della stringa senza eseguire nessuna concatenazione, ma questo esempio serve proprio a mostrare come convertire valori numerici in stringhe ed eseguire i concatenameni, cose che mi sono state chieste innumerevoli volte, segno che è un argomento poco chiaro ad una buona fetta di persone.

Per convertire un valore float / double in stringa vi consiglio di usare la funzione dtostrf inclusa nella libreria avr. Essa utilizza 4 parametri che sono: valore da convertire, il numero minimo di cifre da visualizzare, il numero di cifre decimali e per finire il buffer della stringa. Se invece dovete convertire un intero potete usare la più classica itoa. In questo secondo caso vanno passati tre parametri che sono il valore da convertire, la stringa e la base numerica, fatto che ci permette di ottenere facilmente non solo valori decimali, ma anche binari o esadecimali. Nell’esempio che vi propongo faccio notare anche la presenza della funzione DisplayClean che rimuove il contenuto dello schermo prima di sovrascriverlo con un contenuto nuovo. Tale funzione non è indispensabile, ma se il nuovo contenuto presenta meno caratteri di quello vecchio, lo schermo resterà “sporcato” con i vecchi caratteri che non verranno rimossi.

Lasciamo perdere come funziona la readVcc() in quanto ci serve unicamente per ottenere un valore qualsiasi, avrei potuto usare anche un semplice numero casuale o qualunque altra altro sistema che mi fornisse un valore numerico variabile nel tempo. Le funzioni stringa usate sono, oltre alle due sopra menzionate, le classicissime strcpy per copiare una stringa dentro l’altra e la strcat che serve ad unire una stringa all’altra. Il resto del listato serve a misurare quanto tempo viene impiegato da Arduino per eseguire la lettura cosa che viene fatta usando la funzione micros() deputata alla lettura dei microsecondi trascorsi dall’accensione di Arduino. Il delay finale viene utilizzato perchè altrimenti l’esecuzione è troppo veloce e l’occhio umano non è in grado di leggere le cifre che cambiano sul display troppo repentinamente, cosa che risulta decisamente fastidiosa.

#include <Ss_McMajan_Config.h>

hc595 My595(12,11,13,1); // latch,clock,data,number of 74hc595
unsigned long t1,t2,delta,media,vcc;
char display[25];
char temp[12];

void setup()
{
 My595.DisplayReset(LCD595_BASIC_DISPLAY_INIT | LCD595_MORELINES ,0); // multiline display
 My595.SetCursor(0,0,1,0); // cursor at 0,0, display type 1 on first 595
 My595.DisplayWrite("Setup is OK...",0); // Write string
 delay(1500); // waiting for...
 My595.DisplayClean(0); // clear display
 media=0;
}

void loop()
{
 t1=micros();
 My595.SetCursor(0,0,1,0);
 vcc=readVcc();
 strcpy(display,"Tens: ");
 dtostrf(vcc,2,0,temp);
 strcat(display,temp);
 strcat(display," mV ");
 My595.DisplayWrite(display,0);
 t2=micros();
 delta=t2-t1;
 if(media==0) media=delta;
 else media=(media+delta)/2;
 My595.SetCursor(0,1,1,0);
 strcpy(display,"Tempo ");
 itoa(media,temp,10);
 strcat(display,temp);
 strcat(display," us ");
 My595.DisplayWrite(display,0);
 delay(500);
}

long readVcc() {
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif 
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Start conversion
while (bit_is_set(ADCSRA,ADSC)); // measuring
uint8_t low = ADCL; // must read ADCL first - it then locks ADCH 
uint8_t high = ADCH; // unlocks both
long result = (high<<8) | low;
result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
return result; // Vcc in millivolts
}

Questo sketch dopo la compilazione su Arduino UNO occupa 5636 bytes e viene eseguito in 25510us circa. Ora vediamo il medesimo esempio utilizzando la classe String che vi dico subito occupare 5096 bytes ed essere eseguito in 24110ms circa il che significa che occupa meno memoria ed è pure più veloce. Questo è sicuramente dato dal fatto che la dtostrf è una funzione troppo lenta anche se comunque si tratta di un risultato che non mi aspettavo, segno che la classe String è stata ben ottimizzata. A prima vista vi verrebbe ovviamente voglia di usare la classe String in quanto è più facile da usare, lo sketch è più compatto e leggermente più veloce. Ma attenzione: lo sketch con l’uso delle stringhe durante l’esecuzione mi lascia liberi 1724bytes di RAM mentre la versione con la classe string ne lascia liberi 1650. Tale differenza potrebbe sembrare ininfluente ed in questo semplice esempio lo è sicuramente, ma nel caso si utilizzino più stringhe, queste differenze potrebbero diventare fondamentali per un microcontrollore che di RAM ne ha ben poca. Tra le altre vi segnalo che non sono riuscito a convertire gli interi, non ne ho capito la ragione, ma l’output sul display è sempre rimasto “vuoto” in tutte le prove che ho fatto e la forzatura a float fa si che l’autput presenti sempre un antiestenico .0 finale anche se il numero è intero.

#include <Ss_McMajan_Config.h>

hc595 My595(12,11,13,1); // latch,clock,data,number of 74hc595
unsigned long t1,t2,delta,media,vcc;
char display[25];
char temp[12];
String Sdisplay;

void setup()
{
 My595.DisplayReset(LCD595_BASIC_DISPLAY_INIT | LCD595_MORELINES ,0); // multiline display
 My595.SetCursor(0,0,1,0); // cursor at 0,0, display type 1 on first 595
 My595.DisplayWrite("Setup is OK...",0); // Write string
 delay(1500); // waiting for...
 My595.DisplayClean(0); // clear display
 media=0;
}

void loop()
{
 t1=micros();
 My595.SetCursor(0,0,1,0);
 vcc=readVcc();
 Sdisplay="Tens: "+String(vcc)+" mv ";
 Sdisplay.toCharArray(display,Sdisplay.length());

 My595.DisplayWrite(display,0);
 t2=micros();
 delta=t2-t1;
 if(media==0) media=delta;
 else media=(media+delta)/2;
 My595.SetCursor(0,1,1,0);
 strcpy(display,"Tempo ");
 itoa(media,temp,10);
 strcat(display,temp);
 strcat(display," us ");
 My595.DisplayWrite(display,0);
 delay(500);
}

long readVcc() {
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif 
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Start conversion
while (bit_is_set(ADCSRA,ADSC)); // measuring
uint8_t low = ADCL; // must read ADCL first - it then locks ADCH 
uint8_t high = ADCH; // unlocks both
long result = (high<<8) | low;
result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
return result; // Vcc in millivolts
}

Esempio 3: display multipli

Ora cominciamo a vedere le potenzialità del sistema e colleghiamo altri due display. In questo primo esempio vediamo come stampare tre messaggi differenti sui tre display. Inoltre uno dei display sarà di tipo 20×4 anzichè 16×2, così vediamo come sia possibile gestire display di dimensioni differenti senza alcun problema. Le differenze rispetto il primo esempio sono quasi nulle, infatti non dobbiamo far altro che le stesse medesime cose ma replicate sui tre display. Vediamo ora l’esempio: non faccio altro che scrivere sui tre display alternativamente, l’incremento di un contatore interno che conta da 0 a 9, inoltre sul display a 4 righe vado a scrivere anche nelle due righe rimanenti in modo da evidenziare anche questo particolare. L’esempio non è nulla di spettacolare, ma permette di capire con quale semplicità possiamo gestire tre display in contemporanea con soli tre pin digitali.

#include <Ss_McMajan_Config.h>

hc595 My595(12,11,13,3); // latch,clock,data,number of 74hc595
char display[25];
char temp[12];

void setup()
{
My595.DisplayReset(LCD595_BASIC_DISPLAY_INIT | LCD595_MORELINES ,0); // multiline display
My595.SetCursor(0,0,1,0); // cursor at 0,0, display type 1 on first 595
My595.DisplayWrite("Setup is OK...",0); // Write string

My595.DisplayReset(LCD595_BASIC_DISPLAY_INIT | LCD595_MORELINES ,1); // multiline display
My595.SetCursor(0,0,1,1); // cursor at 0,0, display type 1 on first 595
My595.DisplayWrite("Setup is OK...",1); // Write string

My595.DisplayReset(LCD595_BASIC_DISPLAY_INIT | LCD595_MORELINES ,2); // multiline display
My595.SetCursor(0,0,1,2); // cursor at 0,0, display type 1 on first 595
My595.DisplayWrite("Setup is OK...",2); // Write string

My595.SetBackLight(0,HIGH,0); 
My595.SetBackLight(0,LOW,1); 
My595.SetBackLight(0,HIGH,2); 
delay(1500); // waiting for...

}

void loop()
{
for(double dis=0;dis<3;dis++)
{
for(long int i=0;i<10;i++)
{
My595.DisplayClean(dis); // clear display
My595.SetCursor(0,0,1,dis); // cursor at 0,0, display type 1 on first 595
strcpy(display,"Disp: ");
itoa(dis,temp,10);
strcat(display,temp);
My595.SetCursor(0,1,1,dis); // cursor at 0,0, display type 1 on first 595
strcpy(display,"Line 2: ");
itoa(i,temp,10);
strcat(display,temp);
My595.DisplayWrite(display,dis); // Write string

if(dis==1)
{
My595.SetCursor(0,2,1,dis); // cursor at 0,0, display type 1 on first 595
My595.DisplayWrite("Line 3",dis); // Write string
My595.SetCursor(0,3,1,dis); // cursor at 0,0, display type 1 on first 595
strcpy(display,"Ciclo: ");
itoa(i,temp,10);
strcat(display,temp);
My595.DisplayWrite(display,dis); // Write string 
}
delay(500);
}
}
delay(500);
}

Esempio 4: i pin digitali rimanenti

Se avete fatto attenzione, ci sono due pin digitali del 74HC595 che non vengono utilizzati per il display. Questi sono il pin 2 e 0, scelti volutamente per mantere compatibilità fra la mia libreria e la LiquidCrystal modificata per utilizzare il 74HC595. La mia libreria permette di utilizzare anche quei due pin digitali per cui per ogni display avete in realtà anche due uscite digitali da poter utilizzare liberamente. Nel pcb che mi sono stampato, ho in realtà lasciato libera l’uscita 2, mentre la 0 l’ho utilizzata per controllare l’accensione e spegnimento della retroilluminazione del display. In questo modo ci è possibile spegnere o accendere la retroilluminazione quando ne abbiamo necessità. Qui sotto vi allego lo schema di collegamento. Come vedete c’è un semplice transistor ed un resistore, nulla di più semplice.

Una cosa interessante è che possiamo decidere se utilizzare un transistor NPN come in figura, o sostituirlo con un PNP. Se utilizziamo un NPN all’accensione, dopo le fasi di inizializzazione, il relativo pin sarà a livello LOW per cui il transistor non è in conduzione e la retroilluminazione sarà spenta. Per accenderla dovremmo portare quel pin ad un livello HIGH. Se invece utilizziamo un transistor PNP il livello LOW iniziale tiene il transistor in conduzione per cui, anche senza impartire alcun comando, la retroilluminazione sarà attiva anche senza impartire alcun comando specifico. Portando invece il livello ad HIGH la retroilluminazione si spegnerà. Nel video che vi riporto a fine articolo, vi mostro che delle tre schede a mia disposizione, due sono equipaggiate con un transistor NPN mentre una è equipaggiata con un PNP, in questo modo posso mostrarvi il diverso comportamento della retroilluminazione. Ci sono due funzioni del tutto identiche che permettono l’uso dei due pin digitali liberi, ho usato questa doppia nomenclatura per semplici motivi di richiamo mnemonico ma internamente sono la stessa cosa.

Lcd_SetFreePin(pin,value,sr);
SetBackLight(pin,value,sr);

Ovviamente il pin può contenere solamente in valori 0 e 2, ossia i due pin digitali liberi. Value può essere settato a LOW o HIGH ed sr come sempre è il numero di shift register a cui è collegato il display. Quindi se come nel mio caso avete usato il pin 0 per controllare la retroilluminazione, potreste ad esempio usare SetBackLight(0,LOW,0); per portare a LOW il pin di controllo della retroilluminazione. Ciò poi corrisponderà all’accensione o spegnimento a seconda del tipo di transistor che avete utilizzato.

Nell’esempio ho pensato di stampare su un display una scritta fissa e faccio lampeggiare la retroilluminazione. Nel secondo display mostro lo stato ON – OFF della retroilluminazione, inoltre userò anche il secondo pin digitale libero per collegarci un led, quindi farò in modo che quando la retroilluminazione è accesa il led sia anch’esso acceso e viceversa. Nulla vieta ovviamente di sostituire il led con qualsiasi altro tipo di dispositivo, ad esempio potreste controllare un transistor per accendere o spegnere un relè.  Avendo già pronti anche gli altri display, ho utilizzato due display altrimenti con la retroilluminazione spenta la scritta diventava poco leggibile, così vi mosto ancora una volta come usare più display in contemporanea. Nel video noterete la presenza di 8 led per il semplice motivo che l’ho girato quando avevo già preparato il tutto per far girare anche i successivi esempi, compresi quelli con otto uscite contemporanee.

#include <Ss_McMajan_Config.h>

hc595 My595(12,11,13,2); // latch,clock,data,number of 74hc595

void setup()
{
 My595.DisplayReset(LCD595_BASIC_DISPLAY_INIT | LCD595_MORELINES ,0); // multiline display
 My595.SetCursor(0,0,1,0); // cursor at 0,0, display type 1 on first 595
 My595.DisplayWrite("Setup is OK...",0); // Write string
 delay(1500); // waiting for...
 My595.DisplayClean(0); // clear display
 My595.SetCursor(0,0,1,0); // cursor at 0,0, display type 1 on first 595
 My595.DisplayWrite("Line 0",0); // Write string
}

void loop() 
{
 My595.SetBackLight(2,HIGHT,3); //BL ON using NPN transistor
 My595.SetCursor(0,1,1,0); // cursor at 0,0, display type 1 on first 595
 My595.DisplayWrite("Illum: ON ",0); // Write string
 delay(1000);
 My595.SetBackLight(2,LOW,3); //BL OFF using NPN transistor
 My595.SetCursor(0,1,1,0); // cursor at 0,0, display type 1 on first 595
 My595.DisplayWrite("Illum: OFF ",0); // Write string
 delay(500);
}

Esempio 5: le uscite digitali

Ora vediamo come usare la libreria per utilizzare le uscite digitali. Lasciamo collegato un display ed utilizziamo un secondo 74HC595 solamente per controllare le uscite digitali. Benchè la schedina da me preparata non sia pensata per lo sfruttamento delle uscite digitali ma per il collegamento di un display, la userò ugualmente anche se con qualche limite. Prima di cominciare però dobbiamo sottolinerare il fatto che le uscite digitali non sono progettate per sostenere carichi importanti per cui, anche se volessimo semplicemente collegare 8 led, dovremmo provvedere ad assicurare un incremento della corrente a disposizione, cosa che possiamo fare con dei transistors o ancora meglio con l’UL2803. Ho perciò predisposto una breadboard con un ULN2803 al fine di collegare 8 led e mostrarvi il funzionamento della libreria in maniera “visiva”. La manipolazione delle uscite digitali può avvenire in diversi modi, ma prima di tutto dobbiamo capire una cosa fondamentale. Quando noi vogliamo modificare anche una sola delle uscite di uno qualunque degli shift register, Arduino deve reinviare tutti i dati di tutte le uscite di tutti gli shift register. Questo si riflette su quello che dovete fare quando decidete di modificare una o più uscite. Ogni volta vi dovete domandare se le uscite devono per forza di cosa essere modificate una alla volta in maniera sequenziale oppure possiamo modificarle tutte insieme. Se la risposta è la prima non c’è ottimizzazione che possiamo prevedere, infatti dobbiamo modificare l’uscita e aggiornare subito lo stato di tutti gli shift register. In caso contrario, invece, possiamo modificare tutte le uscite andando a modificare unicamente la memoria interna che tiene traccia dello stato delle uscite e solo dopo andiamo ad aggiornare fisicamente gli shift register. Vediamo di chiarire:

Ci sono 4 funzioni deputate alla manipolazione delle uscite digitali:

Set595Pin(D7,D6,D5,D4,D3,D2,D1,D0,sr);
Set595Pin(val,sr);
Send595();
Send595Pin(val,sr);

Le prime due operano sulla memoria interna di Arduino e non influiscono in maniera diretta sullo stato degli shift registers. Questo significa che possiamo modificare lo stato di una o più uscite, ma finchè non viene poi richiamata la terza funzione (Send595), lo stato degli shift registers non viene fisicamente aggiornato. La quarta funzione, invece, serve a modificare in maniera immediata lo stato di uno shift register, ed è quindi la funzione di scelta quando occorra modificare una o più uscite di un singolo register.

La prima funzione permette di specificare lo stato LOW, HIGH di ogni singola uscita per settarle tutte insieme. Un esempio potrebbe essere Set595Pin(LOW,LOW,HIGH,LOW,HIGH,HIGH,LOW,LOW,0);

Nell’esempio sopra le uscite vengono inviate come 00101100, che tradotto in un numero decimale corrispondono a 44. E guarda caso la seconda funzione permette proprio di specificare le uscite come numero, infatti lo stesso identico esempio può essere scritto come Set595Pin(44,0).

Ripeto che entrambe necessitano poi della funzione Send595() per effettuare l’aggiornamento fisico degli shift registers.

Usando il medesimo esempio, con l’ultima funzione potremmo scrivere Send595Pin(44,0) che modifica la memoria interna e ne invia direttamente il risultato allo shift register con conseguente aggiornamento dell’hardware. Questa ultima funzione è da preferirsi quando le modifiche interessano un singolo shift register, viceversa quando dobbiamo modificarne più di uno e non abbiamo l’esigenza di un aggiornamento immediato, conviene usare una o più Set595Pin e successivamente la Send595().

Ora, in tanti fanno fatica a capire come modificare una singola uscita. Ho scritto un apposito articolo su questo argomento ma vediamo di riassumere le nozioni basilari. Per tenere “a mente” lo stato delle uscite dello shift register potreste usare un char per ogni register, infatti un char è composto da 8 bit, esattamente quante sono le uscite digitali di ogni singolo 74HC595. Ma la mia libreria già contiene delle variabili per mantenere “a mente” lo stato delle uscite e guarda caso vi permette di modificare in maniera diretta quest’area di memoria, senza dover creare nuove variabili. La variabile si chiama semplicemente Buffer e possiamo andarla a modificare a nostro piacimento. Poi dobbiamo però ricordarci di inviare le modifiche apportate all’hardware con la funzione Send595().

Vediamo un esempio. Vogliamo modificare l’uscita 4 del 3° shift register e portarla ad HIGH:

SetBitN(My595.Buffer[2],4,HIGH);

Semplice vero? Attenzione che il 3° shift register è il numero due visto che il primo è lo zero. Come detto nel precedente articolo la SetBitN è una funzione interna della McMajan Library, ma che potete utilizzare liberamente per modificare i singoli bit di qualunque variabile. Dopo aver modificato il buffer interno non scordiamoci di inviarlo agli shift register con la Send595(). Ora passiamo all’esempio vero e proprio che poi vi mostrerò nel video conclusivo.

#include <Ss_McMajan_Config.h>

hc595 My595(12,11,13,3); // latch,clock,data,number of 74hc595

void setup()
{
My595.SetBackLight(0,HIGH,2);
My595.SetBackLight(0,LOW,1);
My595.DisplayReset(LCD595_BASIC_DISPLAY_INIT | LCD595_MORELINES ,1); // multiline display
My595.SetCursor(0,0,1,1); // cursor at 0,0, display type 1 on first 595
My595.DisplayWrite("Setup is OK...",1); // Write string
My595.DisplayReset(LCD595_BASIC_DISPLAY_INIT | LCD595_MORELINES ,2); // multiline display
My595.SetCursor(0,0,1,2); // cursor at 0,0, display type 1 on first 595
My595.DisplayWrite("Setup is OK...",2); // Write string


delay(3500); // waiting for...
// My595.DisplayClean(1); // clear display
//My595.DisplayClean(2); // clear display
My595.SetBackLight(0,LOW,2);
My595.SetBackLight(0,HIGH,1);
My595.Send595Pin(255,0);
delay(4000);
My595.SetBackLight(0,HIGH,2);
My595.SetBackLight(0,LOW,1);
My595.Send595Pin(0,0);
}

void loop() 
{
for(int i=0;i<8;i++) {My595.Send595Pin(1<<i,0);delay(500);}
for(int i=0;i<8;i++) {My595.Send595Pin(1<<(7-i),0);delay(500);}
My595.Send595Pin(0,0);

SetBitN(&(My595.Buffer[0]),2,HIGH);
My595.Send595();
delay(500);
SetBitN(&(My595.Buffer[0]),4,HIGH);
My595.Send595();
delay(500);
SetBitN(&(My595.Buffer[0]),6,HIGH);
My595.Send595();
delay(500);
SetBitN(&(My595.Buffer[0]),2,LOW);
My595.Send595();
delay(500);
SetBitN(&(My595.Buffer[0]),4,LOW);
My595.Send595();
delay(500);
SetBitN(&(My595.Buffer[0]),6,LOW);
My595.Send595();
delay(500);
delay(500);
}

Esempio 6: Uso diretto dei registri

Si lo so, non userete mai questa opzione, dubito che possiate arrivare al punto di necessitare di un incremento prestazionale visto che comunque la libreria è già di base estremamente ottimizzata e per avvertirne dei limiti prestazionali dovreste davvero spremerla a fondo. Ma visto che l’opzione è prevista, vi mostro come fare. Per testare l’incremento prestazionale, ho utilizzato uno sketch che utilizza gli stessi due display e le 8 uscite digitali visti sopra, ho fatto alcune modifiche per raccogliere le tempistiche e scriverle direttamente sul display in modo da avere un confronto diretto. Prima di copiare l’esempio, ricordatevi che siamo costretti a modificare i pin di controllo, ossia anzhichè usare i pin 12, 11 e 13 per latch, clock e data, dovremmo utilizzare rispettivamente i pin 2, 4 e 7. Modifichiamo perciò l’hardware e vediamo come si comporta il seguente sketch:

#include <Ss_McMajan_Config.h>

hc595 My595(2,4,7,3); // latch,clock,data,number of 74hc595 12 11 13
unsigned long t1,t2,delta,media;
char display[25];
char temp[12];


void setup()
{
 t1=micros();
 My595.SetBackLight(0,HIGH,2);
 My595.SetBackLight(0,LOW,1);
 My595.DisplayReset(LCD595_BASIC_DISPLAY_INIT | LCD595_MORELINES ,1); // multiline display
 My595.SetCursor(0,0,1,1); // cursor at 0,0, display type 1 on first 595
 My595.DisplayWrite("Setup is OK...",1); // Write string
 My595.DisplayReset(LCD595_BASIC_DISPLAY_INIT | LCD595_MORELINES ,2); // multiline display
 My595.SetCursor(0,0,1,2); // cursor at 0,0, display type 1 on first 595
 My595.DisplayWrite("Setup is OK...",2); // Write string


  My595.SetBackLight(0,LOW,2);
 My595.SetBackLight(0,HIGH,1);
 My595.Send595Pin(255,0);
   My595.SetBackLight(0,HIGH,2);
 My595.SetBackLight(0,LOW,1);
 My595.Send595Pin(0,0);


 My595.DisplayClean(1); // clear display
   My595.SetCursor(0,1,1,1);    
   
   t2=micros();
   delta=t2-t1;
   strcpy(display,"Tempo ");
   itoa(delta,temp,10);
   strcat(display,temp);
   strcat(display," us  ");
   My595.DisplayWrite(display,1);

}

void loop() 
{
  t1=micros();
  for(int i=0;i<8;i++) {My595.Send595Pin(1<<i,0);}
  for(int i=0;i<8;i++) {My595.Send595Pin(1<<(7-i),0);}
  My595.Send595Pin(0,0);

  SetBitN(&(My595.Buffer[0]),2,HIGH);
  My595.Send595();
    SetBitN(&(My595.Buffer[0]),4,HIGH);
    My595.Send595();
    SetBitN(&(My595.Buffer[0]),6,HIGH);
    My595.Send595();
    SetBitN(&(My595.Buffer[0]),2,LOW);
    My595.Send595();
    SetBitN(&(My595.Buffer[0]),4,LOW);
    My595.Send595();
    SetBitN(&(My595.Buffer[0]),6,LOW);
    My595.Send595();
  
  //My595.DisplayClean(2); // clear display
  My595.SetCursor(0,1,1,2);
    t2=micros();
   delta=t2-t1;
   if(media==0) media=delta;
   else media=(media+delta)/2;

   strcpy(display,"Tempo ");
   itoa(delta,temp,10);
   strcat(display,temp);
   strcat(display," us  ");
   My595.DisplayWrite(display,2);
}

Come potete notare ho rimosso tutti i delay in quanto sarebbero stati cronometrati anche quelli andando a falsare i dati ottenuti. I due display andranno a mostrare due tempi, uno riguarda la fase iniziale di setup, il secondo riguarda il contenuto del loop principale. Lo sketch occupa 4232 bytes ed i tempi riportati in modalità “normale” sono rispettivamente di 19252 e 26716us.

Ora proviamo ad attivare la modalità “Direct Port”. Per farlo dobbiamo andare nei files della libreria ed andare a modificare direttamente il file Ss_McMajan_Config.h che, a titolo di esempio, nel mio pc (Windows) si trova al percorso C:\Users\smani\Documents\Arduino\libraries\McMajanLibraryPack\src\Ss_McMajan_Config.h. Non dovete far altro che andare nella sezione hc595 Library e togliere il commento alla riga //#define HC595_DP. Una volta rimosso il // non dovete far altro che salvare, ricompilare lo sketch ed inviarlo ad Arduino. Fate però molta attenzione perchè da questo momento in poi TUTTI i progetti che usano la mia libreria avranno il DirectPort attivo per cui per ripristinare la situazione precedente dovete reinserire il commento che avete tolto. Quali sono i risultati? Lo sketch occupa 3966 bytes ed i due tempi diventano 12844 e 17026. Attenzione che i tempi qui riportati non corrispondono a quelli del video riportato sotto perchè ho fatto delle modifiche (rimosso la DisplayClean) per migliorare a visibilità nel video stesso.

Sketch Tempo 1 Tempo 2
Base 4232 19252 26716
DirectPort 3966 12844 17026

Credo che questi dati si commentino da soli e mostrino ancora una volta quanto sia importante ottimizzare il software per ottenere i massimi risultati, specie quando l’hardware è così povero di risorse. Tutti gli esempi che ho utilizzato in questo articolo saranno a breve inclusi nella libreria per cui basterà scaricare la versione aggiornata per avere tutti gli esempi già pronti all’uso. Ora non avete più scuse, se è vero che sinora la mia libreria peccava nella documentazione, questo articolo ha messo in luce tutte le principali caratteristiche del ramo hc595 con tutte le sue potenzialità. Spero perciò di aver risposto a tutte le domande che mi sono state fatte nel tempo e a cui avevo cercato di dare delle risposte frammentarie nei commenti, via mail, etc anche se a dirvela tutta non ho elencato proprio tutte le caratteristiche della libreria, mancava ancora qualcosa come ad esempio i caratteri personalizzati, la visualizzazione del cursore, etc ma per quelli vi rimando alla documentazione.

Ora vi lascio con il video finale che vi mostra gli esempi all’opera. Vi chiedo inoltre la cortesia di supportarmi iscrivendovi al mio canale youtube, credo sia il minimo per ricompensare tutte le ore che ho speso a scrivere l’articolo, provare gli esempi e registrare il video. Buona visione!.




Salva