---
title: Moniteur série de l'ESP32
type: tuto
phases:
  - preuve-de-concept
  - integration-et-tests
tags:
  - eee
  - tuto
  - esp32
prerequis:
  - esp32
  - esp32-prise-en-main
aa:
  - RA-PROJET-C03-3/PROJ/5
draft: false
---

Le **moniteur série** est l'outil d'observation universel d'un programme embarqué : il permet d'envoyer du texte depuis la carte vers l'ordinateur (et inversement) pendant l'exécution, via le câble USB. C'est le premier outil de débogage, avant l'oscilloscope ou les sondes. Quand on veut savoir « qu'est-ce que mon programme fait, là, maintenant ? », on imprime au moniteur série. Sur ESP32, deux particularités le distinguent de l'Arduino : la **vitesse usuelle de 115200 bauds** (celle des messages de démarrage) et, sur les puces à **USB natif**, un réglage à connaître pour que la sortie apparaisse.

## À quoi ça sert ?

Le moniteur série remplit trois rôles complémentaires en projet :

- **Voir l'invisible.** L'état d'une variable, le résultat d'un capteur, le passage dans une branche conditionnelle (toutes choses qu'on ne peut pas observer de l'extérieur) sont accessibles d'une ligne `Serial.println()`.
- **Calibrer.** Avant d'asservir un actionneur sur une consigne, on imprime la valeur du capteur : on lit la plage de mesure, on ajuste les seuils, on chiffre les retards.
- **Communiquer dans l'autre sens.** L'ordinateur peut envoyer des commandes à la carte (consigne, déclenchement), via `Serial.read()` et ses variantes. C'est le canal d'interaction le plus simple, sans interface graphique.

Au démarrage, l'ESP32 imprime de lui-même des **messages de boot** sur ce port (à 115200 bauds) : journal utile, mais qui explique pourquoi un moniteur réglé sur une autre vitesse affiche du charabia dès le reset.

## Procédure pas à pas

Quatre étapes : initialiser la liaison, imprimer, ouvrir le moniteur, lire les retours.

### 1. Initialiser la liaison dans `setup()`

Une seule ligne :

```cpp
void setup() {
  Serial.begin(115200);
}
```

Le paramètre est la **vitesse de transmission** (baud rate). Sur ESP32, on prend `115200` par défaut : c'est la vitesse des messages de démarrage de la puce, et un débit confortable. Côté carte et côté moniteur, **la valeur doit être strictement identique**, sinon le texte s'affiche en caractères incompréhensibles.

> [!warning]
> **Puces à USB natif : activer « USB CDC On Boot ».** Sur les ESP32 dont le port USB est natif (C3, S3, C6…), `Serial` passe par l'USB Serial/JTAG. Pour que la sortie apparaisse dans le moniteur après un reset, activez *Outils → USB CDC On Boot → Enabled* avant de téléverser. Sur un ESP32 d'origine (puce bridge CP2102/CH340), ce réglage n'existe pas et `Serial` fonctionne directement.

### 2. Imprimer dans `loop()`

Deux fonctions principales : `Serial.print()` imprime sans aller à la ligne, `Serial.println()` imprime puis saute une ligne. Les deux acceptent tout : chaîne, nombre, booléen.

```cpp
int valeur = analogRead(34);
Serial.print("Valeur du capteur : ");
Serial.println(valeur);
```

Pour imprimer plusieurs grandeurs séparées par une tabulation (utile pour le traceur série et pour copier vers un tableur) :

```cpp
Serial.print(temps);
Serial.print("\t");
Serial.print(consigne);
Serial.print("\t");
Serial.println(mesure);
```

### 3. Ouvrir le moniteur série

Téléversez d'abord le code, puis ouvrez le **moniteur série** (icône en haut à droite de la barre d'outils, ou *Outils → Moniteur série*). Une fenêtre s'ouvre en bas.

**Vérifiez le baud rate** dans le menu déroulant à droite de la barre du moniteur : il doit valoir `115200`. Sinon, le texte ressemble à des symboles cassés, y compris les messages de démarrage de la puce.

![Fenêtre de l'IDE Arduino 2.x : le sketch de mesure dans l'éditeur, l'icône du moniteur série annotée en rouge en haut à droite, et en bas le moniteur ouvert affichant les lignes « Valeur du capteur », avec le sélecteur de débit encadré sur 115200 baud.|600](/ressources/img/esp32-serie/moniteur-serie-115200.png)

Sur cette capture, `GPIO34` n'est reliée à rien : la valeur reste à `0` ligne après ligne. Tout fonctionne pourtant : il n'y a simplement rien à mesurer. C'est l'écran que vous verrez en téléversant avant d'avoir câblé quoi que ce soit, et il ne signale aucune panne (voir *Pièges*).

> [!tip]
> **Le téléversement (ou le bouton EN) redémarre la carte.** À chaque reset, le programme repart de zéro et l'ESP32 réémet ses messages de boot. Si rien ne s'affiche, vérifier dans l'ordre : le baud rate (115200), la présence de `Serial.begin()`, et, sur puce USB native, le réglage *USB CDC On Boot*.

### 4. Lire une entrée depuis l'ordinateur

L'ordinateur peut envoyer du texte à la carte. Tapez dans la zone de saisie du moniteur, validez par Entrée.

Côté code, deux fonctions de base :

- `Serial.available()` renvoie le nombre d'octets reçus en attente (`0` si rien).
- `Serial.read()` lit **un seul octet** dans la file.

```cpp
void loop() {
  if (Serial.available() > 0) {
    char c = Serial.read();
    Serial.print("Reçu : ");
    Serial.println(c);
  }
}
```

Tapez `a`, validez : la sortie n'est pas celle qu'on attend.

```
Reçu : a

Reçu : 
```

**Deux passages dans la boucle, pas un.** Le sélecteur de fin de ligne du moniteur (le menu à gauche du débit, réglé sur *New Line*) ajoute un caractère invisible derrière votre saisie. `Serial.read()` ne lisant qu'un octet, il prend d'abord le `a`, puis au tour suivant ce caractère de fin de ligne, qui s'imprime en ligne vide. Avec *Both NL & CR*, deux caractères sont ajoutés : **trois** passages.

> [!warning]
> **`Serial.read()` renvoie un caractère, pas un nombre.** Si vous tapez `42`, vous recevrez successivement `'4'` puis `'2'`, pas l'entier 42. Pour lire un nombre, `Serial.parseInt()` agrège les chiffres ; pour lire une ligne entière, `Serial.readStringUntil('\n')`.

## Exemple — Piloter une LED par commande texte

Cas concret : la carte lit une commande envoyée depuis l'ordinateur (`ON`, `OFF`) et pilote une LED, tout en imprimant périodiquement une mesure analogique. On combine les deux sens de la liaison.

*Câblage : LED sur `GPIO16` et capteur sur `GPIO34` (voir les montages de [[esp32-gpio|configurer les GPIO]]).*

```cpp
const int LED = 16;
const int CAPTEUR = 34;        // ADC1

unsigned long dernierEnvoi = 0;

void setup() {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  Serial.println("Commandes : ON / OFF");
}

void loop() {
  // Sens ordinateur -> carte : lire une commande ligne par ligne
  if (Serial.available() > 0) {
    String cmd = Serial.readStringUntil('\n');
    cmd.trim();                       // retire espaces et retour chariot
    if (cmd == "ON") {
      digitalWrite(LED, HIGH);
      Serial.println("LED allumee");
    } else if (cmd == "OFF") {
      digitalWrite(LED, LOW);
      Serial.println("LED eteinte");
    } else if (cmd.length() > 0) {
      Serial.print("Commande inconnue : ");
      Serial.println(cmd);
    }
  }

  // Sens carte -> ordinateur : imprimer la mesure toutes les 500 ms
  if (millis() - dernierEnvoi >= 500) {
    dernierEnvoi = millis();
    Serial.print("capteur = ");
    Serial.println(analogRead(CAPTEUR));
  }
}
```

Au moniteur, on voit défiler la mesure toutes les demi-secondes. Taper `ON` ou `OFF` allume ou éteint la LED et renvoie une confirmation. Noter le **non-bloquant** : on n'utilise pas `delay()` pour cadencer l'envoi, mais une comparaison sur `millis()` : la lecture des commandes reste réactive en permanence.

En tapant `ON` puis `OFF` en cours de défilement :

```
Commandes : ON / OFF
capteur = 1832
capteur = 1847
capteur = 1795
LED allumee
capteur = 1811
capteur = 1826
LED eteinte
capteur = 1804
```

La réponse à la commande **s'insère** dans le défilé sans l'interrompre : c'est le non-bloquant à l'œuvre.

## Pièges

**Baud rate désaccordé.** Le piège n°1 : du texte en symboles cassés, dès les messages de boot. La cause est toujours `Serial.begin(X)` ≠ valeur du moniteur. Aligner sur `115200`.

**Puce USB native sans « USB CDC On Boot ».** Sur C3/S3/C6, moniteur ouvert au bon baud mais **aucune** sortie : le réglage *USB CDC On Boot* est sur *Disabled*. L'activer et re-téléverser.

**Oublier `Serial.begin()`.** Sans cette ligne, les `Serial.print()` n'envoient rien — moniteur ouvert, baud correct, mais sortie muette.

**Entrée analogique non câblée : un plancher, pas du bruit.** Une broche d'entrée analogique laissée en l'air n'affiche pas des valeurs qui sautent dans tous les sens, mais un **plancher** : des `0`, parfois des `1`. Le réflexe est d'accuser le moniteur, le baud ou le code, alors que les trois vont bien. Pour vérifier la chaîne **avant** de câbler un capteur, relier brièvement la broche à `3V3` : la valeur doit bondir près de `4095`, puis retomber à `0` sur `GND`. Si elle suit, la mesure est opérationnelle et il ne manque que le capteur.

**`print` partout, `println` nulle part.** Toute la sortie sur une ligne illisible. Réflexe : `println` à la fin de chaque ligne logique, `print` au milieu.

**Inonder le port.** Imprimer chaque milliseconde sature la liaison et rend la sortie illisible. Limiter à ~10-50 Hz pour de l'observation humaine (cadencer sur `millis()`, pas figer la boucle avec `delay`).

**Moniteur ouvert ailleurs verrouille le port.** Si PlatformIO ou un terminal externe tient le port, l'IDE ne peut ni ouvrir son moniteur, ni téléverser. Fermer l'autre application.

**`Serial.read()` ne lit qu'un octet.** Un appel unique ne lit qu'un caractère même si l'ordinateur a envoyé un mot. Pour une ligne, `readStringUntil('\n')` ; pour un nombre, `parseInt()`.

**Le sélecteur de fin de ligne compte dans ce qui est reçu.** À gauche du débit, un menu déroulant choisit ce que le moniteur ajoute derrière votre saisie — rien, `\n`, `\r`, ou les deux. Ces caractères arrivent dans la file au même titre que votre texte. Deux conséquences : `Serial.read()` fait **un passage de boucle en trop par caractère ajouté** (voir l'étape 4), et `readStringUntil('\n')` ne trouve son `\n` que si le sélecteur en envoie un, faute de quoi il attend l'expiration de son délai, **une seconde par défaut**, et la boucle se fige d'autant. C'est le même mécanisme que le piège `parseInt()` ci-dessous, déclenché cette fois par un réglage d'interface et non par le code. C'est aussi ce que nettoie le `cmd.trim()` de l'exemple.

**`parseInt()` attend une seconde, puis renvoie 0.** `Serial.parseInt()` bloque la boucle jusqu'à recevoir un entier complet, ou jusqu'à expiration de son délai d'attente, **une seconde par défaut**, auquel cas il renvoie `0`. Deux conséquences en TP : la boucle se fige brièvement à chaque commande, et une saisie mal terminée applique silencieusement la valeur 0 (LED éteinte, sans message). `Serial.setTimeout(50)` raccourcit l'attente. Lire la ligne avec `readStringUntil()` puis convertir explicitement évite le piège.

## Exercices

> [!question] Exercice 1 — Consigne numérique
> Faites lire à la carte un **nombre** tapé au moniteur (par exemple `180`) et utilisez-le comme rapport cyclique PWM (0-255, borné) d'une LED sur `GPIO16`. Comment lire un entier proprement ?

> [!success]- Corrigé
> On lit la ligne, on la convertit en entier, on borne, on applique en PWM.
> ```cpp
> const int LED = 16;
>
> void setup() {
>   Serial.begin(115200);
>   ledcAttach(LED, 5000, 8);   // 8 bits : duty 0..255
>   Serial.println("Tapez une intensite 0-255 :");
> }
>
> void loop() {
>   if (Serial.available() > 0) {
>     int duty = Serial.parseInt();        // agrège les chiffres
>     duty = constrain(duty, 0, 255);      // borne dans 0..255
>     ledcWrite(LED, duty);
>     Serial.print("Intensite = ");
>     Serial.println(duty);
>   }
> }
> ```
> `parseInt()` lit l'entier, `constrain()` garantit qu'on reste dans la plage valide même si l'utilisateur tape 999 ou -5.

> [!question] Exercice 2 — Relevé pour tableur
> Imprimez, toutes les 100 ms, trois colonnes séparées par des tabulations : le temps écoulé en ms, la valeur brute d'un capteur sur `GPIO34`, et cette valeur convertie en volts. But : copier directement la sortie dans un tableur.

> [!success]- Corrigé
> ```cpp
> const int CAPTEUR = 34;   // ADC1
> unsigned long t0;
>
> void setup() {
>   Serial.begin(115200);
>   t0 = millis();
>   Serial.println("temps_ms\tbrut\tvolts");   // en-têtes
> }
>
> void loop() {
>   int brut = analogRead(CAPTEUR);
>   float volts = analogReadMilliVolts(CAPTEUR) / 1000.0;   // tension calibrée
>   Serial.print(millis() - t0);
>   Serial.print("\t");
>   Serial.print(brut);
>   Serial.print("\t");
>   Serial.println(volts, 2);
>   delay(100);
> }
> ```
> Les trois colonnes tabulées se collent telles quelles dans un tableur. La ligne d'en-têtes nomme les colonnes (et sert de légende au traceur série).

## Cas particulier — Plusieurs ports série et le traceur

L'ESP32 dispose de **plusieurs UART matériels**. `Serial` (UART0) est réservé au moniteur et au journal de boot. Pour dialoguer avec un second appareil (module GPS, autre carte) sans perturber le moniteur, on utilise un autre port, dont on peut choisir les broches (le sujet complet de [[esp32-uart|l'UART sur ESP32]]) :

```cpp
Serial2.begin(9600, SERIAL_8N1, 16, 17);  // RX=GPIO16, TX=GPIO17
```

Le **traceur série** (*Outils → Traceur série*) est un [[oscilloscope|oscilloscope]] logiciel miniature : il trace toute valeur numérique imprimée (une par ligne, ou plusieurs en tabulations → courbes superposées). Idéal pour caler des seuils, observer le bruit d'un capteur, comparer consigne et mesure. Limite : pas d'échelle de temps réglable, fenêtre courte — pour des analyses poussées, exporter vers un tableur.

## Raccrochage projet

- **Tout au long de la [[preuve-de-concept|phase de preuve de concept]]** — le moniteur série est l'outil de debug par défaut. Avant l'oscilloscope, avant le profilage, on imprime.
- **Étape 1 de la [[integration-et-tests|phase d'intégration et tests]]** — pour qualifier une fonction sur un parcours de mesures connues, tracer mesure attendue / mesure obtenue côte à côte.
- **Toutes les étapes de calibration** — relevés rapides, vérification de plage, observation d'un transitoire.

Apprendre dès la prise en main à imprimer proprement (en-têtes, séparateurs cohérents, fréquence raisonnable, cadence non bloquante) évite de refaire son instrumentation à chaque nouveau capteur. C'est l'instrument le moins cher du projet et l'un des plus payants.

## Aller plus loin

- [Référence de la classe Serial (Arduino)](https://www.arduino.cc/reference/en/language/functions/communication/serial/) — `peek`, `parseFloat`, `readBytes`…
- [[arduino-programmation-non-bloquante|Programmation non bloquante]] — cadencer les envois sans figer la boucle (le motif `millis()` de l'exemple). Traité côté Arduino, il se transpose tel quel sur ESP32.
- Pour traiter la sortie en aval : un script Python avec `pyserial` lit le flux (graphique, log fichier, déclenchement d'actions).

## Voir aussi

- [[esp32|ESP32]] — hub des tutoriels ESP32
- [[esp32-prise-en-main|Prise en main de l'ESP32]] — prérequis (IDE + support + premier téléversement)
- [[esp32-gpio|Configurer les GPIO]] — les broches lues et pilotées, observées via le moniteur
- [[esp32-uart|UART sur l'ESP32]] — parler à un module série sur un second port, sans perturber le moniteur
- [[cpp|C++]] — le langage utilisé dans les sketches
- [[bus-de-communication|Bus de communication]] — l'UART, bus sous-jacent du moniteur série
