Le moniteur série Arduino : Tout ce que vous devez savoir

Le moniteur série Arduino

 

Le moniteur série Arduino, c'est quoi ?

Le moniteur série est un outil très utile dans le domaine de la programmation Arduino. Il offre une interface graphique de communication entre la carte Arduino et l’ordinateur, vous permettant de surveiller et de déboguer votre code. Il permet d'afficher des valeurs, des textes, etc... Le moniteur série est un outil souvent sous-estimé, et peu connu des débutants. Dans cet article, nous allons explorer comment il fonctionne, les commandes associées et comment bien l'utiliser.

Comment fonctionne le moniteur série Arduino :

Le moniteur série Arduino utilise une connexion série pour établir une communication bidirectionnelle entre la carte Arduino et votre ordinateur. Il utilise sur le port USB de la carte Arduino pour établir cette connexion. Une fois que vous avez téléversé votre code sur la carte Arduino, vous pouvez ouvrir le moniteur série dans l'IDE pour afficher les messages envoyés par le microcontrôleur, et envoyer des messages a votre tour.

Voici un article très bien fait qui explique le fonctionnement de la transmission de données par voie série : http://projet.eu.org/pedago/sin/1ere/5-transmission_donnees.pdf

Comment ouvrir le moniteur série sur l'IDE :

Pour ouvrir le moniteur série, il y a trois possibilités. Soit vous utiliser le raccourci clavier Ctrl+Maj+M. Vous pouvez aller dans l’onglet outils, puis cliquez sur moniteur série. Ou bien vous pouvez cliquez sur la petite icône de loupe en haut à droite :

Attention, sur l’IDE 1, le moniteur série est une page qui s’ouvre en dehors de l’IDE. En revanche, sur l’IDE 2, le moniteur série est intégré à l’IDE. Il est sous forme de terminal qui s’ouvre au niveau de la barre d’indication :

Petite astuce valable uniquement dans l’IDE 2 ou VS code platformIO. Pour voir les différentes fonction proposées par une commande – ici par exemple, Serial.commande – vous pouvez taper «Serial.» Et il vous proposera automatiquement toutes les fonctions rattachées à cette classe. Cela s’appelle l’autosuggestion.

Utilisation des fonctions du moniteur série dans le code :

La fonction Serial.begin(baudRate)

La fonction Serial.begin initialise la communication série entre votre Arduino et l'ordinateur. Elle doit être appelée une fois au début du programme. Le paramètre baudRate spécifie la vitesse de transmission en bauds (bit/seconde). Par exemple, Serial.begin(9600); initialise la communication série avec une vitesse de transmission de 9600 bauds, soit 9600 bits/secondes. Assurez-vous de choisir la même vitesse dans le moniteur série de l'IDE. Vous pouvez choisir d'autres vitesses, comme 115200 bauds, en fonction de l'utilisation de votre programme (je vous conseille vivement d'utiliser une vitesse supérieure à 9600, c'est la vitesse des dinosaures 😄)

Attention, une fois dans l'interface, vous devrez paramétrer la vitesse de communication (bauds) en fonction de celle de votre code. Il faut mettre la même vitesse sinon, l'un des deux périphérique (l'Arduino ou le PC) ira trop vite pour l'autre, et vous n’obtiendrez que des caractères inconnus.

La fonction Serial.end

La fonction Serial.end() est utilisée pour arrêter la communication série (UART). Cela ferme le port série et libère les ressources associées à la communication série. Lorsque vous appelez Serial.end(), vous ne pouvez plus envoyer ou recevoir de données via le port série tant que vous n'avez pas redémarré la communication en appelant Serial.begin() avec les paramètres appropriés.

Cette fonction peut être utile lorsque vous souhaitez désactiver temporairement la communication série pour économiser de l'énergie ou lorsque vous avez besoin de libérer les ressources pour d'autres tâches.

Voici un exemple d'utilisation de la fonction Serial.end() :

void setup() {
Serial.begin(115200);
Serial.println("Communication série active !");
delay(5000); // Attendre 5 secondes
Serial.println("Communication série désactivée !");
Serial.end(); // Arrêter la communication série
}

void loop() {
// Le code ici s'exécute normalement même si la communication
// série est désactivée, sauf bien entendu la communication
// série
}

Dans cet exemple, la communication série est activée avec Serial.begin(115200) au démarrage. Un message est envoyé via Serial.println(), puis la communication série est désactivée à l'aide de Serial.end(). Après cela, aucune communication série ne peut avoir lieu.

Le résultat dan la console :

Communication série active !
Attente de 5 secondes...
Communication série désactivée !

Serial.print(data), Serial.println(data)

Les fonctions Serial.print() et Serial.println() permettent d'afficher des données dans le moniteur série. Elles prennent en paramètre la valeur ou la variable que vous souhaitez afficher.

La seule différence entre ces deux fonctions, c’est que Serial.println ajoute la séquence "\r\n" (carriage return, new line) qui correspond à un retour à la ligne et un retour chariot. On appelle ce caractère une "séquences d'échappement"

Ce n’est pas la seul façon d’inclure un saut de ligne : Vous pouvez donner à un print un caractère de nouvelle ligne pour faire revenir le curseur à la ligne, comme ceci : Serial.print("\n");

Notez qu'il existe d'autre séquences ASCII d'échappement en C/C++, comme :

  1. \\ : Antislash \.
  2. \" : Double guillemet " (dans une chaîne de caractères délimitée par des guillemets doubles).
  3. \' : Simple guillemet ' (dans une chaîne de caractères délimitée par des guillemets simples).
  4. \t : Tabulation.
  5. \r : Retour de chariot.
  6. \b : Caractère de retour en arrière (backspace).
  7. \0 : Caractère nul.

Voir ici pour plus d'informations : cf Escape sequences - cppreference.com.

Voici un exemple de code avec les fonction Serial.print et Serial.println :

void setup() {
Serial.begin(115200); // 115200 bauds
}

void loop() {
Serial.println("coucou !"); // retour à la ligne
Serial.print("Ça va ?"); // pas de retour à la ligne
}

La fonction Serial.write :

La fonction Serial.write est utilisée pour envoyer des données brutes sous forme d'octets. Elle n'effectue aucune conversion, et les données sont envoyées directement comme elles sont. Elle est principalement utilisée pour envoyer des données binaires ou des données ASCII non textuelles.

L'utilité de Serial.write réside dans le fait qu'elle permet d'envoyer des données non textuelles (comme des données binaires, des valeurs codées, etc.) de manière plus efficace, car elle évite la surcharge liée à la conversion en chaînes de caractères. Elle est également utile lorsque vous avez besoin d'envoyer des octets spécifiques, par exemple pour contrôler des périphériques externes via les broches série (UART).

Voici un exemple d'utilisation de la fonction Serial.write pour envoyer des octets bruts :

void setup() {
Serial.begin(115200);
}
void loop() {
// Données (octets) à envoyer :
byte dataToSend[] = {0x55, 0xAA, 0xC0, 0xDE, 0xAD, 0xBE, 0xEF};
 
// Envoyer les données : 
Serial.write(dataToSend, sizeof dataToSend);
delay(1000); // Attendre une seconde
}

Dans cet exemple, un tableau d'octets dataToSend est envoyé via la fonction Serial.write. Les données sont envoyées directement sans conversion en chaînes de caractères. Bien entendu, dans un projet, il faut s’assurer que le récepteur est prêt à interpréter correctement les données sous cette forme.

Communication bidirectionnelle avec le moniteur série - fonction Serial.read et Serial.available :

Le moniteur série permet également une communication bidirectionnelle entre l'Arduino et l'ordinateur. Vous pouvez envoyer des commandes ou des instructions depuis le moniteur série vers l'Arduino, et votre Arduino peut les recevoir et y répondre.

Pour cela, nous utiliserons utiliser la fonction Serial.available() - vérifie si des données sont présent dans le buffer d’entrée - pour vérifier si des données sont disponibles pour la lecture, et Serial.read() - Lit un octet de données de l'entrée série et renvoie -1 s'il n'y a pas de données disponibles - pour lire les données reçues.

Donc, supposons que nous voulons afficher les caractères tapés dans le moniteur série. Rien de plus simple ! Voici un petit code exemple qui reçois les données du moniteur et les stocke dans une variable de type char (à noter que les caractères transmis via liaison série sont en code ASCII, il faut donc utiliser un char pour les recevoir et les convertir en caractères alphanumériques) :

char data = ""; // char pour stocker un caractère
void setup() {
Serial.begin(115200); // 115200 bauds
}

void loop() {
if(Serial.available()) { // si des données sont disponibles
data = Serial.read(); // lire le buffer d’entrée
Serial.println(data); // afficher txt
}
}

Le code fonctionne, mais dans le moniteur série, on n’obtient que des caractères séparés…

Nous avons deux possibilité pour remédier à ça. Soit nous utilisons un print sans retour à la ligne, soit on remplace les caractères char séparés par une chaîne de caractère. Je vous invite à lire le paragraphe suivant pour comprendre comment ça marche.

Fonction Serial.readString :

La classe String permet de stocker une chaîne de caractère. Voici un exemple de code Arduino utilisant les fonctions Serial.readString():

String txt = "";
// ou ‘String txt("")’, cela marche aussi
 
void setup() {
// Démarre la communication série à une vitesse de 9600 bauds
Serial.begin(115200);
}

void loop() {
// Vérifie si des données sont disponibles sur le moniteur série
if (Serial.available()) {
// Lit les données reçues du moniteur série
txt = Serial.readString();
// Supprime les espaces blancs en début et fin de chaîne 
txt.trim();
// Affiche les données lues sur le moniteur série
Serial.print("Donnees reçues : ");
Serial.println(txt);
}
// Attente de 1 seconde entre chaque itération
delay(1000);
}

La fonction loop() est exécutée en boucle. À chaque itération, elle vérifie si des données sont disponibles sur le moniteur série en utilisant la fonction Serial.available(). Si des données sont disponibles, elle les lit à l'aide de la fonction Serial.readString() et les stocke dans la variable data. Grâce à le fonction trim(), on supprime tous les espaces blancs de la chaîne de caractère. Ensuite, elle affiche ces données sur le moniteur série en utilisant les fonctions Serial.print() et Serial.println().

N'oubliez pas de régler la vitesse de communication (bauds) à 115200 dans le code mais également dans le moniteur série.

Attention, c’est n’est pas une technique à utiliser sur un projet fini, c’est seulement un exemple. Si vous souhaitez poursuivre votre apprentissage, je vous conseil cet article qui explique la bonne manière de lire le port série, et qui explique également pourquoi la classe String n’est pas l’ami du développeur :

==>  écouter le port série Arduino (applicable à 1 flux de données asynchrone) - Arduino forum.

Il est important de noter que l'utilisation excessive de la classe String peut entraîner des problèmes de mémoire et de performance, surtout sur des cartes Arduino avec des ressources limitées. Dans de nombreux cas, il est préférable d'utiliser des tableaux de caractères (char) pour gérer les chaînes de manière plus efficace, comme ceci :

const byte bufferSize = 50; // Taille du tableau de caractères
char receivedData[bufferSize]; // Déclaration du tableau
int charIndex = 0; // Index pour la lecture des caractères

void setup() {
Serial.begin(115200); // 115200 bauds (b/s)
}

void loop() {
if (Serial.available()) { // si des données sont disponibles
char receivedChar = Serial.read();
if (receivedChar != '\n' && charIndex < bufferSize - 1) {
receivedData[charIndex] = receivedChar;
charIndex++;
}
else {
receivedData[charIndex] = '\0'; // Terminer la chaîne
// Réinitialiser l'index pour la prochaine lecture 
charIndex = 0;
// Utiliser les données reçues
Serial.print("Données reçues : ");
Serial.println(receivedData);
// Réinitialiser le tableau pour la prochaine lecture
memset(receivedData, 0, bufferSize);
}
}
}

Et le résultat est le même :

Données reçues : coucou
Données reçues : bonjour
Données reçues : salut !
Données reçues : Hey hey !
Données reçues : ug !

voici comment il fonctionne :

  1. const int bufferSize = 50;: Cette ligne déclare une constante bufferSize qui définit la taille du tableau de caractères pour stocker les données reçues. Vous pouvez ajuster cette valeur en fonction de la longueur maximale des données que vous prévoyez de recevoir.

  2. char receivedData[bufferSize];: Cette ligne déclare le tableau de caractères receivedData qui sera utilisé pour stocker les données lues depuis le Moniteur Série.

  3. int charIndex = 0;: Cette ligne déclare une variable charIndex pour suivre la position actuelle dans le tableau de caractères pendant la lecture.

  4. void setup(): La fonction de configuration setup est la première à s'exécuter lors du démarrage du microcontrôleur. Ici, nous démarrons la communication série à 9600 bauds et utilisons la fonction memset pour initialiser le tableau receivedData avec des zéros.

  5. void loop(): La fonction loop est exécutée en boucle après l'exécution de setup. Dans cette boucle, nous vérifions si des données sont disponibles dans le Moniteur Série à l'aide de Serial.available(). Si des données sont disponibles, nous lisons un caractère à la fois en utilisant Serial.read().

  6. Le reste du code à l'intérieur de loop gère la lecture des caractères et la construction de la chaîne de caractères receivedData. Lorsque le caractère de nouvelle ligne ('\n') est détecté ou lorsque la taille maximale du tableau est atteinte, la chaîne est terminée en ajoutant un caractère nul ('\0'). Ensuite, les données sont affichées via le Moniteur Série, le tableau est réinitialisé pour la prochaine lecture et l'index est remis à zéro.

Le code A.S.C.I.I :

Les caractères alphabétiques suivent un codage standard appelé ASCII (American Standard Code for Information Interchange). Pour plus d'information sur le code ASCII, allez sur cette page ou sur wikipedia (lien plus haut). Ce code n'inclut nativement pas les accents, les caractères spéciaux, et les smileys, mais la console série arduino connait le codage UTF8 en lecture et en écriture. En codage UTF8 les caractères ASCII ont la même représentation donc c'est compatible, mais vous pouvez envoyer autre chose

- l'UTF8 est un codage à taille variable qui représente les symboles sur 1 à 4 octets donc quand vous tapez quelque chose légerement non standard comme un emoji, vous envoyez plusieurs octets d'un coup mais c'est pas grave, ils sont bien reçus et stockés dans le tableau dans le code

Cette petite parenthèse est là car le code ASCII est utilisé lors de la communication série.

La fonction Serial.readStringUntil(\n) :

A la place de la fonction Serial.readString(), vous pouvez utiliser la version qui donne un caractère de fin. Cette fonction s'appelle Serial.readStringUntil('\n'). Si vous utilisez la fonction readString, vous dépendez du timeout pour recevoir ce qu'il y a sur le buffer d'entrée, et la fonction va bloquer toute une seconde le code arduino (à moins de changer le timeout avec la fonction Serial.setTimeout()

La fonction Serial.readStringUntil() est utilisée pour lire une chaîne de caractères depuis le port série jusqu'à ce qu'un caractère spécifié soit rencontré. Cette fonction est pratique pour lire des données textuelles qui sont terminées par un caractère de délimitation, généralement un caractère de nouvelle ligne ('\n') par défaut.

Pour plus d'informations, lisez la doc sur le site officiel d'Arduino® :                  https://www.arduino.cc/reference/fr/language/functions/communication/serial/readstringuntil/

Voici un exemple de code utilisant la fonction readStringUntil :

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

void loop() {
if (Serial.available()) {
// Lire jusqu'au caractère de nouvelle ligne
String receivedString = Serial.readStringUntil('\n');
if (receivedString != "") {
Serial.print("Chaîne reçue : ");
Serial.println(receivedString);
}
}
}

Dans cet exemple, le code attend la réception de données sur le port série. Lorsque des données sont disponibles, la fonction Serial.readStringUntil('\n') est utilisée pour lire les données jusqu'au caractère de nouvelle ligne. La chaîne résultante est stockée dans la variable receivedString, et si elle n'est pas vide, elle est affichée via le port série.

La fonction Serial.flush :

voici un exemple de code qui utilise la fonction Serial.flush() pour attendre que toutes les données en attente dans le buffer de sortie de la communication série soient envoyées avant de continuer l'exécution du programme :

// Variable pour stocker les données reçues
String receivedData = "";

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

void loop() {
if (Serial.available()) {
// Lire les données jusqu'à la nouvelle ligne 
receivedData = Serial.readStringUntil('\n');
if (receivedData != "") {
// Utiliser les données reçues
Serial.print("Données reçues : ");
Serial.println(receivedData);
}
// Attendre l'envoi de toutes les données en attente
Serial.flush();
}
}

Dans ce code, après avoir lu les données du Moniteur Série et les avoir stockées dans la variable receivedData, la fonction Serial.flush() est appelée pour attendre que toutes les données en attente dans le tampon de sortie (buffer) de la communication série soient effectivement envoyées.

L'utilisation de Serial.flush() peut être utile lorsque vous avez besoin de vous assurer que toutes les données sont transmises avant de continuer avec d'autres opérations.

Notez que Serial.readString()n'attend pas indéfiniment si aucune donnée n'est reçue pendant plus d'une seconde (comme indiqué plus haut, fonction Serial.setTimeout). Si au bout d'une seconde il n'y a rien à lire, alors la fonction retourne une chaîne vide.

Attention, s'il n'y a pas de pause dans la transmission de plus d'une seconde, alors la chaîne va grandir, grandir et votre programme va planter, faute de mémoire.

Les fonctions Serial.parseInt et Serial.parseFloat :

La fonction Serial.parseInt()permet de lire un entier (int) à partir de l'entrée série. Son nom est d'ailleurs bien mal choissi car elle ne lit en réalité pas un int mais un long. C'est important sur les arduino, car les int sont assez limités (2 octets signés ça veut dire que la valeur min est -32768 et la valeur max est 32767 ==> en passant en long on est sur 4 octets et l'amplitude est déjà bien mieux).

En revanche, la fonction Serial.parseFloat() permet de lire un nombre à virgule flottante (float) à partir de l'entrée série.

Voici d'abord un exemple de code utilisant Serial.parseInt() pour lire un entier depuis le Moniteur Série :

int receivedInt = 0; // Variable pour stocker l'entier reçu
void setup() {
Serial.begin(115200);
}

void loop() {
if (Serial.available()) { // si des données sont disponibles
receivedInt = Serial.parseInt(); // Lire l'entier
// Attendre le caractère de nouvelle ligne 
if (Serial.read() == '\n') {
// Utiliser l'entier reçu
Serial.print("Entier reçu : ");
Serial.println(receivedInt);
}
}
}

Et voici un autre exemple avec cette fois la fonction Serial.parseFloat :

// Variable pour stocker le nombre à virgule flottante reçu
float receivedFloat = 0.0;
 
void setup() {
Serial.begin(9600);
}

void loop() {
if (Serial.available()) {
// Lire le nombre à virgule
receivedFloat = Serial.parseFloat();
// Attendre le caractère nouvelle ligne
if (Serial.read() == '\n') {
// Utiliser le nombre à virgule flottante reçu
Serial.print("Nombre à virgule flottante reçu : ");
// Affichage avec 2 décimales 
Serial.println(receivedFloat, 2);
}
}
}

Et voici le résultat dans le moniteur série :

Nombre à virgule flottante reçu : 1.10
Nombre à virgule flottante reçu : 1.22
Nombre à virgule flottante reçu : 3.10
Nombre à virgule flottante reçu : 8.00

A noter : Utilisez avec prudence les fonctions Serial.parseInt et Serial.parseFloat car si la fonction n'a pas réussi à lire un entier ou un float, elle retourne 0 et pas d'erreur, ce qui fait que votre code ne peut pas savoir si l'utilisateur a entré 0 et que c'était son choix ou s'il a tapé autre chose qui ne correspondait pas à un nombre.

Mieux vaut utiliser la technique de ce tuto et employer les fonction strtol() (et autres strtoul() ou strtod()) standards. Ces fonctions prennent un paramètre supplémentaire endptr qui permet de voir si l'analyse du texte a été correctement effectuée.

Manipuler des formats de données

A présent que nous avons vue l'affichage ainsi que la lecture, nous allons manipuler les formats. Vous savez, comme sur la calculette de votre gosse au collège, avec un bouton pour passer d'un résultat fractionnaire à un résultat décimale ? Il serait utile de pouvoir convertir l'affichage de cette donnée et de voir quelle est sa valeur. Par exemple, vous pouvez utiliser le format DEC pour convertir en décimal la valeur d'un tag RFID (en HEX)

Si vous faites un print d'un nombre avec juste un paramètre , auquel cas c'est DEC qui est pris par défaut, ou passer en paramètre la base choisie DEC, HEX, BIN

Il y a trois types de format de données que l'on peut exploiter.

  • Le décimale DEC. Le système décimale est un système en base 10 (avec les chiffres de 0 à 9).

  • L’hexadécimale HEX. L'hexadécimale en base 16 avec les chiffres de 0 à 9 en plus des lettres de A à F qui représentent les valeurs de 10 à 15.

  • Le binaire BIN. Le système binaire en base 2 avec les chiffres 0 et 1 seulement. (mais à lui seul a permis le montage de système complexe que l'on connaît)

Pour afficher une donnée dans un format voulu, il suffit de l'indiquer avec une virgule "," dans la fonction print(); ==>

Serial.print(variable virgule format);

Par exemple : 

int val = 0; // valeur

void setup() {
Serial.begin(115200); // 115200 bauds
val = 23700;
}

void loop() {
Serial.println("***** Execution de loop() *****");
Serial.print("Le résultat Décimale est : ");
Serial.println(val, DEC); // en décimale
Serial.print("Le résultat Héxadecimale est : ");
Serial.println(val, HEX); // en hexa
Serial.print("Le résultat Binaire est : ");
Serial.println(val, BIN); // en binaire
while(1==1){} // arrêt forcé du programme
}

Voici le résultat dans le moniteur :

***** Execution de loop() *****
Le résultat Décimale est : 23700
Le résultat Héxadecimale est : 5C94
Le résultat Binaire est : 101110010010100

L'utilisation de l'affichage sous un format différent est parfois utile pour un traitement plus lisible des données binaires vers l'hexadécimal.

Les autres fonctions Serial :

  • Serial.setTimeout(timeout): Définit le délai maximal pour attendre les données en lecture.

  • Serial.readBytes(): Lit une séquence de caractères depuis le port série et les stocke dans le tableau buffer de longueur length. Utile pour lire des données binaires ou structurées.

  • Serial.readBytesUntil(): Serial.readBytesUntil(character, buffer, length) : Lit des caractères depuis le port série jusqu'à ce que le caractère spécifié (character) soit rencontré, puis les stocke dans le tableau buffer de longueur length. Pratique pour lire jusqu'à une délimitation.

  • serialEvent(): Appelé lorsque les données sont disponibles. Vous pouvez utiliser la fonction Serial.read pour capturer ces données (1). voir cette page pour les exemples : https://docs.arduino.cc/built-in-examples/communication/SerialEvent

  • Serial.find(): Recherche la première occurrence de la séquence de caractères spécifiée (target) dans le flux série. Renvoie true si la séquence est trouvée.

  • Serial.peek(): Retourne le prochain octet disponible dans le tampon de réception sans le supprimer. Utile pour vérifier sans consommer les données en attente.

  • Serial.availableForWrite(): Serial.availableForWrite() : Renvoie le nombre d'octets pouvant être écrits dans le tampon de transmission sans bloquer. Utile pour éviter les saturations lors d'écritures rapides.

  • Etc.

Vous retrouverez toutes les informations relatives a ces fonctions, ainsi qu’une liste de celle-ci, sur la page officiel de documentation Arduino® «Serial».

Un autre outil très utile, le traceur série

Il n’y a pas que le moniteur série qui permet d’afficher des valeurs sur une interface graphique dans l’IDE Arduino. Il existe aussi le traceur série, qui permet quand à lui d’afficher des valeurs (pas de texte) sous forme de courbe. Voici un exemple :

void setup() {
// Démarrer la communication série
Serial.begin(115200);
randomSeed(A0);
}

void loop() {
unsigned int val1 = random(0, 200);
Serial.println(val1);
delay(10);
}

Sources / Pour plus d'information :