Sei sulla pagina 1di 128

La Conversione Analogico/Digitale con

Arduino
Prima edizione: Febbraio 2016
Copyright © 2016 Giulio Tamberi

Ad eccezione degli esempi di codice, nessuna parte del presente libro può essere riprodotta,
memorizzata in un sistema che ne permetta l’elaborazione, né trasmessa in qualsivoglia forma
e con qualsivoglia mezzo elettronico o meccanico, né può essere fotocopiata, riprodotta o
registrata altrimenti, senza previo consenso scritto dell’autore, tranne nel caso di brevi
citazioni contenute in articoli di critica o recensioni. La presente pubblicazione contiene
le opinioni dell’autore e ha lo scopo di fornire informazioni precise e accurate.
L’elaborazione dei testi, anche se curata con scrupolosa attenzione, non può comportare
specifiche responsabilità in capo all’autore e/o all’editore per eventuali errori o
inesattezze.
Nomi e marchi citati nel testo sono generalmente depositati o registrati dalle rispettive
aziende. L’autore detiene i diritti per tutte le fotografie, i testi e le illustrazioni che
compongono questo libro, salvo quando diversamente indicato.
INDICE
INTRODUZIONE
Note
L’ADC DELL’ATmega328
I microprocessori delle schede Arduino
Microprocessori a 8-bit
Microprocessori a 32-bit
Il convertitore Successive Approximation Register (SAR)
GLI INGRESSI
Il multiplexer interno e la funzione analogRead()
L’intervallo delle tensioni in ingresso
Ridurre le tensioni in ingresso
Partitore di tensione
Ottimizzare l’intervallo delle tensioni di ingresso
Amplificazione del segnale di ingresso
Riduzione della tensione di riferimento
Buffer di ingresso digitale
Resistori di pull-up interni
L’impedenza di uscita del segnale in ingresso
LA RISOLUZIONE E LE CIFRE SIGNIFICATIVE
La risoluzione
Come migliorare la risoluzione?
Stimare la tensione
1023 oppure 1024?
Ottimizzazione dell’errore di quantizzazione (Half LSB Adder)
Le cifre significative
IL RIFERIMENTO DI TENSIONE DEL CONVERTITORE
Le possibili impostazioni della funzione analogReference()
Modalità DEFAULT
Modalità EXTERNAL
Modalità INTERNAL
Le tensioni disponibili sulla scheda Arduino
USB POWER SUPPLY
LDO 5V
On-board 3.3V
Esempio con AREF
L’ACCURATEZZA DELLA CONVERSIONE
La stabilità del riferimento di tensione
L’accuratezza sulla stima della tensione in ingresso
L’accuratezza sulla stima della misura da un sensore
Esempio della misura della temperatura
Esempio della misura dell’umidità relativa
La calibrazione del riferimento di tensione dell’ADC
Misura esterna con un voltmetro
Misura interna riferita alla tensione interna di bandgap
Misura interna riferita a una tensione esterna
La calibrazione dell’ADC
IL CAMPIONAMENTO
Il teorema del campionamento
La frequenza di campionamento con l’ATmega328
Il tempo di conversione
La scelta della frequenza di campionamento
Il campionamento uniforme
LA TECNICA DELL’OVERSAMPLING
Massimo oversampling
Oversampling vs. rumore
Oversampling vs. velocità di variazione
Le cifre significative in caso di oversampling
La frequenza di campionamento con oversampling
Filtraggi avanzati per l’oversampling
Filtri a media ponderata
Svantaggi nell’utilizzo della finestra mobile
Filtri a mediana
BIBLIOGRAFIA
CONTATTI
INTRODUZIONE
Il principale merito di Arduino è aver reso accessibile a una larga
base di persone uno strumento versatile, di semplice utilizzo e a
basso costo per realizzare progetti anche senza possedere una
specifica preparazione tecnica in elettronica o nella programmazione
di microcontrollori. Questo lo ha consacrato all’attuale successo
internazionale presso quella variopinta comunità di designer,
inventori e appassionati che siamo ormai abituati a indicare con il
termine di Makers.
Accanto alla documentazione ufficiale, il progetto Arduino ha
generato nel corso degli anni moltissimi contenuti sul web, tra cui
il Forum, il Playground e il Project Hub sul sito Arduino stesso, ma
anche le numerose pagine sui social network come Facebook (Arduino
Projects, Arduino Arts, ecc.) e Google+ (Arduino Basics, Xtreme
Arduino, ecc.), i vari canali di Instructables (Arduino, Arduino
Projects, ecc.) e le aree dedicate su Dangerous Prototypes, SparkFun
Electronics, Adafruit, Reddit e Flickr.
La popolarità di Arduino risiede proprio in questa fertile web
community i cui confini digitali sono in costante espansione grazie
alla creazione di nuovi contenuti declinati su applicazioni
tecnologiche sempre più diversificate (domotica, stampa 3D,
droni/modellismo, fotografia, ecc.).
Tuttavia, accanto alla possibilità di consultare liberamente questa
documentazione condivisa, resta aperta la questione
dell’attendibilità delle informazioni, poiché in questi spazi
virtuali è possibile pubblicare liberamente dei contenuti per
contribuire alla discussione collettiva, ma per quanto le intenzioni
possano essere nobili non ne sono sempre garantite l’esattezza e
l’affidabilità. In particolare, quello del convertitore
analogico/digitale (ADC) è appunto uno degli argomenti su cui si
registrano maggiori imprecisioni e grossolani errori che tendono
purtroppo a diffondersi rapidamente in rete, rimanendo consultabili
nel tempo e riverberandosi negativamente nel codice (sketch) e negli
schemi elettrici utilizzati da altri utenti. In molti casi questo
argomento potrebbe essere considerato un problema minore, ma sta
diventando centrale da quando la famiglia delle schede Arduino ha
iniziato a diffondersi in applicazioni prototipali avanzate,
specialmente nelle aree emergenti delle reti di sensori intelligenti
(Internet of Things) e delle tecnologie indossabili (Wearables).
Questa guida nasce da esperienze raccolte progettando con le schede
Arduino e illustra le possibilità per utilizzarne al meglio l’ADC,
con una particolare attenzione per quelle più diffuse basate sul
microprocessore Atmel ATmega328, come la Arduino UNO. Con
riferimenti teorici ed esempi applicativi, offre una base per
sviluppare progetti più accurati e migliorare quelli già realizzati,
specialmente nell’ambito delle acquisizioni di segnali da sensori
analogici.
Consultando questa guida troverete la risposta a domande come
queste:
─ Quanti segnali analogici può acquisire contemporaneamente una
scheda Arduino?
─ È vero che alcune schede Arduino basate sul microprocessore
ATmega328 consentono due ulteriori ingressi single-ended?
─ Qual è la massima tensione consentita in ingresso all’ADC di una
scheda Arduino UNO? E nel caso di Arduino ZERO?
─ È possibile in qualche modo acquisire segnali la cui tensione sia
maggiore di quella massima consentita in ingresso all’ADC di una
scheda Arduino?
─ Qual è la risoluzione di tensione dell’ADC di una scheda Arduino
UNO? E qual è la sua dipendenza dalla tensione di riferimento?
─ Nelle formule di conversione della tensione nel codice è più
corretto utilizzare il valore 1023 o 1024?
─ È ragionevole eseguire misure di tensione con una risoluzione
dell’ordine dei microVolt con una scheda Arduino UNO?
─ Quante sono le cifre decimali significative su cui ha senso
visualizzare le acquisizioni dell’ADC?
─ In seguito a un cambiamento del riferimento di tensione con la
funzione analogReference(), quante conversioni dell'ADC occorre
scartare prima di poterle considerare attendibili?
─ È possibile aumentare la risoluzione dell’ADC di una scheda
Arduino?
─ Si ottengono acquisizioni più accurate con una scheda Arduino UNO
oppure con Leonardo, Yún o Mega?
─ Utilizzando schede Arduino con sensori di temperatura analogici, è
possibile misurare temperature con un’accuratezza di ±0.1°C?
─ Quanto incide l’accuratezza della tensione di alimentazione sulle
misure dell’ADC di una scheda Arduino?
─ È sufficiente acquisire un campione ogni secondo per monitorare la
temperatura ambiente?
─ Qual è il tempo di conversione effettivo di una scheda Arduino?
─ È possibile utilizzare Arduino per acquisire segnali audio?
─ In cosa consiste la tecnica dell’oversampling?
─ Ha senso implementare un oversampling a 13-bit con una scheda
Arduino UNO?
Note
Se vi siete avvicinati a questa guida molto probabilmente avrete già
familiarità con Arduino e per questo motivo non sono state riportate
le indicazioni su come muovere i primi passi con questa scheda e
installarne l’ambiente di sviluppo integrato IDE (Integrated
Development Environment), per le quali è comunque possibile fare
riferimento alle letture consigliate nella bibliografia o
all’indirizzo Getting Started with Arduino.
Le illustrazioni dei circuiti sulle breadboard sono state realizzate
grazie a Fritzing.
Le analisi con il metodo Monte Carlo sono state eseguite con Oracle
Crystal Ball.
Per una maggiore praticità per il lettore, tutti gli sketch presenti
in questo testo sono disponibili online tramite degli appositi link
ad Arduino Web Editor. È possibile distribuirli, modificarli,
ottimizzarli e utilizzarli, anche commercialmente. La citazione
dell’autore è gradita, ma non indispensabile.
Per indicare il convertitore analogico/digitale in letteratura si
utilizzano diversi termini (Analog-to-Digital Converter,
Analog/Digital Converter, ecc.) e acronimi (ADC, A/D, A-D, A2D). In
questo testo si fa riferimento all’acronimo ADC (Analog-to-Digital
Converter). Inoltre, per ragioni di semplicità, si impiega il
termine microprocessore anche laddove sarebbe più pertinente
ricorrere al termine microcontrollore.
L’ADC DELL’ATmega328
I microprocessori delle schede Arduino
Il seguente elenco riassume i principali microprocessori utilizzati
nelle schede Arduino e Arduino-compatibili:

Microprocessori a 8-bit
Atmel AVR:
─ ATmega328: è presente sulla celebre scheda Arduino/Genuino UNO e
sulle schede Duemilanove, Pro e Pro Mini, Nano 3.0, Fio, LilyPad (e
le varianti Simple e SimpleSnap), Bluetooth, Ethernet, SquareWear
2.0, Sparkfun RedBoard, Seeeduino 4.0, Iteaduino UNO, AVR.duino U+,
Olimexino-328, Eleven, Fishino UNO, Arduino UNO WiFi e Adafruit
METRO 328
─ ATmega1280: è presente sulla versione Arduino/Genuino Mega
─ ATmega2560: si trova sulle schede Arduino/Genuino Mega 2560 e ADK
─ ATmega32u4: è presente sulle versioni Arduino/Genuino Micro,
Leonardo, Leonardo ETH, Yún, Yún Mini, LilyPad USB, Industrial 101,
Qduino Mini, Bare Conductive Touch Board, Olimexino-32U4 e Adafruit
Feather 32u4 Adalogger

Microprocessori a 32-bit
Atmel ARM Cortex:
─ ATSAMD21: è il microprocessore Cortex M0+ delle schede
Arduino/Genuino ZERO, M0, M0 PRO, Tian, Adafruit Feather M0 ed è
contenuto anche nel modulo System on Chip (SoC) Atmel ATSAMW25 della
scheda Arduino/Genuino MKR1000
─ ATSAM3X8E: è il microprocessore Cortex M3 della scheda Arduino DUE
Texas Instruments ARM Cortex:
─ Sitara AM335x: è il microprocessore Cortex A8 della scheda Arduino
TRE
Intel Quark System on Chip (SoC):
─ Quark X1000: è il microprocessore x86 delle schede Intel Galileo e
Intel Galileo Gen 2
─ Quark SE Curie: è presente sulla scheda Arduino/Genuino 101

Tuttavia, la maggior parte delle schede Arduino e Arduino-


compatibili presenti oggi sul mercato utilizza i microprocessori
Atmel AVR e, principalmente, l’ATmega328. Ed è appunto sul suo
convertitore analogico/digitale che si concentra l’analisi di questa
guida, con un particolare riferimento alla sua implementazione sulla
scheda Arduino UNO, che è di fatto la più popolare e diffusa di
tutta la famiglia Arduino.
Il convertitore Successive Approximation
Register (SAR)
L’ADC del microprocessore ATmega328 è basato su un’architettura di
tipo Successive Approximation Register (SAR) a 10-bit, che sebbene
sia più lenta rispetto ad altri convertitori (come, ad esempio,
quelli di tipo Flash) è piuttosto comune nei microcontrollori
commerciali per l’interfaccia semplice, la linearità e l’efficienza
dei consumi. L’architettura SAR utilizza, infatti, un comparatore
che riceve in ingresso il segnale da convertire e un segnale
generato internamente da un convertitore digitale-analogico (DAC,
Digital-to-Analog Converter) e, seguendo una sorta di algoritmo di
ricerca binaria, consente di approssimare la tensione in ingresso,
dal bit più significativo a quello meno significativo, da cui il
nome “ad approssimazioni successive”. Ad ogni ciclo del clock
interno all’ADC, l’uscita del DAC converge verso il valore del
segnale in ingresso e il risultato è memorizzato nel registro di
uscita. Questa operazione di conversione richiede un certo numero di
cicli, che aumenta al crescere del numero di bit del convertitore.
La durata di ciascun ciclo è comunque limita dai tempi di
commutazione della logica interna e del DAC.
Le principali caratteristiche dell’ADC dell’ATmega328 sono comuni
anche agli ADC degli altri microprocessori Atmel AVR, tutti infatti
basati sulla stessa architettura SAR a 10-bit. In sintesi, le
principali differenze dei microprocessori ATmega1280, ATmega2560 e
ATmega32u4 rispetto all’ATmega328 sono:
─ un maggior numero di ingressi single-ended (16 su ATmega1280/2560;
12 su ATmega32u4)
─ la presenza anche di ingressi differenziali (14 su
ATmega1280/2560, di cui 4 con guadagno opzionale di 10× 200×; 1 su
ATmega32u4 con guadagno opzionale di 1× 10× 40× 200×)
─ la possibilità di impostare il riferimento di tensione interno
dell’ADC a 2.56V

I microprocessori utilizzati invece nelle schede Arduino ZERO, DUE e


TRE hanno un ADC a 12-bit e un maggior numero di ingressi (ad
esempio, su DUE sono presenti 12 single-ended), mentre le schede
Intel Galileo utilizzano un ADC esterno collegato al microprocessore
Quark X1000 con una porta SPI: nel caso della prima generazione si
tratta di un AD7298 a 12-bit di Analog Devices con 8 ingressi
single-ended, mentre nella seconda generazione è un ADC108S102 a 10-
bit di Texas Instruments con 8 ingressi single-ended.
Fig. 1 – Il diagramma a blocchi dell’ATmega328 (in giallo sono evidenziati i moduli
dell’ADC).
GLI INGRESSI
Nella maggior parte dei casi pratici i sistemi realizzati con
Arduino utilizzano in ingresso uno, due o al più tre segnali
analogici (temperature, umidità relativa di ambienti, tensioni di
batterie, segnali da sensori di pressione (piezoelettrici),
forza/flessione (estensimetri), ecc.).
Il microprocessore ATmega328 è comunque dotato di un convertitore
ADC, che nella versione con package PDIP a 28 pin consente fino a 6
ingressi, mentre nelle versioni SMD a 32 pin (nei package TQFP and
QFN/MLF) offre ulteriori 2 ingressi. In tutti i casi, si tratta di
ingressi per segnali di tipo single-ended (ovvero in cui la tensione
è riferita al pin GND). Il convertitore ADC dell’ATmega328, infatti,
non ha ingressi per segnali differenziali, ma è comunque possibile
ricorrere a un amplificatore operazionale differenziale esterno come
il diffuso LM358, che è disponibile anche nel pratico package DIP a
8-pin e alimentabile direttamente con la stessa tensione di 5V
normalmente utilizzata per la scheda Arduino.
Nel caso invece occorra acquisire un numero di ingressi maggiore di
quelli presenti su Arduino, è possibile ricorrere a un multiplexer
esterno come il CD74HC4067 che con 4 pin di controllo, collegabili
ai pin digitali di Arduino, consente di gestire fino a 16 ingressi.
Fig. 2 – Le versioni di package dell’ATmega328 (in giallo sono evidenziati i pin dell’ADC).
PIN (ATmega328) PIN (Arduino) Package
ADC0 A0
ADC1 A1
ADC2 A2
Tutti
ADC3 A3
ADC4 A4
ADC5 A5
ADC6 A6
Solo 32-pin TQFP e QFN/MLF
ADC7 A7

In realtà, confrontando le schede Arduino e Arduino-compatibili


basate sull’ATmega328 con package TQFP o QFN/MLF, i due ingressi
aggiuntivi sono presenti soltanto sulle schede Arduino Pro, Arduino
Nano 3.x, Arduino Bluetooth, Seeeduino 3.0 e Olimexino-328.

Fig. 3 – La scheda Arduino UNO con evidenziati i pin che riguardano l’ADC.
Fig. 4 – La scheda Seeeduino 3.0 con evidenziati i pin 6 e 7 di ulteriore ingresso all’ADC.

Fig. 5 – La scheda Olimexino-328 con evidenziati i pin 6 e 7 di ulteriore ingresso all’ADC.


Il multiplexer interno e la funzione
analogRead()
Per acquisire una tensione in ingresso all’ADC, Arduino utilizza la
funzione analogRead() che seleziona il pin di ingresso e comanda la
conversione. Tutti gli ingressi analogici sono gestiti da un
multiplexer che, in conformità a quanto previsto dall’utente nello
sketch, li connette all’unico convertitore ADC interno che può
convertire soltanto un ingresso per volta.
Ad esempio, una scheda Arduino UNO programmata con il seguente
sketch acquisisce ogni secondo il segnale sul pin A0 e ne stampa sul
monitor seriale il valore convertito su 10-bit:

byte analogPin = 0; // set the analog input pin


unsigned int ADC_value = 0; // variable storing the ADC reading

void setup()
{
Serial.begin(9600); // baud rate for the serial communication
}

void loop()
{
ADC_value = analogRead(analogPin); // read the analog input
Serial.println(ADC_value); // print the ADC reading
delay(1000); // wait for a second
}

Per la variabile analogpin che definisce l’ingresso per l’ADC è


stato utilizzato il tipo byte che consente di memorizzare numeri
interi postivi da 0 a 255, quindi ampiamente sufficiente per tutte
le schede Arduino.
Nel caso si desideri campionare più ingressi in parallelo (sensori
di diverso tipo, uscite multiple di uno stesso sensore, segnali
audio relativi a più canali, ecc.) questi saranno in realtà
campionati in modo sequenziale, producendo così uno sfasamento
temporale (generalmente indicato in letteratura come channel-to-
channel skew) che dipende sia dal tempo di conversione intrinseco
dell’ADC e sia dal tipo di operazioni che sono programmate nello
sketch dall’utente. In altre parole, per via della particolare
architettura, non si ha la contemporaneità delle conversioni sui
vari ingressi analogici. Nella maggior parte dei casi questo non
costituisce un problema, ma in alcune applicazioni (ad esempio, con
sensori high-speed o nel caso di analisi in frequenza su campioni
provenienti da diversi canali) occorre tenerne conto per evitare
grossolani errori nella fase dell’elaborazione dei dati, per cui
potrebbe essere necessario compensare i ritardi nel codice.
Ad esempio, una scheda Arduino UNO programmata con il seguente
sketch acquisisce ogni secondo i segnali presenti sui pin A0, A1,
A2, A3 e ne stampa su una riga del monitor seriale il relativo
valore convertito su 10-bit:

unsigned int ADC_value_0 = 0; // variable storing the ADC reading for pin A0
unsigned int ADC_value_1 = 0; // variable storing the ADC reading for pin A1
unsigned int ADC_value_2 = 0; // variable storing the ADC reading for pin A2
unsigned int ADC_value_3 = 0; // variable storing the ADC reading for pin A3

void setup()
{
Serial.begin(9600); // baud rate for the serial communication
}

void loop()
{
ADC_value_0 = analogRead(0); // read the input on analogPin A0
ADC_value_1 = analogRead(1); // read the input on analogPin A1
ADC_value_2 = analogRead(2); // read the input on analogPin A2
ADC_value_3 = analogRead(3); // read the input on analogPin A3

Serial.print(ADC_value_0); // print the ADC reading for pin A0


Serial.print("\t"); // prints a tab
Serial.print(ADC_value_1); // print the ADC reading for pin A1
Serial.print("\t"); // prints a tab
Serial.print(ADC_value_2); // print the ADC reading for pin A0
Serial.print("\t"); // prints a tab
Serial.println(ADC_value_3); // print the ADC reading for pin A0 and a carriage return

delay(1000); // wait for a second


}

Quando si acquisiscono più ingressi, specialmente se provenienti da


sensori con impedenza di uscita elevata, è sempre meglio scartare la
prima lettura dopo una chiamata della funzione analogRead() su un
pin diverso; infatti, la prima lettura potrebbe essere errata a
causa del transitorio necessario affinché l’uscita del multiplexer
sia andata completamente a regime. Una possibile soluzione,
facilmente implementabile nel codice, consiste nel definire una
nuova funzione personalizzata sulla base della analogRead() in cui
una variabile statica interna tenga traccia dell’ultimo pin
utilizzato.

ESERCIZIO: Si invita il lettore a definire questa nuova funzione


personalizzata e a utilizzarla nel codice per convertire
sequenzialmente quattro segnali analogici collegati in ingresso sui
pin A0, A1, A2 e A3 di una scheda Arduino UNO.
Fig. 6 – Il diagramma a blocchi del convertitore analogico/digitale dell’ATmega328.
L’intervallo delle tensioni in ingresso
L’ADC dell’ATmega328 è di tipo unipolare, cioè accetta tensioni in
ingresso positive comprese tra 0V e la tensione di fondoscala, che è
pari alla tensione di riferimento (VREF) dell’ADC stesso. Ad
esempio, su Arduino UNO nelle condizioni di default VREF = 5V
l’intervallo di tensione consentito è tra 0V e 5V. Applicare una
tensione al di fuori da questo intervallo potrebbe danneggiare
irreversibilmente il microprocessore.

ATTENZIONE: Nelle schede Arduino alimentate a 3.3V (come la ZERO, la


DUE e la TRE), la massima tensione consentita in ingresso all’ADC è
appunto 3.3V. Occorre quindi tenerne conto quanto si riutilizzano
circuiti progettati per Arduino UNO per gestire segnali di ingresso
con tensioni inferiori a 5V ma maggiori di 3.3V.
Ridurre le tensioni in ingresso
Partitore di tensione
Nel caso in cui i segnali di ingresso siano tensioni positive, ma
con un valore massimo maggiore della tensione VREF dell’ADC, è
possibile utilizzare un partitore di tensione come mostrato nella
figura seguente:

Fig. 7 – Un esempio di partitore di tensione.

In cui:

VOUT = VIN × R2 / (R1 + R2)

I valori delle resistenze R1 e R2 devono essere scelti in modo tale


che la massima tensione del segnale d’ingresso corrisponda al più
alla tensione VREF dell’ADC, ovvero:

VREF = VIN_MAX × R2 / (R1 + R2)

Da cui si ricava il rapporto:

R1 / R2 = VIN_MAX / VREF − 1

Ad esempio, volendo scalare un intervallo di tensioni da 0÷12V a


0÷5V:

R1 / R2 = 12V / 5V – 1 = 1.4

Per la scelta dei valori delle resistenze tali da soddisfare questo


rapporto sono possibili molte possibilità. Tuttavia, bisogna
considerare alcuni vincoli.
Innanzitutto, occorre non sovraccaricare la sorgente del segnale VIN
(ad esempio, nel caso di una tensione da una batteria questa
potrebbe scaricarsi oppure un sensore potrebbe avere comportamenti
anomali nella sua caratteristica di uscita), ovvero:

R1 + R2 >> RIN

Dove RIN indica la resistenza di uscita della sorgente del segnale


VIN che, nella maggior parte delle applicazioni, è compresa tra
qualche decimo di Ohm e alcune decine di Ohm, per cui sono
certamente adeguati valori tali che:

R1 + R2 > 1kΩ

D’altra parte però, scegliendo dei valori molto elevati, potrebbe


verificarsi che la resistenza equivalente di uscita su VOUT sia
maggiore di quella consentita dall’ingresso dell’ADC.
Secondo il datasheet, l’ADC dell’ATmega328 è ottimizzato per
ricevere in ingresso segnali provenienti da una sorgente la cui
impedenza di uscita sia al massimo 10kΩ, ovvero:

R1 // R2 = R1R2 / (R1 + R2) < 10kΩ


R1R2 < (R1 + R2) × 10kΩ < 1kΩ × 10kΩ = 10MΩ2

Quindi riassumendo i vincoli che occorre rispettare sono:

R1 + R2 > 1kΩ
R1R2 < 10MΩ2

È inoltre opportuno scegliere le resistenze con una piccola


tolleranza poiché il rapporto tra i loro valori inciderà nella
catena dell’accuratezza complessiva della conversione
analogico/digitale.
Considerando la serie standard E96 (con una tolleranza pari a ±1%),
una scelta compatibile per questo esempio è:

R2 = 1kΩ
R1 = 1.4kΩ

Ovviamente utilizzando la serie standard E192 (con tolleranze di


±0.5%, ±0.25% e ±0.1%) si otterrebbe un risultato più accurato ma
con maggiori costi e difficoltà nella reperibilità.

ESERCIZIO: Calcolare i valori delle resistenze R1 e R2 della serie


E192 nel caso occorra scalare la massima tensione da VIN_MAX = 12V a
VREF = 1.1V.
Ottimizzare l’intervallo delle tensioni
di ingresso
Per minimizzare l’effetto dell’errore di quantizzazione occorre
sfruttare quanto più possibile tutto l’intervallo delle tensioni
consentite in ingresso all’ADC. A questo scopo è possibile
condizionare i segnali di ingresso in modo da renderli compatibili
con l’ADC.
Ad esempio, supponiamo che l’ADC riceva in ingresso il segnale da un
sensore analogico la cui tensione sia compresa tra 0V e VIN_MAX <
VREF. Si possono seguire sostanzialmente due strategie,
implementabili singolarmente o anche in modo combinato:

Amplificazione del segnale di ingresso


In questo caso si amplifica il segnale da convertire di un fattore
quanto più possibile uguale a VREF / VIN_MAX. Ad esempio, utilizzando
un amplificatore non invertente il cui guadagno sia impostato come:

1 + R2 / R1 = VREF / VIN_MAX

Fig. 8 – Un esempio di un amplificatore operazionale in configurazione non invertente.

Nel caso in cui la tensione minima del sensore VIN_MIN sia diversa da
zero, occorrerà anche eseguire una traslazione di livello per
adattare il fondoscala alla tensione minima dell’ADC:

VOUT = V2 × (1 + R2 / R1) × R4 / (R3 + R4) – V1 × R2 / R1

Per cui:

R4 / R1 × (R1 + R2) / (R3 + R4) = VREF / VIN_MAX


V1 × R2 / R1 = VIN_MIN

E nel caso particolare in cui si scelga opportunamente R1 // R2 = R3


// R4 allora:

R2 / R1 = VREF / VIN_MAX
(R2 / R1) × V1 = VIN_MIN → V1 = (VIN_MAX × VIN_MIN) / VREF

Fig. 9 – Un esempio di un amplificatore operazionale in configurazione differenziale.

Riduzione della tensione di riferimento


In questo caso si sceglie la minima VREF in modo che il massimo
valore del segnale in ingresso all’ADC sia inferiore ad essa. Ad
esempio, nell’ipotesi di utilizzare un sensore di temperatura LM35
per misurare una temperatura ambiente compresa tra 0°C e 50°C, è
possibile ridurre la tensione di riferimento dell’ADC da 5V al
riferimento interno di 1.1V. Infatti, con 50°C l’uscita del sensore
è:

50°C × 10mV/°C = 500mV < 1.1V

Se l’uscita del sensore fosse 1.1V corrisponderebbe a una


temperatura di 110°C.
Buffer di ingresso digitale
I pin di ingresso analogici di Arduino A0-A5 possono in realtà
essere programmati anche come pin I/O digitali, e per questo motivo
hanno un buffer interno per le funzioni digitali. Quando si desidera
impiegarli soltanto come ingressi analogici è opportuno
disabilitarne i relativi buffer digitali sia per ridurre il
crosstalk del rumore prodotto dai circuiti digitali ma anche per
limitare i consumi di corrente e per non sovraccaricare eventuali
segnali in ingresso nel caso di elevati carichi resistivi (a causa
della capacità di ingresso dei buffer digitali).
Per disabilitare i buffer digitali occorre configurare i relativi
bit del registro Digital Input Disable Register (DIDR0), inserendo
le seguenti righe di codice all’interno della sezione void setup():
void setup()
{
//…

bitSet(DIDR0, ADC0D); // disable digital input on ADC0


bitSet(DIDR0, ADC1D); // disable digital input on ADC1
bitSet(DIDR0, ADC2D); // disable digital input on ADC2
bitSet(DIDR0, ADC3D); // disable digital input on ADC3
bitSet(DIDR0, ADC4D); // disable digital input on ADC4
bitSet(DIDR0, ADC5D); // disable digital input on ADC5

//…
}

Fig. 10 – Il registro Digital Input Disable Register (DIDR0).

Gli ingressi di Arduino A6 e A7 (che corrispondono ai pin ADC6 e


ADC7 dell’ATmega328) non hanno i buffer di ingresso digitale e
quindi non richiedono di disabilitare i relativi bit nel registro
DIDR0.
Nel caso in cui alcuni dei pin A0-A5 di Arduino siano utilizzati
come uscite digitali è importante che questi siano gestiti
opportunamente nello sketch in modo che non si verifichino
commutazioni di uscita mentre è in corso una conversione
analogico/digitale su uno degli altri pin eventualmente configurato
come ingresso analogico. Infatti, il rumore dovuto alle commutazioni
delle uscite digitali interferirebbe con l’accuratezza della
conversione del segnale analogico di ingresso.
Una soluzione suggerita al lettore è quella di introdurre un piccolo
ritardo nel codice tra la commutazione di un segnale di uscita e
l’acquisizione di un segnale analogico di ingresso.
Resistori di pull-up interni
Ciascuno dei pin A0-A5 di Arduino (ADC0-ADC5 dell’ATmega328) ha un
pull-up interno abilitabile di valore compreso tra 20kΩ e 50kΩ. I
pin A6-A7 (ADC6 e ADC7 dell’ATmega328) non hanno invece alcun pull-
up interno.

Fig. 11 – Lo schema equivalente di un pin di ingresso dell’ADC.

La corrente di perdita dovuta alle protezioni d’ingresso sul pin è


trascurabile per le applicazioni tipiche a temperatura ambiente.
La capacità interna di 14pF è dovuta in parte al multiplexer ma
soprattutto al condensatore interno di Sample and Hold (S/H).
È importante che il pin di ingresso dell’ADC sia configurato come
ingresso in alta impedenza. Infatti, nel caso in cui il suo pull-up
interno fosse abilitato, formerebbe un partitore di tensione con
l’impedenza di uscita del segnale da acquisire riducendo così
l’accuratezza della conversione. Inoltre, l’assorbimento di corrente
dall’alimentazione sul pull-up inciderebbe, anche se in piccola
quota, sui consumi.
Fino alla versione Arduino IDE 1.0 per gestire i pull-up interni era
necessario ricorrere a due distinti comandi nel codice. Ad esempio,
per abilitare la resistenza di pull-up occorreva prima configurare
un pin come INPUT con la funzione pinMode() e poi impostare la
relativa uscita come HIGH con il comando digitalWrite(). Dalla
versione Arduino IDE 1.0.1 il software può gestire la configurazione
dei pull-up interni con il solo comando pinMode(). Ad esempio, per
configurare il pin A0 come ingresso e disabilitarne il relativo
pull-up interno è sufficiente eseguire il seguente comando:

pinMode(A0, INPUT); // set pin A0 to input and the internal pull-up is not enabled

Fortunatamente di default tutti i pin A0-A5 di Arduino (ADC0-ADC5


dell’ATmega328) sono configurati come ingressi con il pull-up
interno disabilitato e quindi non è necessaria alcuna impostazione
inziale nella sezione void setup().
L’impedenza di uscita del segnale in
ingresso
Il convertitore dell’ATmega328 è ottimizzato per ricevere in
ingresso segnali provenienti da una sorgente la cui impedenza di
uscita sia sufficientemente bassa. Secondo il datasheet Atmel,
questa deve essere al massimo di 10kΩ. Infatti, in presenza di
sorgenti a impedenza maggiore, per via del carico RC interno all’ADC
(ovvero, con riferimento allo schema precedente, RADC che è compresa
tra 1kΩ e 100kΩ e CADC che ha un valore di ≈ 14pF) il tempo di
trasferimento della carica al circuito di Sample and Hold potrebbe
risultare eccessivo per garantire una corretta conversione del
segnale.
Per acquisire segnali da sensori a impedenza di uscita maggiore di
10kΩ si suggerisce l’impiego di amplificatori operazionali low noise
in configurazione voltage follower in modo da diminuire l’impedenza
di uscita e mantenere il livello del segnale.
LA RISOLUZIONE E LE CIFRE
SIGNIFICATIVE
La risoluzione
La conversione analogico/digitale è costituita da due fasi
sequenziali: il campionamento e la quantizzazione.
Campionare un segnale significa registrarne il valore a certi
istanti, normalmente a intervalli di tempo di durata costante. La
quantizzazione è il processo successivo per cui i campioni sono
approssimati con numeri interi, la cui codifica binaria è infine
memorizzata in appositi registri accessibili dall’utente. Il numero
di livelli su cui si opera l’approssimazione dei valori campionati è
2N, dove N indica il numero di bit dell’ADC. Su questi livelli è
mappato l’intervallo delle tensioni accettabili in ingresso all’ADC.
Nel caso dell’ATmega328, l’ADC è di tipo unipolare, cioè consente in
ingresso tensioni positive comprese tra 0V e la tensione di
fondoscala, che è pari alla tensione VREF dell’ADC stesso. In questo
caso la quantizzazione è detta lineare poiché tutti gli intervalli
hanno la stessa ampiezza che determina la cosiddetta risoluzione di
tensione dell’ADC, ovvero la più piccola variazione di tensione in
ingresso che causa la variazione di 1 bit del valore convertito in
uscita:

ΔV = VREF / 2N

Nel caso dell’ADC a 10-bit di Arduino, considerando la tensione VREF


di default pari a 5V, la risoluzione di tensione è:

ΔV = VREF / 2N = 5V / 1024 = 4.883mV ≈ 4.9mV

Questo significa che a fronte di una variazione di 4.9mV nella


tensione di ingresso, l’uscita digitale del convertitore cambia di 1
bit. Questa risoluzione è più che sufficiente nella maggior parte
delle applicazioni, ma è opportuno tenerne conto in relazione alla
grandezza che si desidera misurare.
Ad esempio, nel caso in cui Arduino sia collegato a un sensore di
temperatura analogico con guadagno (spesso indicato anche come
sensibilità) G = 10mV/°C, si avrebbe una risoluzione di temperatura
di:

ΔT = ΔV / G = 4.883mV / 10mV/°C ≈ 0.49°C

Ovvero, a fronte di una variazione di temperatura di 0.49°C,


l’uscita del convertitore cambia di 1 bit. Questa risoluzione non
sarebbe tuttavia adeguata nel caso in cui occorresse distinguere le
temperature con una risoluzione di 0.1°C.

Come migliorare la risoluzione?


Per come è definita, la risoluzione migliora (ovvero diminuisce,
permettendo così una conversione più fine) all’aumentare del numero
di bit (N) e/o al diminuire della tensione di riferimento (VREF).
L’aumento del numero di bit comporta un miglioramento della
risoluzione poiché aumenta il numero di livelli su cui l’intervallo
di tensione di ingresso è suddiviso. Tuttavia, da un punto di vista
hardware, non è un’opzione particolarmente flessibile poiché la
quasi totalità delle schede Arduino è basata su microprocessori il
cui ADC interno è fissato a 10-bit (a meno di utilizzare alcune
versioni più recenti, come Arduino DUE, con microprocessori dotati
di un ADC interno a 12-bit). Da un punto di vista software invece
l’aumento del numero di bit è più facilmente perseguibile in quanto,
come si vedrà più avanti, è possibile implementare nel codice la
cosiddetta tecnica di oversampling che consente di aumentare
“virtualmente” il numero di bit effettivi del convertitore ADC.
Per quanto riguarda la diminuzione della tensione di riferimento, è
possibile utilizzare le tensioni già presenti sulle schede Arduino
(ad esempio, per Arduino UNO: 3.3V e 1.1V) oppure fornirne una
esterna sul pin AREF. Tuttavia, questa non è sempre un’opzione
percorribile poiché diminuirebbe anche l’intervallo di tensione
consentito in ingresso all’ADC e, di conseguenza, anche l’intervallo
delle eventuali grandezze potenzialmente misurabili dai sensori
analogici collegati alla scheda Arduino.
Fig. 12 – L’andamento della risoluzione di tensione al variare della tensione di riferimento
e del numero di bit.

Nel caso del convertitore dell’ATmega328 di Arduino, la funzione


analogRead() restituisce un numero intero a 10-bit compreso appunto
tra 0 e 210 − 1 = 1023, che è memorizzato su due registri ciascuno
di 8-bit: ADCL che contiene gli 8 bit meno significativi e ADCH che
contiene i 2 bit più significativi (gli altri 6 bit sono riempiti
con zeri). Il compilatore dell’ambiente di Arduino consente di
gestire questi due registri come se fossero virtualmente un unico
registro 16-bit.
Su questi 1024 intervalli è mappato l’intervallo di tensione
compreso tra 0V e la tensione VREF dell’ADC, con cui l’ingresso
analogico è confrontato per produrre l’uscita digitale della
conversione.
Il valore acquisito con la funzione analogRead() è dato dalla
seguente relazione:

ADC = trunc[VIN / ΔV] = trunc[2N × VIN / VREF]


Ad esempio, con una tensione di VIN = 3.3V in ingresso all’ADC di
Arduino UNO si acquisisce un valore di:

ADC = trunc[1024 × 3.3V / 5V] = trunc[675.84] = 675

Per effetto di questo troncamento, tutti i valori del segnale di


ingresso compresi all’interno di 1 LSB (Least Significant Bit) sono
rappresentati dalla stessa codifica digitale. Ad esempio, nel caso
di una scheda Arduino UNO con una tensione VREF di 5V, le tensioni
in ingresso all’ADC comprese tra 5 × 1023 / 1024 ≈ 4.996V e 5V sono
tutte acquisite con lo stesso valore ADC di 1023.
Il troncamento comporta l’introduzione di un errore detto errore di
quantizzazione, dovuto appunto al fatto che si considera il valore
quantizzato del segnale al posto del suo valore continuo.
Fig. 13 – La caratteristica della conversione dell'ADC a 10-bit con una tensione di
riferimento di 5V.
Stimare la tensione
La tensione in ingresso al convertitore può essere stimata a partire
dal valore acquisito moltiplicandolo per la risoluzione di tensione:

Ṽ = ADC × ΔV = ADC × VREF / 2N

Nel caso dell’ADC a 10-bit di Arduino, considerando la tensione VREF


di default pari a 5V, si ottiene:

Ṽ = ADC × 5 / 210 = ADC × 5 / 1024

Ecco un esempio di sketch nel caso di una scheda Arduino UNO


configurata per misurare una tensione in ingresso sul pin A0:

byte analogPin = 0; // set the analog input pin


unsigned int ADC_value = 0; // variable storing the ADC reading
float vref = 5.0; // set the ADC voltage reference
float voltage = 0.0; // variable storing the calculated voltage

void setup()
{
Serial.begin(9600); // baud rate for the serial communication
}

void loop()
{
ADC_value = analogRead(analogPin); // read the input
voltage = (float)ADC_value * vref / 1024.0; // calculate the voltage
Serial.println(voltage, 3); // print the voltage with 3 decimal places
delay(1000); // wait for a second
}

1023 oppure 1024?


Talvolta su Internet (e purtroppo anche in letteratura) si trovano
sketch per Arduino dove la formula per la stima della tensione
contiene il numero 1023 anziché 1024. Ad esempio:

voltage = (float)ADC_value * 5.0 / 1023.0;

Ovviamente utilizzare 1023 non è corretto perché i livelli su cui è


mappata la conversione sono in realtà 1024, ovvero da 0 a 1023.
Il motivo per cui alcuni utilizzano 1023 è perché in questo modo il
risultato della divisione è leggermente maggiore rispetto al caso di
1024 e così facendo ritengono erroneamente di “recuperare” una parte
del troncamento dovuta all’ADC.
Utilizzare 1023 nella formula introduce un errore sistematico nella
stima. Quanto incide? Molto poco, sul singolo LSB:

VREF / 1023 – VREF / 1024 = VREF / (1023 × 1024) = 0.955 × 10−6 × VREF

Ovvero circa 1μV per ogni Volt della tensione VREF dell’ADC (ad
esempio, con VREF = 5V si tratta di circa 5μV). Tuttavia, occorre
considerare che questo errore incide maggiormente al crescere della
tensione in ingresso al convertitore e al crescere della tensione di
riferimento:

Fig. 14 – L’andamento dell’errore dovuto al fattore 1023 al variare della tensione in


ingresso e della tensione di riferimento dell’ADC.

Ad esempio, nel caso di Arduino con VREF = 5V con una tensione di


ingresso pari a metà VREF, l’errore è di circa 2.5mV (ovvero, nel
caso di un sensore di temperatura con sensibilità di 10mV/°C, si
avrebbe un errore di temperatura di 0.25°C), mentre a fondoscala
raddoppia e vale circa 5mV (che comporterebbe, ad esempio, un errore
di temperatura di 0.5°C).
Ottimizzazione dell’errore di
quantizzazione (Half LSB Adder)
Il valore stimato della tensione in ingresso all’ADC si calcola
come:

ṼIN = ADC × ΔV = ADC × VREF / 2N = trunc[2N × VIN / VREF] × VREF / 2N

Nel caso di Arduino UNO con N = 10 e VREF = 5V si ha:

ṼIN = trunc[210 × VIN / 5] × 5 / 210 = trunc[1024 × VIN / 5] × 5 /


1024

Come si è detto, questa relazione introduce un errore di


quantizzazione, per cui la differenza tra la tensione stimata e
quella effettivamente presente all’ingresso del convertitore ha il
seguente andamento:
Fig. 15 – L’andamento dell’errore di quantizzazione dell'ADC a 10-bit con una tensione di
riferimento di 5V.

La tensione stimata è sempre minore o uguale alla tensione


effettivamente presente all’ingresso dell’ADC e il modulo
dell’errore di quantizzazione è compreso tra 0V e la risoluzione di
ingresso (o, come spesso si usa dire, è inferiore a 1 LSB):

ṼIN – VIN ≤ 0

|ṼIN – VIN| ≤ ΔV = VREF / 2N = 5 / 1024 = 4.883mV ≈ 4.9mV

In pratica è possibile ottenere una riduzione degli effetti


associati al disturbo di quantizzazione, facendo in modo che
l’intervallo di indifferenza risulti centrato rispetto al livello
nominale di quantizzazione.
La tecnica è quella della cosiddetta compensazione Half LSB Adder
per la quale al valore letto dall’ADC si somma un valore uguale alla
metà della risoluzione di ingresso (0.5 LSB). Il grafico (ovvero la
funzione di trasferimento) si sposta dunque verso l’alto di 0.5 LSB
e l’errore di quantizzazione risulta a valor medio nullo e dimezzato
in valore assoluto, cosicché il rumore di quantizzazione è contenuto
entro una fascia simmetrica di ±0.5 LSB:

|ṼIN – VIN| ≤ ΔV / 2 = 0.5 × VREF / 2N = 0.5 × 5 / 1024 = 2.441mV

Fig. 16 – La caratteristica della conversione dell'ADC a 10-bit con una tensione di


riferimento di 5V e compensazione Half LSB Adder.
Fig. 17 – Andamento dell’errore di quantizzazione dell'ADC a 10-bit con una tensione di
riferimento di 5V e compensazione Half LSB Adder.

Questo metodo è facilmente implementabile nel codice sommando 0.5 al


valore acquisito dall’ADC, come nel seguente sketch:

byte analogPin = 0; // set the analog input pin


unsigned int ADC_value = 0; // variable storing the ADC reading
float vref = 5.0; // set the ADC voltage reference
float voltage = 0.0; // variable storing the calculated voltage

void setup()
{
bitSet(DIDR0, ADC0D); // disable digital input on ADC0
Serial.begin(9600); // baud rate for the serial communication
}

void loop()
{
ADC_value = analogRead(analogPin); // read the input
voltage = ((float)ADC_value + 0.5) * vref / 1024.0; // optimize the quantization error and
calculate the voltage
Serial.println(voltage, 3); // print the voltage with 3 decimal places
delay(1000); // wait for a second
}
Le cifre significative
Nel linguaggio C di Arduino, la più alta precisione che si può
ottenere per un numero di tipo float è di 7 cifre. Pertanto
eventuali altre cifre non sono significative poiché il
microprocessore non è comunque in grado di rappresentarle nelle
operazioni matematiche con una precisione migliore.
Ad esempio, volendo definire in uno sketch la costante PI Greco, non
avrebbe senso elencarne decine di cifre come:

#define PI 3.141592653589793238462643383279502884197169399

Sarebbe sufficiente limitarsi semplicemente alla definizione


seguente:

#define PI 3.141592

Per lo stesso motivo, volendo definire in uno sketch una costante


per una tensione di riferimento espressa in Volt, non avrebbe senso
spingersi oltre la sesta cifra decimale (ovvero limitarsi ai
microVolt):

#define VREF 5.123456

Analogamente, con variabili di tipo float per tensioni espresse in


Volt ha senso mostrare a schermo fino a un massimo di 6 cifre
decimali (ovvero i microVolt):

float my_voltage = 0.0;




Serial.println(my_voltage, 6);

ATTENZIONE: È opportuno precisare che a causa di una limitazione del


compilatore avr-gcc con i microprocessori Atmel AVR a 8-bit (come
appunto l'ATmega328), l'implementazione del tipo double viene
realizzata allo stesso modo del tipo float, ovvero su 32 bits (4
bytes). Quindi, sebbene negli sketch di Arduino UNO sia consentito
l’utilizzo del tipo double per le variabili, queste saranno in
realtà trattate come se fossero di tipo float, senza alcun guadagno
in termini di precisione nella rappresentazione numerica. In
presenza di schede Arduino con microprocessori a 32-bit, come nel
caso della Arduino DUE, il tipo double viene invece correttamente
gestito su 64 bits (8 bytes) con il relativo miglioramento della
precisione (15 cifre) rispetto al caso del tipo float.

Tuttavia, è opportuno chiedersi se abbia veramente senso


visualizzare sempre tutte queste cifre decimali. Per rispondere a
questa domanda occorre fare riferimento alla risoluzione dell’ADC,
che determina appunto il numero di cifre decimali visualizzabili per
la grandezza che si desidera misurare. Come si è visto, nel caso di
una scheda Arduino UNO con tensione VREF = 5V, la risoluzione di
tensione di ingresso è:

ΔV = 4.883mV ≈ 0.005V

Pertanto in uno sketch di Arduino UNO per una tensione in ingresso


all’ADC rappresentata da una variabile di tipo float non ha senso
impostarne la stampa a schermo oltre la terza cifra decimale (ovvero
nel caso di una tensione espressa in Volt è sufficiente limitarsi ai
milliVolt), poiché le cifre decimali successive alla terza non
sarebbero significative:

float my_voltage = 0.0;




Serial.println(my_voltage, 3);

Al diminuire della risoluzione, il numero di cifre decimali


significative può aumentare. Tuttavia, è importante sottolineare che
con le schede Arduino UNO a 10-bit, anche supponendo di migliorare
la risoluzione utilizzando il più piccolo riferimento di tensione
disponibile (ovvero 1.1V), non è possibile spingersi oltre le 3
cifre decimali significative (ovvero oltre i milliVolt).
Consideriamo adesso il caso in cui la tensione in ingresso all’ADC
di Arduino provenga da un sensore di temperatura analogico con una
sensibilità di 10mV/°C (come nel caso dei sensori LM35 e TMP36): su
quante cifre avrebbe senso visualizzare la temperatura (espressa in
°C)?
La tabella seguente riassume la risoluzione di temperatura e il
numero di cifre significative al variare della tensione di
riferimento nel caso dell’ADC a 10-bit di Arduino:

DECIMALI DECIMALI
VREF [V] RISOLUZIONE DI SIGNIFICATIVI RISOLUZIONE DI SIGNIFCATIVI
TENSIONE [mV] DELLA TENSIONE TEMPERATURA [°C] DELLA TEMPERATURA
(in Volt) (in °C)
5.0 4.883 3 0.488 1
3.3 3.223 3 0.322 1
2.56 2.500 3 0.250 1
1.1 1.074 3 0.107 1

A meno di impiegare tecniche di oversampling, diversamente da quanto


talvolta si trova erroneamente indicato in rete, con Arduino UNO e
un sensore di temperatura con una sensibilità di 10mV/°C non ha
senso visualizzare temperature in °C oltre la prima cifra decimale,
qualunque sia l’alimentazione tra quelle disponibili sulla scheda
(1.1V, 3.3V, 5V). Infatti, anche supponendo di utilizzare il più
piccolo riferimento di tensione disponibile (ovvero 1.1V), la
risoluzione di temperatura in ingresso sarebbe appunto:

ΔT = ΔV / G = VREF / (G × 2N) = 1.1V / (10mV/°C × 1024) ≈ 0.1°C

Fig. 18 – L’andamento della risoluzione di temperatura con un sensore analogico (con una
sensibilità di 10mV/°C) al variare della tensione di riferimento e del numero di bit.
IL RIFERIMENTO DI TENSIONE DEL
CONVERTITORE
Le possibili impostazioni della funzione
analogReference()
La tensione di riferimento dell’ADC di una scheda Arduino basata su
ATmega328 può essere impostata con la funzione analogReference() in
tre possibili modalità di seguito illustrate.

Modalità DEFAULT
Questa è la modalità predefinita per le schede Arduino e, se non
diversamente specificato nello sketch, configura automaticamente
VREF = AVCC, dove AVCC indica l’alimentazione analogica della scheda
Arduino che varia a seconda del microprocessore presente sulla
scheda stessa. Nel caso specifico dell’ATmega328 è 5V per le schede
come la UNO, Duemilanove, Pro/Pro Mini (ver. 5V), Nano 3.0 mentre è
3.3V per le schede come la Fio, Pro/Pro Mini (ver. 3.3V). Nelle
schede Arduino con microprocessori a 32-bit (come la ZERO, DUE o
101), l’alimentazione analogica è tipicamente 3.3V. Si invita
comunque il lettore a fare sempre riferimento alle specifiche
elettriche della particolare scheda utilizzata.
Nelle applicazioni in cui l’accuratezza della conversione rivesta un
ruolo critico, è possibile ridurre il rumore sull’alimentazione
analogica AVCC filtrandola da quella digitale VCC con un filtro LC
realizzato con un induttore da 10μH e un condensatore da 100nF, come
peraltro indicato nel datasheet dell’ATmega328:
Fig. 19 – Un esempio di filtraggio LC sull’alimentazione secondo il datasheet dell’ATmega328.

Tuttavia sulla scheda Arduino UNO, sebbene sia presente il


condensatore da 100nF sul pin AVCC, l’alimentazione analogica AVCC è
cortocircuitata con quella digitale VCC e non è quindi possibile
introdurre l’induttore di filtraggio:
Fig. 20 – Il condensatore da 100nF sulle alimentazioni AVCC e VCC nello schema elettrico di
Arduino UNO.

Di tutte le schede Arduino-compatibili che ho potuto analizzare, il


filtraggio LC sull’alimentazione AVCC è presente solo sulla
Olimexino-328. In questa scheda o, in generale, nel caso sia
comunque implementato un qualche tipo di filtraggio
sull’alimentazione analogica, si suggerisce di preferire gli
ingressi A0, A1, A2, A3 (e, dove presenti, A6 e A7) poiché
alimentati direttamente dal power supply analogico AVCC rispetto
agli ingressi A4 e A5 che, essendo configurabili anche come pin
SDA/SCL di comunicazione digitale, sono alimentati dal power supply
digitale VCC che è soggetto ai disturbi indotti dalle commutazioni
dei segnali digitali. Infatti, come sarà chiarito più avanti,
l’accuratezza della conversione analogico/digitale dipende anche
dalla stabilità dell’alimentazione dei circuiti elettronici
coinvolti.

PIN (ATmega328) PIN (Arduino) Alimentazione del PIN


ADC0 A0 Analog (AVCC)
ADC1 A1 Analog (AVCC)
ADC2 A2 Analog (AVCC)
ADC3 A3 Digital (VCC)
ADC4 A4 Digital (VCC)
ADC5 A5 Analog (AVCC)
ADC6 A6 Analog (AVCC)
ADC7 A7 Analog (AVCC)

Modalità EXTERNAL
Questa modalità configura VREF = AREF, dove AREF indica il
riferimento di tensione esterno (Analog REFerence) della scheda
Arduino. Per abilitarlo occorre utilizzare il seguente comando
all’interno della sezione di setup():

analogReference(EXTERNAL);

ATTENZIONE: Quando si utilizza un riferimento di tensione esterno


occorre sempre utilizzare il comando sopra indicato prima di
chiamare la funzione analogRead() per evitare di cortocircuitare
internamente la tensione AREF con il riferimento di tensione
predefinito AVCC. Inoltre, la tensione di riferimento esterna deve
essere necessariamente compresa tra 0V e AVCC per non danneggiare il
microcontrollore.

Quando si utilizza un riferimento di tensione esterno occorre


considerare la presenza del resistore interno di 32kΩ sul pin AREF,
che insieme alla resistenza di uscita dell’alimentazione esterna,
crea un partitore di tensione per cui la tensione effettivamente
utilizzata come riferimento dell’ADC è in realtà:

VREF = VEXT × 32kΩ / (32kΩ + REXT)

Fig. 21 – Lo schema equivalente del pin AREF.

Ad esempio, nel caso di una tensione di riferimento esterna di 2.5V


con una resistenza serie equivalente di 5kΩ, si otterrebbe:
VREF = 2.5V × 32kΩ / (32kΩ + 2kΩ) ≈ 2.2V

Per ritornare alla condizione di default con riferimento AVCC,


occorre utilizzare il seguente comando:

analogReference(DEFAULT);

ATTENZIONE: Non bisogna mai cambiare la tensione in DEFAULT o


INTERNAL quando un’altra tensione di alimentazione è collegata al
pin AREF, poiché questo cortocircuito rischierebbe di danneggiare
irreparabilmente il microprocessore.

Il riferimento di tensione AREF può essere reso più immune al rumore


collegando esternamente un condensatore ceramico da 100nF tra i pin
AREF e GND.

Fig. 22 – Un esempio di una scheda Arduino UNO con un condensatore ceramico da 100nF tra i
pin AREF e GND.

Tuttavia, poiché questo condensatore non è presente sulla scheda


Arduino UNO, molti utenti lo aggiungono saldandolo direttamente sul
retro della scheda.

Fig. 23 – Un esempio tratto da Internet di un condensatore ceramico saldato tra i pin AREF e
GND sul retro di una scheda Arduino UNO.

A questo scopo, un condensatore ceramico da 100nF è già presente


sulla scheda stessa nel caso di Arduino Fio, Arduino Nano 3.x,
Arduino Bluetooth e Arduino Ethernet e anche in alcune schede
Arduino-compatibili (come Olimexino-328, Sparkfun RedBoard,
Freaduino UNO).

ESERCIZIO: Valutare come esercizio l’acquisizione di una tensione


con una scheda Arduino DUE che utilizzi una tensione di riferimento
esterna di 2.048V (con accuratezza di 0.1%) prodotta da uno Zener
della serie Texas Instruments LM4040-N. Arduino DUE è progettata per
una tensione massima di 3.3V, (non 5V come per Arduino UNO):
applicare tensioni superiori a 3.3V può comportare danni
irreparabili alla scheda.
Si tenga presente che Arduino DUE integra un ADC a 12-bit, ma per
mantenere la compatibilità con le altre schede Arduino la
risoluzione è configurata di default a 10-bit. È tuttavia possibile
modificarla con la funzione analogReadResolution(). Ad esempio,
impostando nel codice analogReadResolution(12) la funzione
analogRead() fornirà valori su 12-bit, compresi cioè tra 0 e 212 − 1
= 4095.

Modalità INTERNAL
Questa modalità configura VREF al riferimento di tensione di tipo
bandgap interno al microprocessore, che genera una tensione nominale
di 1.1V.
Per abilitare il riferimento interno occorre utilizzare il seguente
comando:
analogReference(INTERNAL);

Il riferimento di tensione interno è di 1.1V anche con ATmega168,


mentre con ATmega8 è di 2.56V. Con Arduino Mega anziché il parametro
INTERNAL occorre utilizzare INTERNAL1V1 (che configura un
riferimento interno di 1.1V) o INTERNAL2V56 (che configura un
riferimento interno di 2.56V).
Secondo il datasheet dell’ATmega328 la tensione di riferimento
bandgap ha un valore nominale di 1.1V con un’accuratezza massima di
±100mV. È presente inoltre una dipendenza dalla temperatura come
visibile nei seguenti grafici:

Fig. 24 – La tensione di riferimento bandgap dell’ATmega328 al variare della temperatura.


Fig. 25 – La tensione di riferimento bandgap dell’ATmega328P (P = calibrated bandgap) al
variare della temperatura.

La tabella seguente riassume i risultati di alcune misure eseguite a


temperatura ambiente:

CONDENSATORE SU VREF [V] ERRORE MEDIO


ALIMENTAZIONE TOLLERANZA [mV]
AREF [mV]
9V (alimentatore 100nF 1.082 −17.6 ±3.3
esterno) Non presente 1.082 −17.5 ±3.4
100nF 1.086 −13.7 ±6.0
5V (PC USB)
Non presente 1.087 −13.1 ±6.3
Fig. 26 – Una misura della tensione bandgap sul pin AREF di una scheda Arduino UNO alimentata
con un alimentatore esterno da 9V, con un condensatore ceramico da 100nF sul pin AREF stesso.
Fig. 27 – Una misura della tensione bandgap sul pin AREF di una scheda Arduino UNO alimentata
con un alimentatore esterno da 9V, senza condensatore sul pin AREF.
Fig. 28 – Una misura della tensione bandgap sul pin AREF di una scheda Arduino UNO alimentata
con un cavo USB collegato a un PC, con un condensatore ceramico da 100nF sul pin AREF stesso.
Fig. 29 – Una misura della tensione bandgap sul pin AREF di una scheda Arduino UNO alimentata
con un cavo USB collegato a un PC, senza condensatore sul pin AREF.

Utilizzando un alimentatore esterno sia l’errore sul valor medio che


il rumore picco-picco sono maggiori rispetto al caso di
un’alimentazione da USB 5V.
Il condensatore ceramico da 100nF non comporta miglioramenti
sensibili sul rumore picco-picco.

ATTENZIONE: È importante tenere presente che, come peraltro indicato


anche nel datasheet dell’ATmega328, dopo un cambiamento del
riferimento di tensione con la funzione analogReference() le prime
conversioni dell’ADC che si ottengono potrebbero non essere accurate
per effetto dei transitori interni al circuito di acquisizione ed è
quindi buona norma scartarle.
A questo proposito, l’immagine seguente mostra il comportamento
della tensione di riferimento nel passaggio da 5V (DEFAULT) a 1.1V
(INTERNAL) nel caso in cui non ci sia alcun condensatore sul pin
AREF della scheda Arduino:

Fig. 30 – Una misura del transitorio della tensione di riferimento da 5V (DEFAULT) a 1.1V
(INTERNAL) senza alcun condensatore sul pin AREF.

Si misura un tempo di discesa 90−10% di circa 4ms che (considerando


un tempo di conversione pari a 112µs) corrisponde a circa 35
conversioni dell’ADC da scartare prima di considerare il sistema di
acquisizione effettivamente a regime.
È comunque importante considerare che la durata di questi transitori
aumenta in presenza di condensatori esterni sul pin AREF che insieme
al resistore interno da 32kΩ determinano la costante di tempo RC che
domina il transitorio. Com’è noto, esiste una relazione per stimare
il tempo di transizione a partire dalla costante di tempo:

Δt ≈ 2.2 × RC
Ad esempio, nel caso di un condensatore da 100nF sul pin AREF si
otterrebbe:

Δt ≈ 2.2 × RC = 2.2 × 32kΩ × 100nF = 7ms

Considerando un tempo di conversione pari a 112µs, il numero di


letture da scartare prima di considerare il sistema di acquisizione
effettivamente a regime è:

7ms / 112µs ≈ 63

È possibile scartare queste acquisizioni iniziali come è


implementato ad esempio nello sketch seguente (nel caso di una
scheda Arduino UNO configurata per misurare una tensione in ingresso
sul pin A0):

byte analogPin = 0; // set the analog input pin


unsigned int ADC_value = 0; // variable storing the ADC reading
unsigned int dummyreadings = 63; // set the number of initial ADC readings to discard
float vref = 1.1; // set the ADC voltage reference
float voltage = 0.0; // variable storing the calculated voltage

void setup()
{
bitSet(DIDR0, ADC0D); // disable digital input on ADC0
analogReference(INTERNAL);
for (int i = 0; i < dummyreadings; i++) {
unsigned int a = analogRead(analogPin); // ADC readings to discard
}
Serial.begin(9600); // baud rate for the serial communication
}

void loop()
{
ADC_value = analogRead(analogPin); // read the input
voltage = ((float)ADC_value + 0.5) * vref / 1024.0; // optimize the quantization error and
calculate the voltage
Serial.println(voltage, 3); // print the voltage with 3 decimal places
delay(1000); // wait for a second
}
Le tensioni disponibili sulla scheda
Arduino
USB POWER SUPPLY
La stabilità della tensione USB lascia molto a desiderare a causa
delle armoniche della frequenza di rete (50Hz o 60Hz), di quelle
dovute a eventuali regolatori switching e agli spikes e dips causati
dalle variazioni di assorbimento dei carichi. Le specifiche dello
standard USB contemplano un’alimentazione di 5V con una tolleranza
di ±5%, ovvero ±250mV per cui teoricamente la tensione può essere
compresa tra 4.75V e 5.25V. Nella pratica, le tensioni fornite da
alimentatori USB commerciali e porte USB di PC mostrano in genere un
rumore dell’ordine di ±10/20mV e comunque quasi mai superiore a
±100mV.
La tabella seguente riassume i risultati di alcune misure eseguite a
temperatura ambiente:

CONDENSATORE SU VREF [V] ERRORE MEDIO


ALIMENTAZIONE TOLLERANZA [mV]
AREF [mV]
100nF 5.045 45.2 ±14.1
5V (PC USB)
Non presente 5.046 45.7 ±14.2
Fig. 31 – Una misura della tensione 5V sul pin AREF di una scheda Arduino UNO alimentata con
un cavo USB collegato a un PC, con un condensatore ceramico da 100nF sul pin AREF stesso.
Fig. 32 – Una misura della tensione 5V sul pin AREF di una scheda Arduino UNO alimentata con
un cavo USB collegato a un PC, senza condensatore sul pin AREF.

LDO 5V
Su Arduino UNO Rev. 3 è montato un regolatore low-dropout di
OnSemiconductor modello NCP1117ST50T3G in package SOT-223 e con
tensione di uscita nominale di 5.0V e una tolleranza di ±50mV a
+25°C. Il datasheet riporta che questo regolatore ha un operating
temperature range tra 0 e +125°C, ovvero non è adatto per le
temperature negative, per le quali esiste il modello NCV1117 che è
progettato per funzionare tra −40°C e +125°C, ma la cui tolleranza
sulla tensione di uscita peggiora a ±100mV.
Per quanto riguarda le principali schede Arduino-compatibili, tranne
il caso della Seeeduino 4.0 che utilizza il regolatore LM78M05 di
Fairchild Semiconductor con una tolleranza di ±200mV, la situazione
è sostanzialmente equiparabile a quella di Arduino UNO. Ad esempio,
le schede Freaduino UNO e Sparkfun RedBoard utilizzano regolatori
del tipo LM1117 di Texas Instruments in package SOT-223 con una
tolleranza di ±50mV. AVR.duino U+ utilizza il regolatore
LD1117DT50CTR di STM in package DPAK con una tolleranza di ±50mV.
La tabella seguente riassume i risultati di alcune misure eseguite a
temperatura ambiente:

CONDENSATORE SU VREF [V] ERRORE MEDIO


ALIMENTAZIONE TOLLERANZA [mV]
AREF [mV]
9V (alimentatore 100nF 4.999 −1.2 ±2.9
esterno) Non presente 4.998 −2.0 ±3.3

Fig. 33 – Una misura della tensione 5V sul pin AREF di una scheda Arduino UNO alimentata con
un alimentatore esterno da 9V, con un condensatore ceramico da 100nF sul pin AREF stesso.
Fig. 34 – Una misura della tensione 5V sul pin AREF di una scheda Arduino UNO alimentata con
un alimentatore esterno da 9V, senza condensatore sul pin AREF.

Si conclude che è decisamente preferibile utilizzare come tensione


di riferimento da 5V per l’ADC quella fornita da un alimentatore
esterno e non quella derivata dall’alimentazione USB, sia in termini
di errore rispetto al valore atteso (circa 2mV contro 45mV) e sia in
termini di rumore picco-picco (una tolleranza di circa ±3mV contro
±14mV).

On-board 3.3V
Sulla scheda Arduino UNO Rev. 3 è presente anche un regolatore LDO
di Texas Instruments LP2985 in package SOT-23 con tensione di uscita
nominale di 3.3V e tolleranza a 25°C di ±1.5% (ovvero ±49.5mV), in
grado di erogare una corrente di 150mA. La tensione di uscita di
questo regolatore ha una dipendenza dalla temperatura, come
riportato nel grafico seguente:
Fig. 35 – L’andamento della tensione di uscita del regolatore LP2985 al variare della
temperatura.

La tabella seguente riassume i risultati di alcune misure eseguite a


temperatura ambiente:

CONDENSATORE SU VREF [V] ERRORE MEDIO


ALIMENTAZIONE TOLLERANZA [mV]
AREF [mV]
9V (alimentatore 100nF 3.292 −7.8 ±7.6
esterno) Non presente 3.292 −7.5 ±7.6
100nF 3.296 −4.0 ±9.8
5V (PC USB)
Non presente 3.296 −3.9 ±9.7
Fig. 36 – Una misura della tensione 3.3V sul pin AREF di una scheda Arduino UNO alimentata
con un alimentatore esterno da 9V, con un condensatore ceramico da 100nF sul pin AREF stesso.
Fig. 37 – Una misura della tensione 3.3V sul pin AREF di una scheda Arduino UNO alimentata
con un alimentatore esterno da 9V, senza condensatore sul pin AREF.
Fig. 38 – Una misura della tensione 3.3V sul pin AREF di una scheda Arduino UNO alimentata
con un cavo USB collegato a un PC, con un condensatore ceramico da 100nF sul pin AREF stesso.
Fig. 39 – Una misura della tensione 3.3V sul pin AREF di una scheda Arduino UNO alimentata
con un cavo USB collegato a un PC, senza condensatore sul pin AREF.

Si conclude che è preferibile utilizzare come tensione di


riferimento da 3.3V per l’ADC quella fornita da un alimentatore
esterno e non quella derivata dall’alimentazione USB, sia in termini
di errore rispetto al valore atteso (circa 4mV contro 8mV) e sia in
termini di rumore picco-picco (una tolleranza di circa ±8mV contro
±10mV).
Per quanto riguarda altre versioni di Arduino e le principali schede
Arduino-compatibili, la situazione è piuttosto variegata. Ad
esempio:
─ Olimexino-328: ha il regolatore MCP1700T-3302E/MB di Microchip con
una tolleranza tipica a 25°C di ±0.4% (ovvero ±13.2mV) e di ±3%
(ovvero ±99mV) su tutto l’intervallo tra −40°C e +125°C; la corrente
erogabile è di 250mA.
─ AVR.duino U+: ha la stessa soluzione adottata sulla scheda Arduino
UNO.
─ Sparkfun RedBoard: utilizza un regolatore LDO MIC5205 di Micrel
con una tolleranza massima del 2% (ovvero ±66mV); la corrente
erogabile è di 150mA.
─ Seeeduino 3.0: è montato il regolatore LD1117-3.3V di STM con una
tolleranza di ±33mV a 25°C, mentre la tolleranza sale a ±65mV su
tutto l’intervallo di corrente erogabile, che è al massimo 800mA.
─ Iteaduino UNO: utilizza il regolatore LDO CE6209-3.3 di Chipower
con una tolleranza tipica di ±2% (ovvero ±66mV) e una corrente
erogabile fino a 250mA.

Esempio con AREF


Ad esempio, è possibile utilizzare la tensione di 3.3V generata
sulla scheda Arduino collegando un ponticello tra il pin 3.3V e il
pin AREF:

Fig. 40 – Un esempio in cui la tensione di riferimento dell’ADC utilizza la tensione di 3.3V


generata sulla scheda Arduino UNO stessa.

In questo caso, si può considerare la tensione di riferimento


dell’ADC uguale alla tensione di 3.3V stessa poiché l’impedenza di
uscita del regolatore LP2985 che genera la tensione di 3.3V è di
circa 0.5Ω, ovvero due ordini di grandezza inferiore a quella
interna al pin AREF (32kΩ) e quindi trascurabile nel partitore.

Fig. 41 – L’impedenza di uscita del regolatore LP2985.

ESERCIZIO: Nel caso si desideri ottenere acquisizioni più accurate


con Arduino, è opportuno ricorrere a riferimenti di tensione esterni
basati su Zener, come quelli della serie Texas Instruments LM4040-N
disponibili con varie tensioni di uscita (tra cui 2.048V, 2.500V,
3.000V, 4.096V, 5.000V). Grazie a una processo di calibrazione del
fornitore, offrono un’elevata accuratezza sulla tensione di uscita:
per versioni in package TO-92 fino a ±0.2% e per i componenti “A-
grade” in package SOT-23 addirittura ±0.1%. Come esercizio si invita
il lettore a valutare l’utilizzo della scheda Precision LM4040
Voltage Reference Breakout di Adafruit che fornisce due tensioni di
riferimento: 2.048V e 4.096V con accuratezza di 0.1%. In
particolare, si valuti la tensione effettivamente utilizzata come
riferimento dall’ADC per effetto del partitore dovuto alla
resistenza di uscita della scheda e di quella interna (32kΩ) al pin
AREF.
L’ACCURATEZZA DELLA CONVERSIONE
Spesso quando si parla di accuratezza a proposito dell’ADC di
Arduino si fa erroneamente riferimento alla sua risoluzione di
tensione di ingresso.
In un sistema ADC la risoluzione e l’accuratezza non devono essere
confusi: la risoluzione rappresenta il peso dell’ultima cifra,
l’accuratezza rappresenta la qualità della misura. La risoluzione
indica cioè quanto l’ADC è in grado di discriminare in ingresso;
infatti, come si è detto, rappresenta la più piccola variazione
della grandezza di ingresso che produce una variazione apprezzabile
della grandezza di uscita.
La stabilità del riferimento di tensione
Riprendiamo la relazione con cui l’ADC acquisisce una tensione in
ingresso con la funzione analogRead():

ADC = trunc[2N × VIN / VREF]

Una piccola variazione della tensione VREF potrebbe essere


ininfluente ai fini del valore acquisito dall’ADC poiché potrebbe
non essere sufficiente da far sforare di un’unità l’argomento della
funzione troncamento. Tuttavia, specialmente con tensioni VIN
prossime a VREF, variazioni della tensione di riferimento potrebbero
comportare una variazione di una o più unità sul valore acquisito.
In particolare, essendo la tensione VREF a denominatore, una sua
diminuzione potrebbe causare un aumento del valore acquisito dal
convertitore e, viceversa, un suo aumento comporterebbe una
diminuzione del valore acquisito.
In pratica, l’accuratezza della tensione di riferimento può incidere
sull’accuratezza dell’acquisizione dell’ADC e riverberarsi infine
sui calcoli matematici dello sketch.
Per farsi un’idea di quanto questo aspetto possa influire,
immaginiamo di voler acquisire una tensione VIN = 3.3V utilizzando
una scheda Arduino UNO con ADC a 10-bit e tensione VREF = 5V. In un
caso ideale con una tensione di riferimento costante si otterrebbe
un’acquisizione di:

ADC = trunc[1024 × 3.3V / 5V] = trunc[675.84] = 675

Considerando invece la tensione VREF con un valor medio di 5V ma con


una tolleranza di ±3mV (come nel caso della tensione di riferimento
fornita dal regolatore LDO da 5V), con una simulazione con il metodo
Monte Carlo eseguita su 1 milione di trial si ottiene la seguente
distribuzione per i valori acquisiti dall’ADC:
Fig. 42 – La distribuzione dei valori acquisiti dall’ADC con una tensione di riferimento di
5V ±3mV.

Considerando ancora la tensione VREF con un valor medio di 5V ma con


una tolleranza maggiore, ovvero di ±14mV (come nel caso della
tensione di riferimento fornita dall’alimentazione USB POWER SUPPLY
5V), allora si ottiene invece la seguente distribuzione dei valori
acquisiti dall’ADC:
Fig. 43 – La distribuzione dei valori acquisiti dall’ADC con una tensione di riferimento di
5V ±14mV.

Da queste analisi si conclude che l’effetto dell’accuratezza della


tensione di riferimento sull’accuratezza dell’acquisizione dell’ADC
è di circa ±1 LSB se il riferimento è particolarmente stabile,
altrimenti può essere anche di circa ±2 LSB se la tensione di
riferimento è soggetta a una maggiore variabilità.
In realtà, per valutare l’accuratezza complessiva occorre
considerare anche il contributo della cosiddetta absolute accuracy
dell’ADC (che tiene conto di tutti i comportamenti che lo discostano
dal modello ideale: errori di offset, di guadagno, di quantizzazione
(±0.5 LSB), non linearità integrali e differenziali). Nel caso
dell’ATmega328, il datasheet riporta un’absolute accuracy di ±2 LSB.
Quindi in definitiva, si può stimare l’accuratezza complessiva
dell’acquisizione dell’ADC di Arduino con un valore di circa ±3 LSB
se la tensione di riferimento è particolarmente stabile (≈ ±5mV)
oppure di circa ±4 LSB se la tensione di riferimento è soggetta a
una maggiore variabilità (≈ ±20mV).

ESERCIZIO: Si invita il lettore a provare il seguente esempio.


Collegare il pin 3.3V di una scheda Arduino UNO al pin A0 e leggere
sul monitor seriale i valori acquisiti dall’ADC quando la scheda è
alimentata con i 5V della porta USB. Come si è detto, teoricamente
ci si aspetterebbe di misurare un valore costante pari a:

ADC = trunc[1024 × 3.3V / 5V] = trunc[675.84] = 675

In realtà, come il lettore potrà verificare, sul monitor seriale si


leggono valori oscillanti (nel caso delle mie misure, tra 678 e
680). Come mai? Questo dipende dal fatto che il valore acquisito
dall'ADC dipende dalla tensione VREF del convertitore (che nel caso
di default di 5V fornita dalla porta USB del PC è poco accurata) e
dal fatto che la tensione sul pin 3.3V è soggetta a una variabilità
per cui non è sempre esattamente pari a 3.300V.
Utilizzando un alimentatore esterno e un condensatore da 100nF tra i
pin AREF e GND la situazione migliora; infatti, nel caso delle mie
misure, adesso sulla porta seriale di Arduino si legge un valore
costante pari a 673, diverso dal valore atteso di 675 ma comunque
compatibile con il sistema di acquisizione. Infatti, misurando con
un multimetro digitale professionale la tensione sul piedino 3.3V si
legge un valore di 3.304V mentre la tensione sul pin 5V è di 5.025V,
per cui sostituendo questi valori nella formula si ottiene
effettivamente:

ADC = trunc[1024 × 3.304V / 5.025V] = trunc[673.29] = 673

In altri casi si possono ottenere anche altri valori costanti ma


diversi da quello calcolato utilizzando i valori di tensione
misurati con il multimetro. Nel mio caso, ad esempio, ho talvolta
ottenuto il valore 674. Questo è comunque comprensibile poiché per
l’ADC dell’ATmega328, il datasheet riporta un’absolute accuracy di
±2 LSB, quindi tutti i valori compresi tra 673 e 677 sono
compatibili con quanto effettivamente misurato.
Fig. 44 – Lo schema per l’esempio dell’acquisizione della tensione di 3.3V generata sulla
scheda Arduino UNO stessa.
L’accuratezza sulla stima della tensione
in ingresso
Per valutare gli effetti dell’accuratezza complessiva
dell’acquisizione dell’ADC sulla stima della tensione in ingresso ad
Arduino, occorre ricordare che ciascun bit della conversione
digitale ha un peso pari alla risoluzione di tensione dell’ADC,
ovvero:

ΔV = VREF / 2N = VREF / 1024

Pertanto, ad esempio, un’accuratezza di ±3 LSB sull’acquisizione


dell’ADC con una VREF = 5V si ottiene un’accuratezza sulla stima
della tensione in ingresso di:

±3 × ΔV = ±3 × VREF / 2N = ±3 × 5V / 1024 ≈ ±15mV

La tabella seguente mostra l’accuratezza sulla stima della tensione


in ingresso al variare della tensione di riferimento dell’ADC di
Arduino UNO:

VREF [V] ACCURATEZZA DELLA STIMA DELLA TENSIONE


ACCURATEZZA TOTALE DELL’ADC [LSB]
[mV]
±3 (con accuratezza VREF di ≈±5mV)
±15
5.0 ±4 (con accuratezza VREF di ≈
±20
±20mV)
±3 (con accuratezza VREF di ≈±5mV)
±10
3.3 ±4 (con accuratezza VREF di ≈
±13
±20mV)
±3 (con accuratezza VREF di ≈±5mV)
±3
1.1 ±4 (con accuratezza VREF di ≈
±4
±20mV)

Per confronto, un multimetro digitale professionale come il Fluke


175 (il cui prezzo è di circa 250€) fornisce misure di tensione con
risoluzione di 0.001V e accuratezza totale di 0.15% + 2, ovvero nel
caso di un ingresso pari a 5V l'accuratezza è di 0.15% x 5V + 2 x
0.001V = ±9.5mV.
Un tipico multimetro digitale hobbistico, come il tascabile Amprobe
PM51A (il cui prezzo è di circa 35€) è in grado di fornire misure di
tensione con risoluzione di 0.001V e un’accuratezza di ±2%, ovvero
con una tensione in ingresso di 5V l’accuratezza è di ±100mV.
È interessante inoltre ricordare che, essendo l’absolute accuracy la
stessa (±2 LSB) nel caso di tutti gli ADC a 10-bit dei
microprocessori Atmel AVR (ATmega328, ATmega1280, ATmega2560,
ATmega32u4), le considerazioni riportate a proposito di Arduino UNO
sono valide anche nel caso di altre schede Arduino (come, ad
esempio, Leonardo, Yún o Mega) a parità di alimentazione.
L’accuratezza sulla stima della misura da
un sensore
Per valutare quanto l’accuratezza sulla stima della tensione in
ingresso incida sull’accuratezza della misura da un sensore la cui
tensione di uscita sia appunto in ingresso all’ADC di Arduino,
occorre convertire l’accuratezza della tensione nell’equivalente
accuratezza della grandezza fisica misurata (tramite il guadagno del
sensore) e poi sommare a questo valore l’accuratezza della misura
del sensore stesso che è riportata nel datasheet.

Esempio della misura della temperatura


Ad esempio, consideriamo il caso in cui la tensione in ingresso
all’ADC di Arduino UNO provenga da un sensore di temperatura
analogico come il TMP36 o LM35. Per entrambi i sensori il guadagno è
di 10mV/°C, mentre l’accuratezza è di ±1°C per il TMP36 e di ±0.2°C
per LM35.
Utilizzando una tensione VREF = 5V con accuratezza di circa ±5mV, si
è visto sopra che l’accuratezza sulla stima della tensione in
ingresso è di circa ±15mV, che corrisponde dunque a un’accuratezza
in temperatura di:

±15mV / (10mV/°C) = ±1.5°C

A questa si deve sommare l’accuratezza del sensore. Nel caso del


LM35, si otterrebbe:

±1.5°C + ±0.2°C = ±1.7°C ≈ ±2°C

La tabella seguente mostra l’accuratezza sulla stima della


temperatura misurata nel caso di sensori TMP36 e LM35 al variare
della tensione di riferimento dell’ADC di Arduino UNO:

VREF ACCURATEZZA DELLA RISOLUZIONE IN


ACCURATEZZA DELLA STIMADELLA
TEMPERATURA MISURATA [°C] TEMPERATURA DELL’ADC
[V] TENSIONE [mV]
TMP36 LM35 10-bit [°C]
±15 (con accuratezza VREF di ≈
±5mV) ±2.5 ±1.7
5.0 ≈0.5
±20 (con accuratezza VREF di ≈ ±3.0 ±2.2
±20mV)
±10 (con accuratezza VREF di ≈
3.3 ±5mV) ±2.0 ±1.2 ≈0.3
±13 (con accuratezza VREF di ≈ ±2.3 ±1.5
±20mV)
±3 (con accuratezza VREF di ≈±5mV)
±1.3 ±0.5
1.1 ±4 (con accuratezza VREF di ≈ ≈0.1
±1.4 ±0.6
±20mV)

Si tratta di valori accettabili? I sistemi professionali per il


datalogging di temperature in ambito industriale (impiegati, ad
esempio, per il monitoraggio delle merci trasportate o dell'aria
nelle celle frigorifere) hanno una risoluzione di 0.1°C e
un’accuratezza tipica di ±0.5°C (che in alcuni dispositivi di fascia
alta su intervalli di temperatura meno estesi, generalmente tra
−10°C e +70°C, può essere anche di ±0.3°C). Dispositivi commerciali
per misurare la temperatura per usi domestici hanno invece
tipicamente una risoluzione di 0.1°C e un’accuratezza di ±1°C per
temperature comprese tra 0°C e +80°C, oppure tra ±2°C e ±5°C per
intervalli di temperatura più estesi.
Nel caso si desideri ottenere dei livelli di accuratezza più spinta,
occorre impiegare sensori con accuratezza migliore come il Texas
Instruments LMT70 che ha un’accuratezza tipica di ±0.05°C per
temperature comprese tra +20°C e +42°C. Inoltre, per quanto la
maggior parte dei sensori di temperatura integrati possa presentare
limiti nell’accuratezza, questi possiedono normalmente delle ottime
prestazioni di linearità, per cui spesso ricorrere a una
calibrazione con una temperatura nota (single point calibration) può
portare a risultati comunque veramente accurati. Ad esempio, nel
caso di applicazioni per misure della temperatura corporea, ovvero
nell’intorno di 37°C, è possibile calibrare il sistema di misura
sfruttando come riferimento il punto di ebollizione del Pentano
(36.1°C) o il punto di fusione del Gallio (29.7646 °C).

Esempio della misura dell’umidità relativa


Consideriamo, ad esempio, il sensore Honeywell HIH-4030 per
l’umidità relativa (%RH) che, alimentato con una tensione di 5V,
fornisce un segnale analogico di uscita come nella tabella seguente:
Fig. 45 – La tensione di uscita del sensore Honeywell HIH-4030 al variare dell’umidità
relativa.

Il guadagno tipico di questo sensore (che rappresenta la pendenza


della linea nel grafico) secondo il datasheet è 30.680 mV/%RH e
l’accuratezza del sensore è di ±3.5 %RH.
Utilizzando una scheda Arduino UNO con una tensione VREF = 5V con
accuratezza di circa ±5mV, si è visto sopra che l’accuratezza sulla
stima della tensione in ingresso è di circa ±15mV, che in termini di
umidità relativa corrisponde a un’accuratezza di:

±15mV / (30.680 mV/%RH) = ±0.49 %RH

A questa si deve sommare l’accuratezza del sensore, per cui si


ottiene un’accuratezza totale di:
±0.49 %RH + ±3.5 %RH ≈ ±4 %RH
La calibrazione del riferimento di
tensione dell’ADC
Come si è visto sopra l’accuratezza della misura dipende
notevolmente dalla qualità del riferimento di tensione utilizzato
che nelle applicazioni pratiche ha una tolleranza non trascurabile:

TENSIONE NOMINALE ACCURATEZZA


TIPO DI RIFERIMENTO
[V] (misure)
USB 5.0 ±14.2mV
LDO 5.0 ±3.3mV
On-board 3.3V (PC USB) 3.3 ±9.8mV
On-board 3.3V (alimentatore esterno) 3.3 ±7.6mV
Internal bandgap (PC USB) 1.1 ±6.3mV
Internal bandgap (alimentatore
1.1 ±3.4mV
esterno)
External LM4040-N 2.048 ±2.0mV

Conoscere con esattezza la tensione di riferimento utilizzata per il


convertitore ADC è fondamentale per evitare errori sulla stima della
tensione in ingresso.
Infatti, supponiamo ad esempio che la tensione VREF sia in realtà
4.980V, ovvero 20mV inferiore al valore nominale; nell’ipotesi che
una tensione di 2.5V sia in ingresso al convertitore, questo
acquisirà:

ADC = trunc[2N × VIN / VREF] = trunc[1024 × 2.5V / 4.980V] = 514

Se invece nello sketch si utilizzasse per il riferimento di tensione


una tensione pari esattamente al valore nominale di 5V, allora
l’acquisizione verrebbe erroneamente interpretata come:

ṼIN = ADC × VREF / 2N = 514 × 5V / 1024 = 2.510V

Pertanto, rispetto alla tensione effettivamente presente in ingresso


al convertitore si avrebbe un errore di:

2.510V – 2.500V = 10mV

Se la tensione in ingresso provenisse da un sensore di temperatura


tipo TMP36 o LM35, si avrebbe un errore di:

10mV / 10mV/°C = 1°C


Per ridurre l’incidenza di questo tipo di errore, è buona norma
utilizzare nello sketch per la tensione VREF un valore misurato
piuttosto che uno puramente nominale.
Esistono sostanzialmente tre metodi per misurare il valore del
riferimento di tensione dell’ADC ai fini della calibrazione
dell’algoritmo di stima della tensione di ingresso:

Misura esterna con un voltmetro


Facendo riferimento al diagramma a blocchi dell’ADC, si nota che
indipendentemente dalla modalità impostata con la funzione
analogReference(), la tensione VREF è sempre presente sul pin AREF.
Quindi, su una scheda Arduino è sempre possibile misurarla con un
voltmetro esterno e calibrare la conversione coerentemente in modo
da migliorarne l’accuratezza.
Ad esempio, nel caso si misuri sul pin AREF una tensione di 4.985V è
possibile definire all’interno del codice la tensione di riferimento
come:

float vref = 4.985; // calibrated ADC voltage reference

E poi riutilizzare questa costante nel codice per tutte le


conversioni.
Ovviamente non è possibile utilizzare l’ADC stesso per leggere la
tensione AREF poiché, essendo l’architettura del convertitore ad
essa riferita, produrrebbe una lettura sempre uguale a 0x3FF, ovvero
210 − 1 = 1023.

Misura interna riferita alla tensione interna di


bandgap
Facendo ancora riferimento al diagramma a blocchi, è possibile
configurare il multiplexer degli ingressi all’ADC in modo che sia
selezionato come ingresso il riferimento interno bandgap, che come
si è detto su Arduino UNO ha un valore nominale di 1.1V. In questo
caso, il valore letto dall’ADC è:

ADCbg = trunc[210 × 1.1 / VREF] = trunc[1024 × 1.1 / VREF] =


trunc[1126.4 / VREF]

Supponendo di essere nel caso di default con la tensione VREF


impostata alla tensione AVCC e che questa sia una tensione ideale
stabile a 5V, allora la lettura dell’ADC sarebbe:

ADCbg = trunc[1126.4 / 5] = trunc[225.28] = 225

Se però la tensione di riferimento si discosta dal valore ideale di


5V allora la lettura della tensione di riferimento bandgap sarà
diversa dal valore atteso di 225. In particolare esiste una
relazione inversa: se la VREF diminuisce, leggeremo valori maggiori
di 225, in caso contrario leggeremo valori minori di 225. L’idea è
appunto quella di utilizzare questa lettura per risalire alla
tensione VREF effettiva:

VREF ≈ 1.1 × 1024 / ADCbg = 1126.4 / ADCbg

Questo metodo di lettura del riferimento di tensione non richiede


uno strumento di misura esterno e in linea di principio consente di
realizzare una sorta di calibrazione periodica, monitorando nel
tempo le eventuali fluttuazioni della tensione VREF. Tuttavia,
questo metodo è fortemente legato all’accuratezza della tensione di
riferimento bandgap che, come si è visto, a temperatura ambiente può
variare anche di ±20mV rispetto al valore nominale di 1.1V. Questo
comporta che il valore atteso della lettura dell’ADC con in ingresso
il riferimento bandgap interno possa in realtà essere compreso tra
221 e 229, così una tensione ideale stabile VREF = 5.0V sarebbe in
realtà erroneamente ricalibrata a un valore compreso tra 4.919V e
5.097V, ovvero con un errore di circa ±90mV che si riverberebbero
sul segnale acquisito (ad esempio, nel caso di una misura di
temperatura si potrebbe avere anche un errore di ±9°C).
Pertanto questo metodo di calibrazione, sebbene accreditato in
alcuni forum in rete, è certamente sconsigliato.

Misura interna riferita a una tensione esterna


Su alcuni forum in rete è suggerita anche un’altra possibilità,
simile al metodo in precedenza illustrato, che consiste
nell’acquisire il segnale di ingresso insieme a una tensione
costante di valore noto con cui operare una calibrazione mediante
una scalatura in modo che se la tensione nota viene acquisita con un
valore diverso da quello atteso allora il valore corrispondente alla
tensione da acquisire è scalato di conseguenza. In pratica, se la
tensione da 5V che l’ADC utilizza come riferimento di default
subisce variazioni dovute a rumore o ad altri carichi da essa
alimentati, il valore della conversione della tensione di ingresso
nota varia di conseguenza e pertanto permette di compensare il
valore della lettura dell’uscita del sensore.
Tuttavia, nella pratica anche questo metodo è sconsigliabile perché
non sufficientemente accurato. Infatti, a causa dell’errore di
quantizzazione il rapporto tra i due valori interi acquisiti
dall’ADC, in quanto troncati, produce risultati oscillanti.

ESERCIZIO: Come esercizio valutare l’implementazione di quest’ultimo


metodo considerando il seguente schema in cui si acquisisce sul pin
A1 il segnale da un sensore di temperatura tipo LM35 e sul pin A0 la
tensione di 3.3V generata dalla scheda Arduino UNO e impiegata come
riferimento per la calibrazione:
Fig. 46 – Lo schema per l’esempio sulla valutazione della misura interna riferita a una
tensione esterna.

Un esempio di sketch in questo caso è:

unsigned int ADC_value = 0; // variable storing the ADC reading


unsigned int ADC_reference = 0; // variable storing the ADC reading at the reference voltage
for the calibration
float V_reference = 3.3; // set the reference voltage for the calibration
float sensor_voltage = 0.0; // variable storing the calculated voltage

void setup()
{
bitSet(DIDR0, ADC0D); // disable digital input on ADC0
bitSet(DIDR0, ADC1D); // disable digital input on ADC1
Serial.begin(9600); // baud rate for the serial communication
}
void loop()
{
ADC_value = analogRead(1); // read the analog input
ADC_reference = analogRead(0); // read the reference voltage for the calibration
sensor_voltage = V_reference * (float)ADC_value / (float)ADC_reference; // calculate the
voltage with the calibration
Serial.println(sensor_voltage, 2); // print the voltage with 2 decimal places
delay(1000); // wait for a second
}
La calibrazione dell’ADC
Gli eventuali errori di offset e di guadagno dell’ADC possono essere
ridotti con una calibrazione. Sfortunatamente i microprocessori
Atmel delle schede Arduino non offrono meccanismi interni di auto-
calibrazione, quindi la calibrazione deve essere prevista
dall’utente.
Ad esempio, è possibile eseguire la calibrazione una sola volta
all’inizio dell’utilizzo (la cosiddetta power up calibration) oppure
prevedere una calibrazione periodica durante l’esecuzione del codice
(la cosiddetta run time calibration) per ovviare a eventuali
problemi di deriva termica, specialmente nel caso di impieghi su
tempi lunghi.
In generale, la calibrazione dell’ADC è realizzata in due fasi
distinte: in una prima fase, si compensa l’errore di offset mentre
nella seconda è compensato l’errore di guadagno. Esistono varie
implementazioni della procedura di calibrazione. Si suggerisce al
lettore di consultare l’Application Note AN2989 di Freescale e la
AVR120 di Atmel dove ne sono indicate alcune di riferimento.
IL CAMPIONAMENTO
Il teorema del campionamento
In generale, campionare un segnale significa registrarne il valore a
certi istanti e il numero di campioni registrati al secondo (ovvero
la frequenza di campionamento) deve essere sufficientemente elevato
in relazione alla velocità di variazione del segnale, che aumenta al
crescere delle componenti ad alta frequenza contenute dal segnale
stesso.
Com’è noto, questo problema è oggetto del teorema del campionamento,
per il quale dato un segnale a tempo continuo e a banda limitata
FMAX, la minima frequenza di campionamento FS (generalmente indicata
in letteratura come frequenza di Nyquist) necessaria affinché il
segnale campionato contenga la stessa informazione del segnale
originario (cioè sia possibile ricostruire il segnale a partire
dalla sequenza dei suoi campioni) è:

FS ≥ 2 × FMAX

In altre parole, il minimo sampling rate deve essere almeno il


doppio della banda del segnale da campionare. Se questa relazione
non è rispettata, la ricostruzione basata sui singoli campioni
produce un segnale assai diverso da quello originario: questo
fenomeno è chiamato aliasing e non è correggibile a posteriori, ma
deve essere risolto a monte del convertitore.
Per evitare il fenomeno dell’aliasing esistono principalmente due
soluzioni:
─ Aumentare la frequenza di campionamento
─ Introdurre un filtro passa-basso (anti-aliasing) prima del
convertitore

L’aumento della frequenza di campionamento non è sempre perseguibile


poiché, a causa dei limiti hardware e dell’architettura del
convertitore, può comportare una riduzione della risoluzione
effettiva, quella che in letteratura è indicata come Effective
Number Of Bits (ENOB).
L’introduzione del filtro è invece generalmente più semplice da
implementare. I filtri anti-aliasing sono di tipo passa-basso (low
pass) e hanno lo scopo di eliminare le frequenze del segnale
superiori a FS / 2, anche se nella realtà non trattandosi di filtri
ideali si tende a prevedere un certo margine e progettarli con una
frequenza di taglio inferiore (ad esempio, FS / 2.56). Infatti, al
di sopra di questa frequenza la banda del segnale non è
completamente azzerata, ma è attenuata con un fattore che nel caso
di filtri del primo ordine è di 20dB/decade, mentre nei filtri del
secondo ordine è di 40dB/decade e, in generale, un filtro passa-
basso di ordine N attenua N × 20dB/decade, a patto però di
utilizzare un maggior numero di componenti (induttori e
condensatori) e quindi con un maggiore costo e spazio per
realizzarli.
La frequenza di campionamento con
l’ATmega328
Le schede Arduino basate sul microprocessore ATmega328 hanno
generalmente un system clock di 16MHz, determinato da un quarzo
montato sulla scheda stessa. Fanno eccezione poche versioni (tra cui
LilyPad, Fio e Pro Mini da 3.3V) in cui il system clock è di 8MHz.
Il clock del convertitore (ADC clock) è ottenuto dal system clock
tramite un divisore di frequenza (prescaler) il cui valore è
determinato in base al contenuto dei primi tre bit (ADPS2, ADPS1,
ADPS0) del registro ADCSRA (ADC Control and Status Register A).
Per modificare i bit dei registri si utilizzano le funzioni sbi() e
cbi() e i valori programmabili sono 2, 4, 8, 16, 32, 64 e 128, come
riassunto nella tabella seguente:

DIVISORE DEL
ADPS2 ADPS1 ADPS0
PRESCALER
0 0 1 2
0 1 0 4
0 1 1 8
1 0 0 16
1 0 1 32
1 1 0 64
1 1 1 128

Secondo il datasheet dell’ATmega328, affinché sia garantita la


risoluzione di 10-bit, la frequenza di clock ottimale per l’ADC deve
essere compresa tra 50kHz e 200kHz. A frequenze maggiori di questo
intervallo, la risoluzione non è più garantita. Nelle schede Arduino
UNO il prescaler di default è configurato per una divisione di 128,
per cui si ottiene un ADC clock di:

16MHz / 128 = 125kHz

che è appunto una frequenza contenuta all’interno dell’intervallo di


funzionamento ottimale.
Questa configurazione di default con il divisore di 128 è contenuta
nel file wiring.c del software di Arduino:

sbi(ADCSRA, ADPS2);
sbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);

La seguente tabella mostra le possibili opzioni dell’ADC clock


(espresso in kHz) in base al system clock della scheda e al divisore
del prescaler:

SYSTEM CLOCK DIVISORE DEL PRESCALER


FREQUENCY
2 4 8 16 32 64 128
[MHz]
8 4000 2000 1000 500 250 125 62.5
12 6000 3000 1500 750 375 187.5 93.8
16 8000 4000 2000 1000 500 250 125
20 10000 5000 2500 1250 625 313 156

In alcune applicazioni in cui i vincoli di banda sul segnale di


ingresso siano limitanti (come nel caso dei segnali audio) è
possibile utilizzare frequenze di campionamento maggiori di quella
nominale di 125kHz, programmando diversamente il divisore del
prescaler e/o utilizzando una diversa frequenza del system clock
(ovvero ricorrendo a versioni di Arduino con un diverso quarzo
montato sulla scheda).
L’accuratezza dell’ADC dipende dall’ADC clock, la cui massima
frequenza come si è detto non deve superare 200kHz affinché il
circuito di conversione operi in condizioni ottimali. Tuttavia,
nella pratica si rileva che impostando frequenze dell’ADC clock fino
a valori di 500kHz/1MHz la risoluzione effettiva (ENOB) dell’ADC non
si riduce significativamente. Per frequenze maggiori di 1MHz il
comportamento non è però caratterizzato.
Ad esempio, su una scheda Arduino UNO, per ottenere un ADC clock di
1MHz occorre configurare il divisore a 16 inserendo le seguenti
righe di codice all’interno della sezione void setup():

sbi(ADCSRA,ADPS2);
cbi(ADCSRA,ADPS1);
cbi(ADCSRA,ADPS0);
Il tempo di conversione
Il tempo di conversione è il tempo necessario affinché il segnale in
ingresso al convertitore sia campionato e convertito su uno dei
livelli di quantizzazione. Ovviamente, è importante che il segnale
rimanga stabile durante questo intervallo per evitare un errore
sulla conversione.
A questo tempo si deve aggiungere anche quello computazionale
necessario per elaborare il dato acquisito secondo quanto definito
nello sketch. Tuttavia, nelle applicazioni pratiche questo tempo di
elaborazione è generalmente molto più piccolo di quello necessario
all’acquisizione e pertanto non sarà qui considerato.

Fig. 47 – Il tempo di conversione dell’ADC e l’intervallo di campionamento.

L’architettura SAR dell’ADC dell’ATmega328 determina che, terminata


la prima conversione, ciascuna conversione ADC richieda 13 cicli di
ADC clock, che nelle condizioni di default di una scheda Arduino UNO
corrisponde a una frequenza di campionamento di:
FS = 125kHz / 13 = 9.615kHz

ovvero a un intervallo di campionamento teorico di:

1 / 9.615kHz = 104µs

Proviamo adesso a valutare il tempo di conversione “effettivo” di


una scheda Arduino UNO con microprocessore ATmega328. Lo sketch da
utilizzare è il seguente:

unsigned int samples = 1000;


unsigned long time;
unsigned long duration;
unsigned int dummy_input;

void setup()
{
Serial.begin(9600); // baud rate for the serial communication
}

void loop()
{
time = micros();
for (int i = 0; i < samples; i++) {
dummy_input = analogRead(0);
}
duration = micros() - time;
Serial.println(duration);
}

La funzione micros() riporta il numero di microsecondi trascorsi da


quando è iniziato a girare il programma su Arduino. Sulle schede con
clock a 16MHz (come Arduino UNO, Duemilanove, Nano) questa funzione
ha una risoluzione di 4 microsecondi, ovvero il valore che produce è
sempre un multiplo di 4. Sulle schede con clock a 8MHz (come
LilyPad), questa funzione ha invece una risoluzione di 8
microsecondi, ovvero il valore che produce è sempre un multiplo di
8.
Sulla base di circa 12000 misure eseguite a temperatura ambiente sul
tempo necessario a convertire 1000 acquisizione (su un totale cioè
di circa 12 milioni di valori) il test ha prodotto questi risultati:
─ valore minimo: 112.000μs
─ valore medio: 112.009μs
─ valore massimo: 112.024μs

Si tratta di valori molto stabili, da cui si ricava una stima per il


tempo di acquisizione di 112μs (leggermente superiore al valore
teorico atteso di 104µs).

Fig. 48 – La distribuzione delle misure del tempo di conversione dell'ADC di una scheda
Arduino UNO.

ESERCIZIO: Si suggerisce al lettore di provare a ripetere la misura


del tempo di acquisizione utilizzando delle schede Arduino con
microprocessori diversi dall’ATmega328 (ad esempio, le schede
Arduino DUE o Leonardo).

Considerando dunque un intervallo di campionamento misurato di 112μs


si ricava che la frequenza di campionamento effettiva è:

FS = 1 / 112µs = 8.929kHz

Da cui, la massima banda del segnale campionabile:

FMAX = FS / 2 = 4.464kHz ≈ 4.5kHz

Si tratta di una banda sufficientemente ampia per la maggior parte


delle applicazioni realizzabili con una scheda Arduino UNO. Tra
queste, ad esempio, il monitoraggio di segnali biomedici (battito
cardiaco < 100Hz, respiro < 10Hz), l’acquisizione di segnali sismici
(< 200Hz) o le applicazioni in ambito audio a bassa fedeltà.
Infatti, sebbene la banda audio standard si consideri estesa fino a
20kHz, la parte più significativa dei segnali audio vocali è
concentrata nelle frequenze più basse e, d’altra parte, la
maggioranza delle persone in età adulta difficilmente percepisce
frequenze superiori a 16kHz. Questo aspetto è sfruttato, ad esempio,
nelle trasmissioni di segnali telefonici dove, nonostante la banda
sia limitata a 4kHz, la comprensibilità del parlato non è
pregiudicata.

ATTENZIONE: Nel caso in cui si acquisiscano più segnali analogici in


parallelo, la frequenza di campionamento dell’ADC dovrà essere
divisa per il numero dei segnali (a causa del citato meccanismo che
porta a turno i vari canali dal multiplexer al convertitore ADC
interno).
La scelta della frequenza di
campionamento
Come si è detto, la frequenza di campionamento deve essere almeno il
doppio della banda del segnale da campionare. Tuttavia, nella
maggior parte delle applicazioni con schede Arduino collegate a
sensori analogici (temperatura, pH, luminosità, umidità relativa,
ecc.) i segnali da campionare provengono da sensori che misurano
grandezze fisiche che variano lentamente, ovvero con una banda
inferiore a 10Hz. Il convertitore ADC della scheda Arduino consente
di rispettare ampiamente la relazione del teorema del campionamento
in quanto, già in condizioni di default, il massimo clock dell’ADC è
almeno un paio di ordini di grandezza maggiore della banda di questi
segnali. Pertanto, è più pratico scegliere la frequenza di
campionamento considerando la rapidità con cui può variare il
segnale da acquisire in relazione all’accuratezza desiderata.
Facciamo qualche esempio per fissare le idee.
Volendo monitorare con un’accuratezza di 0.1°C la temperatura
interna di una cella termica che può variare di circa 10°C/min, è
necessario campionare la temperatura al più ogni:

0.1°C / 10°C/min = 600ms

Quindi una scelta di un intervallo di campionamento di 500ms (ovvero


2 volte al secondo) sarebbe adeguata.
Consideriamo adesso il caso della misura della temperatura sul
balcone in città. In questo caso, occorre valutare la cosiddetta
escursione termica diurna, cioè la differenza fra la massima
temperatura diurna e la minima temperatura notturna. Alle latitudini
europee, durante l’anno questa escursione è al più di circa 30°C
nell’arco di 12 ore. Quindi la massima variazione di temperatura può
essere stimata come:

30°C / 12h = 2.5°C/h ≈ 0.042°C/min

Puntando ancora a un’accuratezza di 0.1°C, è necessario campionare


al più ogni:

0.1°C / 0.042°C/min ≈ 143s

Quindi una scelta di un intervallo di campionamento di 2 minuti (120


secondi) sarebbe adeguata.
Consideriamo il caso della misura dell’umidità relativa (con
un’accuratezza di ±5%RH) in un ambiente in cui questa possa variare
con una massima escursione di 80%RH nell’arco di 12 ore, il periodo
di campionamento dovrà essere al più:

5%RH / (80%RX / 12h) = 45min

Quindi una scelta di un intervallo di campionamento di 30 minuti


(ovvero 2 volte ogni ora) sarebbe adeguata.
Il campionamento uniforme
In realtà, il problema del campionamento non uniforme con le schede
Arduino non è una questione realmente critica nelle applicazioni
pratiche. Infatti, riprendendo le conclusioni dell’Application Note
4466 (Aperture Jitter Calculator for ADCs) di Maxim Integrated, la
massima aperture jitter di un ADC dipende dalla sua risoluzione e
dalla massima frequenza del segnale di ingresso secondo la
relazione:

tJ = 1 / (π × fIN_MAX × 2N+1)

Nel caso di Arduino UNO, in cui N = 10 e considerando una massima


banda del segnale in ingresso di ≈ 4.5kHz, si ottiene:

tJ = 1 / (π × 4500 × 210+1) = 0.035µs

Questa tolleranza non sarebbe tuttavia perseguibile a meno di


implementare un campionamento uniforme con un codice opportuno (ad
esempio, con la gestione dei cosiddetti timer interrupts).
Fortunatamente, in presenza di segnali a contenuto frequenziale più
limitato, la tolleranza sugli istanti di campionamento è decisamente
più rilassata. Infatti, per segnali con una banda inferiore a 10Hz
si ottiene:

tJ = 1 / (π × 10 × 210+1) = 15.6µs

E anche considerando un eventuale oversampling a 12-bit si avrebbe:

tJ = 1 / (π × 10 × 212+1) = 3.9µs

Si tratta di una variazione massima trascurabile se confrontata


appunto con il tempo di campionamento che per le applicazioni con
questi segnali su Arduino è generalmente impostata su valori di
almeno 100ms e pertanto ampiamente tollerabile senza dover
necessariamente ricorrere nel codice a un’implementazione di un
campionamento uniforme basato sulle interruzioni del timer.
LA TECNICA DELL’OVERSAMPLING
La tecnica dell’oversampling consente di aumentare la risoluzione di
un convertitore analogico/digitale operando un campionamento con una
frequenza maggiore di quella inizialmente prevista FS (comunque
superiore a quella di Nyquist) ed eseguendone un filtraggio a media.
Tralasciando la giustificazione teorica, piuttosto complicata per
l’utente medio di Arduino, si può sostanzialmente riassumere che
nell’ipotesi di voler aumentare la risoluzione di un numero
aggiuntivo B di bit occorre sovra-campionare il segnale di ingresso
con una frequenza di 4B × FS, ovvero per ciascun bit aggiuntivo di
risoluzione occorre campionare il segnale quattro volte più
rapidamente. Si è soliti indicare il valore 4B come oversampling
factor o tasso di sovra-campionamento.
Ad esempio, per ottenere una risoluzione di 12-bit da un ADC a 10-
bit con frequenza di campionamento di 10Hz, occorre campionare 42 =
16 volte più rapidamente, ovvero a 160Hz.
La tecnica dell’oversampling trova un corretto impiego quando il
segnale di ingresso non varia significativamente all’interno della
finestra temporale in cui si acquisiscono i sovra-campioni, in altre
parole quando la tensione che si desidera misurare varia molto più
lentamente del tempo necessario per effettuare le acquisizioni.
Tuttavia, all’aumentare dei bit aggiuntivi questo intervallo di
tempo aumenta esponenzialmente, come illustrato nel grafico seguente
(considerando un tempo di conversione di 112μs):
Fig. 49 – L’intervallo di tempo richiesto al variare del numero di extra bit di oversampling.

Per quanto riguarda le formule da impiegare nel codice, occorre


considerare che la media sui 4B sovra-campioni deve essere scalata
di un valore pari a 2B, poiché i bit aggiuntivi comportano una
moltiplicazione del fondoscala (e quindi dei livelli della
conversione) appunto di un fattore 2B:

Poiché aumenta il numero di bit complessivi del convertitore e


dunque anche il numero di livelli su cui viene mappato l’intervallo
delle tensioni in ingresso, la risoluzione di tensione di ingresso
migliora:
VREF / 2N+B

La tensione stimata è data da:

Nel caso di un oversampling di 2-bit aggiuntivi a partire da un ADC


a 10-bit (come quello di Arduino UNO) occorre utilizzare 42 = 16
sovra-campioni e si ottiene una risoluzione di tensione di ingresso
pari a:

5V / 210+2 = 1.221mV

Inoltre si ottiene:

Oltre a consentire un aumento della risoluzione della conversione


analogico/digitale, la tecnica dell’oversampling offre anche altri
vantaggi. Tra questi è utile segnalare la possibilità di impiegare
dei filtri passa-basso anti-aliasing con prestazioni non
necessariamente elevate (e quindi meno complessi e costosi), perché
lavorando a frequenze maggiori sono in grado di garantire
attenuazioni migliori. Un altro vantaggio consiste nella riduzione
del rumore nella banda di interesse: per ogni bit aggiuntivo di
oversampling si ottiene un miglioramento di circa 6dB nel rapporto
segnale/rumore Signal-to-Quantization-Noise Ratio (SQNR), questo
però al prezzo di quadruplicare la frequenza di campionamento per
ciascun bit.
Il seguente sketch è un esempio di implementazione di un
oversampling a 12-bit (16 sovra-campioni) nel caso di una misura di
tensione sul piedino A0 di una scheda Arduino UNO. Considerando un
tempo di acquisizione di 112µs e nell’ipotesi che la tensione sia
campionata ogni secondo, il tempo di campionamento da utilizzare
nello sketch per l’oversampling può essere calcolato come:

Δt = 1s / 16 − 112µs ≈ 62ms

byte analogPin = 0; // set the analog input to pin


unsigned int total = 0; // variable storing the ADC sum
float vref = 5.0; // set the ADC voltage reference (it’s strongly suggested to use a measured
voltage)
float voltage = 0.0; // variable storing the calculated voltage

void setup()
{
bitSet(DIDR0, ADC0D); // disable digital input on ADC0
Serial.begin(9600); // baud rate for the serial communication
}

void loop()
{
total = 0;
for (int i = 0; i < 16; i++) {
unsigned int a = analogRead(analogPin); // read the input on analogPin
total += a; // accumulate
delay(62); // delay to define the sampling time
}
voltage = (float)total * vref / 16384.0; // calculate the corresponding voltage
Serial.println(voltage, 3); // print the voltage with 3 decimal places
}

Il seguente sketch implementa alcune modifiche rispetto al


precedente in modo da potersi adattare al fattore di oversampling
desiderato e al tempo di campionamento scelto per il segnale, che su
Arduino UNO per la limitazione del tipo unsigned int può essere al
massimo:

(216 − 1)ms = 65535ms = 65.535s

byte analogPin = 0; // set the analog input to pin


unsigned int total = 0; // variable storing the ADC sum (<= 65535)
float vref = 5.0; // set the ADC voltage reference (it’s strongly suggested to use a measured
voltage)
float voltage = 0.0; // variable storing the calculated voltage
unsigned int main_sampling_time_ms = 1000; // variable storing the main sampling time in ms
(<= 65535)
byte extrabits = 2; // set the number of the additional bits of the oversampling (<= 4)
float oversampling_factor = pow(4.0, (float)extrabits);
unsigned int oversampling_time = (unsigned int)((float)main_sampling_time_ms -
(oversampling_factor * 0.112));

void setup()
{
bitSet(DIDR0, ADC0D); // disable digital input on ADC0
Serial.begin(9600); // baud rate for the serial communication
}

void loop()
{
total = 0;
for (int i = 0; i < oversampling_factor; i++) {
unsigned int a = analogRead(analogPin); // read the input on analogPin
total += a; // accumulate
}
voltage = (float)total * vref / (1024.0 * oversampling_factor); // calculate the
corresponding voltage
Serial.println(voltage, 3); // print the voltage with 3 decimal places
delay(oversampling_time); // delay to define the sampling time
}

Questo sketch è molto flessibile e funziona anche nel caso speciale


in cui non si implementi alcun oversampling, ovvero extrabits = 0.
Essendo la variabile total di tipo unsigned long, il massimo
oversampling_factor compatibile con questo sketch è:

(216 – 1) / (210 – 1) = 65535 / 1023 = 64.061

Da cui, il massimo extrabits è:

LN(64.061) / LN(4) = 4.159 ≈ 4

Ovvero, questo sketch supporta oversampling fino a 14-bit.


Nel caso in cui la tensione acquisita provenga da un sensore di
temperatura con un guadagno di 10mV/°C (come LM35), allora volendo
misurare la temperatura è sufficiente modificare lo sketch come
segue:

float temperature = 0.0; // variable storing the calculated temperature


...
...
void loop()
{
...
...
voltage = (float)total * vref / (1024.0 * oversampling_factor); // calculate the
corresponding voltage
temperature = voltage * 100.0; // calculate the corresponding temperature (10mV = 1°C)
Serial.println(temperature, 1); // print the temperature with 1 decimal places
delay(oversampling_time); // delay to define the sampling time
}

ESERCIZIO: Come esercizio, si suggerisce al lettore di implementare


la tecnica dell’oversampling, considerando anche la compensazione
Half LSB Adder dell’errore di quantizzazione per la quale la formula
della tensione stimata diventa:

Il seguente sketch è un esempio che mostra l’impiego di un


oversampling a 12-bit (42 = 16 sovra-campioni) nel caso di una
misura di tensione ogni 500ms, implementando anche la compensazione
Half LSB Adder dell’errore di quantizzazione. In questo esempio,
come tensione di ingresso all’ADC della scheda Arduino UNO è stata
utilizzata la scarica di un condensatore precedentemente caricato
con il regolatore LDO 3.3V presente sulla scheda stessa.

byte analogPin = 0; // set the analog input to pin


unsigned int total = 0; // variable storing the ADC averaged sum
float ADC_over_value = 0.0; // variable storing the oversampling ADC value
float voltage = 0.0; // variable storing the calculated voltage
float vref = 5.020; // set the ADC voltage reference (value measured with a multimeter)

void setup()
{
bitSet(DIDR0, ADC0D); // disable digital input on ADC0
Serial.begin(9600); // baud rate for the serial communication
}

void loop()
{
total = 0;
for (int i = 0; i < 16; i++) {
unsigned int a = analogRead(analogPin); // read the input on analogPin with 10-bit
total += a; // accumulate
delay(31); // delay to define the oversampling time = 500ms / 16
}
ADC_over_value = (float)total / 16.0 + 0.125; // average and compensate the quantization
error [0.125 = 0.5 / 4]
voltage = ADC_over_value * vref / 1024.0; // calculate the corresponding 12-bit voltage
Serial.println(voltage, 3); // print the 12-bit voltage
}
Fig. 50 – Lo schema per l’esempio del campionamento con oversampling a 12-bit della scarica
di un condensatore.
Fig. 51 – I valori di tensione ottenuti nell’esempio del campionamento con oversampling a 12-
bit della scarica del condensatore.
Massimo oversampling
Potrebbe sembrare che, a patto di avere un segnale di ingresso
sufficientemente lento, aumentando il fattore di oversampling sia
possibile migliorare la risoluzione dell’ADC indefinitamente. Ma
fino a che punto ha effettivamente senso spingersi con la tecnica
dell’oversampling? La risposta è legata al rumore del segnale da
acquisire e alla velocità con cui esso varia. In particolare,
occorre rispettare il più stringente dei due vincoli seguenti.

Oversampling vs. rumore


Il primo vincolo deriva dalla constatazione per cui è inutile
scegliere un numero di bit tale che la risoluzione di tensione di
ingresso risulti inferiore al rumore di tensione del segnale da
acquisire.
Consideriamo, ad esempio, la tensione di 3.3V generata sulla scheda
Arduino UNO su cui si è misurato un rumore con un valore quadratico
medio RMS (Root Mean Square) di circa 100µV.
Com’è noto, dal valore RMS si può stimare il valore picco-picco del
rumore moltiplicando per un fattore pari a 6.6, ovvero:

6.6 × 100µV = 660µV

Quindi, con una tensione VREF = 5V, si ottiene un numero di bit di:

log2(5V / 660µV) = 12.887 ≈ 13

E dunque in questo caso, rispetto ai 10-bit di default, è


ragionevole implementare al massimo 3 bit di oversampling.
Consideriamo, un altro esempio. Nel caso dell’accelerometro Analog
Devices ADXL50, la cui densità spettrale di rumore è 125µV/√Hz
supponendo che l’uscita sia filtrata passa-basso a 10Hz, si ricava
un valore quadratico medio (RMS) del rumore di:

125µV/√Hz × √10Hz ≈ 395µV

Da cui si può stimare il valore picco-picco del rumore di tensione:

6.6 × 395µV ≈ 2.61mV

Quindi, con una tensione VREF = 5V, si ottiene un numero di bit di:
log2(5V / 2.61mV) = 10.9 ≈ 11

E dunque in questo caso, rispetto ai 10-bit di default, è


ragionevole implementare al massimo 1 bit di oversampling. Inoltre,
essendo la sensibilità dell’accelerometro di 19mV/g (dove 1g =
9.81m/s2) il valore picco-picco del rumore in termini di
accelerazione è pari a:

2.61mV / 19mV/g = 0.137g = 1.35m/s2

ESERCIZIO: Analog Devices ha sviluppato nuove generazioni di


accelerometri con prestazioni migliorate, come l’ADXL78 la cui la
sensibilità è 55mV/g e la densità spettrale di rumore è 1.1mg/√Hz,
ovvero:

55mV/g × 1.1mg/√Hz = 60.5µV/√Hz

Supponendo ancora che la banda sia limitata a 10Hz, calcolare quanti


bit di oversampling è ragionevole implementare in questo caso
rispetto ai 10-bit di default e tensione VREF = 5V.

Oversampling vs. velocità di variazione


Il secondo vincolo è legato alla considerazione per cui il massimo
numero di bit di oversampling deve essere scelto anche in relazione
alla rapidità di variazione del segnale che si desidera acquisire.
Implementare l’oversampling richiede infatti di acquisire un numero
di sovra-campioni che cresce esponenzialmente all’aumentare del
numero di bit di oversampling. Considerando una scheda Arduino con
un tempo di acquisizione di 112µs, 6 bit di oversampling richiedono:

46 × 112µs ≈ 460ms

Mentre 9 bit richiedono:

49 × 112µs ≈ 29.4s

Ad esempio, come si è detto più sopra, volendo monitorare con


un’accuratezza di 0.1°C la temperatura interna di una cella termica
che può variare di circa 10°C/min, è necessario campionare la
temperatura al più ogni:
0.1°C / 10°C/min = 600ms

Quindi in questo caso il numero di bit di oversampling rispetto ai


10-bit di default potrà essere al massimo

log4(600ms / 112µs) ≈ 6
Le cifre significative in caso di
oversampling
Con la tecnica dell’oversampling aumentano le cifre significative su
cui ha senso rappresentare i valori acquisiti dall’ADC.
Facendo riferimento alle considerazioni precedenti relative al
massimo oversampling per misure di temperatura con un sensore di
tipo TMP36 o LM35, la tabella seguente riassume la risoluzione e il
numero di cifre significative al variare della tensione di
riferimento e del numero di bit:

RISOLUZIONE DECIMALI RISOLUZIONE DI DECIMALI


VREF [V] BIT DI TENSIONE SIGNIFICATIVI DELLA TEMPERATURA SIGNIFCATIVI DELLA
[mV] TENSIONE (in Volt) [°C] TEMPERATURA (in °C)
5 10 4.883 3 0.488 1
3.3 10 3.223 3 0.322 1
2.56 10 2.500 3 0.250 1
1.1 10 1.074 3 0.107 1
5 11 2.441 3 0.244 1
3.3 11 1.611 3 0.161 1
2.56 11 1.250 3 0.125 1
1.1 11 0.537 4 0.054 2
5 12 1.221 3 0.122 1
3.3 12 0.806 4 0.081 2
2.56 12 0.625 4 0.063 2
1.1 12 0.269 4 0.027 2
5 13 0.610 4 0.061 2
3.3 13 0.403 4 0.040 2
2.56 13 0.313 4 0.031 2
1.1 13 0.134 4 0.013 2

ESERCIZIO: Volendo utilizzare una scheda Arduino UNO per misurare


una temperatura è preferibile utilizzare un oversampling a 12-bit
con tensione VREF = 5V o un oversampling a 11-bit con tensione VREF =
1.1V ?
La frequenza di campionamento con
oversampling
Come si è detto, in presenza di segnali che variano lentamente,
ovvero con una banda inferiore a 10Hz, è possibile scegliere la
frequenza di campionamento considerando la rapidità con cui può
variare il segnale da acquisire in relazione all’accuratezza
desiderata. Tuttavia, considerando di implementare la tecnica
dell’oversampling, occorre ridurre l’intervallo di campionamento del
fattore di oversampling. Ad esempio, riprendendo il caso della
misura della temperatura sul balcone in città con Arduino UNO per
cui si era stimato necessario campionare al più ogni 143 secondi,
aumentando la risoluzione da 10-bit a 12-bit (quindi con un fattore
di oversampling di 42 = 16), il massimo periodo di campionamento
scende a:

143s / 16 ≈ 9s

In questo caso, una scelta adeguata potrebbe essere quella di


campionare la temperatura ogni 3.75 secondi così che, dopo 16
campioni (ovvero ogni minuto), si otterrebbe in uscita il valore
finale a 12-bit.
Volendo invece applicare un oversampling a 13-bit, ovvero con 43 =
64 campioni, il massimo periodo di campionamento scende a:

143s / 64 ≈ 2.23s

Anche in questo caso, una scelta adeguata potrebbe essere quella di


campionare la temperatura ogni 937 millisecondi così che dopo 64
campioni (ovvero circa 1 minuto) si avrebbe in uscita il valore
finale a 13-bit.

ESERCIZIO: Considerando una scheda Arduino UNO, valutare


l’intervallo di campionamento per misurare con un’accuratezza di
0.1°C la temperatura di una serra o di un appartamento dotato di
sistema di riscaldamento in cui la temperatura può variare di 10°C
in un’ora, nell’ipotesi di utilizzare un oversampling a 12-bit.
Filtraggi avanzati per l’oversampling
A seconda delle applicazioni il filtro che opera la media sui 4B
campioni aggiuntivi dell’oversampling può essere opportunamente
modificato in modo da adattarsi al particolare tipo di segnale da
acquisire.

Filtri a media ponderata


Si tratta sempre di filtri di tipo FIR (Finite Impulse Response),
dove segnale di uscita è ottenuto come la combinazione lineare di un
numero finito di campioni in ingresso. Per ottenere un oversampling
più “reattivo”, ovvero con una media che risponda più rapidamente
alle variazioni del segnale, è possibile attribuire dei pesi diversi
ai campioni in modo che il contributo di quelli acquisiti prima sia
maggiore.
Quindi dove nel codice si calcolava la media aritmetica

si introducono i pesi, ovvero:

scegliendoli in modo che:

p0 > p1 > … > p4B-1

e che:

La scelta può essere fatta in vari modi. Ad esempio, una soluzione è


quella di utilizzare dei pesi decrescenti in modo lineare e, nel
caso di oversampling a 12-bit, un’implementazione con numeri
razionali può essere la seguente:

p0 = 1.15; p1 = 1.13; p2 = 1.11; p3 = 1.09;


p4 = 1.07; p5 = 1.05; p6 = 1.03; p7 = 1.01;
p8 = 0.99; p9 = 0.97; p10 =0.95; p11 = 0.93;
p12 = 0.91; p13 = 0.89; p14 = 0.87; p15 = 0.85

È molto utilizzata anche la cosiddetta Exponentially Weighted Moving


Average (EWMA) per la quale i pesi diminuiscano esponenzialmente.
Per cui, ad esempio, un’implementazione con numeri razionali nel
caso di un oversampling a 11-bit può essere la seguente:

p0 = 2.7; p1 = 0.9; p2 = 0.3; p3 = 0.1

Svantaggi nell’utilizzo della finestra mobile


Dal punto di vista dell’elaborazione del segnale, il filtro FIR con
media a finestra mobile di durata L = 4B passi utilizzato per
ottenere l’oversampling è un filtro passa-basso la cui frequenza di
taglio è circa:

FS / 2L = FS / (2 × 4B) = FS / 22B+1

Dove FS è la frequenza di campionamento del convertitore.


Nel dominio del tempo la media smorza le variazioni realizzando un
effetto di smoothing sul segnale, quindi le transizioni brusche sono
attenuate. Infatti, questo filtraggio ha una risposta al gradino
caratterizzata da un ritardo di gruppo pari alla metà dei passi,
ovvero 4B / 2. In termini temporali, detto τS l’intervallo di
campionamento, il ritardo introdotto dall’oversampling è

τS × 4B / 2

In pratica, dovendo il filtro acquisire più letture e poi mediarle,


il tempo di elaborazione dei dati aumenta e si riduce la risposta in
frequenza del sistema e dunque anche la sua reattività.
Nell’acquisizione di segnali analogici con Arduino è molto
importante stabilire quale possa essere un ritardo accettabile per
la particolare applicazione. La tabella seguente mostra i valori di
ritardo che si ottengono nel caso di τS = 100ms al crescere dei bit
aggiuntivi di oversampling:

BIT aggiuntivi FATTORE DI Ritardo Ritardo (τS =


BIT totali
(oversampling) OVERSAMPLING [campioni] 100ms)
1 11 4 2 200 ms
2 12 16 8 800 ms
3 13 64 32 3.2 s
4 14 256 128 12.8 s

Filtri a mediana
Il filtraggio a media dell’oversampling è efficace specialmente in
presenza di rumore gaussiano sui segnali in ingresso. Quando invece
sono presenti disturbi impulsivi di ampiezza significativa, il loro
contributo nella media può incidere significativamente sul risultato
finale. In questo caso è preferibile utilizzare filtri non lineari
come quello basato sulla mediana, che considera un certo numero di
campioni, li riordina in modo ascendente (o discendente) e poi ne
ritorna il valore centrale.
Tuttavia, se si sostituisse integralmente il filtraggio a media
dell’oversampling con quello a mediana non si otterrebbe il relativo
aumento della risoluzione poiché questo deriva appunto dall’effetto
della media dei valori quantizzati dell’ADC che genera nuovi livelli
intermedi. Infatti, la mediana consentirebbe soltanto di escludere i
valori quantizzati dell’ADC corrispondenti a picchi anomali dovuti
al rumore sul segnale di ingresso, ma non produrrebbe un aumento
della risoluzione.
Per ottenere dunque una buona immunità ai disturbi impulsivi e un
aumento della risoluzione dell’ADC è necessario combinare i due
filtraggi a livello di codice con il seguente algoritmo:
─ si acquisisce inizialmente un numero di campioni M maggiore di
quello richiesto dal normale oversampling, ovvero M > 4B (conviene
scegliere un numero M pari)
─ si ordinano i campioni aggiuntivi in ordine crescente (o
decrescente);
─ si esegue la media aritmetica sui 4B valori al centro
dell’ordinamento, escludendo cioè dal calcolo i campioni più grandi
e più piccoli tra quelli acquisiti.

Il rovescio della medaglia è che occorre acquisire un numero


maggiore di campioni e che l’ordinamento richiede un impegno
computazionale più oneroso.
Il numero di campioni che occorre acquisire in più rispetto a quelli
richiesti dal normale oversampling dipende dall’ampiezza dei
disturbi impulsivi a cui è soggetto il segnale di ingresso.
Di seguito è riportato un esempio di sketch che implementa questo
filtraggio nel caso di un oversampling a 13-bit con un’acquisizione
di M = 96 campioni anziché i 43 = 64 richiesti, nel caso di una
misura con un sensore di temperatura LM35 alimentato dalla tensione
5V della scheda Arduino UNO, a sua volta alimentata con un
alimentatore esterno, che in questo caso è da 9V. Si utilizza un
campionamento di 25ms e come tensione di riferimento per il
convertitore la tensione bandgap di 1.1V generata internamente
all’ATmega328. In questo caso la risoluzione di temperatura in
ingresso è:

ΔT = ΔV / G = VREF / (G × 2N) = 1.1V / (10mV/°C × 212) ≈ 0.01°C

Fig. 52 – Lo schema per l’esempio dell’acquisizione con filtraggio nel caso di un


oversampling a 13-bit.
byte analogPin = 0; // set the analog input to pin A0
unsigned int total = 0; // variable storing the ADC averaged sum
float ADC_value = 0.0; // variable storing the ADC reading
unsigned int dummyreadings = 63; // set the number of initial ADC readings to discard
float voltage = 0.0; // variable storing the calculated voltage
float temperature = 0.0; // variable storing the calculated temperature
float vref = 1.090; // set the ADC voltage reference (it’s strongly suggested to use a
calibrated voltage)
unsigned int samples[95];

void setup()
{
bitSet(DIDR0, ADC0D); // disable digital input on ADC0
analogReference(INTERNAL);
for (int i = 0; i < dummyreadings; i++) {
unsigned int a = analogRead(analogPin); // ADC readings to discard
}
Serial.begin(9600); // baud rate for the serial communication
}

void loop()
{
for (int i = 0; i < 96; i++) {
unsigned int a = analogRead(analogPin); // read the input on analogPin
int j;
if (a < samples[0] || i == 0) {
j = 0; //insert at the first position
}
else {
for (j = 1; j < i; j++) {
if (samples[j - 1] <= a && samples[j] >= a) {
// j is the insert position
break;
}
}
}
for (int k = i; k > j; k--) {
// move all values higher than the current reading up one position
samples[k] = samples[k - 1];
}
samples[j] = a; //insert the current reading
}

total = 0;
for (int i = 16; i < 80; i++) {
total += samples[i]; // accumulate
}
ADC_value = (float)total / 64.0 + 0.0625; // average and compensate the quantization error
[0.0625 = 0.5 / 8]
voltage = ADC_value * vref / 1024.0; // calculate the corresponding voltage
temperature = 100.0 * voltage; // calculate the corresponding temperature
Serial.println(temperature, 2); // print the voltage with 2 decimal places
delay(989); // delay to define the sampling time of 1s (1s = 1000ms = 989ms + 96samples x
112us)
}
BIBLIOGRAFIA
─ Texas Instruments, LM35 Precision Centigrade Temperature Sensors,
Rev. F, 01/2016.
─ Atmel, ATmega48A/PA/88A/PA/168A/PA/328/P Datasheet Complete, Rev.
J, 11/2015.
─ Texas Instruments, LMT70, LMT70A ±0.05°C Precision Analog
Temperature Sensor, RTD and Precision NTC Thermistor IC, Rev. A,
07/2015.
─ M. Banzi & M. Shiloh, Getting Started with Arduino, O'Reilly Make,
3rd Ed, 2014.
─ T. Karvinen, K. Karvinen, V. Valtokari, Make: Sensors – A Hands-On
Primer for Monitoring the Real World with Arduino and Raspberry Pi,
O'Reilly Make, 2014.
─ E. Williams, Make: AVR Programming Learning to Write Software for
Hardware, O'Reilly Make, 2014.
─ A. Trevennor, Experimenting with AVR Microcontrollers, Apress,
2014.
─ J. Blum, Exploring Arduino: Tools and Techniques for Engineering
Wizardry, John Wiley & Sons, 2013.
─ Atmel, AVR127: Understanding ADC Parameters, 2013.
─ Silicon Labs, AN118: Improving ADC resolution by oversampling and
averaging, 2013.
─ C. Man, MT-229: Quantization Noise: An Expanded Derivation of the
Equation SNR = 6.02 N + 1.76 dB, Analog Devices, 2012.
─ Analog Devices, MT-228: High Speed ADC Analog Input Interface
Considerations, 2012.
─ O. Leuthold, Investigation on Atmega328 Accuracy, 2012.
─ M. Margolis, Arduino Cookbook, O'Reilly, 2nd Ed, 2011.
─ D. Wheat, Arduino Internals, Apress, 2011.
─ Atmel, Analog-to-Digital Converter in the SAM3S4, 2011.
─ Freescale Semiconductor, AN2989: Application Note for Design,
Accuracy, and Calibration of Analog to Digital Converters on the
MPC5500 Family, 2010.
─ NXP Semiconductors, AN10974 – LPC176x/175x 12-bit ADC design
guidelines, 2010.
─ Maxim Integrated, AN4466: Aperture Jitter Calculator for ADCs,
2009.
─ Honeywell, HIH-4030/31 Series – Humidity Sensors, 03/2008.
─ Actel, Improving ADC Results Through Oversampling and Post-
Processing of Data, 2007.
─ Atmel, AVR120: Characterization and Calibration of the ADC on an
AVR, 2006.
─ N. Gray, ABCs of ADCs – Analog-to-Digital Converter Basics,
National Semiconductor, 2006.
─ Atmel, AVR121: Enhancing ADC resolution by oversampling, 2005.
─ STMicroelectronics, AN1636: Understanding and minimising ADC
conversion errors, 2003.
─ Microchip, AN693: Understanding A/D Converter Performance
Specifications, 2002.
─ S.W. Smith, The Scientist and Engineer's Guide to Digital Signal
Processing, 1997.
CONTATTI
Giulio Tamberi è nato a Livorno ed è cresciuto a La Spezia. Si è
laureato in Ingegneria Elettronica presso l’Università di Pisa. Da
alcuni anni vive a Torino dove si è occupato della progettazione
hardware di dispositivi mobile per il settore delle
Telecomunicazioni. Attualmente è responsabile della qualità di
prodotti elettronici in ambito Automotive. È un maker, appassionato
di tecnologie emergenti come la stampa 3D e, ovviamente,
dell’universo Arduino.
È possibile contattare l’autore per suggerimenti, idee o progetti ai
seguenti recapiti:
giulio.tamberi@gmail.com
https://it.linkedin.com/in/giuliotamberi
https://twitter.com/giuliotamberi