PrettyPrint

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; }