Le concept précédent, les GPIO, ne connaissait que deux valeurs : allumé ou éteint. Mais le monde réel est plein de nuances : un joystick n’est pas seulement « à gauche » ou « à droite », il se pousse plus ou moins loin ; une LED peut briller à mi-puissance ; un moteur tourner lentement.
Pour manipuler ces valeurs intermédiaires, le microcontrôleur dispose de deux outils, tous deux au menu de ce concept : l’ADC pour lire une grandeur qui varie en continu, et le PWM pour en simuler une en sortie.
À la fin de ce concept, tu sauras :
convertir une tension analogique en nombre avec l’ADC ;
simuler une tension variable en sortie avec le PWM ;
mettre une lecture à l’échelle avec map().
Du continu au numérique : l’ADC
Le monde physique est analogique : une température, une position de joystick, une luminosité produisent des tensions qui varient en continu entre 0 V et 3,3 V. Le microcontrôleur, lui, ne comprend que des nombres entiers.
Le convertisseur analogique-numérique (ADC, Analog-to-Digital Converter) transforme cette tension continue en un entier.
Résolution : 12 bits sur l’ESP32-S3
L’ADC de l’ESP32-S3 a une résolution de 12 bits, ce qui donne $2^{12} = 4096$ valeurs possibles :
intvaleur=analogRead(PIN_JOYSTICK_X);// retourne 0 à 4095
L'ADC n'est pas parfaitement linéaire
La formule ci-dessus est une approximation pédagogique. En réalité, l’ADC de l’ESP32 est légèrement non linéaire (surtout près de 0 V et de 3,3 V) et sa pleine échelle dépend de l’atténuation configurée. Pour une mesure précise on calibre l’ADC ; pour lire un joystick, cette approximation suffit largement.
La plage de mesure : l’atténuation
Pour couvrir une plage proche de 0–3,3 V, Arduino-ESP32 applique par défaut l’atténuation maximale (11 dB) à analogRead() : c’est pourquoi elle « fonctionne » sans réglage. Sur l’ESP32-S3, la plage réellement mesurable va alors d’environ 0 à 3,1 V ; au-delà, la lecture sature à 4095. On peut ajuster ce comportement et, surtout, obtenir directement une valeur en millivolts déjà calibrée :
analogSetAttenuation(ADC_11db);// plage étendue (~0–3,1 V), le défautintmv=analogReadMilliVolts(POT_PIN);// tension en mV, corrigée d'usine
analogReadMilliVolts() est plus fiable qu’une conversion manuelle par la formule ci-dessus : il tient compte de la calibration propre à chaque puce.
Échantillonnage
L’ADC ne lit pas le signal en continu : il le prélève à intervalles réguliers (échantillons). Entre deux lectures, ce que fait le signal est ignoré. C’est suffisant pour un joystick (variation lente), mais pas pour un signal audio haute fréquence.
Lisser une lecture bruitée
Une lecture ADC isolée « tremble » de quelques unités à cause du bruit électrique. Pour une valeur stable, on moyenne plusieurs lectures :
longsomme=0;for(inti=0;i<16;i++)somme+=analogRead(POT_PIN);intvaleur=somme/16;// moyenne de 16 lectures
Plus on moyenne, plus la valeur est stable, mais plus la lecture prend de temps. 8 à 16 échantillons sont un bon compromis pour un potentiomètre ou un joystick.
Le piège ADC2 + Wi-Fi sur l’ESP32
L’ESP32 possède deux banques d’ADC :
Banque
Broches
Remarque
ADC1
GPIO 1–10
Toujours disponible ✅
ADC2
GPIO 11–20
Inutilisable quand le Wi-Fi est actif ⚠️
Sur la XIAO ESP32S3 de l’atelier, toutes les broches analogiques exposées (D0 à D5 et D8 à D10, soit GPIO1 à GPIO9) sont sur l’ADC1 : le piège ne peut pas se produire avec cette carte, mais il guette dès qu’on passe sur une autre carte ESP32.
Toujours utiliser ADC1 pour le joystick
Dans ce projet, le Wi-Fi est utilisé pour le multijoueur. Brancher le joystick sur des broches ADC2 (GPIO 11–20) donnera des lectures erratiques ou bloquera l’ADC. Utilisez impérativement des broches ADC1 (GPIO 1–10) : c’est le cas de D0 et D1 sur la XIAO.
Lire un joystick
Un joystick analogique contient deux potentiomètres (résistances réglables qui délivrent une tension variable selon leur position ; un par axe, X et Y) et un bouton (appui sur le stick).
intx=analogRead(JOYSTICK_X);// 0 à une butée → 4095 à l'autreinty=analogRead(JOYSTICK_Y);// le sens dépend du montage du moduleboolbtn=!digitalRead(JOYSTICK_BTN);// pull-up interne → LOW = pressé
La plage 1500-2500 est une zone morte : au repos, la valeur n’est jamais exactement 2048 et tremble de quelques unités, et ces petites variations ne doivent pas compter comme un mouvement.
Calibrer et normaliser
Pour un jeu, on veut souvent plus qu’une direction : de combien le stick est poussé. On convertit alors la lecture en une valeur entre -1 (butée basse) et +1 (butée haute), qui vaut 0 au repos :
Calibrer : au démarrage, stick relâché, on mesure le vrai centre (moyenne de 16 lectures) au lieu de supposer 2048.
Zone morte : un écart au centre inférieur à ~300 renvoie 0.
Normaliser : on divise l’écart au centre par la course disponible du côté où se trouve le stick (4095 - centre d’un côté, centre de l’autre), pour que chaque butée donne bien -1 ou +1.
floatnormaliser(intbrut,intcentre){intecart=brut-centre;if(abs(ecart)<300)return0.0f;// zone morteintcourse=(ecart>0)?(4095-centre):centre;returnconstrain((float)ecart/course,-1.0f,1.0f);}
Une raquette peut alors avancer à une vitesse proportionnelle à cette valeur, et les directions se déduisent par des seuils lisibles (x < -0.5 pour GAUCHE), quel que soit le centre réel du joystick.
Mettre une lecture à l’échelle avec map()
Une lecture ADC (0–4095) doit souvent être convertie vers une autre plage : un angle de servo (0–180°), une valeur PWM (0–255), un pourcentage… La fonction map() fait cette règle de trois d’un coup :
map(valeur, entrée_min, entrée_max, sortie_min, sortie_max) suppose une relation linéaire et ne borne pas le résultat : si la lecture peut déborder de la plage attendue, encadre-la avec constrain(pwm, 0, 255).
Du numérique vers l’analogique : le PWM
Un GPIO numérique ne peut produire que 0 V ou 3,3 V. Pour simuler une tension intermédiaire (régler la luminosité d’une LED, la vitesse d’un moteur, la position d’un servo), on utilise le PWM (Pulse Width Modulation, modulation de largeur d’impulsion).
Principe
Le PWM alterne rapidement entre HIGH et LOW à haute fréquence. La proportion de temps à HIGH s’appelle le rapport cyclique (duty cycle) :
Duty cycle
Effet perçu
0 %
Toujours éteint
50 %
Mi-luminosité
100 %
Plein éclat
La fréquence est suffisamment élevée (typiquement 1–50 kHz) pour que l’œil (ou un moteur) ne perçoive pas le clignotement, seulement la valeur moyenne.
Tension moyenne : 1.65 V sur 3,3 V
· analogWrite : 128 / 255
PWM sur ESP32-S3 avec Arduino
// Valeur de 0 (0%) à 255 (100%) sur une résolution 8 bitsanalogWrite(LED_PIN,128);// ~50% → demi-luminositéanalogWrite(LED_PIN,0);// éteintanalogWrite(LED_PIN,255);// plein éclat
L’ESP32-S3 dispose de 8 canaux LEDC (LED Control, le générateur de PWM matériel de l’ESP32) indépendants, avec une fréquence et une résolution configurables (jusqu’à 14 bits sur l’ESP32-S3). analogWrite() utilise la couche LEDC d’Arduino-ESP32 en arrière-plan.
Applications du PWM
Application
Principe
LED dimmable
Duty cycle ∝ luminosité
Servomoteur
Impulsion 1–2 ms toutes les 20 ms (position angulaire)
Moteur DC
Duty cycle ∝ vitesse
Buzzer
Fréquence audio → ton
Quiz express
1. Que renvoie analogRead() sur une résolution de 12 bits ?
Voir la réponseUn entier de 0 à 4095 (soit 2¹² = 4096 valeurs).
2. Comment convertir une lecture 0–4095 en angle 0–180° ?
Voir la réponseAvec `map(lecture, 0, 4095, 0, 180)`, et `constrain()` si la lecture peut déborder.
3. Le PWM produit-il une vraie tension analogique ?
Voir la réponseNon : il alterne très vite entre 0 V et 3,3 V. C'est la valeur moyenne (fixée par le duty cycle) qui simule une tension intermédiaire.