Tutorial · Principiante · 30 min

Lettura delle tensioni analogiche: riferimenti ADC e divisori

analogRead restituisce un rapporto, non una tensione: scegli un riferimento stabile, dimensiona un divisore e costruisci un monitor della batteria affidabile.

Introduzione

analogRead() è la funzione più amichevole di un Arduino e anche una delle più insidiosamente fuorvianti. Restituisce un numero compreso tra 0 e 1023, tutti lo trattano come una tensione e il più delle volte non succede nulla di male.

Su un robot, invece, qualcosa va storto, perché un robot ha motori e i motori muovono proprio la grandezza rispetto alla quale quel numero viene misurato.

analogRead restituisce un rapporto

L’ADC non misura i volt. Confronta il proprio ingresso con un riferimento e restituisce il rapporto:

reading = 1023 × V_in / V_ref

Su un Arduino Uno, V_ref vale per impostazione predefinita AVCC, cioè la linea di alimentazione a 5 V della scheda. Quindi analogRead() risponde davvero alla domanda «quale frazione della linea da 5 V è questa?» e convertirla in volt moltiplicando per 5/1023 è corretto solo finché la linea è esattamente a 5 V.

Sai già che non lo è, ogni volta che i motori sono in funzione.

Il riferimento è il bug

Collega una batteria attraverso un divisore ad A0 e guarda il numero mentre i motori si avviano.

Due grafici sovrapposti. Quello superiore mostra i conteggi ADC rispetto alla tensione del pacco da 6 a 8.4 volt per due progetti: un riferimento sulla linea da 5 volt con un divisore 20k/10k che sale da circa 409 a 573 conteggi, e un riferimento interno da 1.1 volt con un divisore 68k/10k che sale da circa 715 a 1002. Il grafico inferiore mostra la tensione della batteria che ogni progetto riporta per un pacco stabile da 7.4 volt mentre la linea reale da 5 volt varia da 4.2 a 5.2 volt: il progetto basato sulla linea scende da 8.8 a 7.1 volt, mentre quello basato sul riferimento interno resta stabile a 7.4.
In basso: il pacco non si muove. Mentre la linea cala da 5.0 V a 4.2 V sotto il carico dei motori, il codice che si fida di un riferimento da 5 V riporta la batteria in aumento da 7.4 V a 8.8 V. Il riferimento bandgap riporta sempre 7.40 V. In alto: gli stessi 10 bit si distribuiscono su 286 conteggi invece di 164, perché il divisore è stato dimensionato per riempire il riferimento. Scarica SVG

Un blocco per bassa tensione costruito sul primo progetto fa esattamente il contrario del suo lavoro: più il robot lavora, più la batteria sembra sana. E il guasto è silenzioso, perché ogni lettura singola è un rapporto perfettamente valido.

La soluzione è un riferimento che non si muove. L’ATmega328P ha a bordo un riferimento bandgap da 1.1 V:

analogReference(INTERNAL);   // 1.1 V on an Uno; INTERNAL1V1 on a Mega
analogRead(A0);              // discard — the reference needs a moment to settle

Scarta sempre la prima lettura dopo aver cambiato il riferimento. Sia il riferimento interno sia il condensatore d’ingresso hanno bisogno di tempo, e il primo campione viene acquisito prima che quel tempo sia trascorso.

Dimensionare il divisore

Ora il riferimento è 1.1 V e il tuo pacco è 8.4 V, quindi il divisore deve portare il massimo dell’intervallo appena sotto il riferimento:

V_adc = V_pack × R2 / (R1 + R2)

Con R1 = 68 kΩ e R2 = 10 kΩ il rapporto è 0.128, quindi un pacco pieno da 8.4 V presenta 1.077 V—appena sotto il riferimento e usando quasi tutta la scala. Tre numeri da controllare in qualsiasi divisore che progetti:

Controllo Questo divisore Perché
Massimo dell’intervallo rispetto al riferimento 1.077 V contro 1.1 V Se supera il riferimento, si appiattisce
Corrente a riposo 108 µA Drena 2.6 mAh al giorno, trascurabili rispetto a 2000 mAh
Impedenza di uscita (R1∥R2) 8.7 kΩ Tienila sotto circa 10 kΩ, vedi sotto

Confrontalo con la versione ingenua—un divisore da 20 kΩ/10 kΩ letto rispetto alla linea da 5 V. Mette un pacco pieno a 2.80 V, cioè 573 conteggi su 1023 possibili, e usa 164 conteggi per tutto l’intervallo del pacco da 6.0–8.4 V contro i 286 del progetto migliore. Quasi il doppio della risoluzione con le stesse due resistenze e una riga di codice.

La risoluzione non è la precisione

Dieci bit rispetto a un riferimento da 5 V sono 4.88 mV per passo; rispetto a quello da 1.1 V sono 1.07 mV. Nessuno dei due numeri è la tua precisione.

Ciò che ti limita davvero è:

  • Tolleranza del riferimento. Il bandgap è nominalmente 1.1 V e varia di qualche punto percentuale tra i chip. È un errore di scala fisso, quindi misuralo una volta per scheda: applica una tensione nota, leggi i conteggi e ricava il riferimento reale. Memorizza quella costante. È una fase di calibrazione, non un affinamento opzionale.
  • Rumore. L’alimentazione di un robot è rumorosa e la media è la risposta economica. La media di N campioni riduce il rumore casuale di √N–16 campioni sono 4× più silenziosi, un bit in più di risoluzione utilizzabile. Non fa nulla per un errore di scala.
  • Impedenza della sorgente. L’ADC carica un piccolo condensatore di campionamento e memorizzazione attraverso ciò a cui è collegato. Una sorgente ad alta impedenza non riesce a riempirlo nel tempo consentito e il residuo del canale letto in precedenza è ancora dentro.

Quest’ultimo punto produce un bug caratteristico che vale la pena nominare.

Canali che leggono come i loro vicini

Leggi A0 e A1 a turno con sorgenti ad alta impedenza e ogni lettura viene tirata verso la precedente. Fai scorrere un potenziometro su A0 e guarda A1 muoversi in sintonia. Non c’è niente di collegato male.

Ci sono due soluzioni e puoi usarle entrambe:

int readSettled(uint8_t pin) {
  analogRead(pin);        // charge the sample-and-hold on this channel
  return analogRead(pin); // then take the one you keep
}

Leggere due volte e scartare la prima dà al condensatore una seconda finestra di campionamento per raggiungere la nuova tensione. L’altra soluzione è ridurre l’impedenza della sorgente—ecco perché il divisore sopra usa 68 kΩ/10 kΩ invece del 100 kΩ/100 kΩ che sembra più ordinato e dà un’impedenza di uscita di 50 kΩ.

Dove si manifesta su un robot

  • Monitoraggio della batteria, come sopra—ed è ciò che rende affidabile quanto basta un blocco per bassa tensione per proteggere un pacco agli ioni di litio.
  • Sensori di linea analogici. Una schiera di sensori a riflessione IR fornisce un valore continuo per sensore, ed è ciò che permette a un robot seguilinea di calcolare una posizione fluida anziché un indicatore sinistra/destra. Questi sensori vengono letti rispetto alla stessa linea che i motori stanno facendo calare, ed è uno dei motivi per cui la calibrazione di un seguilinea deriva mentre la batteria si scarica.
  • Misura della corrente, tramite uno shunt o un sensore Hall la cui uscita è una tensione.
  • Potenziometri come manopole di regolazione, il modo più semplice di regolare un guadagno PID senza riprogrammare.
  • Non per il sensore da 5 V di una scheda da 3.3 V. Un ESP32 o un Raspberry Pi ha bisogno del divisore per la protezione, non solo per la portata.

Quando qualcosa va storto

Sintomo Di solito
La lettura della batteria aumenta quando si avviano i motori Riferimento alla linea che cala; usa INTERNAL
Lettura bloccata a 1023 Ingresso sopra il riferimento—rapporto del divisore troppo alto
Un canale ripete quello letto prima Impedenza della sorgente troppo alta; leggi due volte e scarta
Le letture saltano di decine di conteggi Rumore dell’alimentazione; fai la media e controlla le linee di alimentazione
La tensione è sempre sbagliata di qualche punto percentuale Riferimento interno non calibrato—misuralo una volta
La prima lettura dopo analogReference() è sbagliata Previsto; scartala
La calibrazione del sensore di linea deriva mentre la batteria si scarica Letture riferite alla linea mentre la tensione scende
Viene usato solo il 15% dell’intervallo Divisore dimensionato per 5 V mentre leggi rispetto a 1.1 V

La regola che evita quasi tutto questo: decidi il riferimento prima di decidere qualsiasi altra cosa. Ogni numero che l’ADC ti dà è relativo a esso, quindi un riferimento che si muove trasforma ogni lettura in una storia su due cose allo stesso tempo.

Esplora il grafo

Fa parte di queste costruzioni

Progetti e percorsi di apprendimento che includono questo tutorial.

Per approfondire

Fonti