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.
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