3.12. Leggere segnali analogici con l’ADC

Finora la camera ha letto segnali digitali: un pin è 0 oppure 1, un interruttore è aperto o chiuso. La maggior parte dei segnali provenienti da sensori del mondo reale è invece analogica: una tensione continua che varia in modo regolare entro un certo intervallo. Una fotoresistenza attraversa tutte le tensioni tra le alimentazioni al variare della luminosità ambientale. L’uscita di un sensore di temperatura si sposta di qualche millivolt mentre una stanza si scalda. L’uscita di un microfono sale e scende con il suono che lo circonda.

Un convertitore analogico-digitale (ADC) è il ponte tra i due mondi. Campiona la tensione su un pin e restituisce un intero che Python può leggere come qualsiasi altro valore.

3.12.1. Quantizzazione

Un valore digitale non può rappresentare esattamente una tensione continua. Il compito dell’ADC è quantizzare, ovvero ricondurre ogni campione al livello più vicino di un insieme fisso di livelli. Un ADC a N bit ha 2^N livelli; un convertitore a 12 bit ne ha 4096 distribuiti lungo il suo intervallo di ingresso.

Una curva analogica regolare tracciata in funzione del tempo, sovrapposta a un'approssimazione digitale a gradini. Linee orizzontali tratteggiate indicano i livelli di quantizzazione; la curva a gradini si aggancia al livello più vicino al segnale analogico in ciascun punto di campionamento.

Quantizzazione: ogni campione del segnale analogico (linea continua) viene arrotondato a uno di un insieme finito di livelli digitali (linea tratteggiata a gradini).

La tensione tra due livelli adiacenti è il passo di quantizzazione dell’ADC; qualsiasi cosa più piccola di questo valore si perde nell’arrotondamento. Un ADC a 12 bit su un intervallo di 3,3 V ha un passo di circa 3.3 / 4096 0.8 mV, abbastanza fine perché la maggior parte dei segnali appaia di fatto continua nel software.

3.12.2. La classe machine.ADC

machine.ADC incapsula un singolo canale di ingresso analogico. Costruiscila con il pin che vuoi leggere, poi chiama read_u16():

from machine import ADC

adc = ADC("P6")
value = adc.read_u16()
print(value)

read_u16() restituisce sempre un intero a 16 bit senza segno compreso tra 0 e 65535. La risoluzione nativa dell’ADC varia a seconda della scheda (12 bit su STM32, specifica della porta altrove); il risultato è allineato a sinistra su 16 bit così che il dettaglio hardware non trapeli in Python: un valore di 65535 corrisponde al fondo scala indipendentemente dal chip.

La tensione di riferimento, ovvero l’ingresso che corrisponde al fondo scala, dipende dalla scheda. Consulta la Schede OpenMV per il valore relativo alla tua camera. Qualsiasi tensione superiore al riferimento viene letta come fondo scala (e può danneggiare il pin se supera la tensione di ingresso massima assoluta).

3.12.2.1. Convertire i conteggi in tensione

La conversione dai conteggi alla tensione è lineare, con i conteggi di fondo scala che corrispondono esattamente a Vref:

voltage = counts × Vref / 65535

In codice:

VREF = 3.3  # cam-dependent; see the quickref
counts = adc.read_u16()
voltage = counts * VREF / 65535
print(voltage, "V")

3.12.3. Partitori di tensione

Due resistori in serie tra un’alimentazione e massa formano un partitore di tensione. Il nodo tra i due si trova a una tensione determinata dal rapporto tra i due resistori:

Un partitore di tensione. Vin in alto si collega attraverso R1 a un nodo prelevato come V_out, che a sua volta si collega attraverso R2 a massa.

Un partitore di tensione: R1 e R2 in serie riducono Vin a V_out.

V_out = Vin × R2 / (R1 + R2)

Resistori uguali danno metà della tensione di alimentazione; un R2 molto più piccolo di R1 porta il prelievo vicino a massa; un R2 molto più grande lo porta vicino all’alimentazione.

La formula presuppone che nient’altro assorba una corrente apprezzabile da V_out. Un pin ADC è ad alta impedenza (megaohm, nanoampere) e soddisfa facilmente questa condizione, quindi un partitore che alimenta un ADC si comporta come previsto dalla formula.

3.12.4. Potenziometri

Un potenziometro è un singolo componente fisico che è esattamente un partitore di tensione, dotato di un cursore scorrevole che sposta il prelievo tra i due estremi. Ruotando la manopola si modificano R1 e R2 insieme, mantenendo costante la loro somma (la resistenza totale del potenziometro).

Un potenziometro collegato tra 3,3 V e massa. Il cursore è prelevato verso un pin ADC.

Un potenziometro cablato come sorgente di tensione manuale per l’ADC: 3,3 V a un estremo, massa all’altro, cursore al pin.

Un potenziometro è il dispositivo di ingresso per eccellenza per provare l’ADC. Collega un estremo a 3.3 V, l’altro a massa e il cursore a un pin compatibile con l’ADC; ruotando la manopola il cursore attraversa tutte le tensioni tra le alimentazioni.

import time
from machine import ADC

pot = ADC("P6")
VREF = 3.3

while True:
    counts = pot.read_u16()
    voltage = counts * VREF / 65535
    print(voltage, "V")
    time.sleep_ms(100)

3.12.5. Leggere tensioni più elevate con un partitore

Una tensione superiore a Vref porterà l’ADC al fondo scala e può danneggiare l’ingresso se supera il valore massimo assoluto. Per leggere una sorgente più alta, come una batteria o l’uscita di un sensore che eccede Vref, riducila con un partitore di tensione fisso prima che raggiunga il pin:

Un partitore di tensione che riduce una V_in elevata fino a un pin ADC. R1 va da V_in verso una giunzione, che viene prelevata orizzontalmente verso il pin ADC; R2 prosegue dalla giunzione fino a massa.

Riduzione di una sorgente ad alta tensione per adattarla all’ADC: R1 e R2 formano un partitore di tensione fisso il cui prelievo alimenta il pin ADC.

Scegli R1 e R2 in modo che la tensione partita rimanga entro l’intervallo dell’ADC alla massima tensione di ingresso prevista:

V_adc = V_in × R2 / (R1 + R2)

Per un valore massimo V_in = 12 V e un riferimento di 3,3 V, il rapporto R2 / (R1 + R2) deve essere al più 3.3 / 12 0.275. Una scelta comune con un po” di margine è R1 = 33 , R2 = 10 . Il rapporto è 10 / 43 0.233, quindi V_adc raggiunge al massimo circa 12 × 0.233 2.79 V, comodamente al di sotto di Vref.

Per ricavare la V_in originale da una lettura dell’ADC, inverti la formula del partitore:

V_in = V_adc × (R1 + R2) / R2

In codice:

from machine import ADC

R1 = 33_000
R2 = 10_000
VREF = 3.3

adc = ADC("P6")

counts = adc.read_u16()
v_adc = counts * VREF / 65535
v_in = v_adc * (R1 + R2) / R2
print(v_in, "V")

Alcune note pratiche:

  • Il partitore assorbe V_in / (R1 + R2) in modo continuo. Con R1 + R2 = 43 e V_in = 12 V, sono circa 280 µA: di solito trascurabili, ma se la sorgente è alimentata a batteria valuta resistori più grandi (da 100 kΩ a 1 MΩ) per ridurre l’assorbimento a riposo.

  • La tolleranza dei resistori (tipicamente ±1 % o ±5 %) si ripercuote direttamente sull’accuratezza della misura. Due resistori al ±5 % possono dare alla V_in ricavata un errore nel caso peggiore di circa ±10 %.

  • L’impedenza di sorgente del partitore si combina con qualsiasi capacità parassita filtrando l’ingresso come un passa-basso. Per i segnali che variano rapidamente questo conta; per il controllo della tensione di una batteria no.