Comme cela a été souligné au chapitre C2‑I , si une carte Arduino ne permet pas de mettre en œuvre des entrées‑sorties standards, elle peut néanmoins échanger des données textuelles avec le poste de travail auquel elle est reliée via une liaison série W. De manière générale, une liaison série est constituée d'un médium – c'est‑à‑dire un câble ou autre support de transmission – associé à un protocole de communication (cf. chap. R1‑IV ) permettant, entre deux systèmes numériques :

  • d'établir la communication (avec, par exemple, un bit de start), c'est‑à‑dire préparer chaque système à la transmission de données ;
  • de transmettre des données les unes après les autres sur le médium – ce que sous‑tend le terme « série » ;
  • de clore la communication (avec, par exemple, un bit de stop), éventuellement avec des éléments de vérification de bonne transmission (typiquement, par contrôle de parité).

Parce qu'elles ne requièrent qu'un nombre minimal de conducteurs (3 fils) dans le support de transmission, les liaisons séries ont depuis longtemps supplanté les liaisons parallèles. Ainsi, un protocole série ancien comme l'UART (universal asynchronous receiver transmitterW reste très employé en informatique embarquée. Mais même s'il peut sembler simple par rapport à d'autres (Ethernet, etc.), sa mise en œuvre complète reste assez complexe Il ne suffit pas de quelques heures d'étude pour en comprendre tous les ressorts.

Heureusement, en programmation des microcontrôleurs, cette complexité est masquée par l'emploi de fonctions de haut niveau comme, par exemple, Serial.print dans le framework Arduino. Néanmoins, pour une bonne compréhension de ces fonctions, il est souhaitable de connaître quelques concepts et mécanismes sous‑jacents : buffer d'entrée‑sortie, circuit de sérialisation/désérialisation, etc. Mais attention ! Certains de ces aspects font appel des notions de codage qui n'ont été que peu ou pas étudiées jusqu'à présent dans ce module : objets, chaînes de caractères, pointeurs, interruptions… certaines notions n'étant même pas au programme du module de formation au langage C.

Ce chapitre n'a donc pas vocation à exposer en détail tous les aspects d'une liaison série (pour des compléments, on pourra se reporter à ce cours ). L'objectif est ici d'aborder les principes essentiels de communication par liaison série, afin de pouvoir ensuite coder, avec quand même une bonne maîtrise, des opérations de lecture et d'écriture de données dans le moniteur série émulé par le logiciel Arduino IDE ou dans l'environnement de simulation Tinkercad. Seront donc étudiés dans l'ordre :

  • des rudiments sur les aspects matériels d'une liaison série de type UART ;
  • les éléments sur lesquels reposent aspects logiciels d'une telle liaison série dans le framework Arduino, notamment l'objet Serial et ses buffers, ainsi que le moniteur série ;
  • les quelques fonctions d'administration d'une telle liaison série ;
  • les principales fonctions associées aux opérations de sortie, c'est‑à‑dire d'écriture dans le moniteur série ;
  • les principales fonctions associées aux opérations d'entrée, c'est‑à‑dire de lecture dans le moniteur série ;

sachant que ce sont ces deux dernières sections du chapitre qui apportent les éléments de langage les plus opérationnels dans le bagage d'un codeur.

Aspects matériels  –  généralités

Cas des cartes Arduino

Sauf cas particuliers, toute carte Arduino possède au moins (cf. chap. C1‑III ) :

  • un circuit USART W (universal synchronous & asynchronous receiver transmitter) intégré à son microcontrôleur principal ;
  • un port USB avec un circuit intégré dédié à l'interface USBUSART.

Ces dispositifs permettent une mise en œuvre facile des communications par liaison série standard avec un autre système, notamment un poste de travail – cf. figure ci‑dessous.

À titre d'exemple emblématique, une carte Arduino Uno R3 est équipée d'un port USB type B et embarque, en plus de son microcontrôleur principal, un deuxième microcontrôleur (un Atmel ATmega16U2), cadencé par un circuit oscillant externe à quartz 16 MHz. Ce dispositif est entièrement dédié à l'interface USBUSART (cf. la figure ci‑contre).

Attention ! Sur les cartes clones de type « Uno » produites par des fabricants tiers (principalement chinois), à la place d'un microcontrôleur dédié, c'est plutôt un ASIC W (application‑specific integrated circuit) avec un circuit oscillant interne qui réalise l'interface USB‑USART. Typiquement, il peut s'agir :

  • d'un circuit CP2102  de Silicon Labs ;
  • ou d'un circuit CH340  de WCH.

Cette différence technologique peut poser des problèmes de connexion de la carte avec le poste de travail, notamment si le pilote du circuit n'est pas installé sur le système d'exploitation. Si tel est le cas, on pourra se reporter à cette remarque du chap. C1‑III .

Pour une liaison série, le médium de transmission employé est typiquement un câble USB (universal serial bus) raccordé au port USB de la carte. Cette liaison sert notamment au téléversement du programme utilisateur mais aussi, durant son exécution, à afficher du texte et lire des valeurs saisies par l'utilisateur sur le poste de travail. On parle de moniteur série, comme l'illustre la figure ci‑dessous.

Mais plus généralement, une liaison série peut aussi être employée pour échanger des données entre une carte à microcontrôleur et un serveur de données, une machine (station de mesure, dispositif contrôlé à distance…), un instrument de musique, etc.

Médium direct – broches TX & RX

Sur les cartes Arduino, on peut aussi employer un médium direct (cf. la carte Uno représentée partiellement en figure ci‑contre) constitué :

  • d'une paire de fils torsadés reliée aux broches 0 et 1 du port numérique, lesquelles sont respectivement dédiées à l'émission (celle désignée TX) et à la réception (celle désignée RX) ;
  • et d'un fil de masse relié à la broche GND, ce dernier fournissant le potentiel de référence pour déterminer les niveaux de tension bas et haut (respectivement 0 V et 5 V) sur les broches TX et RX.

On rappelle que c'est la raison pour laquelle il est recommandé de ne pas utiliser comme des entrées‑sorties usuelles les broches 0 et 1 du port numérique – même si cela reste possible moyennant certaines précautions – cf. chap. C2‑XIII .

Par ailleurs, la carte comporte deux leds désignées également TX et RX mais ces dernières ne sont pas reliées électriquement aux broches 0 et 1 du port numérique du microcontrôleur principal de la carte (cf. par exemple le schéma électronique d'une carte Uno R3 A). Elles ne reproduisent donc pas les niveaux de tension sur ces broches. En fait, ces led sont reliées à des broches du microcontrôleur dédié à l'interface USBUSART, leur rôle étant de signaler l'activité de la liaison entre la carte et le poste de travail.

Remarque. La led TX clignote lorsque l'on génère (via le programme utilisateur) un signal variable sur la broche 1 du port numérique. Mais il s'agit alors simplement d'un artefact : le microcontrôleur de l'interface USBUSART interprète à tort ce signal variable comme une activité de la liaison entre la carte et le poste de travail.

Ports série multiples

Sur certains modèles de cartes à microcontrôleur, il peut exister plusieurs autres paires de broches permettant la mise en œuvre d'autres liaisons série en plus de celle utilisée couramment.

Sur les cartes Arduino Mega et Arduino Due, On dispose en tout de 4 liaisons série avec les associations suivantes :

Nº broches Fonctions Objet
1 & 0 TX & RX Serial
18 & 19 TX1 & RX1 Serial1
16 & 17 TX2 & RX2 Serial2
14 & 15 TX3 & RX3 Serial3

Schéma‑bloc de l'USART interne au microcontrôleur Atmel ATmega328p

Le schéma‑bloc ci‑dessous détaille l'implémentation matérielle de l'USART intégré au microcontrôleur Atmel ATmega328p, lequel équipe notamment les cartes Arduino Uno et Nano (cf. chap. C1‑III ).

Il est constitué de 3 principaux circuits :

  • un circuit d'horloge (clock generator) qui, en divisant la fréquence du signal issu du circuit oscillant externe, génère le signal de cadencement de la liaison série à la vitesse spécifiée – ce qu'on appelle en anglais le baud rate ;
  • un circuit d'émission (transmitter) dédié à la sortie des données, qui est lui‑même composé :
    • d'un registre d'émission pour stocker un octet à émettre ;
    • et d'un sous‑circuit de sérialisation qui, à partir des bits de l'octet contenu dans le registre d'émission, génère à la fréquence du circuit d'horloge le signal logique à écrire sur la broche TX ;
  • un circuit de réception (receiver) dédié à l'entrée des données, qui est lui‑même composé :
    • d'un sous‑circuit de désérialisation qui, à partir du signal logique reçu sur la broche RX, reconstitue un octet reçu ;
    • cet octet étant stocké dans le registre de réception ;
  • d'un groupe de 3 registres, d'un octet chacun, pour stocker les bits d'état et de commande de l'USART, à des fins de contrôle logiciel.

Cas des cartes à modules ESP8266 et ESP32

À la différence de la plupart des cartes Arduino, les cartes à modules ESP8266 et ESP32 (cf. chap. C1‑III ) ne sont pas dotées d'un microcontrôleur et d'un quartz externe dédiés pour la conversion USBUSART.

Comme les cartes clones Arduino, elles sont équipées à la place d'un ASIC W (application‑specific integrated circuit) qui intègre un circuit oscillant interne. Cela peut éventuellement poser les mêmes problèmes de connexion de la carte au poste de travail si les pilotes de ces circuits ne sont pas installés sur le poste de travail (chap. C1‑III ).

En règle générale, on retrouve les mêmes fonctionnalités de liaisons séries que sur des cartes Arduino.

À titre d'exemple, la carte SBC‑NodeMCU à module ESP8266 de l'assembleur Joy‑It permet deux liaisons séries UART (cf. la figure ci‑contre dite pinout et le chap. C2‑VIII pour plus de détails) :

  • UART0 sur les broches GPIO 1 & 3 (TXD0 & RXD0) ;
  • UART2 sur les broches GPIO 15 & 13, étiquetée D8 & D7 sur la carte (TXD2 & RXD2) ;

sachant que la liaison UART0 est celle qui est prise en charge par défaut via le circuit d'interface USBUSART de typeCP2102 (cf. supra ).

La carte SBC‑NodeMCU illustrant l'exemple supra possède aussi une liaison UART1 qui est réservée au téléversement du programme utilisateur et du firmware de la carte. Seule la broche TXD1 est déployée et porte le numéro GPIO 2, étiqueté D4 sur la carte .

Aspects logiciels  –  généralités

L'objet Serial

La liaison série entre une carte Arduino ou compatible et le moniteur série de l'application Arduino IDE s'exécutant sur un poste de travail est une liaison asynchrone W spécifiée par le protocole UART W (universal asynchronous receiver transmitter).

Du point de vue logiciel, cette liaison est implémentée par défaut sur la base d'un objet déclaré, désigné par l'identificateur Serial A.

Instanciation de l'objet Serial

Attention ! Les notions de base de la programmation orientée objet (classe, objet, constructeur) ont été ébauchées au chapitre C2‑VI , justement pour pouvoir comprendre les grandes lignes d'éléments de code figurant les fichiers d'en‑tête et d'implémentation du framework Arduino. Toutefois, ce ne sera pas facile car, pour des questions de pédagogie, certains notions comme par exemple celle d'héritage W n'ont pas été abordées.

  • L'objet Serial est déclaré dans le fichier d'implémentation HardwareSerial0.cpp comme une instance de la classe HardwareSerial, laquelle est déclarée le fichier d'en‑tête HardwareSerial.h, inclus par défaut dans tout programme d'extension .ino par une directive codée dans le fichier Arduino.h G (ligne nº 233).
  • La classe HardwareSerial est elle‑même une fille de la classe Stream qui est déclarée dans fichier d'en‑tête Stream.h, lequel est inclus par une directive codée dans le fichier d'en‑tête HardwareSerial.h G (ligne nº 29).
  • La classe Stream est elle‑même une fille de la classe Print qui est déclarée dans le fichier d'en‑tête Print.h, lequel est inclus par une directive codée dans le fichier d'en‑tête Stream.h G(ligne nº 26).

Donc, en résumé :

L'objet Serial n'a pas besoin d'être instancié par le codeur dans le code source des programmes pour cartes Arduino. Il est déjà instancié dans un fichier d'en‑tête du framework Arduino et est donc inclus par défaut dans le programme utilisateur.

Cet objet hérite de tous les champs et méthodes (cf. infra ) définis pour les classes HardwareSerial, Stream et Print.

Parce qu'il n'est pas une classe mais une instance de classe, l'objet Serial devrait, conformément aux bonnes pratiques du codage en C++ (cf. chap. C2‑X ), être nommé avec une lettre minuscule initiale, c'est‑à‑dire serial.

Le choix de la majuscule initiale par les concepteurs du framework Arduino se justifie par la nécessité de faire la distinction de cet objet par rapport aux données déclarées par utilisateur, aux yeux d'un public cible de codeurs débutants ou amateurs.

Buffers de l'objet Serial

On rappelle que d'une manière générale, tout objet est une variable structurée, définie avec divers champs, c'est‑à‑dire des composantes de stockage de données (en quelque sorte, des « sous‑variables »).

L'objet Serial possède deux champs essentiels issus de la classe HardwareSerial, déclarés dans le fichier d'en‑tête HardwareSerial.h G :

  • le buffer de réception :
  •     unsigned char _rx_buffer[SERIAL_RX_BUFFER_SIZE];
    
  • le buffer d'émission :
  •     unsigned char _tx_buffer[SERIAL_TX_BUFFER_SIZE];
    

sachant qu'un buffer est un espace mémoire tampon permettant de stocker des données de façon temporaire.

Dans les instructions ci‑dessus, les deux buffers sont déclarés :

  • comme des tableaux (notion abordée au chap. C5‑III ),
  • dont les éléments sont de type unsigned char, donc encodés en binaire naturel sur 1 octet (8 bits) chacun,
  • le nombre d'éléments étant respectivement spécifié par les pseudo‑constantes SERIAL_RX_BUFFER_SIZE et SERIAL_TX_BUFFER_SIZE, définies dans le fichier d'en‑tête HardwareSerial.h avec comme valeur par défaut :
    • 16 octets si la mémoire pour les données est inférieure à 1 ko (par exemple, sur un microcontrôleur Atmel ATtiny – cf. chap. C1‑III ) ;
    • 64 octets pour les cartes Arduino à cœur AVR (Uno R3, Nano, Mega…)  ;
    • 128 octets pour les cartes Arduino à cœur ARM et les cartes à module ESP8266 et ESP32.

Remarque. Dans le framework Arduino, les identificateurs de ces deux pseudo‑constantes sont utilisables pour former des expressions dans les programmes sources.

De plus, les buffers d'émission et de réception de l'objet Serial sont l'un et l'autre implémentés de façon circulaire avec, pour chacun, un indice de queue (_buffer_tail) et un indice de tête (_buffer_headW.

Comme illustré sur la figure ci‑contre, chaque buffer est opéré avec une gestion des priorités comme celle d'une file d'attente FIFO (first in first out W) :

  • L'indice de queue cible l'octet arrivé en premier dans le buffer (le plus ancien), qui doit être traité en premier (parmi tous ceux qu'il contient).
  • Il est incrémenté d'une unité à chaque octet traité ; par cette opération, un octet du buffer est libéré (son contenu n'est pas effacé, il sera simplement ultérieurement écrasé par un octet entrant).
  • L'indice de tête cible l'élément placé juste après l'octet arrivé en dernier dans le buffer (le plus récent), qui sera traité en dernier (parmi tous ceux qu'il contient).
  • Il est incrémenté d'une unité à chaque octet arrivé ; par cette opération, l'octet arrivé est comptabilisé dans le buffer.

Sachant sa circularité, le buffer est donc :

  • vide lorsque l'indice de queue est égal à l'indice de tête (cf. le cas de figure ci‑contre) ;
  • partiellement plein lorsque l'indice de tête est différent d'au moins deux unités (supérieur ou inférieur) à l'indice de queue (cf. les deux cas de figure ci‑dessous) ;
  • plein lorsque l'indice de tête est égal moins une unité à l'indice de queue (cf. le cas de figure ci‑contre).

Avec cette structure de données, la capacité effective du buffer est inférieure d'une unité au nombre d'éléments du tableau.

Exemple. Dans le cas d'une carte Arduino Uno R3, puisque le nombre d'éléments des tableaux _rx_buffer et _tx_buffer vaut 64, la capacité effective des buffers est de 63 octets.

En résumé, pour une mise en œuvre asynchrone de la liaison série :

L'objet Serial dispose, respectivement pour l'émission et la réception de données d'un buffer circulaire d'une capacité effective qui dépend du type de carte à microcontrôleur employée, typiquement 63 octets pour une Arduino Uno.

Ces deux buffers constituent l'un et l'autre un espace mémoire tampon géré comme une file d'attente FIFO (first in first out) :

  • avant sérialisation en signal logique de chaque octet émis vers le système auquel la carte est raccordée ;
  • après désérialisation du signal logique de chaque octet reçu depuis le système auquel la carte est raccordée.

Ports série multiples

Pour les cartes Arduino disposant de plusieurs ports série (Mega, Due…, cf. supra ), le fichier HardwareSerial0.cpp déclare des instances supplémentaires de la classe HardwareSerial, qui sont identifiées respectivement Serial1, Serial2 et Serial3.

Ces objets ont exactement les mêmes propriétés (mêmes champs et mêmes méthodes) que l'objet Serial.

Méthodes associées à l'objet Serial

Dans la logique de la programmation orientée objet, un objet se manipule à l'aide de méthodes, c'est‑à‑dire des fonctions dédiées qui sont définies dans la déclaration de la classe à laquelle l'objet appartient ou des classes dont il descend (c'est la notion d'héritage).

L'objet Serial compte 20 méthodes publiques, recensées sur la page de référence suivante A. On peut les classer en trois catégories comme dans le tableau ci‑dessous.

Administration begin   end
Écriture availableForWrite   flush   print   println   write
Lecture available   find   findUntil   parseFloat   parseInt   peek   read   readBytes   readByteUntil   readString   serialEvent   setTimeout

Toutes ces méthodes ne peuvent être appelées que via l'opérateur de sélection (cf. chap. C2‑IV ), codé par le symbole . et appliqué à l'objet Serial (ou un autre objet de la même classe).

Pour mettre fin à la liaison série entre une carte Arduino et un équipement (poste de travail, etc.), on code une instruction appelant la méthode end via la syntaxe :

  Serial.end();

Cas des cartes à modules ESP8266 et ESP32

Dans le cas des cartes à modules ESP8266 et ESP32 (cf. supra ), les fichiers de bibliothèque qui implémentent les liaisons série UART (HardwareSerial.h, etc.) sont similaires à ceux pour les cartes Arduino, mais pas identiques. En effet :

  • il existe quelques méthodes spécifiques, par exemple baudRate qui retourne la valeur de vitesse de transmission (cf. infra ) de la liaison série associée à l'objet Serial ou autre ;
  • de plus, certaines méthodes communes acceptent des arguments supplémentaires.

Pour plus de détails, on pourra se rapporter à ce lien .

Le moniteur série

Sur le poste de travail, l'application Arduino IDE V2 émule un moniteur série dans le cadre multi‑fonctions en dessous de la fenêtre d'édition du code (cf. chap. C1‑III . Par des instructions codées dans le programme utilisateur téléversé sur la carte,il permet à l'utilisateur de visualiser les affichages (sorties) et de saisir des valeurs (entrées) via la liaison série.

L'onglet du moniteur série peut être ouvert dans le cadre multi‑fonctions (sous la fenêtre d'édition de code) soit par une commande éponyme du menu Outils, soit en cliquant sur le bouton de raccourci en haut à droite de l'éditeur.

Dans cet onglet, on trouve :

  • en haut, la barre de saisie pour les entrées de la carte, qui sont envoyées après validation par la touche ENTRÉE ↵ du clavier sur le poste de travail ;
  • au centre, la zone d'affichage pour les sorties de la carte ;
  • en haut à gauche, des boutons pour contrôler quelques fonctionnalités de la zone d'affichage des sorties – le défilement automatique (autoscroll), l'horodatage (timestamp) et l'effaçage ;
  • juste en dessous, deux menus déroulants pour respectivement sélectionner :
    • la séquence d'échappement newline générée à la fin de la saisie en entrée ;
    • la vitesse de transmission de la liaison série (baudrate) – cf. infra .

La barre de saisie et la zone d'affichage opèrent l'une comme l'autre au format UTF‑8 qui est, rappelons-le, compatible avec le jeu de caractères ASCII restreint (cf. chap. C3‑IX ).

Vitesse de transmission

On parle de liaison asynchrone entre deux machines lorsqu'elles ne partagent pas d'horloge commune (ce qui permet d'économiser un fil sur le médium de transmission). Il est alors indispensable que chacune des deux machines ajuste à la même fréquence un signal d'horloge interne pour cadencer la transmission. Le choix de cette fréquence commune conditionne directement la vitesse de transmission des données sur la liaison.

Dans une transmission en série, l'unité de vitesse est le baud W (symbole Bd). Cette unité est définie comme étant égale à 1 symbole par seconde, où le symbole est l'unité de taille des données transmises. Dans la plupart des cas, et notamment avec les cartes Arduino ou compatible, le symbole est simplement le bit. On a donc la formule :

Bd = 1 bit/s

Concrètement on spécifie la même vitesse de transmission de la liaison série entre un poste de travail et une carte Arduino ou compatible qui y est raccordée :

  • sur la carte, via le code source du programme, en argument d'appel de la méthode begin associée à l'objet Serial (cf. infra ) ;
  • sur le poste de travail, dans un menu déroulant de l'onglet du moniteur série (cf. supra ).

Les valeurs standards de la vitesse de transmission vont de 300 à 2 000 000 Bd. Pour faire un bon choix, il faut avoir en tête les considérations suivantes :

  • La valeur 9600 Bd est restée durant longtemps la vitesse par défaut. C'est un choix « historique » qui aujourd'hui est déconseillé car il peut être pénalisant pour la réactivité du programme utilisateur.
  • En effet, sachant qu'il faut au minimum 10 bits pour transmettre un octet, il faut compter environ 1 ms par octet transmis avec cette vitesse. Si le buffer d'émission en vient à être saturé, l'exécution du programme va inévitablement ralentir dans des proportions inacceptables pour un système temps‑réel.
    Quant aux valeurs inférieures à 9600 Bd, elles sont complètement désuètes.
  • La valeur 115200 Bd est aujourd'hui celle qui est employée préférentiellement ; elle tient lieu de nouvelle valeur par défaut. Elle est environ 10 fois supérieure à la précédente.
  • Mais lorsque le programme échange un gros volume de données échangées par liaison série sont très nombreuses, pour ne pas ralentir l'exécution des programmes, on peut éventuellement augmenter encore cette vitesse d'un facteur 10, c'est‑à‑dire jusqu'à 1000000 Bd. Toutefois, sur certaines cartes à microcontrôleur hautes‑performances et très miniaturisées (Teensy, par exemple), il peut exister un risque échauffement du convertisseur USB‑serial.

Le traceur série

En alternative au moniteur série, l'application Arduino IDE permet d'afficher sous forme de diagramme temporel les valeurs numériques de sortie d'une carte à microcontrôleur. C'est le traceur série (serial plotter).

On accède à ce mode d'affichage soit via une commande éponyme du menu Tools, soit en cliquant sur le bouton de raccourci en haut à droite de l'éditeur. Il peut être opérationnel même lorsque le moniteur série est également activé.

Pour chaque variable du programme exécuté dans la carte, dont les valeurs sont envoyées via la liaison série par une instruction répétitive (c'est‑à‑dire codée dans la fonction loop) de la forme :
Serial.print(identificateur);   (cf. infra )
le traceur trace sur un diagramme cartésien une courbe dont les points ont pour abscisse l'instant de réception et pour ordonnée la valeur de la variable transmise à cet instant.

On voit donc apparaître sur le diagramme autant de courbes de que variables suivies, chacune étant tracée avec une couleur distincte, automatiquement choisie.

Considérons le programme académique ci‑dessous qui, sur une carte Arduino Uno, effectue une lecture analogique de la broche A0 laissée flottante (c'est‑à‑dire reliée à aucun dispositif). Un appel de la fonction delay permet de limiter la fréquence des lectures.

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.flush();
  delay(100);
}

void loop()
{
  int randomAnalog = analogRead(A0);
  Serial.println(randomAnalog);
  delay(100);
}

La capture d'écran ci‑dessous montre l'affichage du traceur série lors de l'exécution :

Dans l'IDE VS Code

Dans l'IDE VS code avec installées les extensions Arduino Community Edition et Serial Monitor de Microsoft (cf. chap. C1‑III ), le moniteur série est accessible dans un onglet dédié du cadre inférieur sous le cadre d'édition de code – cf. la capture d'écran ci‑dessous. Il s'ouvre simplement par un clic de souris.

Ce moniteur série fournit plus de fonctionnalités et de possibilités de paramétrage que celui de l'application Arduino IDE. En particulier, il permet de gérer en parallèle plusieurs liaisons séries entre une carte Arduino ou compatible et le poste de travail. C'est pourquoi l'ouverture du moniteur série ne provoque pas d'activation immédiate de ce dernier. Pour cela, il faut effectuer deux actions :

  1. via un menu déroulant, sélectionner le port USB auquel la carte est raccordée ;
  2. cliquer sur le bouton ▷ Start Monitoring

Les messages envoyés par la carte peuvent alors apparaître dans la zone d'affichage. De plus, la barre de saisie s'active pour permettre à l'utilisateur d'envoyer des messages vers la carte.

Comme sur l'application Arduino IDE (et avec davantage de fonctionnalités), on dispose de plusieurs boutons de contrôle, respectivement pour :

  • l'effacement du contenu de la zone d'affichage ;
  • l'activation/désactivation de l'horodatage des messages (timestamp), du défilement automatique du texte (autoscroll), de la barre de saisie (terminal mode) et des messages internes à l'application (sent message echoing).

Par ailleurs, le bouton permet d'accéder aux réglages fins du protocole de communication de la liaison série, que l'on ne détaillera pas ici (cf. chap. R3‑VII ).

L'extensions Arduino Community Edition, tout comme l'extension Serial Monitor de Microsoft, n'implémentent pas de traceur série.

On peut néanmoins pallier cette lacune en installant une extension dédiée, comme par exemple Teleplot.

Dans l'environnement en ligne Tinkercad

L'environnement de simulation Tinkercad permet de simuler le moniteur série comme si une vraie carte était reliée à l'ordinateur par liaison série.

Dans une fenêtre de circuit dont le volet code est ouvert, la simulation du moniteur série est activée par un clic en bas du volet.

Il apparaît alors un zone d'affichage pour visualiser les sorties et en dessous une barre de saisie pour taper au clavier des entrées, à valider en cliquant sur le bouton de droite « envoyer ».

Il n'est pas nécessaire de paramétrer la vitesse de transmission : elle s'ajuste automatiquement à celle du code source (il ne s'agit pas d'une véritable liaison série mais d'une simulation).

En plus du bouton « envoyer », les deux autres boutons à droite dans la barre de saisie ont respectivement pour fonction :

  • d'effacer le contenu de la zone d'affichage,
  • d'activer le mode traceur série.

On l'a déjà vu à plusieurs reprises, l'environnement Tinkercad ne simule pas bien certains aspects du fonctionnement de l'application Arduino IDE.

En particulier, la fenêtre du moniteur série n'interprète pas les octets au format UTF‑8 (cf. chap. C3‑IX ), mais en ASCII (cf. chap. C3‑VIII ). En conséquence, les caractères accentués ne sont pas correctement affichés : on est confronté à un problème classique de transcodage comme celui abordé dans l'exercice nº 4 de la feuille C3 ).

Administration d'une liaison série

Initialisation

Même si les objets comme Serial sont déjà déclarés par défaut, toute liaison série doit impérativement être initialisée avant d'être opérationnelle.

Dans le cas de la liaison série usuelle avec le poste de travail, l'initialisation est effectuée par une instruction d'appel de la méthode begin de la forme :
Serial.begin(vitesse); A
dont l'argument vitesse est une expression à valeur dans le type unsigned long qui spécifie la vitesse de transmission en baud.

En fait, la méthode begin possède une syntaxe générale d'appel plus détaillée. Elle admet un deuxième argument nommé config qui spécifie par une pseudo‑constantes de la forme SERIAL_xLy les options possibles du protocole UART, où :

  • x est un nombre allant de 5 à 8 qui code nombre de bits de données du mot transmis (code 8 par défaut) ;
  • L est une lettre qui code le contrôle de parité W mis en œuvre, avec N pour no parity (code par défaut), E pour even parity (parité paire) ou 0 pour odd parity (parité impaire) ;
  • y est un nombre à un chiffre valant 1 ou 2 pour coder nombre de bits de stop marquant la fin d'émission (code 1 par défaut).

La pseudo‑constante par défaut est donc SERIAL_8N1 (transmission par mots de 8 bits sans contrôle de parité et avec un seul bit de stop).

Pour se défaire de quelques idées reçues, certains aspects de l'exécution de l'appel de la méthode begin nécessitent des explications.

  • Elle procède simplement au paramétrage du protocole UART.
  • Elle n'engendre aucun signal logique sur les broches TX et RX associées à la liaison série.
  • Elle opère même en l'absence de medium de transmission (câble USB ou autre) raccordé à la carte.
  • Elle peut, sur certaines cartes (notamment celles à module ESP8266 et ESP32) être parasitée par le téléversement ou la réinitialisation du programme, avec :
    • l'affichage d'une chaîne de caractères inattendus dits garbage (déchets) ou gibberish (charabia), typiquement :
    • ⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮
      
    • et a contrario, le non affichage de l'argument du premier appel de la fonction print ou println ;
    Aussi pour éviter toute perte d'affichage consécutive à l'initialisation de la liaison série, avant tout appel d'une fonction de sortie, il est conseillé de coder une temporisation et de commencer par un saut de ligne, comme proposé ci‑dessous :
    void setup()
    {
      delay(500);
      Serial.begin(115200);
      Serial.println();
      
    

Terminaison

Pour coder la terminaison de la liaison série, il suffit d'appeler la méthode end, sans argument, dans une instruction de la forme :
Serial.end(); A

Ensuite, pour réactiver la liaison série, il suffit d'appeler à nouveau la méthode begin.

La terminaison de la liaison série (si elle n'est plus d'utilité) permet notamment d'employer autrement les broches nº 0 et 1 du port numérique auxquelles la liaison est associée, sans risque de voir les niveaux logiques sur ces broches perturbés par une liaison maintenue sans motif.

La première instruction dans le code de définition de la méthode end G est un appel de la méthode flush afin de laisser le temps d'achever l'envoi des octets encore présents dans le buffer d'émission (cf. infra ). Il n'est donc pas nécessaire de coder cet appel avant celui de la méthode end dans le programme utilisateur.

Test

Le test if (Serial)… A est implémenté par surcharge de l'opérateur bool() pour déterminer si la liaison série USB est « ouverte ».

Attention ! Un tel test n'est valable que pour les très rares cartes Arduino ayant un port USB natif – c'est‑à‑dire directement relié au microcontrôleur principal de la carte – donc pas pour les modèles usuels de cartes (Uno, Mega, Nano, etc.).

En revanche, ce test peut être employé avec une carte Arduino Due. L'objet associé à son port natif étant identifié par SerialUSB, l'expression SerialUSB prend la valeur 1 (true) :

  • si la carte est bien raccordée à un terminal via un câble USB,
  • et si la liaison série est opérationnelle, par exemple si le numéro de port est bien sélectionné avec le logiciel Arduino IDE et si le moniteur série est activé.

Sinon, l'expression SerialUSB prend la valeur 0 (false). L'intérêt du test de donc pouvoir faire ce diagnostic.

Opérations de sortie par liaison série

Principe général d'une opération de sortie

Dès lors qu'une liaison série est initialisée par un programme sur une carte Arduino, effectuer une opération de sortie – c'est‑à‑dire l'émission de données depuis la carte – se déroule en 3 étapes : 1) écriture d'octets dans le buffer par appel d'une méthode d'écriture, 2) sérialisation et émission sur la broche de sortie (TX), 3) réception et traitement sur le système raccordé à la carte.

Plus en détails :

  1. Les données sont d'abord écrites octet par octet dans le buffer d'émission de l'objet Serial associé à la liaison, sachant que chaque octet écrit occupe un élément du buffer.
  2. Tant que le buffer n'est pas vide, ces octets sont ensuite l'un après l'autre sérialisés, c'est‑à‑dire décomposés bit par bit pour générer signal logique par l'USART, cadencé par la vitesse de transmission réglée ;
  3. Enfin, la réception (mémorisation) et le traitement (affichage ou autre) des données émises sont pris en charge par le système avec lequel la carte communique, donc au rythme de ce système, et indépendamment de l'exécution du programme sur la carte.
  4. S'il s'agit de l'application Arduino IDE ou un programme équivalent (VS Code) s'exécutant sur un poste de travail, c'est la partie moniteur série qui procède à l'affichage des caractères ou l'exécution des actions (tabulation, etc.) dans la zone d'affichage, en interprétant ces derniers au format UTF‑8.

Pour mémoire (cf. supra ) :

  • l'occupation d'un élément du buffer d'émission s'effectue simplement par incrémentation unitaire de l'indice de tête du buffer ;
  • la libération d'un élément du buffer d'émission s'effectue par incrémentation unitaire de l'indice de queue du buffer ;

Si l'indice de tête « rattrape » l'indice de queue (moins une unité), le buffer est plein.

Quant à l'USART, il opère en arrière‑plan de l'exécution du programme sur le microcontrôleur :

  • Sur déclenchement d'une interruption, l'octet de queue du buffer d'émission est d'abord copié dans le registre d'émission, ce qui libère un élément du buffer d'émission.
  • L'USART sérialise les bits de l'octet stocké dans le registre d'émission et génère un signal logique conforme au protocole UART (c'est‑à‑dire avec les bits de start et de stop ajoutés à chaque octet) sur la broche TX associée à la liaison série.
  • Lorsque la génération du signal logique est achevée, l'USART déclenche une interruption pour signifier que son registre d'émission est prêt pour la sérialisation d'un nouvel octet.

Ce processus est mis en œuvre même en l'absence de medium de transmission branché à la carte (câble USB ou autre).

Lorsque le buffer d'émission est vide, tout octet à émettre est directement copié dans le registre d'émission de l'USART, ce qui augmente significativement la vitesse maximale de transmission possible.

Modes d'écriture

Pour coder une émission de données par liaison série, le codeur dispose principalement de deux modes d'écriture :

  • le mode par octets, via la méthode write qui est considérée comme étant de bas niveau ;
  • le mode par caractères, via les méthodes print et println qui sont considérée comme étant de haut niveau au sens où elles font appel à la méthode de bas niveau write. (On rappelle qu'au format UTF‑8, un caractère est potentiellement encodé sur plusieurs octets.)

Les méthodes (ou fonctions) d'écriture sont héritées de la classe Print, et sont définies dans le fichier Print.cpp.

Les méthodes d'écriture font elles‑mêmes appel à des méthodes de très bas niveau définies dans le fichier HardwareSerial.cpp, en particulier :

  • la méthode write G (synonyme) pour écrire un nouvel octet en tête du buffer d'émission (cet octet étant le dernier entré parmi de tous ceux déjà présents dans le buffer, il sera donc le dernier à sortir) ;
  • la méthode _tx_udr_empty_irq G pour copier l'octet en queue de buffer d'émission dans le registre d'émission (cet octet étant alors le premier entré parmi de tous ceux présents dans le buffer, il est donc le premier à sortir).

Même si ce sont des méthodes publiques, elles n'ont pas vocation à être appelée dans un programme codé sur une carte Arduino.

La méthode write de très bas niveau définie dans le fichier HardwareSerial.cpp est à ne pas confondre avec celle ayant le même identificateur mais définie dans le fichier Print.cpp.

Écriture par octets

La méthode write définie dans le fichier Print.cpp G est une méthode d'écriture de bas niveau qui procède par octets. Un appel de la forme :
Serial.write(expression) A
écrit octet par octet la valeur de l'expression en tête du buffer d'émission de l'objet Serial.

  • La méthode write retourne le nombre d'octets écrits, encodé dans le type size_t. Mais cette valeur est rarement exploitée ; usuellement, l'appel est codé comme une simple instruction.
  • Dans le moniteur série, les octets reçus sont interprétés au format UTF‑8 et affichés comme des caractères, ce qui peut prêter à confusion sur le mode d'écriture de la méthode write, qui procède bien par octets.

L'expression passée en argument de la méthode write peut être de type :

  1. entier, et en particulier codée par une valeur de caractère saisie entre guillemets simples '' ;
  2. chaîne de caractère, et en particulier codée par suite de caractères simples ou étendus encadrée par des guillemets doubles "".
  1. Si l'argument expression est de type entier :
  2. Elle est évaluée par conversion implicite dans le type unsigned char, avec rebouclage cyclique éventuel (cf. chap. C3‑VI . S'il s'agit d'une valeur de caractère saisie entre guillemets simples seul l'octet de poids faible de son code UTF‑8 est retenu. Dans tous les cas, un seul octet est écrit en tête du buffer d'émission.

    Dans tous les exemples ci‑dessous, on fait l'hypothèse que le programme s'exécute sur une vraie carte Arduino reliée à un poste de travail où le moniteur série est activé. La liaison série est supposée initialisée correctement. Typiquement, le squelette de programme de test donné ci‑dessous suffit :

    void setup()
    {
      delay(500);
      Serial.begin(115200);
      Serial.println();
      // type your statement here
    }
    
    void loop() {}
    

    Les instructions à tester sont à coder en ligne nº 6. Ainsi :

    • L'instruction Serial.write(65); écrit l'octet de valeur numérique 65 (ou 0x41) dans le buffer d'émission. Et cet octet apparaît dans la zone d'affichage du moniteur série :
      A
      car 65 est le code UTF‑8 de la lettre « A » (cf. chap. C3‑VIII ).
    • L'instruction Serial.write(65 + 256); donne exactement le même résultat parce qu'un octet encode seulement 256 valeurs entières, donc le rebouclage opère modulo 256.
    • L'instruction Serial.write('A'); donne aussi le même résultat parce que la valeur de caractère 'A' est évaluée par le compilateur comme son code UTF‑8, à savoir l'entier 65.
    • L'instruction Serial.write(128); écrit l'octet de valeur numérique 128 (ou 0x80) dans le buffer d'émission. Toutefois, cet octet apparaît dans la zone d'affichage du moniteur série comme le symbole générique :

      car en UTF‑8, le code 128 n'est pas un code de caractère affichable.
    • L'instruction Serial.write('é'); écrit l'octet de valeur numérique 169 (ou 0xA9) dans le buffer d'émission car c'est la valeur de l'octet de poids faible du code UTF‑8 du caractère « é ». Et cet octet apparaît dans la zone d'affichage du moniteur série comme le symbole générique :

      car en UTF‑8, le code 1169 n'est pas un code de caractère affichable.
  3. Si l'argument expression est de type chaîne de caractères  :
  4. Les codes UTF‑8 des caractères qui la composent (encodés sur un ou plusieurs octets) sont écrits sont l'un après l'autre en tête du buffer d'émission.

    Comme supra , on fait l'hypothèse que le programme s'exécute sur une vraie carte Arduino reliée à un poste de travail où le moniteur série est activé, la liaison série étant initialisée. Et on utilise le même squelette de code.

    • L'instruction Serial.write("C++"); écrit les trois octets de codes UTF‑8 respectifs 67 ('C'), 43 ('+') et 43 ('+') dans le buffer d'émission. Après réception dans le moniteur série, ils sont interprétés comme tels et apparaissent dans la zone d'affichage pour former le mot :
      C++
    • L'instruction Serial.write("é"); écrit le code UTF‑8 0xC3A9, soit les deux octets de valeur décimale 169 (0xA9) et 195 (0xC3) dans le buffer d'émission. Après réception dans le moniteur série, ils sont interprétés comme tels et apparaissent dans la zone d'affichage comme le caractère affichable attendu :
      é
    • On peut donc apprécier la différence avec le résultat insatisfaisant produit par l'instruction Serial.write('é'); (cf. supra ).
      Remarque. Comme exposé supra , l'environnement Tinkercad ne prend pas en charge le format UTF‑8 dans la simulation du moniteur série. Si l'on y teste l'instruction Serial.write("é"); on obtient l'affichage de la chaîne de caractères Ã© dans la fenêtre de simulation du moniteur série. Ces deux glyphes correspondent respectivement aux valeurs de caractères des codes ASCII étendus 0xC3 et 0xA9 de la page de code CP1252 (cf. chap. C3‑VIII ), sachant que 0xC3A9 est le code UTF‑8 du caractère « é » (cf. l'exercice C3‑4 ).

Limitation du nombre d'octets émis

Dans le cas où l'expression est une chaîne de caractères, on peut limiter le nombre n d'octets effectivement écrits en tête du buffer d'émission de l'objet Serial par un argument optionnel de type size_t dans l'appel de la méthode.

Il suffit pour cela d'employer la syntaxe d'appel :
Serial.write(expression [, n])

L'instruction Serial.write("Bonjour", 3); affiche la chaîne tronquée Bon (3 premiers caractères) sur le moniteur série.

Écriture par caractères

Principe de l'écriture par caractères

Les méthodes print  et println  sont des fonctions similaires à write, mais elles opèrent à plus haut niveau : elles acceptent également comme argument principal une expression à valeurs numériques entières ou décimales, et non plus seulement un octet ou une chaîne d'octets.

Le principe de traitement est le suivant :

  1. Une fois que l'expression est évaluée, la valeur numérique obtenue est éventuellement convertie dans une autre base de numération, ou encore tronquée, si un argument optionnel de format est codé dans l'appel de la méthode.
  2. Cette valeur numérique n'est pas interprétée dans son format usuel d'encodage mais comme une chaîne de caractères numériques codés en ASCII. Cette interprétation est mise en œuvre par la méthode privée printNumber définie dans le fichier d'implémentation Print.cpp G.
  3. Les octets constituant les codes ASCII de chaque chiffre ou symbole de la chaîne de caractères sont ensuite écrits l'un après l'autre dans le buffer d'émission par appel de la méthode de bas niveau write (dernière instruction de la méthode printNumber).
  4. Il faut donc se défaire de l'idée reçue que ce serait la valeur binaire qui est transmise sur la liaison série par une méthode de haut niveau comme print.

Ensuite, le procédé suit le même cours qu'avec la méthode write : les octets contenus dans le buffer d'émission sont copiés l'un après l'autre dans le registre d'émission pour y être sérialisés. S'ils sont émis par l'USART à destination du moniteur série, ils seront interprétés – et donc affichés – comme des caractères au format UTF‑8.

Pour bien comprendre la différence avec la méthode write, comparons l'affichage obtenu pour le même argument qu'au 1er exemple supra  (pour mémoire, l'instruction Serial.write(65); affiche A sur le moniteur série car 65 est le code UTF‑8 du caractère « A »).

Reprenons les hypothèses et le squelette de programme de test précédent .

  • Lors du traitement de l'instruction Serial.print(65); l'argument 65 est interprété non pas comme le code UTF‑8 d'un caractère mais comme la chaîne de caractères numérique formant le nombre entier 65, c'est‑à‑dire les caractères chiffres « 6 » et « 5 » pris successivement dans cet ordre. Et chacun de ces caractères est alors copié dans le buffer d'émission par appel de la méthode de bas niveau write.
  • Ainsi, Dans la zone d'affichage du moniteur série, on obtient donc :
    65
    et non pas le caractère « A » comme avec l'instruction Serial.write(65);.
  • En revanche, l'instruction Serial.print('A'); produit le même effet que Serial.write('A'); à savoir l'émission du code UTF‑8 du caractère « A » passé en argument, qui est ensuite bien affiché A sur le moniteur série.
  • En effet, la méthode print est conçue comme une méthode de haut niveau capable de prendre en charge l'affichage de données de types les plus variés soient‑ils.

Syntaxes d'appel

Les méthodes print et println obéissent à une syntaxe d'appel plus variée que  write. Elle dépend de l'expression passée en premier argument, qui peut être de type :

  1. entier,
  2. décimal,
  3. caractère ou chaîne de caractère,

sachant que dans tous les cas, l'expression peut en particulier être codée par une constante littérale avec la syntaxe appropriée (cf. les chapitres précédents et spécifiquement le chapitre C5‑VI pour les chaînes de caractères).

  1. Si l'expression est à valeur entière, signée ou non, alors la syntaxe d'appel est :
    Serial.print(expression [, base]) A
    où l'argument optionnel base spécifie la base de numération de la valeur formatée à écrire en tête du buffer d'émission de l'objet Serial.

    Ce formatage est appliqué quel que soit le préfixe (0x, 0b…) éventuellement spécifié dans l'expression passée comme premier argument.

    L'argument optionnel base peut être codé :

    • par n'importe quelle expression prenant la valeur 2, 8, 10 ou 16 ;
    • où, pour une meilleure lisibilité, par l'une des pseudo‑constantes BIN, OCT, DEC ou HEX définies dans le fichier d'en‑tête Print.h ;

    sachant que c'est par défaut la base 10 de formatage qui est adoptée.

    Pour tester la méthode print appliquées à des valeurs entières, comme précédemment, on fait l'hypothèse que le programme s'exécute sur une carte Arduino reliée à un poste de travail où le moniteur série est activé, la liaison série étant initialisée (cf. supra  pour le squelette du programme de test qu'on peut utiliser).

    • L'instruction Serial.print(6, BIN); affiche la chaîne de caractères 110.
    • Plus précisément, cette instruction :
      • convertit la valeur 6 en base 2, aboutissant au nombre binaire 110,
      • convertit ce nombre binaire en la chaîne de trois caractères « 1 », « 1 » et « 0 »,
      • convertit ces caractères en leurs codes ASCII 0x31, 0x31 et 0x30,
      • écrit ces octets de code dans le buffer d'émission.
      Copiés l'un après l'autre dans le registre d'émission, puis sérialisés par l'USART, ces octets sont interprétés par le moniteur série comme des codes UTF‑8 (identiques aux codes ASCII restreints). Les caractère qui s'affichent sont donc les digits binaires de la constante littérale 6 passée en argument.
    • L'instruction Serial.print(0b1111, HEX); affiche la chaîne de caractères F.
    • Plus précisément, cette instruction :
      • convertit la valeur binaire 1111 en base 16, aboutissant au nombre hexadécimal à un seul digit F (c'est‑à‑dire 15 en base 10) ;
      • convertit ce nombre en la chaîne de caractères équivalente à "F" ;
      • convertit ce caractère en son code ASCII 0x46,
      • écrit cet octet de code dans le buffer d'émission.
      Comme pour l'exemple précédent, cet octet est ensuite interprété comme un code UTF‑8 et apparaît donc comme F dans la zone d'affichage du moniteur série.
  2. Si expression est à valeur décimale (c'est‑à‑dire codée dans un type flottant) alors la syntaxe d'appel est :
    Serial.print(expression [, digit]) A
    où l'argument optionnel digit spécifie le nombre de décimales près auquel arrondir la valeur formatée écrite en tête du buffer d'émission de l'objet Serial.

    L'argument optionnel digit peut être codé par n'importe quelle expression à valeur entière positive ou nulle.

    En principe, toutes les décimales spécifiées supplémentaires à celles encodées dans le type flottant de l'expression passée en premier argument sont formatées 0.

    Pour tester la méthode print appliquée à des valeurs décimales, comme précédemment, on fait l'hypothèse que le programme s'exécute sur une carte Arduino reliée à un poste de travail où le moniteur série est activé, la liaison série étant initialisée (cf. supra  pour le squelette du programme de test qu'on peut utiliser).

    • L'instruction Serial.print(12.345678, 3); affiche la chaîne de caractères 12.346.
    • Plus précisément, cette instruction :
      • arrondit à 12.346 la valeur formatée (la 3e décimale est passée à la valeur entière supérieure parce que la décimale suivante 6 est supérieure où égale à 5) ;
      • convertit cette valeur numérique en la chaîne de caractères équivalente à "12.346" ;
      • convertit chaque caractère de cette chaîne en son code ASCII ;
      • écrit ces octets de code dans le buffer d'émission.
      Interprétés comme des codes UTF‑8, ils formeront la chaîne de caractères décimaux souhaitée dans la zone d'affichage du moniteur série.
    • L'instruction Serial.print(PI, 25) affiche :
      3.1415927410125732421875000
      sur le moniteur série (pour mémoire, le nombre π vaut 3,14159265 à 10−8 près).
    • Le fait de spécifier 25 décimales n'apporte pas de précision supplémentaire au nombre affiché, qui reste tributaire de la résolution du type double dans lequel la pseudo‑constante PI (définie pourtant avec 31 décimales dans le fichier Arduino.h G) est implicitement convertie, sachant que sur une carte Uno, le type double est implémenté avec la même précision que le type float (cf. chap. C3‑V ).
    • L'instruction Serial.print(123456789.012345, 2) affiche :
      123456792.00
      sur le moniteur série.
    • Là encore, le nombre affiché diffère de celui spécifié à cause de sa conversion implicite dans le type double qui n'est implémenté qu'en simple précision.
  3. Si l'expression prend une valeur de caractère ou de chaîne de caractères, alors la syntaxe d'appel est simplement :
    Serial.print(expression) A
    avec des résultats identiques à ceux obtenus par appel de la méthode write (cf. supra ).

    Pour tester la méthode print appliquées à des caractères ou des chaînes de caractères, comme précédemment, on fait l'hypothèse que le programme s'exécute sur une carte Arduino reliée à un poste de travail où le moniteur série est activé, la liaison série étant initialisée (cf. supra  pour le squelette du programme de test qu'on peut utiliser).

    Dans la zone d'affichage du moniteur série :

    • L'instruction Serial.print('e') donne e mais Serial.print('é') donne . On est confronté au même problème qu'avec la méthode write (cf. supra ), tout simplement parce que les caractères encodés en multi‑octets dans le format UTF‑8 ne sont pas pris en charge dans les expressions de valeurs de caractères – cf. chap. C3‑IX .
    • En revanche, l'instruction Serial.print("Hé") restitue bien la chaîne de caractère , encodée par défaut en UFT‑8.

Spécificité de la méthode println

La méthode println A obéit à la même syntaxe que print.

Sa spécificité est qu'elle écrit en plus un saut de ligne après le dernier octet encodant l'expression passée en argument principal.

Émis par la carte Arduino – et donc indépendamment du système d'exploitation du poste de travail où s'exécute le moniteur série – ce saut de ligne est toujours constitué de deux caractères spéciaux :

  • un caractère retour chariot '\r' (carriage return), code ASCII ou UTF‑8 0x0D ;
  • un caractère nouvelle ligne '\n' (new line ou line feed), code ASCII ou UTF‑8 0x0A.

Les sauts de ligne émis à destination du moniteur série sont toujours effectifs dans la zone d'affichage du moniteur série quel que soit le paramétrage choisi dans le menu déroulant situé bas de la fenêtre – cf. supra . En effet, ce dernier ne s'applique à qu'à la barre de saisie et non pas à la zone d'affichage.

Gestion du buffer d'émission

On vient de voir que les méthodes write ou print(ln) se résument in fine à écrire des octets en tête de buffer d'émission.

Quant au transfert de ces octets dans le registre d'émission puis leur sérialisation en signal logique sur la broche TX, cela est effectué en arrière-plan de l'exécution du programme.

Mais, tant qu'ils ne sont pas émis, les octets restent stockés dans le buffer d'émission. Donc si trop de d'opérations de sortie sont codées, le buffer se remplit plus vite qu'il ne se vide et on s'achemine vers une saturation au sens où le buffer est plein.

En cas de saturation du buffer d'émission, tout nouvel appel d'une méthode de sortie ne provoque pas d'écrasement d'octets.

En revanche, l'exécution de ce nouvel appel comprend une attente de libération d'éléments dans le buffer d'émission jusqu'à ce que tous les octets de l'expression passée en argument y soient écrits.

La préservation de l'intégrité des données se paye donc par une potentielle baisse de réactivité du programme. Pour prévenir ce phénomène, le module de bibliothèque HardwareSerial fournit :

  • flush A, une méthode pour attendre que le buffer soit vide ;
  • availableForWrite A, une méthode pour connaître le nombre d'éléments disponibles dans le buffer.

La méthode flush

La méthode flush est une fonction sans argument et qui ne retourne aucune valeur. Elle suspend l'exécution du programme jusqu'à ce que tous les octets stockés dans le buffer d'émission aient été envoyés, avant de passer à l'exécution de l'instruction suivante.

Ainsi, le codage d'une instruction :
Serial.flush();
garantit, immédiatement après, la disponibilité maximale du buffer d'émission.

En contre‑partie, l'appel de la méthode flush demande un temps d'exécution d'autant plus long que le buffer est rempli. L'idéal est de la programmer durant une phase d'exécution qui n'est pas soumises à des exigences de vitesse trop sévères.

On peut coder également coder l'appel de flush dans une instruction conditionnelle, en testant la valeur du nombre d'éléments disponibles en écriture à l'aide de la méthode availableForWrite (cf. ci‑après).

La méthode availableForWrite

La méthode availableForWrite est une fonction sans argument qui retourne dans le type int le nombre d'éléments vacants dans le buffer d'émission .

Sachant le taille du buffer de réception associé à l'objet Serial (cf. supra ), cette méthode permet aussi de déterminer par complément le nombre d'éléments en attente d'envoi. En effet, il est déterminé par la valeur de l'expression  :
SERIAL_TX_BUFFER_SIZE - 1 - Serial.availableForWrite()

Grâce à la connaissance du nombre d'éléments vacants dans le buffer d'émission, en cas d'impératifs de rapidité, on peut alors conditionner l'appel d'une méthode de sortie, sachant par ailleurs le nombre d'octets à émettre que cette sortie suppose.

Pour envoyer un message non urgent dont on connaît la taille, on peut coder :

  if (Serial.availableForWrite() >= 17) { // message not urgent
    Serial.println("Wait, please...");
  } 

Remarque. La chaîne de caractères à envoyer compte seulement 15 octets, mais on a codé le test avec la valeur de comparaison 17.

En effet, avant de programmer une sortie série par la méthode println, il faut aussi tenir compte des deux caractères spéciaux de saut de ligne (et pas seulement les caractères de la chaîne ou de la valeur numérique à émettre) pour vérifier s'il y a suffisamment d'éléments vacants dans le buffer d'émission.

Gestion de la zone d'affichage du moniteur série

Le moniteur série de l'application Arduino IDE n'a pas toutes les fonctionnalités d'un véritable terminal série.

Il est juste destiné à la mise au point des programmes (debugging).

À l'heure actuelle (cf. la date de version en haut de cette page web), dans les bibliothèques Arduino, il n'existe pas de méthode pour effacer le contenu de la zone d'affichage du moniteur série.

Il est également impossible d'opérer un retour en arrière dans une ligne pour en modifier l'affichage par écrasement.

En effet, lorsqu'ils sont émis via un appel de write ou de toute autre méthode d'écriture, les caractères de contrôle :

  • backspace (code UTF‑8 ou ASCII 0x08),
  • ou delete (code UTF‑8 ou ASCII 0xF7),

ne sont pas exécutés par le moniteur série. Ils sont simplement représentés dans la zone d'affichage par le symbole générique  comme tous les caractères de contrôle.

Le seul pis‑aller consiste à cliquer sur le bouton « Effacer la sortie » en bas à droite de la fenêtre du moniteur série pour effacer « manuellement » le contenu de la zone d'affichage. Mais une telle action ne peut pas être codée dans le programme embarqué sur une carte Arduino.

Opérations d'entrée par liaison série

Principe général d'une opération d'entrée

Dès lors qu'une liaison série est initialisée par un programme sur une carte Arduino, une opération de lecture via cette liaison série, c'est‑à‑dire une réception de données sur la carte, se déroule en 3 étapes : 1) emission par le système raccordé à la carte, 2) réception sur la broche d'entrée RX et désérialisation dans le buffer de réception de la carte, 3) lecture proprement dite d'octets par appel d'une méthode dans le programme utilisateur.

Plus en détails :

  1. L'émission des données est effectuée par le système auquel la carte est raccordée via la liaison série, sous la forme d'un signal logique conforme au protocole UART sur la broche RX associée à la liaison série.
  2. Un tel signal intervient typiquement si un utilisateur tape des caractères dans la barre de saisie du moniteur série émulé sur le un poste de travail par le logiciel Arduino IDE, et valide par appui sur la touche  ENTRÉE ↵ du clavier. Dans ce cas, tout caractère saisi est interprété au format UTF‑8, chacun encodé sur 1 à 4 octets (en rappelant qu'un octet est lui‑même constitué de 8 bits).
  3. Détecté par l'USART en arrière-plan de l'exécution du programme, ce signal logique est désérialisé octet par octet. Chaque octet est stocké en tête du buffer de réception, et occupe donc un élément de plus dans ce buffer.
  4. C'est seulement alors que peut opérer un appel d'une méthode de lecture codé dans le programme utilisateur. Une telle méthode retourne une valeur formée à partir des octets stockés dans le buffer de réception et, éventuellement (cf. infra ), cela libère un élément du buffer.

Plus précisément, tout octet issu de la désérialisation d'un signal logique reçu sur la broche RX de la carte est d'abord stocké dans le registre de réception. Chaque nouvel octet déclenche une interruption du programme, qui appelle la méthode _rx_complete_irq définie dans le fichier HardWareSerial_private.h.

Cette méthode _rx_complete_irq vérifie que le buffer de réception de l'objet Serial n'est pas plein :

  • Si tel est le cas, l'octet du registre de réception est alors écrit en tête de buffer, et donc occupe cet élément – l'occupation étant implémentée par l'incrémentation unitaire de l'indice de tête du buffer.
  • Sinon, l'octet est perdu (il sera écrasé par le prochain octet désérialisé issu d'un nouveau signal logique reçu alors que le buffer de réception dispose enfin d'éléments vacants).

Le processus d'écriture d'octets dans le buffer de réception se répète tant que l'USART détecte un nouveau signal logique sur la broche RX. Comme pour celui d'émission, le buffer de réception n'est plein que quand l'indice de tête n'est plus qu'à une unité de l'indice de queue (cf. supra ).

Méthodes de lecture

Le framework Arduino met à disposition du codeur une dizaine de méthodes de lecture associées à l'objet Serial. On peut les classer en deux catégories :

  • les méthodes de bas niveau available, peek et read, définies dans le fichier hardwareSerial.cpp ;
  • les méthodes de haut niveau, notamment readString, parseInt ou parseFloat, définies dans le fichier Stream.cpp.

Seules les méthodes citées ci‑dessus seront détaillées ci‑après. Pour les autres, on se reportera au lien suivant A.

Temporisation des méthodes de haut niveau

Contrairement à celles de bas niveau, dont la valeur de retour est « immédiate », les méthodes de lecture de haut niveau sont basées sur des variables et des méthodes protégées (c'est‑à‑dire non appelables par le codeur dans son programme utilisateur).

En particulier, elles emploient la méthode protégée de lecture unitaire timedRead qui opère de façon temporisée avec réitération de la lecture jusqu'à expiration d'un délai d'abandon (timeout).

La durée de ce délai est fixée par un champ de la classe Stream : la variable de type unsigned long nommée _timeOut, qui exprime en millisecondes une durée maximale d'attente à ne pas dépasser W.

Cette variable _timeOut prend la valeur 1000 par défaut, ce qui correspond donc à une durée d'une seconde). Protégée, cette variable n'est consultable et modifiable dans le programme utilisateur que via des méthodes spécifiques (cf. infra ).

Gestion des fins de ligne dans la barre de saisie

Lorsque la barre de saisie du moniteur série est active (état signalé par le curseur clignotant dans la barre), l'appui sur la touche ENTRÉE ↵ du clavier est traité de différentes manières selon l'option choisie dans le menu déroulant en bas de la fenêtre (cf. la capture d'écran ci‑contre).

Cette action ajoute dans le buffer de réception, en plus des caractères saisis, zéro, un ou deux des caractères de contrôle ci‑dessous :

  • le caractère retour chariot '\r' (carriage return CR), dont le code ASCII ou UTF‑8 est 0x0D ;
  • le caractère nouvelle ligne '\n' (new line NL ou line feed LF), dont le code ASCII ou UTF‑8 est 0x0A.

Le choix de cette option dépend des traitements que l'on souhaite effectuer dans le programme utilisateur.

Lecture unitaire d'octets

Les méthodes read et peek sont deux méthodes de lecture de bas niveau qui ne lisent qu'un un seul octet par appel : celui situé en queue du buffer de réception de l'objet Serial.

La méthode read

L'expression d'appel :
Serial.read()  A
retourne immédiatement une valeur entière de type int qui peut être :

  • -1 si le buffer de réception de l'objet Serial est vide ;
  • sinon égale à l'octet lu en queue du buffer réception, ce qui libère cet élément du buffer.

La libération d'un élément du buffer de réception consiste simplement en l'incrémentation unitaire de son indice de queue.

Des appels successifs de la méthode read permettent de lire l'un après l'autre tous les octets stockés dans le buffer de réception.

Pour tester la méthode read, comme précédemment, on fait l'hypothèse que le programme s'exécute sur une carte Arduino reliée à un poste de travail où le moniteur série est activé, la liaison série étant initialisée. Un squelette de programme pour tester les exemples d'instructions d'appel des méthodes de lecture données ci‑dessous est le suivant :

void setup()
{
  delay(500);
  Serial.begin(115200);
  Serial.println();
  Serial.flush();
}

void loop()
{
  if (Serial.available()) {
    byte readByte = /* type your statement here */;
    Serial.print(readByte);
  }
  Serial.println();
}

De plus, on suppose qu'avant la saisie, le buffer de réception est vide et que l'option pas de fin de ligne est choisie dans les options de saisie du moniteur série.

  • Après saisie et envoi de la lettre A, l'évaluation de l'expression d'appel Serial.read() retourne la valeur entière 65 qui est le code UTF‑8 du caractère « A ».
  • Suite à cette évaluation, le buffer de réception est à nouveau vide.
  • Après saisie et envoi du nombre 65, l'évaluation de l'expression d'appel Serial.read() retourne la valeur entière 54 qui est le code UTF‑8 du caractère « 6 ».
  • Suite à cet appel, le buffer de réception contient encore un octet de valeur entière 53 qui est le code UTF‑8 du caractère « 5 ». Un nouvelle appel Serial.read() retourne cette valeur (ce que fait le programme de test ci‑dessus, puisque les appels de la méthode read sont codés dans la fonction loop qui s'exécute en boucle).
  • Après saisie et envoi de la lettre é, l'évaluation de l'expression d'appel Serial.read() retourne la valeur entière 195 (c'est‑à‑dire 0xC3) qui est l'octet de poids fort du code UTF‑8 du caractère « é ».
  • Suite à cet appel, le buffer de réception contient encore un octet de valeur entière 169 (c'est‑à‑dire 0xA9) qui est l'octet de poids faible du code UTF‑8 du caractère « é ». Un nouvel appel de Serial.read() retourne cette valeur (ce que fait le programme de test ci‑dessus, puisque les appels de la méthode read sont codés dans la fonction loop qui s'exécute en boucle).

La méthode peek

L'expression d'appel :
Serial.peek()  A
retourne la même valeur (de type int) que la méthode read, mais sans libérer l'élément correspondant dans le buffer de réception.

Elle permet donc de connaître la valeur de l'octet en queue du buffer de réception tout en le laissant « intact ».

Pour tester la méthode peek, on fait les mêmes hypothèses que pour les exemples donnés supra d'application de la méthode read. On peut utiliser le même squelette de test, en ajoutant l'instruction suivante à la fin de la fonction loop (on va voir pourquoi) :

  delay(1000);

Après saisie et envoi de la lettre A, l'évaluation de l'expression d'appel Serial.peek() retourne la valeur entière 65 qui est le code UTF‑8 du caractère « A ».

Et suite à cet appel, le buffer de réception est inchangé. Ainsi, un nouvel appel de Serial.peek() retourne la même valeur. Or justement, le programme de test effectue ces appels en boucle. C'est pourquoi on a codé une pause d'une seconde à la fin de la fonction loop pour limiter les affichages successifs dans le moniteur série.

La méthode peek ne permet donc pas de scanner tout le contenu du buffer de réception, seulement son octet de queue (des appels successifs de cette méthode rendent toujours la même valeur tant qu'une méthode de lecture comme read n'a pas été appelée).

Lecture de chaînes de caractères

Sans argument, l'expression d'appel :
Serial.readString()  A
procède par lecture unitaire temporisée de tous les octets du buffer de réception de l'objet Serial.

Elle retourne une valeur de chaîne de caractère de classe String qui peut être :

  • la chaîne vide (réduite au caractère de fin de chaîne NUL) si le nombre d'octets lus est nul depuis le début de l'évaluation de l'appel jusqu'à expiration du délai d'abandon _timeOut ;
  • sinon la chaîne formée de tous les octets lus dans l'ordre, quelle que soit la valeur de ces octets.

Quel que soit le résultat, l'appel de la méthode readString laisse le buffer de réception complètement vide.

La chaîne de caractères retournée par la méthode readString comporte à la suite des octets lus un octet supplémentaire de valeur nulle, qui constitue pour toutes les valeurs de la classe String le caractère de fin de chaîne dit NUL (de code ASCII 0x0).

Pour tester la méthode readString, comme précédemment, on fait l'hypothèse que le programme s'exécute sur une carte Arduino reliée à un poste de travail où le moniteur série est activé, la liaison série étant initialisée. De plus, on suppose qu'avant la saisie, le buffer de réception est vide. Et on peut utiliser le même squelette de programme de test que supra .

  • Après saisie et envoi des caractères Hé !, l'évaluation de l'expression d'appel Serial.readString() retourne :
    • si l'option « pas de fin de ligne » est choisie, la chaîne de caractères constituée des 6 octets hexadécimaux :
      48 C3 A9 20 21 0
      qui concatène les codes UTF‑8 respectifs des caractères 'H' (0x48), 'é' (0xC3A9), ' ' (0x20), '!' (0x21) et du caractère NUL (0x0).
    • si l'option « Les deux, NL et CR » est choisie, la chaîne de caractères constituée des 8 octets hexadécimaux :
      48 C3 A9 20 21 0D 0A 0
      où l'on retrouve les codes des caractères de contrôle CR (0x0D) et NL (0x0A) juste avant le caractère NUL de fin de chaîne.
  • Si l'on saisit et envoie d'abord le caractère C, puis les caractères ++, l'évaluation de l'expression Serial.readString() retourne :
    • si l'option « pas de fin de ligne » est choisie, la chaîne de caractères constituée des 4 octets hexadécimaux :
      43 2B 2B 0
      qui concatène les codes UTF‑8 respectifs des caractères 'C' (0x43), '+' (0x2B), '+' (0x2B) et NUL (0x0).
    • si l'option « Les deux, NL et CR » est choisie, la chaîne de caractères constituée des 8 octets hexadécimaux :
      43 0D 0A 2B 2B 0D 0A 0
      où l'on retrouve à deux reprises les codes des caractères de contrôle CR (0x0D) et NL (0x0A), puisqu'on a procédé en deux envois.
  • Si l'on saisit et envoie les caractères \n formant la séquence d'échappement usuelle pour un saut de ligne, et que l'on a choisi l'option « pas de fin de ligne », l'évaluation de l'expression Serial.readString() retourne la chaîne de caractères constituée des 3 octets hexadécimaux :
    5C 6E 0
    qui concatène les codes UTF‑8 respectifs des caractères '\' (0x5C), 'n' (0x6E) et NUL (0x0).
  • On comprend à la lumière de ce dernier exemple qu'il n'est pas possible dans la barre de saisie de coder des séquences d'échappement comme dans une valeur de chaîne de caractères entre guillemets doubles "" dans un code source (cf. chap. C3‑VIII .

    En effet, tout caractère dans la barre de saisie – y compris l'antislash '\' – est interprété individuellement par son code UTF‑8.

Lecture de nombres formatés

Pour lire une valeur numérique, la méthode readString n'est pas adaptée. En effet, dans la chaîne d'octets stockées dans le buffer de réception, il faut non seulement délimiter ceux qui codent la valeur, mais aussi distinguer les éléments de sa syntaxe de codage : les chiffres, mais aussi éventuellement un signe, un point décimal…

Le fichier Stream.cpp du framework Arduino définit donc deux méthodes spécifiques de haut niveau pour accomplir cette action :

  • la méthode parseInt pour les valeurs de types entiers, signés ou non (cf. chap. C3‑II ) ;
  • la méthode parseFloat pour les valeurs de types décimaux à virgule flottante – mais sans exposant (cf. chap. C3‑V ) ;

Ces méthodes mettent en œuvre de façon sous-jacente une analyse lexicographique (d'où le mot anglais parse qui signifie « analyser ») des éléments contenus dans le buffer de réception pour y lire une valeur numérique codée conformément aux syntaxes de saisie usuelles des constantes littérales entières et décimales en langage C.

La méthode parseInt

La méthode parseInt A s'utilise usuellement par une expression d'appel de la forme réduite (c'est‑à‑dir,e sans argument) suivante :
Serial.parseInt()
Elle retourne une valeur entière dans le type signé long, qui peut être :

  • 0 en cas d'échec d'identification d'une valeur entière dans le buffer de réception, après expiration du délai d'abandon _timeOut ;
  • sinon, la première valeur entière identifiée et dans ce cas, tous les octets lus sont libérés dans le buffer de réception.

La méthode parseInt admet deux arguments optionnels pour adapter aux besoins du programme l'algorithme d'analyse de la saisie de l'utilisateur. Ils se codent conformément à la syntaxe d'appel complète suivante :
Serial.parseInt([mode de lecture, caractère ignoré])

  • Le mode de lecture (argument identifié lookahead dans le fichier Stream.cpp) permet de spécifier des catégories de caractères dont toutes les occurrences sont à ignorer dans le buffer de réception. Cet argument optionnel peut être codé par l'une des trois constantes énumérées ci‑dessous :
    • SKIP_ALL – les caractères autres que les chiffres (codes ASCII 0x30 à 0x39) et le signe « - » (code ASCII 0x2D) sont ignorés ; c'est le mode par défaut ;
    • SKIP_NONE – aucun caractère n'est ignoré ;
    • SKIP_WHITESPACE – les caractères d'espacement espace (0x20), tabulation horizontale (0x09), nouvelle ligne (0x0C) et retour chariot (0x0D) sont ignorés.
  • L'argument optionnel caractère ignoré permet d'indiquer un caractère spécifique dont toutes les occurrences sont à ignorer dans le buffer de réception. Sa valeur peut être spécifiée par son glyphe entre guillemets simples '' ou par son code ASCII.

À l'heure actuelle (cf. la date de version indiquée en haut de cette la page web), les arguments optionnels des méthodes parseInt et parseFloat ne sont pas codables dans l'environnement de simulation Tinkercad.

Pour tester la méthode parseInt, comme précédemment, on fait l'hypothèse que le programme s'exécute sur une carte Arduino reliée à un poste de travail où le moniteur série est activé, la liaison série étant initialisée. De plus, on suppose qu'avant la saisie, le buffer de réception est vide et que l'option pas de fin de ligne est choisie dans les options de saisie du moniteur série. Et on peut utiliser presque le même squelette de programme de test que supra , en remplaçant la ligne n° 12 par :

    int readInt = /* type your statement here */;
  • Après saisie et envoi des caractères -123, l'évaluation de l'expression d'appel Serial.parseInt() retourne la valeur entière -123 et vide le buffer de réception.
  • Toute spécification optionnelle de caractères à ignorer n'aurait aucun effet ici car les caractères saisis sont tous conformes à la syntaxe d'une valeur entière.
  • Après saisie et envoi des caractères  4 5 (avec un espace initial), l'évaluation de l'expression d'appel :
    • Serial.parseInt() retourne la valeur entière 4 et laisse dans le buffer de réception les octets codants les caractères ' ' et '5' ; en effet :
      • l'espace initial a été ignoré, conformément au mode de lecture par défaut SKIP_ALL ;
      • l'espace saisi après '4' n'étant pas un chiffre, il est analysé comme un séparateur ; comme la chaîne des symboles déjà lus forme un nombre entier, le processus de lecture s'achève ;
    • Serial.parseInt(SKIP_NONE) retourne la valeur entière 0 après expiration du délai d'abandon car l'espace initial n'est pas un caractère autorisé dans une valeur entière et bloque le processus de lecture.
  • Après saisie et envoi des caractères 12,345.6, l'évaluation de l'expression d'appel :
    • Serial.parseInt() retourne la valeur entière 12 et laisse dans le buffer de réception la chaîne de caractères ",345.6" ; en effet :
      • la virgule saisie après '2' n'étant pas un chiffre, elle est analysée comme un séparateur ;
      • comme la chaîne des symboles déjà lus forme un nombre entier, le processus de lecture s'achève ;
    • Serial.parseInt(SKIP_NONE, ',') retourne la valeur entière 12345 et laisse dans le buffer de réception la chaîne de caractères ".6" ; en effet :
      • la virgule saisie après '2' est ignorée, conformément au deuxième argument optionnel spécifié dans l'appel ;
      • le point saisi après '5' n'étant pas un chiffre, il est analysé comme un séparateur ; comme la chaîne des symboles déjà lus forme un nombre entier, le processus de lecture s'achève.

La méthode parseFloat

La méthode parseFloat A obéit à la même syntaxe d'appel que celle de parseInt exposée supra  :
Serial.parseFloat()
mais :

  • elle opère pour lire des chaînes de caractères conformes à la syntaxe des constantes littérales décimales où le symbole . joue le rôle de séparateur décimal ;
  • elle n'opère pas la lecture des constantes littérales formatées avec des exposants ;
  • sa valeur de retour est encodée dans le type float, avec une éventuelle erreur d'encodage à partir du 7e chiffre significatif.

Comme parseInt (cf. supra ), la méthode parseFloat admet deux arguments optionnels – mode de lecture et caractère ignoré – pour adapter aux besoins du programme l'algorithme d'analyse de la saisie de l'utilisateur.

Pour tester la méthode parseFloat, comme précédemment, on fait l'hypothèse que le programme s'exécute sur une carte Arduino reliée à un poste de travail où le moniteur série est activé, la liaison série étant initialisée. De plus, on suppose qu'avant la saisie, le buffer de réception est vide et que l'option pas de fin de ligne est choisie dans les options de saisie du moniteur série. Et on peut utiliser le même squelette de programme de test que supra , en remplaçant la ligne n° 12 par :

    float readFloat = /* type your statement here */;
  • Après saisie et envoi des caractères 0.25, l'évaluation de l'expression d'appel Serial.parseFloat() retourne la valeur décimale 0.2500000023… et vide le buffer de réception.
  • Après saisie et envoi des caractères 12,345.6, l'évaluation de l'expression d'appel Serial.parseFloat(SKIP_NONE, ',') retourne la valeur décimale 12345.60058… et vide le buffer de réception.

Gestion du buffer de réception

Si le buffer de réception est vide :

  • un appel de la méthode read retourne la valeur -1 et un appel de la méthode readString retourne une valeur de chaîne vide ; dans les deux cas, la valeur de retour permet de diagnostiquer sans ambiguïté la vacuité du buffer ;
  • en revanche, un appel d'une méthode comme parseInt retourne la valeur 0 qui peut prêter à confusion ; cette valeur aurait aussi pu avoir été saisie.

Pour permettre un diagnostic fiable de l'état du buffer de réception, on dispose de la méthode available définie dans le fichier Stream.cpp.

L'évaluation de l'expression d'appel :
Serial.available() A
retourne la valeur entière dans le type int égale au nombre d'octets stockés – donc, en attente de lecture – dans le buffer de réception associé à l'objet Serial.

Donc, si un appel de la méthode available retourne la valeur 0, cela signifie que le buffer de réception est vide (autrement dit, il n'y a aucun octet à lire).

La méthode available permet de coder très facilement un algorithme pour vider le buffer de réception, comme ci‑dessous.

while (Serial.available() > 0) {
  Serial.read(); // all bytes in _rx_buffer are lost 
}

Dans cet algorithme de vidage, à la ligne nº 11, l'expression Serial.read() n'est pas composée comme r-value d'une affectation : la méthode s'exécute mais la valeur qu'elle retourne n'est pas exploitée (elle est donc perdue).

Par ailleurs, rappelons que l'on peut déterminer la contenance maximale du buffer de réception via l'expression :
SERIAL_RX_BUFFER_SIZE - 1

Paramétrage de la temporisation

On a vu supra  que la durée du délai d'abandon des méthodes de lecture de haut niveau est fixée par la valeur de la variable _timeout, exprimée en millisecondes, sachant que

  • il s'agit d'un champ de la classe Stream dont hérite l'objet Serial ;
  • ce champ est protégé, donc son identificateur ne peut être employé directement dans un programme Arduino.

Pour consulter ou modifier la valeur du champ _timeout, le fichier Stream.cpp de la bibliothèque Arduino définit respectivement les méthodes getTimeout et setTimeout.

  • L'évaluation de l'expression :
    Serial.getTimeout()  A
    retourne dans le type unsigned long la valeur du champ _timeout.
  • Une instruction d'appel de la forme :
    Serial.setTimeout(durée);  A
    affecte au champ _timeout la valeur prise par l'expression durée passée en argument, de type unsigned long (la méthode setTimeout étant elle‑même de type void).

La valeur par défaut du champ _timeout est de 1000 ms, soit une seconde. Cette valeur est très grande au regard des valeurs typiques de la durée d'exécution de la fonction loop d'un programme Arduino – durée qui se mesure usuellement en microsecondes ou millisecondes.

Avec cette valeur par défaut, il en résulte que coder l'appel d'une méthode de lecture sans condition dans la fonction loop risque de compromettre la réactivité du programme si le buffer de réception n'est pas rempli en permanence par un flux d'octets.

Pour prévenir ce problème, il est judicieux :

  • soit de diminuer à sa valeur minimale – 1 ms – le champ _timeout ; en effet, comme la lecture des octets dans le buffer de réception ne requiert que quelques microsecondes et que la capacité par défaut du buffer est très limitée (63 octets), une milliseconde suffit largement, quelle que soit la méthode de lecture employée ;
  • soit de coder l'appel de la méthode de lecture sous une condition rare et durant laquelle la réactivité du programme n'est pas essentielle.