Come gestire ingressi analogici e uscite PWM con Arduino
Come sfruttare al massimo i comandi analogRead e analogWrite

Uno dei motivi per cui Arduino è diventato così diffuso è la semplicità con cui permette di leggere segnali analogici e pilotare uscite PWM. Con poche istruzioni è possibile leggere un potenziometro, acquisire il valore di un sensore o regolare la luminosità di un LED.
Questa semplicità, però, può generare qualche equivoco. Gli ingressi analogici non leggono “valori analogici” in modo continuo: usano un convertitore analogico-digitale, chiamato ADC. Allo stesso modo, la funzione analogWrite() non produce quasi mai una vera tensione analogica, ma un segnale digitale PWM, cioè un’onda quadra con duty cycle variabile.
Capire questa differenza è fondamentale per progettare circuiti più precisi, scegliere la giusta tensione di riferimento e sfruttare correttamente la risoluzione disponibile sulla propria scheda.
Che cosa fa davvero analogRead()
La funzione analogRead() legge una tensione applicata a un pin analogico e la converte in un numero intero. La tensione deve rimanere compresa tra GND e la tensione di riferimento analogica, spesso indicata come Vref.
Sulle schede Arduino classiche basate su AVR, come Arduino UNO R3, la risoluzione dell’ADC è di 10 bit. Questo significa che il valore restituito è compreso tra 0 e 1023, per un totale di 1024 livelli.
Valore minimo letto: 0 Valore massimo letto: 1023 Numero di livelli: 1024 Risoluzione: 10 bit
Se la tensione di riferimento è 5 V, ogni passo dell’ADC corrisponde teoricamente a circa 4,88 mV.
5 V / 1024 = 0,00488 V 0,00488 V = 4,88 mV
Se invece la scheda lavora a 3,3 V o se si usa una tensione di riferimento più bassa, ogni passo rappresenta una tensione minore. Questo aumenta la sensibilità della misura, a patto che il segnale da leggere non superi mai la tensione di riferimento scelta.
3,3 V / 1024 = 0,00322 V 0,00322 V = 3,22 mV
Vref: la tensione di riferimento dell’ADC
L’ADC non misura una tensione in senso assoluto: la confronta con una tensione di riferimento. Il valore restituito da analogRead() dipende quindi dal rapporto tra la tensione presente sul pin e Vref.
valore_ADC = tensione_ingresso / Vref * valore_massimo_ADC
Con una scheda a 10 bit, il valore massimo è 1023. Per esempio, su Arduino UNO con riferimento a 5 V, una tensione di 2,5 V restituisce circa 512.
2,5 V / 5 V * 1023 = 511,5
La scelta della tensione di riferimento influenza direttamente la precisione pratica della lettura. Se il sensore non supera mai 1 V, usare una Vref da 5 V significa sprecare gran parte della scala disponibile. In quel caso può essere utile usare un riferimento più basso, se supportato dalla scheda e dal circuito.
Usare analogReference()
La funzione analogReference() permette di modificare la tensione di riferimento usata dall’ADC. Le opzioni disponibili cambiano in base alla scheda e al microcontrollore utilizzato.
analogReference(DEFAULT); analogReference(INTERNAL); analogReference(EXTERNAL);
Su molte schede Arduino classiche, DEFAULT usa la tensione di alimentazione della scheda: 5 V sulle schede a 5 V, 3,3 V sulle schede a 3,3 V.
INTERNAL usa un riferimento interno al microcontrollore, per esempio 1,1 V su molte schede basate su ATmega328P. Questa modalità è utile quando si devono misurare segnali piccoli e si vuole sfruttare meglio la risoluzione dell’ADC.
EXTERNAL usa invece la tensione applicata al pin AREF. Questa soluzione permette di usare un riferimento preciso esterno, ma richiede attenzione: la tensione su AREF non deve superare i limiti della scheda e non deve essere collegata in modo errato mentre è attivo un riferimento interno.
void setup() {
analogReference(EXTERNAL);
Serial.begin(9600);
}
void loop() {
int valore = analogRead(A0);
Serial.println(valore);
}
Quando si usa un riferimento esterno, è buona pratica impostare analogReference(EXTERNAL) prima di effettuare letture analogiche. Inoltre il GND della sorgente di riferimento deve essere collegato al GND di Arduino.
Calcolare la tensione reale da analogRead()
Per convertire il valore restituito da analogRead() in una tensione, bisogna conoscere la risoluzione e la tensione di riferimento utilizzata.
tensione = valore_ADC * Vref / valore_massimo_ADC
Con Arduino UNO, ADC a 10 bit e Vref pari a 5 V, il valore massimo è 1023.
int pinSensore = A0;
float vref = 5.0;
void setup() {
Serial.begin(9600);
}
void loop() {
int valore = analogRead(pinSensore);
float tensione = valore * vref / 1023.0;
Serial.print("Valore ADC: ");
Serial.print(valore);
Serial.print(" - Tensione: ");
Serial.print(tensione);
Serial.println(" V");
delay(500);
}
In alcuni calcoli si usa 1024 al posto di 1023 per stimare l’ampiezza del singolo intervallo. Per convertire il valore massimo letto nella tensione corrispondente, però, usare 1023 rende più chiaro il fatto che il valore massimo della scala a 10 bit è proprio 1023.
analogReadResolution(): quando serve davvero
La funzione analogReadResolution() consente di modificare la risoluzione usata da analogRead(), ma non è disponibile su tutte le schede. Sulle schede AVR classiche, come Arduino UNO R3, la risoluzione hardware dell’ADC resta a 10 bit.
Su schede più recenti, come Arduino Due, Zero, MKR, Nano 33 e altre piattaforme compatibili, è possibile impostare risoluzioni superiori, per esempio 12 bit. In questo caso il valore letto può arrivare a 4095.
void setup() {
Serial.begin(9600);
analogReadResolution(12);
}
void loop() {
int valore = analogRead(A0);
Serial.println(valore);
delay(500);
}
Con una risoluzione a 12 bit si ottengono 4096 livelli.
Valore minimo letto: 0 Valore massimo letto: 4095 Numero di livelli: 4096 Risoluzione: 12 bit
Se la tensione di riferimento è 3,3 V, ogni passo corrisponde teoricamente a circa 0,805 mV.
3,3 V / 4096 = 0,000805 V 0,000805 V = 0,805 mV
Aumentare la risoluzione non rende automaticamente più accurata la misura. La risoluzione indica quanti livelli sono disponibili, mentre l’accuratezza dipende anche da rumore elettrico, stabilità della tensione di riferimento, qualità del sensore, cablaggio, alimentazione e caratteristiche reali dell’ADC.
Esempio pratico: lettura di un potenziometro
Un potenziometro è uno degli esempi più semplici per comprendere la lettura analogica. Collegando i due estremi a 5 V e GND e il cursore centrale ad A0, Arduino leggerà un valore proporzionale alla posizione della manopola.
const int pinPotenziometro = A0;
void setup() {
Serial.begin(9600);
}
void loop() {
int valore = analogRead(pinPotenziometro);
Serial.print("Valore letto: ");
Serial.println(valore);
delay(200);
}
Su Arduino UNO il valore sarà compreso tra 0 e 1023. Su una scheda configurata a 12 bit, invece, potrà essere compreso tra 0 e 4095.
Che cosa fa davvero analogWrite()
Il nome analogWrite() può trarre in inganno. Sulla maggior parte delle schede Arduino, questa funzione non genera una vera tensione analogica variabile. Genera invece un segnale PWM, cioè un segnale digitale che passa rapidamente da LOW a HIGH.
Variando il tempo in cui il segnale resta alto rispetto al periodo totale, si modifica il duty cycle. Un LED collegato a un pin PWM apparirà più o meno luminoso perché l’occhio umano percepisce una luminosità media.
Duty cycle 0%: segnale sempre LOW Duty cycle 50%: segnale HIGH per metà del tempo Duty cycle 100%: segnale sempre HIGH
Sulle schede classiche, analogWrite() usa normalmente una risoluzione a 8 bit. Il valore passato alla funzione è quindi compreso tra 0 e 255.
analogWrite(pin, 0); // 0% analogWrite(pin, 127); // circa 50% analogWrite(pin, 255); // 100%
Su Arduino UNO R3 i pin PWM sono indicati dal simbolo ~ e corrispondono ai pin digitali 3, 5, 6, 9, 10 e 11.
Arduino UNO R3 - pin PWM: 3, 5, 6, 9, 10, 11
Esempio pratico: fading di un LED
Questo esempio aumenta e diminuisce gradualmente la luminosità di un LED collegato a un pin PWM.
const int pinLed = 9;
void setup() {
pinMode(pinLed, OUTPUT);
}
void loop() {
for (int luminosita = 0; luminosita <= 255; luminosita++) {
analogWrite(pinLed, luminosita);
delay(5);
}
for (int luminosita = 255; luminosita >= 0; luminosita--) {
analogWrite(pinLed, luminosita);
delay(5);
}
}
Il LED non riceve una tensione realmente continua e variabile: riceve impulsi digitali molto rapidi. Per carichi come LED e piccoli motori DC, il PWM è spesso sufficiente. Per ottenere una vera tensione analogica è necessario usare un DAC oppure filtrare il PWM con un circuito opportuno.
analogWriteResolution(): PWM e DAC sulle schede compatibili
Su alcune schede Arduino moderne è disponibile la funzione analogWriteResolution(), che permette di modificare la risoluzione dei valori usati da analogWrite().
void setup() {
analogWriteResolution(12);
}
void loop() {
analogWrite(9, 2048);
}
Con una risoluzione a 12 bit, il valore massimo diventa 4095.
Risoluzione: 12 bit Valore minimo: 0 Valore massimo: 4095 Valore centrale: circa 2048
Questa funzione è utile sulle schede che supportano PWM a risoluzione configurabile o che dispongono di vere uscite DAC. È importante controllare sempre la documentazione della propria scheda, perché non tutti i pin hanno le stesse capacità.
PWM non significa DAC
Una distinzione importante riguarda PWM e DAC. Il PWM alterna rapidamente due soli livelli logici: LOW e HIGH. Un DAC, invece, genera direttamente una tensione analogica proporzionale al valore impostato.
Per esempio, scrivere un valore intermedio con analogWrite() su un pin PWM non significa ottenere automaticamente 2,5 V stabili su una scheda a 5 V. Significa ottenere un segnale digitale che, mediamente, può produrre un effetto equivalente su alcuni carichi.
analogWrite(9, 127);
Su un pin PWM di Arduino UNO, il comando precedente genera un duty cycle di circa il 50%. Non genera una tensione continua di 2,5 V.
Per ottenere una tensione continua più stabile si può usare un filtro passa-basso RC, un DAC esterno, oppure una scheda dotata di DAC integrato.
Leggere un ingresso analogico e pilotare un’uscita PWM
Uno degli usi più comuni consiste nel leggere un valore analogico e usarlo per controllare un’uscita PWM. Poiché analogRead() su Arduino UNO restituisce valori da 0 a 1023, mentre analogWrite() accetta valori da 0 a 255, bisogna rimappare la scala.
const int pinPotenziometro = A0;
const int pinLed = 9;
void setup() {
pinMode(pinLed, OUTPUT);
}
void loop() {
int valoreAnalogico = analogRead(pinPotenziometro);
int valorePwm = map(valoreAnalogico, 0, 1023, 0, 255);
analogWrite(pinLed, valorePwm);
}
In alternativa, poiché 1024 diviso 256 fa 4, si può dividere il valore letto per 4.
int valorePwm = analogRead(A0) / 4; analogWrite(9, valorePwm);
La funzione map() è più leggibile e più flessibile, soprattutto quando i limiti del sensore non coincidono con l’intera scala dell’ADC.
Ridurre il rumore nelle letture analogiche
Le letture analogiche possono variare anche quando il segnale sembra stabile. Questo è normale: l’ADC è sensibile al rumore elettrico, alla qualità dell’alimentazione, alla lunghezza dei cavi e alla stabilità del riferimento analogico.
Per migliorare la qualità della lettura si possono adottare alcune accortezze:
- usare collegamenti corti e ordinati;
- collegare correttamente tutte le masse;
- evitare che i cavi analogici passino vicino a motori, relè o carichi rumorosi;
- usare condensatori di disaccoppiamento dove necessario;
- mediare più letture consecutive;
- usare una tensione di riferimento stabile.
Una media semplice può ridurre le oscillazioni casuali.
const int pinSensore = A0;
int leggiMediaAnalogica(int pin, int campioni) {
long somma = 0;
for (int i = 0; i < campioni; i++) {
somma += analogRead(pin);
delay(2);
}
return somma / campioni;
}
void setup() {
Serial.begin(9600);
}
void loop() {
int valoreMedio = leggiMediaAnalogica(pinSensore, 20);
Serial.println(valoreMedio);
delay(200);
}
La media rende il valore più stabile, ma rallenta la risposta. Per sensori lenti, come potenziometri o sensori di temperatura, è spesso una soluzione efficace. Per segnali rapidi, invece, bisogna scegliere con attenzione frequenza di campionamento e tecnica di filtraggio.
Quando cambiare la tensione di riferimento
Cambiare Vref è utile quando il segnale da misurare occupa solo una parte ridotta della scala disponibile. Per esempio, se un sensore produce al massimo 1 V e la scheda usa una Vref da 5 V, gran parte dei livelli dell’ADC non verrà mai usata.
Sensore: 0 - 1 V Vref: 5 V ADC: 10 bit Livelli teoricamente usati: 1 V / 5 V * 1024 = circa 205 livelli
Usando un riferimento vicino a 1,1 V, se supportato e sicuro per il circuito, si sfrutterebbe quasi tutta la scala dell’ADC.
Sensore: 0 - 1 V Vref: 1,1 V ADC: 10 bit Livelli teoricamente usati: 1 V / 1,1 V * 1024 = circa 931 livelli
Il vantaggio è evidente, ma c’è una regola da non ignorare: il segnale applicato al pin analogico non deve superare la tensione di riferimento scelta e non deve superare i limiti elettrici della scheda.
Errori comuni da evitare
Un errore frequente è pensare che tutti i pin analogici possano generare PWM. In realtà, su Arduino UNO i pin analogici sono ingressi ADC, mentre i pin PWM sono pin digitali specifici indicati dal simbolo ~.
Un altro errore comune è credere che analogWrite() generi sempre una tensione analogica. Nella maggior parte dei casi genera PWM, non una vera uscita analogica.
Bisogna inoltre evitare di applicare ai pin analogici tensioni superiori alla tensione ammessa dalla scheda. Un sensore a 5 V non è automaticamente compatibile con una scheda a 3,3 V.
Infine, non bisogna confondere risoluzione e accuratezza. Passare da 10 a 12 bit aumenta il numero di livelli disponibili, ma non elimina errori di misura, rumore, offset, instabilità dell’alimentazione o limiti del sensore.
Riepilogo operativo
analogRead() - Legge una tensione su un pin analogico. - Su Arduino UNO R3 restituisce valori da 0 a 1023. - La scala dipende dalla tensione di riferimento Vref. analogReference() - Cambia la tensione di riferimento dell'ADC. - Le opzioni disponibili dipendono dalla scheda. - Va usata con attenzione, soprattutto con riferimenti esterni. analogReadResolution() - Modifica la risoluzione di lettura sulle schede compatibili. - Non aumenta la risoluzione hardware sulle schede che non la supportano. analogWrite() - Genera normalmente un segnale PWM. - Sulle schede classiche usa valori da 0 a 255. - Non produce una vera tensione analogica, salvo pin DAC specifici. analogWriteResolution() - Modifica la risoluzione di scrittura sulle schede compatibili. - È utile con PWM avanzato o vere uscite DAC.
Gestire ingressi analogici e uscite PWM con Arduino è semplice, ma per ottenere risultati affidabili è importante sapere cosa avviene realmente nel microcontrollore.
analogRead() converte una tensione in un numero digitale usando un ADC e una tensione di riferimento. analogWrite(), invece, nella maggior parte dei casi genera un segnale PWM e non una vera uscita analogica.
Modificare la tensione di riferimento, usare la giusta risoluzione e distinguere tra PWM e DAC permette di realizzare misure più sensate, controlli più precisi e progetti più robusti.



