GPIO & monde numérique

Comprendre les GPIO numériques de l'ESP32-S3 : niveaux logiques, pull-up/pull-down et état flottant.

Difficulté :
Documentation réalisée à 90% 90
Créée par :  Alban Petit

Toucher le monde réel

Jusqu’ici, le microcontrôleur réfléchissait dans son coin. Mais une puce qui ne fait que calculer ne sert à rien : pour être utile, elle doit agir sur le monde (allumer une LED, faire tourner un moteur) et le percevoir (savoir si tu appuies sur un bouton).

Ce contact avec le monde physique passe par les broches de la puce, ces petites pattes métalliques alignées sur les bords de la carte. Les plus polyvalentes s’appellent les GPIO, et ce concept est consacré aux plus simples d’entre elles : celles qui ne manipulent que du tout-ou-rien, comme un interrupteur mural qu’on bascule entre allumé et éteint. Pas de demi-mesure, et c’est justement ce qui rend le numérique si fiable.

À la fin de ce concept, tu sauras :

  • configurer une broche en entrée ou en sortie ;
  • éviter l’état flottant avec un pull-up ou un pull-down ;
  • lire un bouton proprement (anti-rebond) et repérer les broches à éviter.

Le numérique, c’est quoi ?

Le monde numérique ne connaît que deux états : 0 ou 1, LOW ou HIGH, 0 V ou 3,3 V. Tout signal intermédiaire est interprété selon un seuil.

Sur l’ESP32-S3 (logique 3,3 V) :

État Tension Arduino
LOW 0 V 0 / false / LOW
HIGH 3,3 V 1 / true / HIGH

Les seuils logiques de l’ESP32-S3

En entrée, une broche ne bascule pas exactement à 0 V ou 3,3 V : elle compare la tension lue à deux seuils, et entre les deux l’état est indéterminé. La datasheet de l’ESP32-S3 (alimenté en 3,3 V) donne :

Seuil Valeur Interprétation
$V_{IL}$ (entrée LOW max) ≤ 0,25 × VDD ≈ 0,8 V En dessous : lu comme LOW
Zone indéterminée ~0,8 V à ~2,5 V À éviter : lecture imprévisible
$V_{IH}$ (entrée HIGH min) ≥ 0,75 × VDD ≈ 2,5 V Au-dessus : lu comme HIGH

Tout ce qui est sous ~0,8 V vaut 0, tout ce qui dépasse ~2,5 V vaut 1. Cette zone grise au milieu explique pourquoi une entrée flottante (voir plus bas) donne des lectures erratiques : sa tension traîne dans cette plage indéterminée.

Échelle des tensions sur une entrée de l'ESP32-S3 : lu LOW sous 0,8 V, zone indéterminée entre 0,8 et 2,5 V, lu HIGH au-dessus de 2,5 V

En sortie, la puce garantit à l’inverse des niveaux francs : proche de 0 V pour LOW ($V_{OL}$) et proche de 3,3 V pour HIGH ($V_{OH}$).

Ce que dit la datasheet

Ces valeurs viennent du tableau DC Characteristics de la datasheet de l’ESP32-S3 (VDD = 3,3 V, 25 °C). C’est là qu’on trouve la réponse à la plupart des questions sur une broche :

Symbole Ce que c’est Valeur
$V_{IH}$ Tension minimale lue comme HIGH 0,75 × VDD
$V_{IL}$ Tension maximale lue comme LOW 0,25 × VDD
$V_{OH}$ Tension minimale garantie en sortie HIGH 0,8 × VDD
$V_{OL}$ Tension maximale garantie en sortie LOW 0,1 × VDD
$I_{OH}$ Courant que peut fournir une sortie HIGH 40 mA
$R_{PU}$, $R_{PD}$ Résistances de tirage internes 45 kΩ

Juste avant, le tableau des valeurs maximales absolues donne la limite à ne jamais dépasser : 3,6 V sur une broche. Au-delà, la puce peut être détruite ; entre 3,3 V et 3,6 V, elle survit mais n’est plus dans ses conditions normales.

Exemple : un capteur qui signale un HIGH par 2,2 V n’est pas lu de façon fiable, car 2,2 V est sous $V_{IH}$ ≈ 2,5 V.

  Attention : 3,3 V, pas 5 V

Les GPIO de l’ESP32-S3 supportent 3,3 V maximum. Une broche mise en sortie produit 3,3 V, pas 5 V comme sur Arduino Uno. Un signal 5 V appliqué en entrée peut endommager la puce.

GPIO : General Purpose Input/Output

Chaque broche GPIO (General Purpose Input/Output, broche d’entrée/sortie à usage général) peut être configurée en entrée ou en sortie par le logiciel.

pinMode(LED_PIN, OUTPUT);   // broche configurée en sortie
pinMode(BTN_PIN, INPUT);    // broche configurée en entrée

Sortie : piloter une LED

Une broche en sortie peut sourcer ou drainer du courant (le fournir ou l’absorber) pour piloter un composant.

digitalWrite(LED_PIN, HIGH);  // LED allumée (3,3 V sur la broche)
digitalWrite(LED_PIN, LOW);   // LED éteinte (0 V)

Courant maximal par broche ESP32-S3 : ~40 mA. En pratique, limiter à 12 mA avec une résistance série.

Calcul de la résistance pour une LED rouge (Vf, la tension de seuil de la LED, ≈ 2 V ; I = 10 mA) :

$$ R = \frac{V_{alim} - V_f}{I} = \frac{3{,}3 - 2}{0{,}01} = 130\ \Omega $$

Valeur normalisée : 150 Ω (série E12).

Calculateur de résistance série pour LED

R ≈ 130 Ω   valeur E12 conseillée : 150 Ω

Push-pull vs open-drain : deux façons de piloter une sortie

Par défaut, OUTPUT configure la broche en push-pull : deux transistors internes permettent de tirer activement la broche à HIGH ou à LOW. C’est le mode utilisé jusqu’ici.

Il existe un second mode, open-drain : la broche ne peut que tirer vers LOW (un seul transistor, côté masse). Pour lire ou produire un HIGH, il faut une résistance de tirage (interne ou externe) qui « relâche » la broche vers HIGH quand personne ne la tire vers le bas.

À gauche, une sortie push-pull : un interrupteur vers 3,3 V et un vers 0 V imposent tous les deux un niveau ; à droite, une sortie open-drain : un seul interrupteur vers 0 V, la résistance de pull-up donne le HIGH

pinMode(PIN, OUTPUT_OPEN_DRAIN);  // la broche ne peut que tirer vers 0 V
digitalWrite(PIN, LOW);           // tire activement à 0 V
digitalWrite(PIN, HIGH);          // relâche la broche (haute impédance)
  Push-pull (OUTPUT) Open-drain (OUTPUT_OPEN_DRAIN)
Peut tirer vers HIGH Oui, activement Non, nécessite un pull-up
Peut tirer vers LOW Oui, activement Oui, activement
Usage typique LED, moteur, sortie numérique classique Bus partagé (I2C), interfaçage avec un niveau logique différent
  Pourquoi l'I2C utilise l'open-drain

Sur un bus I2C, plusieurs périphériques partagent les mêmes fils SDA/SCL. Si un seul poussait activement vers HIGH pendant qu’un autre tire vers LOW, ce serait un court-circuit. En open-drain, chacun ne peut que tirer vers LOW ou relâcher, jamais imposer un HIGH : d’où la résistance de pull-up partagée sur le bus (voir le concept Les bus de communication).

Entrée : lire un bouton

Une broche en entrée mesure le niveau de tension présent sur la broche.

int etat = digitalRead(BTN_PIN);  // retourne HIGH ou LOW

Pull-up, pull-down & état flottant

Le problème de l’état flottant

Une broche en entrée non connectée à rien est dans un état flottant : sa tension est indéterminée, elle peut lire HIGH ou LOW de façon aléatoire selon les interférences électromagnétiques.

flowchart TD
  A["pinMode(PIN, INPUT)\nsans pull-up ni pull-down"] --> B["Broche en haute impédance\n(quasiment aucun courant ne peut y circuler)"]
  B --> C{"Un signal externe\nimpose-t-il une tension ?"}
  C -->|"Non - rien de branché"| D["Tension flottante\ncapte le moindre bruit ambiant"]
  D --> E["digitalRead() imprévisible\nLOW, HIGH, ou oscille sans raison"]
  C -->|"Oui - via une résistance de rappel"| F["Tension définie et stable\nLOW ou HIGH garanti au repos"]

Une broche en haute impédance présente une résistance d’entrée très élevée (plusieurs mégaohms) : elle n’absorbe quasiment aucun courant, donc rien ne la ramène vers 0 V ou 3,3 V. La moindre interférence électromagnétique environnante (un câble voisin, le secteur 50 Hz, ta propre main qui s’approche) suffit alors à déplacer sa tension et à faire basculer sa lecture. D’où le terme « flottant » : rien ne fixe sa tension à une valeur précise.

La solution : résistance de rappel

Une résistance de rappel (pull) force la broche à un niveau défini quand aucun signal n’est appliqué.

Pull-up : repos à HIGH

La résistance relie la broche au +3,3 V. Au repos, la broche lit HIGH. Quand le bouton est pressé (connexion à GND), elle passe à LOW.

graph LR
  V33["3,3 V"] -->|"R (10 kΩ)"| NODE(("Broche GPIO"))
  NODE -->|"Bouton"| GND["GND"]

Pull-down : repos à LOW

La résistance relie la broche au GND. Au repos, la broche lit LOW. Quand le bouton est pressé (connexion à +3,3 V), elle passe à HIGH.

graph LR
  GND["GND"] -->|"R (10 kΩ)"| NODE(("Broche GPIO"))
  NODE -->|"Bouton"| V33["3,3 V"]

Pull-up interne de l’ESP32-S3

L’ESP32-S3 intègre des résistances de pull-up (et pull-down) en interne, activables par logiciel : pas besoin de résistance externe dans la plupart des cas.

pinMode(BTN_PIN, INPUT_PULLUP);    // pull-up interne activé (~45 kΩ)
// ou
pinMode(BTN_PIN, INPUT_PULLDOWN);  // pull-down interne activé

Avec INPUT_PULLUP : le bouton doit connecter la broche à GND pour être détecté (logique inversée : LOW = pressé).

Lire un bouton de façon fiable : l’anti-rebond

Quand tu presses un bouton mécanique, le contact ne s’établit pas net : les lamelles métalliques rebondissent pendant quelques millisecondes, générant une rafale de HIGH/LOW avant de se stabiliser. Un digitalRead() naïf voit alors plusieurs appuis là où tu n’en as fait qu’un.

La parade la plus simple est logicielle : après un changement d’état, on ignore les lectures pendant un court délai (20 à 50 ms), grâce à millis() (le nombre de millisecondes écoulées depuis le démarrage), le temps que le contact se stabilise.

Le signal lu rebondit pendant environ 1 ms ; l'anti-rebond attend qu'il soit stable depuis 30 ms, puis justPressed() renvoie vrai une seule fois

const unsigned long ANTIREBOND = 30;   // ms
int dernierEtat = HIGH;
unsigned long dernierChangement = 0;

void loop() {
  int etat = digitalRead(BTN_PIN);      // INPUT_PULLUP : LOW = pressé

  if (etat != dernierEtat) {
    dernierChangement = millis();       // le signal vient de bouger
    dernierEtat = etat;
  }

  if (millis() - dernierChangement > ANTIREBOND && etat == LOW) {
    // bouton enfoncé de façon stable depuis 30 ms
    // (vrai à chaque tour tant que le bouton reste enfoncé)
  }
}
  Anti-rebond matériel

On peut aussi filtrer le rebond côté électronique (condensateur ~100 nF en parallèle du bouton, ou trigger de Schmitt). Sur un microcontrôleur, l’anti-rebond logiciel suffit néanmoins dans l’immense majorité des cas.

Réagir sans scruter : l’interruption

Scruter un bouton à chaque tour de loop() fonctionne, mais peut rater un appui si la boucle est occupée ailleurs. Une interruption exécute une petite fonction dès que la broche change, sans attendre (voir Architecture d’un microcontrôleur).

volatile bool appui = false;

void IRAM_ATTR surAppui() { appui = true; }   // ISR : la plus courte possible

void setup() {
  pinMode(BTN_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BTN_PIN), surAppui, FALLING);
}

L’ISR (Interrupt Service Routine) se contente de lever un drapeau ; le vrai traitement (et l’anti-rebond) se fait ensuite tranquillement dans loop().

GPIO matrix : une broche n’est pas toujours un simple GPIO

Au-delà de digitalRead()/digitalWrite(), une broche peut être prise en charge par un périphérique : UART, SPI, I2C, PWM (LEDC)… Sur beaucoup de microcontrôleurs, chaque fonction est câblée en dur sur des broches précises (« fonction alternative » fixe).

L’ESP32-S3 est plus flexible : une GPIO matrix interne permet de router presque n’importe quel signal numérique de périphérique vers presque n’importe quelle broche, par logiciel.

UART, SPI, I2C et PWM passent par la GPIO matrix, qui les route vers les broches choisies dans le code ; l'ADC, lui, est câblé en dur sur certaines broches

Serial1.begin(115200, SERIAL_8N1, D2, D3);  // UART1 routé sur D2 (RX) / D3 (TX) de la XIAO
// une autre paire de broches fonctionnerait tout aussi bien

Si aucun périphérique ne convient, il reste possible de piloter les broches à la main, bit après bit, pour émuler un bus : c’est le bit-banging, plus lent et plus gourmand en temps processeur (voir Les bus de communication).

  L'ADC échappe à la règle

Contrairement aux périphériques numériques (UART, SPI, I2C, PWM), l’ADC n’est pas routable via la GPIO matrix : chaque canal ADC1/ADC2 est câblé en dur sur des broches fixes (voir le concept ADC & PWM).

Toutes les broches ne se valent pas

Malgré la souplesse de la GPIO matrix, certaines broches ont un rôle réservé au démarrage ou au fonctionnement interne de l’ESP32-S3. Les détourner en GPIO ordinaire peut empêcher la carte de démarrer, de se flasher ou de communiquer en USB.

Broches Rôle réservé Précaution
GPIO 0, 3, 45, 46 Strapping : lues au reset pour choisir le mode de boot Éviter, ou ne rien y imposer comme niveau au démarrage
GPIO 19, 20 USB natif (D− / D+) Éviter si tu utilises l’USB (flash, port série)
GPIO 43, 44 UART0 par défaut (TX / RX du moniteur série) Libres si le port série n’est pas utilisé, sinon à éviter
GPIO 26–32 Reliées à la Flash SPI interne Ne jamais utiliser
GPIO 33–37 Utilisées par la PSRAM sur les modules « octal » Éviter sur les modules équipés de PSRAM
  En cas de doute, consulte le brochage de ta carte

Le nombre exact de broches libres dépend du module ESP32-S3 précis (avec ou sans PSRAM octal). Le pinout officiel de ta carte indique quelles broches sont réellement disponibles.

Sur la XIAO ESP32S3 de l’atelier, ce tri est déjà fait par la carte : seules 11 broches sont sorties (D0 à D10) et aucune broche critique n’en fait partie. GPIO0 est relié au bouton BOOT, GPIO19/20 au connecteur USB-C, la Flash et la PSRAM restent internes. Deux cas particuliers :

  • D2 (GPIO3) est une broche de strapping, mais elle ne choisit que la source du JTAG, et seulement si les eFuses de la puce ont été programmés pour : elle s’utilise comme une broche ordinaire.
  • D6/D7 (GPIO43/44) portent l’UART0, mais le moniteur série de la XIAO passe par l’USB natif : ces deux broches restent utilisables. Au démarrage, la puce émet toutefois ses messages de boot sur D6 : un bouton qui la relie à GND doit passer par une résistance série (1 kΩ sur la carte de la console) pour ne pas court-circuiter cette sortie.

Courant maximum et protection des broches

Limite Valeur ESP32-S3
Courant max par broche 40 mA (recommandé : ≤ 12 mA)
Tension max en entrée 3,3 V (3,6 V en valeur maximale absolue)
  Ne jamais connecter une charge inductive

Ne jamais connecter une charge inductive (moteur, relais : une charge qui stocke de l’énergie magnétique et renvoie des pics de tension à la coupure) directement sur un GPIO : toujours utiliser un transistor ou un driver (circuit de puissance intermédiaire).

Commander une charge de puissance : le MOSFET

Un moteur, un ruban de LED ou un relais demandent bien plus que les 12 mA d’une broche. La broche ne fournit donc pas la puissance : elle commande un transistor, qui la fournit depuis une alimentation séparée.

Montage côté bas : la broche GPIO de la XIAO commande la grille d'un MOSFET N par une résistance de 100 ohms, avec une résistance de 10 kilo-ohms vers la masse ; le moteur est relié entre le +12 V et le drain, une diode de roue libre à ses bornes, la source à la masse commune

C’est le montage côté bas : un MOSFET canal N coupe ou rétablit la masse de la charge.

  • La broche impose une tension sur la grille, sans débiter de courant (hormis une brève pointe à chaque basculement, limitée par la résistance de 100 Ω).
  • La résistance de 10 kΩ entre grille et source tient le MOSFET bloqué tant que le microcontrôleur démarre et que sa broche flotte.
  • Les masses de la carte et de l’alimentation de la charge sont reliées, sinon la tension de grille n’a pas de référence.
  • Avec analogWrite() sur la grille, le PWM règle la vitesse du moteur (voir ADC & PWM).

Choisir le MOSFET

Paramètre Ce que c’est À viser
$V_{GS(th)}$ Tension de grille où il commence à conduire Bien sous 3,3 V
$R_{DS(on)}$ Résistance une fois passant : elle chauffe Donnée à 2,5 V, quelques dizaines de mΩ
$I_D$ Courant maximal Au moins 2 × celui de la charge
$V_{DS}$ Tension maximale Au-dessus de l’alimentation de la charge

Avec 3,3 V sur la grille, il faut un MOSFET « logic level », dont la datasheet donne $R_{DS(on)}$ à 2,5 V : AO3400 (CMS) ou IRLB8721 (traversant), par exemple. Un IRF540 classique attend 10 V sur sa grille : à 3,3 V, il conduit à peine et chauffe.

La diode de roue libre

Une bobine (moteur, relais, électro-aimant) s’oppose à toute variation brutale de courant. Quand le MOSFET coupe, elle produit une pointe de plusieurs dizaines de volts qui détruit le transistor. La parade est une diode placée aux bornes de la charge, cathode côté + : elle laisse le courant s’éteindre en tournant dans la bobine.

  Charge inductive : une diode, sans exception

Une 1N4007 suffit pour un relais. Pour un moteur commandé en PWM rapide, préférer une diode Schottky (1N5819), qui réagit plus vite.

Connecter un composant 5 V : l’adaptation de niveau

Les GPIO de l’ESP32-S3 sont en logique 3,3 V : appliquer 5 V sur une entrée peut endommager la puce (voir l’avertissement plus haut). Pour dialoguer avec un composant 5 V, il faut adapter le niveau :

  • Entrée 5 V → 3,3 V (lire un signal 5 V) : un simple pont diviseur de deux résistances (ex. 10 kΩ + 20 kΩ) ramène 5 V vers ~3,3 V.
  • Bus ou bidirectionnel (I2C, SPI…) : utiliser un convertisseur de niveau (level shifter) dédié, qui traduit proprement dans les deux sens.
  • Sortie 3,3 V → 5 V : beaucoup de composants 5 V reconnaissent déjà 3,3 V comme un HIGH valide ; sinon, un level shifter s’impose.

Pont diviseur : le signal 5 V d'un module traverse R1 de 10 kilo-ohms puis R2 de 20 kilo-ohms vers la masse ; le point milieu, à 3,3 V, va sur l'entrée de la XIAO

  Le sens du signal compte

Le danger vient surtout des signaux 5 V qui entrent dans l’ESP32-S3. Une sortie 3,3 V vers une entrée 5 V est généralement sans risque pour la puce.

Quiz express

1. Une entrée non connectée lit des valeurs aléatoires. Comment y remédier ?

Voir la réponseAvec une résistance de rappel (pull-up ou pull-down), interne (`INPUT_PULLUP`) ou externe, qui fixe un niveau défini au repos.

2. Avec INPUT_PULLUP, quel niveau lit-on quand le bouton est pressé ?

Voir la réponse`LOW` (logique inversée) : le bouton relie la broche à GND.

3. Pourquoi un digitalRead() compte-t-il parfois plusieurs appuis pour un seul ?

Voir la réponseÀ cause du rebond mécanique du contact ; on le filtre par un anti-rebond logiciel (avec `millis()`).

Pour aller plus loin

Résumé

  • GPIO = broche configurable en entrée (INPUT) ou sortie (OUTPUT).
  • Sortie : produit 0 V (LOW) ou 3,3 V (HIGH) ; ~40 mA au maximum absolu, 12 mA au plus en pratique.
  • Sortie push-pull (défaut) tire activement vers HIGH et LOW ; open-drain ne tire que vers LOW (utile pour un bus partagé comme l’I2C).
  • Entrée sans résistance de rappel → état flottant (lectures aléatoires).
  • INPUT_PULLUP / INPUT_PULLDOWN activent la résistance interne de l’ESP32-S3.
  • Avec pull-up : bouton branché entre GPIO et GND → LOW quand pressé.
  • La GPIO matrix route les périphériques numériques (UART, SPI, I2C, PWM) vers presque n’importe quelle broche, sauf l’ADC, câblé en dur.
  • Un bouton mécanique rebondit : filtrer avec un anti-rebond logiciel (millis()) pour ne pas compter plusieurs appuis.
  • Certaines broches sont réservées (strapping, Flash SPI, USB) : vérifier le brochage de la carte avant de câbler.
  • Une interruption (attachInterrupt) réagit à un changement de broche sans scruter ; l’ISR reste minimale.
  • Une charge de puissance se commande par un MOSFET « logic level » côté bas, avec une diode de roue libre si elle est inductive.
  • Pour connecter du 5 V : pont diviseur (entrée) ou level shifter (bus), jamais 5 V directement sur un GPIO.
Besoin d'aide ?