Fonctions C++ print•println•printf•sprintf pour Arduino ESP32 ESP8266. Combiner•formater → port série • Domotique et objets connectés à faire soi-même

On utilise le moniteur série pour mettre au point le code Arduino, mais il est possible de faire bien d’autres choses. Nous allons découvrir dans cet article les principales méthodes pour imprimer (envoyer) des chaînes de caractères sur le port série. print et println pour envoyer du texte simple. La fonction printf pour convertir, formater et combiner plusieurs variables dans une même chaîne de caractère. sprintf et snprintf pour stocker le résultats dans une variable.

Accès rapide aux fonctions

print ou println   printf  options de printf   sprintf ou snprintf (ESP32 / ESP8266 uniquement)  write Macro F

Comment ouvrir le port série dans du code C++ Arduino ?

Avant de pouvoir envoyer des messages sur le port série, il est nécessaire d’initialiser la communication avec la commande Serial.begin(vitesse). Cette méthode prend comme paramètre la vitesse du port série en bauds.

Par défaut, elle est fixée à 9600 bauds.

Vous pouvez opter pour l’une de ces vitesses 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, ou 115200.

La plupart des cartes de développement actuelles supportent sans aucun problème la vitesse de 115200 bauds.

Serial.begin(115200);

Imprimer du texte sur le port série avec print ou println

La fonction print() est la fonction de base. Elle permet d’envoyer (imprimer) un caractère ou une chaine de caractères sans aucun formatage particulier.

Les caractères sont envoyés sur le port série les uns à la suite des autres.

Exemple Résultat sur le moniteur série
Serial.print("A");
Serial.print("B");
Serial.print("C");
ABC

Pour renvoyer le curseur à la ligne (comme sur un traitement de texte), il existe plusieurs solutions :

  • Utiliser la méthode println()
  • Ajouter \n à la fin de la chaîne publiée
  • Combiner les deux
Exemple Résultat obtenu sur le moniteur série
Serial.print("Une demo ");
Serial.print("avec ");
Serial.println("un retour à la ligne ");
Une demo avec
un retour à la ligne
Equivalent
Serial.print("Une demi avec \n");
Serial.print("un retour à la ligne");
Une demo avec
un retour à la ligne

Comment combiner plusieurs variables avec print ou println

Comme dans la plupart des autres langages (Javascript, PHP..), le C++ autorise de combiner des chaînes à l’aide de l’opérateur +. Il faudra au préalable convertir la donnée en chaîne à l’aide de la fonction String().

Voici un exemple qui combine dans une même chaîne de caractère une variable de type entier, une variable de type float (nombre décimal) et un booléen.

int INT_VAR = 32;
float FLOAT_VAR = 32.23;
bool BOOL_VAR = true;
Serial.println("Un chaine qui combine un entier " + String(INT_VAR) + ", un décimal " + String(FLOAT_VAR) + " est un bool " + String(BOOL_VAR));

Combiner et formater plusieurs variables dans une chaîne avec printf

La méthode printf est un format de sortie standard du C++. Espressif a implémenté la méthode printf dans la classe Serial du framework Arduino-ESP32 et ESP8266. Ce n’est pas le cas avec le framework Arduino.

Comment utiliser printf sur Arduino avec PrintEx

Si vous tentez de compiler du code développé pour ESP32 ou ESP8266 sur un Arduino, vous obtiendrez une erreur de compilation si vous écrivez Serial.printf(). C’est tout simplement parce que la méthode n’a pas été développée pour le framework Arduino.

On trouve des exemples de code un peu partout sur internet pour faire cela, mais le plus facile est d’utiliser la librairie PrintEx de Christopher Andrews. Son fonctionnement est presque aussi complet que l’implémentation faite par Espressif pour l’ESP32 et ESP8266.

Sur PlatformIO, ajouter l’option suivante à votre fichier platformio.ini. Sur l’IDE Arduino, ajouter la librairie depuis le gestionnaire comme d’habitude

lib_deps = 
    chris--a/PrintEx @ ^1.2.0

Ensuite, déclarer PrintEx au début de votre programme

#include 

Enfin, il suffit d’étendre (ajouter les fonctions) à la classe Print du framework Arduino

setup()
{
  Serial.begin(155200);
  PrintEx serial = Serial;
  serial.printf("Hello");
}

Et maintenant on peut utiliser la fonction comme ceci

serial.printf("la pression atmosphérique est de %u hPa", pressure);

Comment utiliser printf() du framework ESP32, ESP8266 ou PrintEx

La méthode printf() permet à la fois de formater une ou plusieurs données et ensuite les combiner dans une même chaîne avant de l’envoyer sur le port série. Pour cela, il suffit de spécifier la position dans la chaîne à l’aide du caractère %.

printf("la pression atmosphérique est de %u hPa", pressure);

L’argument %specifier sera ensuite remplacé par la valeur mise en forme correspondante.

Pour formater comme on le souhaite la chaine de sortie, il est possible d’ajouter des options supplémentaires. Les options sont détaillés dans les paragraphes suivants.

%[flags][width][.precision][length]specifier

Voici un exemple qui permet de formater un nombre décimal avec un seul chiffre significatif. L’arrondi est automatique.

%.1f

On peut combiner autant de variables qu’on le souhaite. Il faudra simplement passer les variables dans le même ordre que dans la chaîne de sortie.

Attention, avec PrintEx ajouter serial. devant printf

Specifier disponibles pour convertir en chaîne

Pour pouvoir combiner des variables de natures différentes dans la chaîne de sortie, il faut indiquer à la commande printf le type de chaque données

En gras les specifiers non supportés par PrintEx

Specifier Type de données C++ Arduino Description Exemple
d ou i Int

Byte

Word

Long (préférable d’utiliser unsigned Int)

Entier décimal signé 392
u Entier décimal non signé 7235
o Octal non signé 610
x unsigned long

unsigned int

Entier hexadécimal non signé (minuscule) 7fa
X Entier hexadécimal non signé (majuscule) 7FA
f ou F float

double

Virgule flottante décimale, minuscule 392.65
e Notation scientifique exposant en minuscule 3.9265e+2
E Notation scientifique exposant en majuscule 3.9265E+2
g Utilisez la représentation la plus courte : %e ou %f 392.65
G Utilisez la représentation la plus courte : %E ou %F 392.65
c char Caractère a
s char [] Chaîne de caractères exemple
n Rien n’est imprimé
% Un % suivi d’un autre caractère % écrira un seul % %

Option Flag (drapeau)

Le flag permet d’ajouter des caractères avant la valeur :

  • Forcer l’ajout d’un signe (+ ou -)
  • Insérer un espace
  • Remplir avec des zéros
  • Remplir avec 0x pour les nombres hexadécimaux
flag Description
Justifiez à gauche dans la largeur de champ donnée. Par défaut, la justification est à droite de la valeur (voir l’option largeur)
+ Force à faire précéder le résultat d’un signe plus ou moins ( + ou – ) même pour les nombres positifs. Par défaut, seuls les nombres négatifs sont précédés du signe –
(space) Si aucun signe ne doit être écrit, un espace est inséré avant la valeur
# Utilisée avec les specifiers o, x ou X, la valeur est précédée de 0 , 0x ou 0X respectivement pour les valeurs différentes de zéro.
Utilisé avec a, A, e, E, f, F, g ou G, la chaîne de sortie doit contenir un point décimal même si aucun chiffre ne suit. Par défaut, si aucun chiffre ne suit, aucun point décimal n’est écrit
0 Remplit à gauche le nombre avec des zéros (0) au lieu d’espaces lorsque le remplissage est spécifié (voir l’option largeur)

Option width (largeur)

Spécifie (ou pas) le nombre de caractères à imprimer

with Description
(nombre) Nombre minimum de caractères à imprimer. Si la valeur à imprimer est inférieure à ce nombre, le résultat est complété par des espaces vides. La valeur n’est pas tronquée même si le résultat est plus grand
* La largeur n’est pas spécifiée dans la chaîne de formatage, mais sous la forme d’un argument de valeur entière supplémentaire précédant l’argument à mettre en forme

Option Precision (précision)

Permet de définir la précision des nombres décimaux (le nombre de chiffres significatifs à imprimer) ou le nombre maximal de caractères si c’est une chaine.

.precision Description
.nombre
  • Pour d, i, o , u , x , X, la précision spécifie le nombre minimum de chiffres à écrire. Si la valeur à écrire est plus courte que ce nombre, le résultat est complété par des zéros non significatifs. La valeur n’est pas tronquée même si le résultat est plus long. Une précision de 0 signifie qu’aucun caractère n’est écrit pour la valeur 0
  • Pour a, A, E, E, f et F, c’est le nombre de chiffres à imprimer après la virgule décimale (par défaut, il s’agit de 6)
  • Pour g et G, il s’agit du nombre maximal de chiffres significatifs à imprimer
  • Pour s, c’est le nombre maximum de caractères à imprimer. Par défaut, tous les caractères sont imprimés jusqu’à ce que le caractère nul de fin soit rencontré
.* La précision n’est pas spécifiée dans la chaîne de format , mais sous la forme d’un argument de valeur entière supplémentaire précédant l’argument à mettre en forme.

Options de conversion tirées de cet article.

Macro F

La macro F() qui permet de déplacer la chaîne dans la mémoire Flash afin d’éviter que la RAM du micro-contrôleur soit saturée si de nombreux messages sont envoyés sur le port série par exemple.

Stocker le résultat d’une chaîne formatée avec sprintf ou snprintf dans une variable (uniquement pour ESP32 et ESP8266)

La méthode sprintf() permet de stocker le résultat de la conversion dans une variable de type char[].

La commande sprintf() nécessite d’initialiser la variable de sortie avec une taille au moins égale à la longueur de la chaîne. C’est tout simplement le nombre de caractères de la chaîne de sortie.

char output_sprintf[6];
sprintf(output_sprintf, "%.1f°C", temp);
printf("Valeur formatée enregistrée dans la variable output %s \n", output_sprintf);

Pour combiner plusieurs variables dans une même chaîne, il faudra utiliser la fonction snprintf() qui s’écrit de manière similaire

char output_snprintf[60];
snprintf(output_snprintf, sizeof(output_snprintf), "La température est de %.1f°C, l'humidité est de %.1f%% \n", temp, hum);
printf("output_snprintf = %s \n", output_snprintf);

Imprimer le contenu d’un buffer de bytes avec write()

On peut être amené à utiliser un tableau de bytes. Ce tableau pourra par exemple contenir le code numérique de chaque caractère d’une String (chaîne de caractères).

Pour imprimer sur le port série un tableau de byte, les fonctions print(), println() ne fonctionnent pas car elles attendent une variable de type char, char[] ou String.

Pour imprimer le contenu d’un buffer avec la fonction write(), il suffit de parcourir ce dernier à l’aide d’une boucle for().

String stringtocopy = "Arduino"; 

// mesure la longueur de la chaine
int buffer_size = stringtocopy.length();
printf("Buffer size: %u\n", buffer_size);

// Cree un buffer ayant la meme taille que la chaine
byte buffer[buffer_size]; 

// Copie le contenu de la chaine a l'aide de la fonction getBytes
// Attention, il faut ajouter 1 a la taille de la chaine pour ne pas avoir de caractere NULL
stringtocopy.getBytes(buffer, buffer_size + 1); 
Serial.println("Print buffer with write function"); 

// On imprime un a un chaque cellule du buffer a l aide d une boucle for
for (int i = 0; i < buffer_size; i++) { 
  Serial.write(buffer[i]); 
} 

Téléverser le code Arduino des exemples

Créer un nouveau croquis sur l’IDE Arduino ou PlatformIO et téléverser le code pour tester tous les exemples présentés précédemment.

Le code détecte automatiquement s’il fonctionne sur un Arduino (plateforme AVR). Le code a été testé sur Arduino Uno et ESP32.

#include "Arduino.h"
#include 

#define SERIAL_SPEED 115200

void setup() {
  Serial.begin(SERIAL_SPEED);

  PrintEx serial = Serial; //Wrap the Serial object in a PrintEx interface.

  Serial.println("\n=== print et println ====");
  Serial.print("A");
  Serial.print("B");
  Serial.print("C");

  Serial.println("\n=======");

  Serial.println("Une demo avec ");
  Serial.print("un retour à la ligne ");
  
  Serial.println("\n---- Ou -----");

  Serial.print("Une demo avec \n");
  Serial.print("un retour à la ligne");

  Serial.println("\n=== Concatenation avec println ====");
  int INT_VAR = 32;
  float FLOAT_VAR = 32.23;
  bool BOOL_VAR = true;
  Serial.println("Un chaine qui combine un entier " + String(INT_VAR) + ", un décimal " + String(FLOAT_VAR) + " est un bool " + String(BOOL_VAR));

  Serial.println("\n=== printf ====");
  unsigned int x = 0x999b989;
  byte b = 120;
  word w = 63450;
  signed long l = 2147483646; // signed long -2,147,483,648 to 2,147,483,647
  char c = 65; // A
  char s[] = "une chaîne de caractères";
  float f = 99.57;
  double fdbl = 99.5769;
  #ifdef __AVR__
    serial.printf("Hexa %x %X \n", x, x);
    serial.printf("Byte %u \n", b);
    serial.printf("Word %u \n", w);
    serial.printf("Long %lu \n", l);
    serial.printf("Caractère %c \n", c);
    serial.printf("%s \n", s);
    serial.printf("Variable float %f | %.2f \n", f, f);
    serial.printf("Variable double %f | %.2f \n", fdbl, fdbl);
  #else
    printf("Hexa %x %X \n", x, x);
    printf("Byte %u \n", b);
    printf("Word %u \n", w);
    printf("Long %u \n", l);
    printf("Caractère %c \n", c);
    printf("%s \n", s);
    printf("Variable float %f | %.2f \n", f, f);
    printf("Variable double %f | %.2f \n", fdbl, fdbl);
  #endif
  Serial.println("\n=== FAKE BME280 ====");
  double temp = 18.68;
  double hum = 67.98;

  #ifdef __AVR__
    serial.printf("La température est de %.2f°C, l'humidité est de %.1f%% \n", temp, hum);
  #else
    printf("La température est de %.1f°C, l'humidité est de %.1f%% \n", temp, hum);
  #endif

  #ifdef __AVR__
    //printf("output_sprintf = %s \n", output_sprintf);
    serial.printf("La température est de %.1f°C, l'humidité est de %.1f%% \n", temp, hum);
  #else  
    char output_sprintf[6];
    sprintf(output_sprintf, "%.1f°C", temp);
    printf("output_sprintf = %s \n", output_sprintf);
  #endif

  
  #ifdef __AVR__
  #else  
    char output_snprintf[60];
    snprintf(output_snprintf, sizeof(output_snprintf), "La température est de %.1f°C, l'humidité est de %.1f%% \n", temp, hum);
    printf("output_snprintf = %s \n", output_snprintf);
  #endif

  Serial.println("\n=== write() buffer to Serial ====");
  String stringtocopy = "Arduino"; 
  int buffer_size = stringtocopy.length();
  #ifdef __AVR__
    serial.printf("Buffer size: %u\n", buffer_size);
  #else  
    printf("Buffer size: %u\n", buffer_size);
  #endif

  byte buffer[buffer_size]; 
  stringtocopy.getBytes(buffer, buffer_size + 1); 
  Serial.println("Print buffer with write() function"); 
  for (int i = 0; i < buffer_size; i++) { 
    Serial.write(buffer[i]); 
  }
}

void loop() {
}
[env:uno]
platform = atmelavr
board = uno
framework = arduino
monitor_speed = 115200
lib_deps = 
    chris--a/PrintEx @ ^1.2.0

[env:lolin_d32]
platform = espressif32
board = lolin_d32
framework = arduino
monitor_speed = 115200

Ouvrez le moniteur série pour visualiser le fonctionnement des fonctions. Journal d’exécution récupéré sur un ESP32.

10/12/2020 Modification de printf. La méthode printf est exposée dans la classe Serial uniquement dans le framework Arduino-ESP32. Merci à Jean pour m’avoir signalé l’erreur.

16/11/2020 Ajout de la macro F

2/10/2020 Publication de l’article