PrettyPrint

domenica 10 marzo 2024

Arduino: visualizzare numero decimale su multidisplay LED 7 segmenti

L'obiettivo è quello di visualizzare la tensione letta da un ingresso analogico di Arduino UNO R3, compresa fra 0 e 5V, su un multidisplay LED 7segmenti integrato, a 4 cifre, ad anodo comune:

I MULTIDISPLAY LED 7SEGMENTI




Questi display LED hanno in comune gli 8 LED di ogni cifra (7 segmenti per il numero e 1 per il punto decimale) , consentendo così di risparmiare notevolmente i pin di I/O per il collegamento al microcontrollore, mentre sono ovviamente separati i 4 pin comuni (anodo o catodo comune) per consentirne l'accensione o spegnimento; la tensione di comando (0V nel caso del CC o 5V nel caso del CA) ovviamente non sarà fissa, ma erogata da un pin I/O di un microcontrollore, come quello della scheda Arduino UNO R3:

La gestione di questi display LED multipli avviene in mutliplexing a divisione di tempo, "accedendo", quindi, un display per volta per un brevissimo tempo (dai 2 ai 5 ms). Sfruttando l'illusione ottica della persistenza della visione dell'occhio umano, il numero da visualizzare apparirà come se fosse fisso:



Fonte: https://en.wikipedia.org/wiki/Multiplexed_display#/media/File:7segment_multiplexing.gif


I vantaggi sono molteplici: si risparmiano 20 pin I/O del microcontrollore, aggiuntivi rispetto al display singolo (ne occorrono infatti 4 per comandare i 4 pin comuni), si riduce l'assorbimento energetico, essendo un solo display acceso, e non quattro, per ogni lettura.

Occorre, però, un software di gestione leggermente più complesso, che "spacchetti" innanzitutto il numero, intero o decimale, a 4 cifre in 4 singole cifre, predisponendo pure l'accensione dell'eventuale punto decimale o l'eventuale spegnimento degli zeri non significativi.

Esistono librerie molto efficienti a tale scopo (come SevSegment) ma ovviamente è sempre formativo provvedere alla creazione di una propria libreria, anche se meno sofisticata, ma innanzitutto orientata alla semplicità e alla comprensibilità per un target "scolastico".

Le funzioni utente sono tre:

DISPLAY7S

Questa funzione attiva i segmenti (e l'eventuale punto decimale) in funzione del numero da visualizzare.

Essa accetta in ingresso due parametri:

- il numero decimale (da 0 a 9) da visualizzare
- un eventuale flag per l'accensione del punto decimale: 1 (acceso), 0 (spento); 
  invocando la funzione solo con il primo parametro, questo flag è di default 0

Una successiva istruzione di selezione multipla, assegna ai vari segmenti lo stato in funzione del numero che si intende visualizzare. Impostati i vari stati, le istruzione di scrittura digitale provvedono a spegnere o accendere i segmenti secondo gli stati impostati.

DISPLAYEN

Questa funzione attiva un solo display alla volta, per un brevissimo tempo.

Essa accetta in ingresso un solo parametro: l'indice del display che si intende accendere (da 0, il primo, a 3, l'ultimo). Il display verrà dunque acceso per 5ms e successivamente spento

FLOATTO4DGT

Questa funzione provvede a "spacchettare" il numero decimale da visualizzare nelle 4 cifre, provvedendo anche a impostare l'accensione dell'eventuale punto decimale.

Essa accetta tre parametri:

- il numero decimale da visualizzare (che ovviamente dovrà essere compreso fra 0 e 9999)
- l'indirizzo del vettore delle cifre del multidisplay
- l'indirizzo del vettore dei punti decimali del multidisplay

DEFINIZIONI FUNZIONAMENTO DEL DISPLAY

Occorrerà innanzitutto impostare delle costanti simboliche: SON e SOFF, rispettivamente i livelli logici per avere l'accensione o lo spegnimento del segmento (e anche del punto decimale). Se abbiamo, quindi, un CA (anodo comune) SON sarà LOW e SOFF sarà HIGH.

Viceversa i livelli logici per l'accensione e lo spegnimento del display, quelli cioè applicati al pin comune, saranno sempre i negati di quelli che accendono i segmenti:

#define SON LOW // SEGM ON :LOW (CA) / HIGH (CC) #define SOFF HIGH // SEGM OFF :HIGH (CA) / LOW (CC) #define CON !SON #define COFF !SOFF

Infine si definiranno i pin di Arduino collegati ai segmenti (con la lettera maiuscola: A,B, C ecc) e le variabili di stato dei segmenti (con la lettera minuscola a,b,c) oltre ai due vettori delle cifre del display e dei punti decimali.

Di seguito lo sketch completo:

MUX_4_LED_ADC.INO

/******************************************************/ /* DEFINIZIONI FUNZIONAMENTO DISPLAY */ /******************************************************/ #define SON LOW // SEGM ON :LOW (CA) / HIGH (CC) #define SOFF HIGH // SEGM OFF :HIGH (CA) / LOW (CC) #define CON !SON // COMUNE ON : NOT SON #define COFF !SOFF // COMUNE OFF: NOT SOFF const int A = 11 ; //pin a cui è collegato il seg. A const int B = 12 ; const int C = 8; const int D = 7 ; const int E = 6 ; const int F = 10 ; const int G = 9; const int DP = 13; const int C1 = 5; // pin comune della cifra 1 (sx) const int C2 = 4; const int C3 = 3; const int C4 = 2; int a,b,c,d,e,f,g; // stato dei singoli segmenti: ON/OFF int display[4]; //vettore cifre display int puntodec[4]; //vettore punti decimali /******************************************************/ /* FUNZIONE SPACCHETTAMENTO FLOAT -> CIFRE */ /******************************************************/ int floatTo4dgt (float n, int dgt[], int dp[]) { int parte_int, parte_dec, n_dec, n_int; float temp; if (n<0 || n>9999) { return -1; // float non valido (max 4 cifre) } else { parte_int = (int) n; temp = n - parte_int; if (n == 0) // visualizzato come 0.000 n_dec = 3; else { if (parte_int<10 || n==0.0) n_dec = 3; //numero cifre dopo la virgola else if (parte_int < 100) n_dec = 2; else if (parte_int < 1000) n_dec = 1; else n_dec = 0; } n_int = 4 - n_dec; // numero cifre prima della virgola for (int i = 0; i < n_dec; i++) { temp=temp*10; // shift a sx di n_dec posiz. } parte_dec = (int) temp; //toglie lo 0 dopo la virgola if (n_dec == 3) { dgt[0] = parte_int; dgt[1] = parte_dec / 100; dgt[2] = (parte_dec % 100) / 10; dgt[3] = (parte_dec % 100) % 10; dp[0] = 1; dp[1] = 0; dp[2] = 0; dp[3] = 0; } if (n_dec == 2) { dgt[0] = parte_int / 10; dgt[1] = parte_int % 10; dgt[2] = parte_dec / 10; dgt[3] = parte_dec % 10; dp[0] = 0; dp[1] = 1; dp[2] = 0; dp[3] = 0; } if (n_dec == 1) { dgt[0] = parte_int / 100; dgt[1] = (parte_int % 100) / 10; dgt[2] = (parte_int % 100) % 10; dgt[3] = parte_dec; dp[0] = 0; dp[1] = 0; dp[2] = 1; dp[3] = 0; } if (n_dec == 0) { dgt[0] = parte_int / 1000; dgt[1] = (parte_int % 1000) / 100; dgt[2] = ((parte_int % 1000) % 100) / 10; dgt[3] = ((parte_int % 1000) % 100) % 10; dp[0] = 0; dp[1] = 0; dp[2] = 0; dp[3] = 0; } return 0; } } /******************************************************/ /* FUNZIONE ATTIVAZIONE SINGOLO DISPLAY */ /******************************************************/ void displayEn(int i) { switch(i) { case 0: digitalWrite(C1, CON); delay(5); digitalWrite(C1, COFF); break; case 1: digitalWrite(C2, CON); delay(5); digitalWrite(C2, COFF); break; case 2: digitalWrite(C3, CON); delay(5); digitalWrite(C3, COFF); break; case 3: digitalWrite(C4, CON); delay(5); digitalWrite(C4, COFF); } } /******************************************************/ /* FUNZIONE CONVERSIONE NUMERO -> COD. 7 SEG */ /******************************************************/ void display7seg(int val, int dp=0) { switch(val) { case 0: a=SON;b=SON;c=SON;d=SON;e=SON;f=SON;g=SOFF; break; case 1: a=SOFF;b=SON;c=SON;d=SOFF;e=SOFF;f=SOFF;g=SOFF; break; case 2: a=SON;b=SON;c=SOFF;d=SON;e=SON;f=SOFF;g=SON; break; case 3: a=SON;b=SON;c=SON;d=SON;e=SOFF;f=SOFF;g=SON; break; case 4: a=SOFF;b=SON;c=SON;d=SOFF;e=SOFF;f=SON;g=SON; break; case 5: a=SON;b=SOFF;c=SON;d=SON;e=SOFF;f=SON;g=SON; break; case 6: a=SON;b=SOFF;c=SON;d=SON;e=SON;f=SON;g=SON; break; case 7: a=SON;b=SON;c=SON;d=SOFF;e=SOFF;f=SOFF;g=SOFF; break; case 8: a=SON;b=SON;c=SON;d=SON;e=SON;f=SON;g=SON; break; case 9: a=SON;b=SON;c=SON;d=SON;e=SOFF;f=SON;g=SON; break; default: // spegne il display a=SOFF;b=SOFF;c=SOFF;d=SOFF;e=SOFF;f=SOFF;g=SOFF; } // accende/spegne ogni seg. secondo il // corrispondente stato assegnato in precedenza digitalWrite(A, a); digitalWrite(B, b); digitalWrite(C, c); digitalWrite(D, d); digitalWrite(E, e); digitalWrite(F, f); digitalWrite(G, g); digitalWrite(DP, dp==1?SON:SOFF); } /******************************************************/ /* CORPO SKETCH */ /******************************************************/ float mvolt; //tensione misurata (in mV) void setup() { pinMode(A, OUTPUT); pinMode(B, OUTPUT); pinMode(C, OUTPUT); pinMode(D, OUTPUT); pinMode(E, OUTPUT); pinMode(F, OUTPUT); pinMode(G, OUTPUT); pinMode(DP, OUTPUT); pinMode(C1,OUTPUT); //pin comune Display cifra 1 pinMode(C2,OUTPUT); pinMode(C3,OUTPUT); pinMode(C4,OUTPUT); } void loop() { // legge la tensione in mV mvolt=map(analogRead(A0),0,1023,0,5000); // Chiama la funzione di "spacchettamento" (passa i Volt) floatTo4dgt (mvolt/1000.0, display, puntodec); // Invia il codice 7segmenti // e attiva in sequenza i 4 display for(int i=0; i<4; i++) { display7seg(display[i], puntodec[i]); displayEn(i); } }


sabato 6 gennaio 2024

Stadio di condizionamento per AD590, con unico op-amp 5V

u.r. 28-Gen-2026

In un precedente post, si è visto come usare un sensore AD590, con Arduino UNO R3, senza ricorrere a stadio di condizionamento, ma a un software un po' più complesso. Volendo semplificare il software occorre quindi un hatdware aggiuntivo, uno stadio di condizionamento (in questo caso un amplificatore di tensione) che "scali", quanto più possibile, la piccola dinamica del sensore a quella più ampia dell'ADC di Arduino. Ma anche in questo caso, si vuole semplificare quanto più possibile: non vogliamo alimentazioni aggiuntive, né riferimenti di tensione esterni e si vuole utilizzare componentistica facilmente reperibile (op-amp e resistori)...

L'idea di questo stadio di condizionamento, con op-amp, nasce da due esigenze: la prima, di usare solo l'alimentazione 5V fornita da Arduino e non ricorrere ad un alimentatore duale esterno, anche per lo stadio di condizionamento. La seconda, di volere usare la tensione di riferimento ADC di default da 5V, dello stesso microcontrollore ATmega328.

Problema n.1

Alimentando il sensore AD590 a 5V, con in serie una resistenza da 1kOhm (non è possibile usare a questa tensione una resistenza da 10k),  abbiamo una tensione sulla stessa resistenza di 1mV per ogni grado Kelvin, mentre la risoluzione, teorica, della conversione ADC di Arduino, con Vref=5V, è circa 5mV.

Si rende dunque necessario amplificare questa tensione, per esempio, di un fattore pari a 10 volte, in modo da avere 10mV/K e quindi una risoluzione in temperatura di 0,5K (ovvero 0,5 °C).

Problema n.2

Abbiamo bisogno di un op-amp che possa lavorare ad alimentazione singola, come l'LM358, componente economico, molto reperibile, e dalle buone prestazioni. Bisogna però tenere conto della sua tensione di uscita massima (VSAT+), che è intorno ai 3,5V, quando lo si alimentata a 5V.

Supponendo di volerci limitare alla misura di una temperatura massima di 50°C, abbiamo dunque in corrispondenza una tensione di uscita massima di circa 3,2V (323K->3,23V),

Supponendo, poi, di volerci limitare alla misura di una temperatura minima di -20 °C avremo, quindi, in corrispondenza una tensione di uscita minima di circa 2,5V (250K -> 2,5V)

 

Dimensionamento dello stadio di condizionamento

Per ottenere un guadagno di tensione di 10 con un unico op-amp, in configurazione non invertente, possiamo usare due resistori da 22k e 68k in serie, per il ramo di retroazione, e un resistore da 10K collegato fra il morsetto invertente e massa; infatti abbiamo:




Risultati più accurati si possono ottenere sostituendo R2A con un'ulteriore resistenza serie formata, per esempio, da un resistore da 18k fisso e un trimmer da 5k, in modo da correggere le tolleranze dei resistori e gli effetti dovuti alle correnti di polarizzazione e alla tensione di offset dell'op-amp.


Schematico




Viene usato il primo dei due op-amp dell'LM358. la parte "A". Il resistore da 1K da 0,1% può essere eventualmente sostituito con uno dall'1% oppure da una resistenza serie compresa da un resistore fisso da 820 Ohm e un trimmer da 220 Ohm, che andrà ovviamente regolato in fase di taratura per avere esattamente 1mV/K.  In caso di disturbi, provare a inserire in parallelo alla Rs un condensatore ceramico a disco da 100nF ed, eventualmente, anche in parallelo alla serie R2A+R2B.

RISULTATI LTSPICE:







Software

float tempC;
float tempK;
const float VREF=5.0;
const float C1=0;  // cal.offset, da determinare mediante calibrazione con termometro campione
const float C2=1;  // cal.pendenza, da determinare mediante calibrazione con termometro campione 
float vin;
int N;


void setup() {
  Serial.begin(9600);
}

void loop() {
  N=analogRead(A0);           // si consiglia misura ripetuta 
  vin=N*VREF/1024.0;            
  tempK=100*vin;               // temp in K  
  tempC=(tempK-273.15)*C2+C1;   // temp in °C
  Serial.println(tempC, 1);
  delay(30000);
}

domenica 17 dicembre 2023

Arduino UNO R3: termometro 0/100°C con AD590 e una sola resistenza.

E' possibile usare il solo sensore AD590 con Arduino, senza ricorrere ad op-amp. alimentatori duali, trimmer e altri componenti? La risposta è affermativa, grazie a una tensione di riferimento ADC "segreta" presente in ogni microcontrollore di Arduino; ma occorre un piccolo accorgimento...

L'AD590 è sensore di temperatura a due terminali, che produce in uscita una corrente proporzionale alla temperatura assoluta rilevata. Esso offre ottime prestazioni in termini di accuratezza (± 0,5 °C) e immunità ai disturbi in virtù del funzionamento in corrente e non in tensione. La sua caratteristica è quella di fornire, linearmente, 1
µA per ogni grado Kelvin rilevato.

Il suo range di misura è  molto ampio: −55°C... +150°C . Grazie al fatto che fornisca una corrente proporzionale ai gradi Kelvin, quindi, anche le temperature negative saranno comunque descritte da correnti, e quindi tensioni, di polarità positiva, il che si traduce in una semplificazione dei successivi stadi di condizionamento. 

L'AD590 può essere alimentato da +4 a +30V e richiede una resistenza in serie di polarizzazione, che viene anche sfruttata per la necessaria conversione corrente/tensione.

Per approfondimenti su altre caratteristiche e sulla tecnologia costruttiva, si rimanda al DATASHEET fornito dal Costruttore.

Per una tensione di alimentazione 5V, tipicamente si usa una R=1kΩ, per 12V o 15V, invece, il valore tipico di R è 10kΩ 

Il costruttore consiglia di utilizzare un resistore di precisione (da 0,1% ), preferibilmente in serie a un trimmer, in modo da effettuare una taratura hardware in fase di messa a punto del sistema di misurazione:



Vedremo, più avanti,  che è possibile usare un resistore fisso e raggiungere lo stesso scopo, via software.

Supponiamo di volere misurare una temperatura compresa fra 0°C e 100°C, usando una R=1kΩ avremo che per T=0°C (273K) ai capi della resistenza si avranno 0,273V (1kΩ * 273µA), mentre a T=100°C (327K) si avranno 0,373V (1kΩ*373µA).

La dinamica (differenza fra tensione massima e tensione minima) è quindi di soli 0,1V. Volendo misurare questo range di tensione con Arduino, si rende dunque necessario uno stadio differenziale e amplificatore con op-amp, che porti il range 0,273...0,373V a 0....5V, in quanto la tensione di riferimento dell'ADC interno è, di default, pari a 5V; diversamente, il quanto sarebbe molto elevato.

LA TENSIONE DI RIFERIMENTO INTERNA DI 1,1V DI ARDUINO                         

La soluzione alternativa al condizionamento con amplificatore differenziale è quella di usare una tensione di riferimento più bassa, più "vicina" all'estremo superiore della dinamica del sensore, in modo da ridurre l'errore di quantizzazione e quindi migliorare la risoluzione. Esistono in commercio diversi riferimenti di tensione esterni da 1,2V a 1,25V, ma vorremmo evitarli, sempre in un'ottica di rendere quanto più semplice possibile l'hardware.

Il microcontrollore della scheda UNO R3 (ATmega328) dispone di una tensione di riferimento interna bandgap, pari a 1,1V nominali, che può essere convenientemente usata a tale scopo. 

Utilizzando una Vref=1,1V si avrà 1LSB teorico pari a circa 1mV e quindi una risoluzione di circa 1K/1°C, nel range indicato, che è sicuramente una specifica accettabile. 

HARDWARE

Lo schema è riportato di seguito:


Essendo la resistenza di ingresso del pin dell'ATmega pari a 100 MΩ, la corrente Is del sensore è praticamente la stessa che circola anche nella resistenza da 1KΩ.


MISURA DELLA RESISTENZA R                                                                                        

Avendo usato una resistenza da 1kΩ, 1%  (e non da 0,1% - ovvero un trimmer di regolazione - come suggerito dalla Casa costruttrice) occorrerà misurare il valore attuale della stessa per non introdurre un errore non trascurabile nel calcolo della corrente. Mediante un DMM 3 1/2 cifre e usando una portata di 2kΩ (è molto importante usare questa portata, per avere una misura quanto più possibile accurata), nel nostro caso è stato misurato un valore effettivo R=987 ΩQuesto valore è stato dunque assegnato alla costante R dello sketch; esso servirà per il calcolo della corrente Is (mediante la Legge di Ohm: Vs/R)


MISURA DELLA TENSIONE VS                     

Per misurare la corrente Is, ci serve, dopo la resistenza, ovviamente la tensione Vs, cioè la caduta di tensione che si localizza ai capi della resistenza R di polarizzazione (precedentemente misurata) e che viene quindi applicata all'ingresso analogico di Arduino, per essere misurata. 

Arduino misura la tensione Vs mediante la classica formula di ricostruzione dell'ADC, partendo dal presupposto che Vref sia effettivamente pari al valore nominale 1,1V; ma la tensione di riferimento interno, sebbene molto stabile, ha una tolleranza non trascurabile, cioè il valore effettivo si discosta da quello nominale di 1,1V (secondo il datasheet dell'ATmega il valore può variare, tipicamente, fra 1.0 e 1.2V).  

E' stato, dunque, caricato ed eseguito su Arduino, propedeuticamente, uno sketch che misura l'effettiva tensione di riferimento,  che determina l'effettivo valore e consente dunque di correggere la differenza fra valore nominale e valore effettivo dovuto alla tolleranza di fabbricazione.



SOFTWARE

N.B. Prima di caricare questo sketch occorre caricare - ed eseguire nel monitor seriale - lo sketch di "correzione" della tensione di riferimento interna, reperibile a questo post.

AD590.INO
/* Misura di una temperatura con Arduino Uno e sensore
 * AD590 alimentato a 5V tramite una R=1kOhm,1% => 1mV/K
 * Viene usato il valore della VREF interna memorizzato
 * sulla EEPROM, precedentemente calcolato e salvato.
 */
 
#include <EEPROM.h>

#define STAMPAVS 1         // 1 stampa VS 
#define STAMPATK 1         // 1 stampa T in gradi K

const int EEADD=0;         // ind. EEPROM dove è stato memorizzato il fatt. correttivo
const int AD590 = A0;      // pin analogico a cui è collegato l'AD590
const int R = 987;         // Valore attuale della Resistenza in serie all'AD590
float VREF;                // VREF: verrà letto dalla EEPROM 
const int DELTA_T = 30000;  // intervallo di misurazione in ms

int TC;                    // Temp misurata, in °C
int TK;                    // Temp misurata, in K
float Vs;                  // Tensione misurata (ai capi di R) da Arduino
float Is;                  // Corrente calcolata come Vs/R (in uA)

unsigned long counter=0;    // progressivo misurazioni di temperatura
unsigned long prevMillis;   // marcatore temporale per millis()

void setup() {
  Serial.begin(9600);        // Imposta rtx seriale a 9600bps (monitor seriale, PuTTY ecc)
  analogReference(INTERNAL); // Vref interna =1,1V nominale
  EEPROM.get(EEADD, VREF);    // legge locazione EEADD e assegna valore a VREF
  prevMillis=millis();       // marca istante iniziale 
}

// Effettua una misura ripetuta di Vs: 

float misuraVs() {
  float somma=0;   // somma delle letture singole di V
  float vmedio=0;  // media delle letture singole di V
  float v;         // lettura singola di V
  int n;           // numero restituito dall'ADC
  int nlett=5;     // quante letture effettive
  
  for (int i=0; i<nlett+2; i++) {
     n=analogRead(AD590);
     v=VREF*n/1024;
     if (i>1)  // scarto prime due misure
        somma=somma+v;
     delay(50);
  }
  
  vmedio=somma/nlett;  //calcola valore medio delle misure di tensione
  return (vmedio);     // ritorna la media della misure di I (in uA)
}


void loop() {
  
  /* Misura e stampa periodica della temperatura ogni DELTA_T millisecondi: */
  if(millis()-prevMillis >= DELTA_T) {
     
      Vs=misuraVs();           //misura la Vs ai capi di R e l'assegna alla var. Vs
      Is=1E6*Vs/R;             //calcola Is, con la Legge di Ohm,e la converte in uA
      TK = (int)Is ;           //es. 298.00 diventa 298K
      TC=TK-273;               //es. 298K diventa 25°C   
       
      Serial.print(++counter);         
      #if STAMPAVS
         Serial.print("\t");
         Serial.print(Vs,3);
         Serial.print("V");   
      #endif  
      #if STAMPATK              
         Serial.print("\t");
         Serial.print(TK);
         Serial.print("K");
      #endif
      Serial.print("\t");
      Serial.print(TC);
      Serial.println("°C");
      prevMillis=millis();
  }

  /* altri compiti da eseguire: */
                     
}

VISUALIZZAZIONE STAMPA DATI

Lanciando il monitor seriale, sarà possibile visualizzare la stampa dei dati:





L'informazione su Vs  può essere ovviamente non stampata, settando a 0 nello sketch la costante simbolica STAMPAVS. E' anche possibile non stampare i gradi KELVIN ponendo il simbolo STAMPATK a 0.

Arduino UNO R3: misurare la tensione di riferimento interna

u.r. 17-FEB-2026 

 

L'idea di questa procedura mi è nata quando ho dovuto effettuare la correzione software della tensione di riferimento generata dal bandgap entro contenuto nell'ATmega328P, per l'uso con il sensore AD590 senza stadi attivi di condizionamento. 

La tensione di riferimento del bandgap interno è estremamente stabile, ottima per misurare range fra 0 e 1,1V,  ma ha un'elevata tolleranza di produzione (tipicamente fra 1,0 e 1,2V) che può quindi introdurre errori non trascurabili nel calcolo della tensione misurata dall'ADC.

Il suo utilizzo corretto richiede, quindi, innanzitutto una stima del suo effettivo valore (mediante la procedura di seguito descritta) ma anche lo scarto della prima lettura (che sarebbe non accurato, così come è scritto nel datasheet).

DESCRIZIONE DELLA PROCEDURA

Il pin AREF, se non collegato a nessuna tensione esterna, fornisce in uscita il valore della tensione di riferimento attualmente configurata nel software in esecuzione su Arduino

Si misurerà, quindi, tale tensione con un voltmetro (es un multimetro digitale), che verrà memorizzato nella memoria EEPROM della scheda Arduino e richiamato, all'occorrenza, in ogni software che impieghi la stessa tensione di riferimento interna.

Evidentemente, tale procedura andrà ripetuta ogni volta si cambia la scheda Arduino UNO R3, ovvero il microcontrollore ATmega328, essendo la tolleranza di fabbricazione del bandgap interno piuttosto variabile.

La procedura è stata testata solo su questo microcontrollore, ma dovrebbe funzionare anche per altri micro ATmegax che hanno un bandgap interno da 1,1V

HARDWARE

Occorrerà semplicemente collegare un comune multimetro 3,5 cifre impostato in tensione (se portata manuale selezionare 2V =)  fra il pin AREF e la massa:


Nel mio caso è stata misurata una tensione di 1,078V:





SOFTWARE

1)  BANDGAP_1V1_CAL.INO

Misura della Vref interna e salvataggio su EEPROM

  
  
  
  
 
/****** Problema:
* la tensione di riferimento del bandgap interno 1,1V degli ATmegaX, sebbene molto
* stabile, ha una tolleranza produttiva non trascurabile. Conoscere l'effettivo valore,
* dunque, migliora notevolmente il calcolo delle tensioni misurate via software
* che usano come riferimento questa tensione.
*
****** Risoluzione:
* Mediante un DMM impostato come voltmetro (portata 2V =), si misurerà la d.d.p.
* fra il pin AREF e la GND. Il valore così misurato, verra chiesto dunque
* all'utente (dal monitor seriale) e salvato in una locazione della memoria EEPROM;
* Tale valore verrà richiamato, successivamente, nel software di lettura A/D
* che utilizza come VREF il bandgap interno.
* La procedura va ovviamente ripetuta a ogni cambio di scheda Arduino/microcontrollore.
*
****** Autore:
* Francesco Parisi, 2025-FEB-05
*/
#include <EEPROM.h>

float VREF =1.1; // tensione nominale b/gap int.
const int EEADD=0; // ind. EEPROM dove salvare la VREF misurata con il DMM

void setup() {
Serial.begin(9600);
analogReference(INTERNAL); // impostiamo Vref interna
Serial.print("\nMisura tensione con bandgap interno: ");
Serial.println(F("\n============================================="));
Serial.print("\nInserire misura tensione DMM in Volt (es. 1.078): ") ;
while (Serial.available()==0)
{ } // attende input da monitor seriale
VREF=Serial.parseFloat(); // trasforma input in float
Serial.println(VREF);
EEPROM.put(EEADD, VREF); // scrive il val. VREF all'ind. EEADD
Serial.println(F("============================================="));
Serial.print("Valore della VREF interna salvato all'indirizzo EEPROM: ");
Serial.println(EEADD);
Serial.println(F("============================================="));
Serial.println("Programma terminato con successo!");
}

void loop() {
analogRead(A0); // lettura a vuoto
}


Avviare il monitor seriale e inserire il valore di tensione misurata con il DMM (es. 1.078), battere INVIO:



Il programma procederà nell'esecuzione lo stamperà e lo salverà nella locazione di memoria EEPROM (valore predefinito 0, che può essere cambiato nella sezione dichiarativa: costante EEADD):


2)  BANDGAP_1V1_TEST.INO

Stampa su monitor seriale della variabile VREF salvata su EEPROM
 
 
 
/*
* Test lettura valore VREF memorizzato nella EEPROM
*
*/

#include <EEPROM.h>

const int EEADD=0; // ind. EEPROM dove è stato memorizzato il fatt. correttivo
float VREF; // valore della vref memorizzato all'indirizzo EEADD

void setup() {
Serial.begin(9600);
Serial.print("Indirizzo EEPROM :");
Serial.println(EEADD);
EEPROM.get(EEADD, VREF); // legge locazione EEADD e assegna valore a VREF
Serial.print("VREF = ");
Serial.println(VREF,3);
}

void loop() {

}



Attivando il monitor seriale avremo questo esempio di output:




USARE IL VALORE MEMORIZZATO NELLO SKETCH UTENTE

Nello sketch utente, ovvero lo sketch che impiegherà questo valore occorrerà innanzitutto inserire all'inizio la direttiva di inclusione del file di intestazione della libreria EEPROM:

#include <EEPROM.h>

A seguire, nella sezione globale, si dichiareranno la costante int EEADD e la variabile float VREF

Nel corpo della setup() si richiamerà la seguente funzione di assegnazione del valore letto dalla EEPROM alla variabile VREF:

EEPROM.get(EEADD, VREF);

Per un esempio di impiego , vedere il seguente progetto: https://telparblog.blogspot.com/2023/12/arduino-uno-r3-termometro-0100c-con.html




domenica 29 ottobre 2023

Un altro debounce (non bloccante) per Arduino Uno

Nell'impiego di interruttori, commutatori o pulsanti meccanici, si verifica quasi sempre un inconveniente che, nella maggioranza dei casi, costituisce causa di malfunzionamento nei circuiti digitali: l'elemento mobile che realizza il contatto, quando viene spostato da una posizione all'altra rimbalza (bouncing) diverse volte prima di stabilizzarsi al valore impostato, sia nella fase di pressione che in quella di rilascio.


Questi continui rimbalzi, che avvengono in un tempo brevissimo (generalmente fra 0,1 e 5ms), vengono percepiti dal software in esecuzione  come ripetute pressioni del pulsante e quindi erroneamente conteggiati. Il problema viene aggirato impiegando soluzioni cosiddette di anti-rimbalzo (debouncing) che possono essere circuitali o software. Una delle soluzioni software più semplici è quella di far eseguire un'istruzione di attesa, di un tempo pari, o leggermente superiore, a quello di rimbalzo, in modo che il programma ignori sicuramente queste innumerevoli commutazioni. Al termine di questo periodo, per sicurezza, si torna a leggere lo stato del pulsante: se è corrispondente a quello attivo (es. livello alto) il pulsante è stato dunque "de-rimbalzato" (debounced) e quindi può essere eseguito il codice previsto all'occorrenza della pressione del bottone.


HARDWARE                                  

Il pulsante N.A. è collegato fra il pin di ingresso (2) e la massa; il resistore di pull-up è quello interno dell'ATmegax8: quindi la pressione del pulsante comporta, a regime, la lettura sul pin 2 di un livello logico LOW, mentre il rilascio comporta, a regime,  la lettura di un livello logico HIGH. Sul pin 9 è collegato un LED (attraverso una resistenza da 220..330 Ohm), che, alla pressione del pulsante, commuterà il suo stato da acceso a spento e viceversa. 




SOFTWARE                                 

Lo sketch è imperniato sull'intercettazione della pressione del pulsante (ovvero del rilascio) attraverso un'istruzione di selezione semplice if, che va a "testare" lo stato corrente del pulsante (memorizzato nella variabile statoP1)  e quello della precedente esecuzione del loop (memorizzato nella variabile statoP1prec).  Se i due stati sono diversi vuol dire che c'è stata una variazione (da HIGH a LOW: pressione oppure da LOW a HIGH: rilascio), ma per essere sicuri che sia la prima variazione - ovvero la pressione o il rilascio - e quindi non una successiva variazione dovuta ai rimbalzi, occorre testare una terza variabile di flag che assume valore 0 solo quando non si è ancora verificata la prima variazione.

Se dunque si verifica la condizione logica (stato attuale pulsante NOT EQUAL a quello precedente AND flag=0) dalla pressione (ovvero dal rilascio) , attraverso un marker temporale (variabile t1) viene "segnato" l'istante conteggiato dalla funzione millis() e ovviamente settata a 1 la variabile flag, essendosi verificato l'evento di prima variazione.

Successivamente , a ogni loop, si andrà a leggere il valore  millis() e a confrontarlo con t1, se la differenza è MAGGIORE O UGUALE a 5ms (tempo tipico di attesa per l'esaurirsi del debounce; potrebbe essere necessario aumentarlo, se la qualità dei pulsanti è bassa) si andrà allora a testare lo stato del pulsante: se LOW, allora è una condizione di pressione e quindi si va ad eseguire il codice previsto all'occorrenza della pressione del pulsante.

Diversamente, verrà eseguito il codice restante dello sketch, cioè quello non relativo all'evento della pressione del pulsante, senza alcuna pausa. Infatti l'uso di millis()rende la gestione "non bloccante" dell'intero software, a differenza delle istruzioni di delay(): infatti il "costo" temporale delle istruzioni di check del tempo trascorso sono assolutamente trascurabili rispetto alle decine di millisecondi che avrebbe richiesto una delay.

Ad ogni loop, ovviamente, occorrerà aggiornare lo stato precedente del pulsante per il successivo loop, che ovviamente sarà uguale a quello attuale. Generalmente questa istruzione viene messa come ultima nel corpo della loop().

MIO_DEBOUNCE.INO

/*
 *  Debounce con millis() sulla pressione e sul rilascio
 *  del pulsante.
 *
*/
const int P1=2;   // pulsante 
const int L1=9;   // LED

int statoP1=LOW;       // stato P1 (premuto LOW / rilasciato HIGH)
int statoP1prec=LOW;   // stato precedente P1
int statoL1=LOW;       // stato LED (spento LOW / acceso HIGH)
int flag=0;            // 1: pressione o rilascio
int T=5;               // tempo attesa bouncing (5ms)
unsigned long t1;      // marker temporale di millis()

void setup() {
  pinMode(P1, INPUT_PULLUP);  // attiva la R pull-up interna
  pinMode(L1, OUTPUT);
  digitalWrite(L1, statoL1);
}

void loop() {

  statoP1 = digitalRead(P1);

  // rilevata una pressione o un rilascio
  if (statoP1 != statoP1prec && !flag)
  {
     flag=1;        // flag di avvenuta pressione/rilascio
     t1=millis();   // marca inizio conteggio tempo
  }

  // se si è esaurito il rimbalzo
  if ( millis()-t1 >= T && flag) 
  {
     flag=0;    // azzera flag  (per successive pressioni/rilasci)
// se è una pressione if (statoP1==LOW) { // istruzioni da eseguire alla pressione di P1: statoL1=!statoL1; digitalWrite(L1, statoL1); } } // istruzioni da eseguire al di fuori della pressione di P1: statoP1prec = statoP1; }