3.12. Lecture de signaux analogiques avec l’ADC

Jusqu’à présent, la caméra a lu des signaux numériques : une broche est soit à 0 soit à 1, un interrupteur est ouvert ou fermé. La plupart des signaux issus de capteurs du monde réel sont analogiques : une tension continue qui varie de façon progressive sur une certaine plage. Une photorésistance balaie toutes les tensions entre les rails à mesure que la luminosité ambiante change. La sortie d’un capteur de température dérive de quelques millivolts lorsqu’une pièce se réchauffe. La sortie d’un microphone monte et descend au gré du son environnant.

Un convertisseur analogique-numérique (ADC) fait le pont. Il échantillonne la tension présente sur une broche et renvoie un entier que Python peut lire comme n’importe quelle autre valeur.

3.12.1. Quantification

Une valeur numérique ne peut pas représenter exactement une tension continue. Le rôle de l’ADC est de quantifier : ramener chaque échantillon au niveau le plus proche parmi un ensemble fixe de niveaux. Un ADC à N bits possède 2^N niveaux ; un convertisseur 12 bits en compte 4096 répartis sur sa plage d’entrée.

Une courbe analogique lisse tracée en fonction du temps, superposée à une approximation numérique en escalier. Des lignes horizontales en pointillés marquent les niveaux de quantification ; la courbe en escalier se cale sur le niveau le plus proche du signal analogique à chaque point d'échantillonnage.

Quantification : chaque échantillon du signal analogique (trait plein) est arrondi à l’un des niveaux numériques d’un ensemble fini (ligne en escalier en pointillés).

La tension entre deux niveaux adjacents est le pas de quantification de l’ADC ; tout ce qui est plus petit que cela disparaît dans l’arrondi. Un ADC 12 bits sur une plage de 3,3 V a un pas d’environ 3.3 / 4096 0.8 mV : assez fin pour que la plupart des signaux paraissent quasiment continus du point de vue logiciel.

3.12.2. La classe machine.ADC

machine.ADC encapsule un canal d’entrée analogique. Construisez-la avec la broche que vous voulez lire, puis appelez read_u16() :

from machine import ADC

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

read_u16() renvoie toujours un entier non signé sur 16 bits compris entre 0 et 65535. La résolution native de l’ADC varie selon la carte (12 bits sur STM32, spécifique au portage ailleurs) ; le résultat est aligné à gauche sur 16 bits, de sorte que ce détail matériel ne transparaît pas dans Python : une valeur de 65535 correspond à la pleine échelle quelle que soit la puce.

La tension de référence (l’entrée qui correspond à la pleine échelle) dépend de la carte. Consultez la Cartes OpenMV pour connaître la valeur sur votre caméra. Toute tension supérieure à la référence est lue comme la pleine échelle (et peut endommager la broche si elle dépasse la tension d’entrée maximale absolue).

3.12.2.1. Conversion des unités en tension

La correspondance entre les unités et la tension est linéaire, la valeur de pleine échelle correspondant exactement à Vref :

voltage = counts × Vref / 65535

En code :

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

3.12.3. Diviseurs de tension

Deux résistances en série entre un rail de tension et la masse forment un diviseur de tension. Le nœud situé entre elles se trouve à une tension déterminée par le rapport des deux résistances :

Un diviseur de tension. Vin en haut se connecte à travers R1 à un nœud prélevé en tant que V_out, qui se connecte ensuite à travers R2 à la masse.

Un diviseur de tension : R1 et R2 en série abaissent Vin vers V_out.

V_out = Vin × R2 / (R1 + R2)

Des résistances égales donnent la moitié de la tension du rail ; R2 bien plus petite que R1 place le point de prélèvement près de la masse ; R2 bien plus grande le place près du rail.

La formule suppose que rien d’autre ne tire un courant appréciable de V_out. Une broche d’ADC est à haute impédance (mégohms, nanoampères) et satisfait facilement cette condition, si bien qu’un diviseur alimentant un ADC se comporte comme le prévoit la formule.

3.12.4. Potentiomètres

Un potentiomètre est un composant physique unique qui constitue exactement un diviseur de tension, doté d’un curseur coulissant qui déplace le point de prélèvement entre les deux extrémités. Tourner le bouton modifie R1 et R2 ensemble tout en maintenant constante leur somme (la résistance totale du potentiomètre).

Un potentiomètre câblé entre 3,3 V et la masse. Le curseur est prélevé vers une broche d'ADC.

Un potentiomètre câblé comme source de tension manuelle pour l’ADC : 3,3 V à une extrémité, la masse à l’autre, le curseur vers la broche.

Un potentiomètre est le dispositif d’entrée par excellence pour expérimenter l’ADC. Câblez une extrémité au 3.3 V, l’autre à la masse, et le curseur à une broche compatible ADC ; tourner le bouton fait balayer au curseur toutes les tensions entre les rails.

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. Lire des tensions plus élevées avec un diviseur

Une tension supérieure à Vref plaquera l’ADC à la pleine échelle et peut endommager l’entrée si elle dépasse la valeur maximale absolue. Pour lire une source plus élevée (une batterie, une sortie de capteur dont la plage dépasse Vref), abaissez-la avec un diviseur de tension fixe avant qu’elle n’atteigne la broche :

Un diviseur de tension abaissant une tension élevée V_in vers une broche d'ADC. R1 va de V_in jusqu'à une jonction, qui est prélevée horizontalement vers la broche d'ADC ; R2 continue de la jonction jusqu'à la masse.

Mise à l’échelle d’une source haute tension pour qu’elle convienne à l’ADC : R1 et R2 forment un diviseur de tension fixe dont le point de prélèvement alimente la broche d’ADC.

Choisissez R1 et R2 de sorte que la tension divisée reste à l’intérieur de la plage de l’ADC à la tension d’entrée maximale que vous prévoyez :

V_adc = V_in × R2 / (R1 + R2)

Pour un maximum V_in = 12 V et une référence de 3,3 V, le rapport R2 / (R1 + R2) doit valoir au plus 3.3 / 12 0.275. Un choix courant avec un peu de marge est R1 = 33 , R2 = 10 . Le rapport vaut 10 / 43 0.233, donc V_adc plafonne à environ 12 × 0.233 2.79 V : confortablement en dessous de Vref.

Pour retrouver le V_in d’origine à partir d’une lecture de l’ADC, inversez la formule du diviseur :

V_in = V_adc × (R1 + R2) / R2

En code :

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")

Quelques remarques pratiques :

  • Le diviseur consomme V_in / (R1 + R2) en permanence. Avec R1 + R2 = 43 et V_in = 12 V, cela représente environ 280 µA : généralement négligeable, mais si la source est alimentée par batterie, envisagez des résistances plus grandes (100 kΩ à 1 MΩ) pour réduire la consommation au repos.

  • La tolérance des résistances (typiquement ±1 % ou ±5 %) se répercute directement sur la précision de la mesure. Deux résistances à ±5 % peuvent entacher le V_in reconstitué d’une erreur d’environ ±10 % dans le pire des cas.

  • L’impédance de source du diviseur se combine à toute capacité parasite pour filtrer l’entrée en passe-bas. Pour des signaux à variation rapide, cela compte ; pour une vérification de tension de batterie, non.