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()
:
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 :
\\
: Antislash\
.\"
: Double guillemet"
(dans une chaîne de caractères délimitée par des guillemets doubles).\'
: Simple guillemet'
(dans une chaîne de caractères délimitée par des guillemets simples).\t
: Tabulation.\r
: Retour de chariot.\b
: Caractère de retour en arrière (backspace).\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 :
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 :
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) :
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.read
String
()
:
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.read
String
()
et les stocke dans la variable data
.
Grâce à le fonction trim()
,
on supprime tous les espaces blancs de la cha
î
n
e
de caractère. E
nsuite, 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 :
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 :
const int bufferSize = 50;
: Cette ligne déclare une constantebufferSize
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.char receivedData[bufferSize];
: Cette ligne déclare le tableau de caractèresreceivedData
qui sera utilisé pour stocker les données lues depuis le Moniteur Série.int charIndex = 0;
: Cette ligne déclare une variablecharIndex
pour suivre la position actuelle dans le tableau de caractères pendant la lecture.void setup()
: La fonction de configurationsetup
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 fonctionmemset
pour initialiser le tableaureceivedData
avec des zéros.void loop()
: La fonctionloop
est exécutée en boucle après l'exécution desetup
. Dans cette boucle, nous vérifions si des données sont disponibles dans le Moniteur Série à l'aide deSerial.available()
. Si des données sont disponibles, nous lisons un caractère à la fois en utilisantSerial.read()
.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èresreceivedData
. 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 :
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 :
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 l
ire 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.parse
Float
()
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 :
Et
voici un autre exemple avec cette fois la fonction
Serial.parseFloat
:
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 :
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 tableaubuffer
de longueurlength
. 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 tableaubuffer
de longueurlength
. 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/SerialEventSerial.
find
()
: Recherche la première occurrence de la séquence de caractères spécifiée (target
) dans le flux série. Renvoietrue
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 :