dimanche 27 octobre 2019

Écran Nokia 5110 et ESP32/ESP8266

Voyons un peu comment piloter un écran à cristaux liquides monochrome PCD8544, communément appelé "Nokia 5110", au moyen d'un ESP32 ou d'un ESP8266.


Ces petits écrans peuvent facilement être obtenus en ligne sous la forme d'un module facile à connecter. Il existe principalement deux modèles: celui qui a été mis au point par Sparkfun, et celui qui a été conçu par Adafruit.


J'ai utilisé le modèle Sparkfun; le modèle Adafruit fonctionne tout aussi bien, sauf que ses connecteurs ne sont pas placés dans le même ordre.

Connexion à un ESP32

J'ai branché l'écran à mon module ESP32 de la façon suivante:

  • Broche 1 VCC de l'écran : Broche 3.3V de l'ESP32
  • Broche 2 GND de l'écran: Broche GND de l'ESP32
  • Broche 3 SCE (CS) de l'écran: Broche D15 de l'ESP32
  • Broche 4 RST de l'écran: Broche D4 de l'ESP32
  • Broche 5 D/C de l'écran: Broche D2 de l'ESP32
  • Broche 6 DN (MOSI) de l'écran: Broche D23 de l'ESP32
  • Broche 7 SCLK de l'écran: Broche D18 de l'ESP32
  • Broche 8 LED de l'écran: pas branchée (je n'avais pas besoin du rétroéclairage)



Connexion à un ESP8266

Avec un ESP8266, j'ai procédé de la façon suivante:
  • Broche 1 VCC de l'écran : Broche 3.3V de l'ESP8266
  • Broche 2 GND de l'écran: Broche GND de l'ESP8266
  • Broche 3 SCE (CS) de l'écran: Broche GPIO15 (D8) de l'ESP8266
  • Broche 4 RST de l'écran: Broche GPIO4 (D2) de l'ESP8266
  • Broche 5 D/C de l'écran: Broche GPIO2 (D4) de l'ESP8266
  • Broche 6 DN (MOSI) de l'écran: Broche GPIO13 (D7) de l'ESP8266
  • Broche 7 SCLK de l'écran: Broche GPIO14 (D5) de l'ESP8266
  • Broche 8 LED de l'écran: pas branchée (je n'avais pas besoin du rétroéclairage)


Installation de la bibliothèque u8g2

Afin de faciliter la programmation, j'ai utilisé la bibliothèque u8g2, qui supporte à peu près tout ce qui existe comme petit écran monochrome, et qui est parfaitement compatible avec l'ESP32 et l'ESP8266.

Les exemples fournis avec la bibliothèque  (GraphicsTest ou HelloWorld, par exemple) permettent de vérifier rapidement que l'écran Nokia est fonctionnel et correctement branché.  Parmi l'interminable liste de constructeurs proposée au début de chaque exemple, il faut choisir "U8G2_PCD8544_84X48_F_4W_HW_SPI" et modifier la numérotation des broches pour qu'elle corresponde à nos branchements:

U8G2_PCD8544_84X48_F_4W_HW_SPI u8g2(U8G2_R0, 15, 2,  4);

Un exemple de sketch

Je vous présente ci-dessous un sketch qui permet de choisir à distance, par l'entremise d'une page web, l'illustration affichée par l'écran Nokia.

Au démarrage, l'écran affiche l'adresse IP du serveur web:


Lorsqu'on accède à cette adresse au moyen d'un navigateur web, on nous propose une liste de 4 fruits.


Suite au clic sur le bouton "Appliquer", l'image choisie s'affiche sur l'écran.


-
/**********************************************************************
ESP_Nokia_web
L'écran Nokia relié à l'ESP32 ou ESP8266 est contrôlé par l'entremise
d'une page web.
https://electroniqueamateur.blogspot.com/2019/10/ecran-nokia-5110-et-esp32esp8266.html
***********************************************************************/
// inclusion des bibliothèques utiles
// pour la communication WiFi
#if defined ARDUINO_ARCH_ESP8266 // s'il s'agit d'un ESP8266
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#elif defined ARDUINO_ARCH_ESP32 // s'il s'agit d'un ESP32
#include "WiFi.h"
#include <WebServer.h>
#endif
#include <WiFiClient.h>
// pour l'écran Nokia
#include <U8g2lib.h>
#include <Wire.h>
// modifiez ces deux constantes pour qu'elles contiennent les caractéristiques de
// votre réseau Wifi
const char* ssid = "**********";
const char* password = "**********";
#if defined ARDUINO_ARCH_ESP8266 // s'il s'agit d'un ESP8266
ESP8266WebServer server(80);
#elif defined ARDUINO_ARCH_ESP32 // s'il s'agit d'un ESP32
WebServer server(80);
#endif
U8G2_PCD8544_84X48_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 15, /* dc=*/ 2, /* reset=*/ 4); // Nokia 5110 Display
// définition des 4 illustrations bitmap:
static const unsigned char banane[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00,
0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01,
0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f,
0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7f,
0x00, 0x00, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7f,
0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff,
0x00, 0x00, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff,
0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x7f,
0x00, 0x00, 0x00, 0x00, 0xfc, 0x7f, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x7f,
0x00, 0x00, 0x00, 0x80, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x80, 0xff, 0x3f,
0x00, 0x00, 0x00, 0xe0, 0xff, 0x3f, 0x00, 0x00, 0x00, 0xf8, 0xff, 0x3f,
0x00, 0x00, 0x00, 0xfe, 0xff, 0x1f, 0x00, 0x00, 0x80, 0xff, 0xff, 0x0f,
0x00, 0x00, 0xf0, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x07,
0x00, 0xf0, 0xff, 0xff, 0xff, 0x03, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x01,
0xf0, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0xff, 0xff, 0xff, 0x7f, 0x00,
0xfe, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x0f, 0x00,
0xff, 0xff, 0xff, 0xff, 0x07, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x00, 0x00,
0xfe, 0xff, 0xff, 0x3f, 0x00, 0x00, 0xf8, 0xff, 0xff, 0x07, 0x00, 0x00,
0x80, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const unsigned char fraise[] PROGMEM = {
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x19, 0x00, 0x00,
0x00, 0x00, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0x01, 0x00,
0x00, 0xc0, 0xff, 0xff, 0x03, 0x00, 0x00, 0xf0, 0xff, 0xff, 0x0f, 0x00,
0x00, 0xf8, 0xff, 0xff, 0x1f, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x3f, 0x00,
0x00, 0xff, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x3f, 0x00,
0x00, 0xfe, 0xfe, 0xfb, 0x7f, 0x00, 0x00, 0xfe, 0xfe, 0xff, 0x7f, 0x00,
0x00, 0xfe, 0xfe, 0xfd, 0xff, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0xff, 0x00,
0x00, 0xfe, 0xfe, 0xfe, 0xff, 0x00, 0x00, 0xff, 0x7f, 0xf7, 0xfb, 0x00,
0x00, 0xef, 0xbf, 0xf7, 0xf9, 0x00, 0x00, 0xce, 0xc7, 0xf7, 0x9b, 0x00,
0x00, 0xee, 0xff, 0xf7, 0xdf, 0x00, 0x00, 0xfe, 0xf7, 0x7f, 0xde, 0x00,
0x00, 0x7e, 0x76, 0x6e, 0xfe, 0x00, 0x00, 0x7e, 0x76, 0x4e, 0xee, 0x00,
0x00, 0x7e, 0x77, 0xcf, 0x67, 0x00, 0x00, 0xcc, 0xff, 0xff, 0x67, 0x00,
0x00, 0xdc, 0xff, 0xff, 0x3f, 0x00, 0x00, 0xdc, 0x76, 0xef, 0x3f, 0x00,
0x00, 0xf8, 0x76, 0xe6, 0x3e, 0x00, 0x00, 0xf8, 0x76, 0xe6, 0x1e, 0x00,
0x00, 0xf0, 0xf7, 0x7f, 0x0e, 0x00, 0x00, 0xf0, 0xff, 0xff, 0x0f, 0x00,
0x00, 0xe0, 0xbe, 0xfb, 0x07, 0x00, 0x00, 0xc0, 0xbc, 0xb3, 0x03, 0x00,
0x00, 0xc0, 0xbd, 0x93, 0x03, 0x00, 0x00, 0x80, 0xe7, 0xdf, 0x01, 0x00,
0x00, 0x00, 0x67, 0xff, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x7e, 0x00, 0x00,
0x00, 0x00, 0x7c, 0x3f, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x1f, 0x00, 0x00,
0x00, 0x00, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00
};
static const unsigned char pomme[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x0f, 0x18, 0x00, 0x00,
0x00, 0xf8, 0x1f, 0x1c, 0x00, 0x00, 0x00, 0xf8, 0x3f, 0x0e, 0x00, 0x00,
0x00, 0xf0, 0x7f, 0x07, 0x00, 0x00, 0x00, 0xe0, 0xff, 0x03, 0x00, 0x00,
0x00, 0x80, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x88, 0x01, 0x00, 0x00,
0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0xf0, 0xbf, 0xff, 0x07, 0x00,
0x00, 0xf8, 0xff, 0xff, 0x3f, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x7f, 0x00,
0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0x01,
0xc0, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe0, 0xff, 0xff, 0xff, 0xff, 0x03,
0xe0, 0xff, 0xff, 0xff, 0xff, 0x07, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x0f,
0xf0, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x0f,
0xf0, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x0f,
0xf0, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x0f,
0xf0, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x0f,
0xf0, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x07,
0xf0, 0xff, 0xff, 0xff, 0xff, 0x07, 0xe0, 0xff, 0xff, 0xff, 0xff, 0x07,
0xe0, 0xff, 0xff, 0xff, 0xff, 0x07, 0xc0, 0xff, 0xff, 0xff, 0xff, 0x03,
0xc0, 0xff, 0xff, 0xff, 0xff, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0x01,
0x80, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00,
0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x7f, 0x00,
0x00, 0xfc, 0xff, 0xff, 0x3f, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x1f, 0x00,
0x00, 0xf8, 0xff, 0xff, 0x1f, 0x00, 0x00, 0xe0, 0xff, 0xff, 0x07, 0x00,
0x00, 0xc0, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x06, 0x50, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const unsigned char poire[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xfc, 0x00, 0x00,
0x00, 0x00, 0xb8, 0xff, 0x03, 0x00, 0x00, 0x00, 0xf8, 0xff, 0x07, 0x00,
0x00, 0x00, 0xf8, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x70, 0xff, 0x1f, 0x00,
0x00, 0x00, 0x70, 0xfe, 0x3f, 0x00, 0x00, 0x00, 0x60, 0xf8, 0x7f, 0x00,
0x00, 0x00, 0x60, 0xc0, 0x3f, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00,
0x00, 0x00, 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0x00, 0x00,
0x00, 0x80, 0xff, 0x1f, 0x00, 0x00, 0x00, 0x80, 0xff, 0x3f, 0x00, 0x00,
0x00, 0x80, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x80, 0xff, 0x3f, 0x00, 0x00,
0x00, 0x80, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x80, 0xff, 0x3f, 0x00, 0x00,
0x00, 0x80, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x80, 0xff, 0x3f, 0x00, 0x00,
0x00, 0xc0, 0xff, 0x3f, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x3f, 0x00, 0x00,
0x00, 0xc0, 0xff, 0x7f, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x7f, 0x00, 0x00,
0x00, 0xe0, 0xff, 0xff, 0x00, 0x00, 0x00, 0xe0, 0xff, 0xff, 0x01, 0x00,
0x00, 0xe0, 0xff, 0xff, 0x01, 0x00, 0x00, 0xf0, 0xff, 0xff, 0x03, 0x00,
0x00, 0xf8, 0xff, 0xff, 0x03, 0x00, 0x00, 0xf8, 0xff, 0xff, 0x07, 0x00,
0x00, 0xf8, 0xff, 0xff, 0x07, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x07, 0x00,
0x00, 0xfc, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x0f, 0x00,
0x00, 0xfe, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x0f, 0x00,
0x00, 0xfe, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x0f, 0x00,
0x00, 0xfe, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x0f, 0x00,
0x00, 0xfc, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x07, 0x00,
0x00, 0xf8, 0xff, 0xff, 0x03, 0x00, 0x00, 0xf0, 0xff, 0xff, 0x03, 0x00,
0x00, 0xf0, 0xff, 0xff, 0x01, 0x00, 0x00, 0xc0, 0xff, 0x7f, 0x00, 0x00,
0x00, 0x00, 0xff, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x02, 0x00, 0x00
};
String quelleImage;
/* La fonction construitPage retourne un string qui contient toute notre page web */
String construitPage() {
String str1, str2, str3, str4;
if (quelleImage == "fraise") {
str1 = "checked";
}
if (quelleImage == "pomme") {
str2 = "checked";
}
if (quelleImage == "poire") {
str3 = "checked";
}
if (quelleImage == "banane") {
str4 = "checked";
}
String page = "<html lang=fr-FR><head>";
page += "<title>ESP et &eacute;cran Nokia</title>";
page += "<style> body { background-color: #fffff; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }</style>";
page += "</head><body><h1>ESP et &Eacute;cran Nokia</h1>";
page += "<p>Choisir l'image &agrave; afficher:</p>";
page += "<form action='/' method='POST'>";
page += "<p><INPUT type='radio' name='image' value='fraise' " + str1 + ">fraise</p>";
page += "<INPUT type='radio' name='image' value='pomme' " + str2 + ">pomme</p>";
page += "<INPUT type='radio' name='image' value='poire' " + str3 + ">poire</p>";
page += "<INPUT type='radio' name='image' value='banane' " + str4 + ">banane</p>";
page += "<INPUT type='submit' value='Appliquer'><br><br>";
page += "</body></html>";
return page;
}
/* La fonction gestionPage affiche l'image appropriée
quand le bouton Appliquer a été cliqué. */
void gestionPage() {
quelleImage = server.arg("image");
afficheImage();
server.send ( 200, "text/html", construitPage() );
}
/* Affichage de la bonne image sur l'écran Nokia */
void afficheImage() {
u8g2.clearBuffer(); // on efface ce qui se trouve déjà dans le buffer
if (quelleImage == "banane") {
u8g2.drawXBMP( 18, 0, 48, 48, banane); // position, largeur, hauteur
}
if (quelleImage == "fraise") {
u8g2.drawXBMP( 18, 0, 48, 48, fraise); // position, largeur, hauteur
}
if (quelleImage == "pomme") {
u8g2.drawXBMP( 18, 0, 48, 48, pomme); // position, largeur, hauteur
}
if (quelleImage == "poire") {
u8g2.drawXBMP( 18, 0, 48, 48, poire); // position, largeur, hauteur
}
u8g2.sendBuffer(); // l'image qu'on vient de construire est affichée à l'écran
}
void setup() {
u8g2.begin(); // initialisation de l'écran Nokia
u8g2.enableUTF8Print(); //nécessaire pour écrire des caractères accentués
u8g2.setFont(u8g2_font_6x10_tf); // choix de la police
// message de bienvenue
u8g2.clearBuffer();
u8g2.setCursor(5, 15); // position du début du texte
u8g2.print("Connexion au"); // on écrit le texte
u8g2.setCursor(5, 35); // position du début du texte
u8g2.print("réseau WiFi"); // on écrit le texte
u8g2.sendBuffer();
WiFi.mode(WIFI_STA);
// initialisation de la communication WiFi
WiFi.begin ( ssid, password );
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
}
// On indique le nom de la fonction qui gère l'interraction avec la page web
server.on ( "/", gestionPage );
server.begin();
// affichage de l'adresse IP
u8g2.clearBuffer();
u8g2.setCursor(5, 15); // position du début du texte
u8g2.print("Adresse IP:"); // on écrit le texte
u8g2.setCursor(5, 35); // position du début du texte
u8g2.print(WiFi.localIP()); // on écrit le texte
u8g2.sendBuffer();
}
void loop() {
server.handleClient();
}
-

Pour produire les données bitmap

Pour obtenir les données qui définissent chacune des 4 illustrations, j'ai ouvert chaque image (qui était, au départ, un fichier de type .png) au moyen du logiciel GIMP. Puisque la résolution de l'écran est de 84 X 48 pixels, je les ai redimensionnées au format 48 X 48 pixels (menu Image / Échelle et taille de l'image...) avant de les exporter en format .xbm (menu Fichier / Exporter sous...).

J'ai ensuite ouvert le fichier .xbm au moyen d'un éditeur de texte, et recopié les données dans mon sketch.



À lire également

Quelques tutos expliquent comment utiliser d'autres types d'afficheurs avec un ESP32 ou un ESP8266: écran couleur SPI ST7735, écran OLED i2c SH1106 , afficheur LCD 16 X 2 et afficheur 7 segments à base de TM1638.

Quant à l'écran Nokia 5110, voyez comment l'utiliser avec un Raspberry Pi, avec un Arduino ou encore avec un MSP430 Launchpad,

Yves Pelletier   (TwitterFacebook)

mercredi 23 octobre 2019

Texte et Arduino (2): les tableaux de caractères du langage C

Dans mon billet précédent, j'ai exploré une première façon de traiter du texte avec Arduino, en utilisant la classe String. Tel que promis, voyons maintenant la façon traditionnelle de procéder en langage C: les tableaux de caractères à terminaison nulle.

Avant de plonger, je récapitule rapidement les avantages et inconvénients des deux façons de procéder:
  • classe String (billet précédent): Plus simple pour les débutants, mais engendre une fragmentation de la mémoire qui pourrait rendre le programme instable.
  • tableaux de caractère: Un peu plus compliqué à utiliser par les débutants , mais pas de problème de fragmentation de mémoire, et applicable à tout ce qui se programme en langage C (pas seulement l'Arduino).

Sketch de démonstration

Pour commencer, voici un sketch qui illustre plusieurs des fonctions énumérées dans la suite de cet article. L'utilisateur écrit un message dans le moniteur série, et le sketch effectue diverses opérations afin d'analyser ce message.

Attention: Pour que le programme fonctionne correctement, le moniteur série doit être réglé à "Nouvelle Ligne".

-
/*
Démonstration des tableaux de caractères en C.
http://electroniqueamateur.blogspot.com/2019/10/texte-et-arduino-2-les-tableaux-de.html
*/
const byte longueurMax = 32; // nombre maximal de caractères
char texteRecu[longueurMax];
char reponse[longueurMax * 2];
char valeur[10];
char * resultat; //// pour quand la fonction retourne un pointeur vers un caractère de la chaîne
boolean nouvellesDonnees = false;
void setup() {
Serial.begin(9600);
Serial.println ( F("Veuillez écrire votre message.") ) ;
}
void loop() {
receptionMessage();
if (nouvellesDonnees == true) {
// Utilisation de strcpy
strcpy(reponse, "Voici votre message: ");
// utilisation de strcat pour combiner deux chaînes
strcat(reponse, texteRecu);
Serial.println(reponse);
// utilisation de strncat pour combiner deux chaînes en limitant le nombre de caractères
strcpy(reponse, "5 premières lettres de votre message: ");
strncat(reponse, texteRecu, 5);
Serial.println(reponse);
// utilisation de strncpy pour copier les premiers caractères d'une chaîne
Serial.print(F("3 premières lettres de votre message: "));
strncpy(reponse, texteRecu, 3);
reponse[3] = '\0'; // ajout manuel du caractère NULL
Serial.println(reponse);
//Utilisation de strlen et de sprintf
strcpy(reponse, "Votre message comporte ");
sprintf(valeur, "%d", strlen(texteRecu) - 1);
strcat(reponse, valeur);
strcat(reponse, " caractères.");
Serial.println(reponse);
// trouver un caractère, modifier un caractère
strcpy(reponse, "Le 4e caractère de votre message est *");
reponse[strlen(reponse) - 1] = texteRecu[3];
Serial.println(reponse);
// Comparaison de 2 chaînes (strcmp)
if (!strcmp(texteRecu, "Arduino"))
{
Serial.println(F("Votre message est Arduino"));
}
else
{
Serial.println(F("Votre message n'est pas Arduino"));
}
// comparaison du début de deux chaînes: strncmp
if (!strncmp(texteRecu, "Bonjour", 7))
{
Serial.println(F("Votre message commence par Bonjour"));
}
else
{
Serial.println(F("Votre message ne commence pas par Bonjour"));
}
// recherche d'un caractère dans une chaîne: strchr()
resultat = strchr(texteRecu, 'a');
if (resultat != NULL) {
Serial.print (F("Votre message, à partir de la première lettre a: "));
Serial.println(resultat);
Serial.print(F("Position de la première occurrence de la lettre a: "));
Serial.println(resultat - texteRecu);
}
else {
Serial.println(F("La lettre a ne figure pas dans votre message"));
}
// recherche d'un caractère dans une chaîne, en commençant par la fin: strrchr()
resultat = strrchr(texteRecu, 'c');
if (resultat != NULL) {
Serial.print (F("Votre message, à partir de la dernière lettre c: "));
Serial.println(resultat);
Serial.print(F("Position de la dernière occurrence de la lettre c: "));
Serial.println(resultat - texteRecu);
}
else {
Serial.println(F("La lettre c ne figure pas dans votre message"));
}
resultat = memchr (texteRecu, 'p', 5);
if (resultat != NULL) {
Serial.print(F("Votre message, à partir de la première lettre p, si elle se trouve parmi les 5 premières lettres: "));
Serial.println(resultat);
Serial.print(F("Position de la première lettre p, si elle se trouve parmi les 5 premières lettres: "));
Serial.println(resultat - texteRecu);
}
else {
Serial.println(F("La lettre p ne figure pas parmi les 5 premières lettres"));
}
// recherche de plusieurs caractères dans une chaîne: strpbrk()
resultat = strpbrk(texteRecu, "aeiouyAEIOUY");
if (resultat != NULL) {
Serial.print (F("Votre message, à partir de la première voyelle: "));
Serial.println(resultat);
Serial.print(F("Position de la première voyelle de votre message: "));
Serial.println(resultat - texteRecu);
}
else {
Serial.println(F("Votre message ne comporte aucune voyelle."));
}
//Recherche d'une chaîne dans une autre: strstr()
resultat = strstr(texteRecu, "ami");
if (resultat != NULL) {
Serial.print (F("Votre message, à partir du mot ami: "));
Serial.println(resultat);
Serial.print(F("Position de la première occurrence du mot ami: "));
Serial.println(resultat - texteRecu);
}
else {
Serial.println(F("Votre message ne comporte pas le mot ami."));
}
// position strspn
Serial.print(F("Nombre de voyelles consécutives au début du message: "));
Serial.println(strspn(texteRecu, "aeiouyAEIOUY"));
// position de la première occurrence d'une lettre parmi un groupe strcspn()
Serial.print(F("La première voyelle de votre message apparaît à la position: "));
Serial.println(strcspn(texteRecu, "aeiouyAEIOUY"));
// remplacement des 3 premiers caractères par un même caractère: memset()
strcpy(reponse, "1234567");
memset(reponse, '*', 3);
Serial.print(F("Utilisation de memset(): "));
Serial.println(reponse);
// Inverser l'ordre des lettres: strrev()
Serial.print(F("Votre message, à l'envers: "));
Serial.println (strrev(texteRecu));
// conversion d'un nombre en texte
int unEntier = 24;
sprintf(reponse, "Valeur mesuree: %d °C", unEntier);
Serial.println(reponse);
strcpy(reponse, "Compteur: ");
itoa(unEntier, valeur, 10);
strcat(reponse, valeur);
Serial.println(reponse);
float valeurDecimale = 8.0 / 7.0;
strcpy(reponse, "Resultat du calcul: ");
dtostrf(valeurDecimale, 8, 4, valeur);
strcat(reponse, valeur);
Serial.println(reponse);
// conversion d'un texte en nombre
strcpy(valeur, "100");
unEntier = atoi(valeur) + 67;
Serial.println(unEntier);
strcpy(valeur,"1.234");
valeurDecimale = atof(valeur) + 2.0;
Serial.println(valeurDecimale);
nouvellesDonnees = false;
Serial.println ( " " ) ;
Serial.println ( F("Veuillez écrire votre message" )) ;
}
}
/*
Fonction permettant de lire un string à partir du moniteur série.
Source: https://forum.arduino.cc/index.php?topic=396450.0
Fonctionne à la condition que le moniteur série soit réglé à "Nouvelle ligne".
*/
void receptionMessage() {
static byte index = 0;
char charRecu;
while (Serial.available() > 0 && nouvellesDonnees == false) {
charRecu = Serial.read();
if (charRecu != '\n') { // ce n'est pas la fin du message
texteRecu[index] = charRecu;
index++;
if (index >= longueurMax) {
index = longueurMax - 1;
}
}
else { // c'est la fin du message
texteRecu[index] = '\0'; // on termine le texte par le caractère nul
index = 0;
nouvellesDonnees = true;
}
}
}
-

Voici ce qu'affiche le moniteur série pour le message "Arduino est l'ami des makers":



Création d'un tableau de caractère

Une chaîne de caractère en C est un tableau (array) qui contient des éléments de type char. Dans le sketch de démonstration ci-dessus, 3 tableaux de caractères sont créés aux lignes 8, 9 et 10.

Par exemple, je peux créer un tableau de caractères intitulé "monTexte" de la façon suivante:

char monTexte[7];

Puisqu'il s'agit d'un tableau comportant 7 éléments, il pourra contenir un texte ayant une taille maximale de...6 caractères. Pourquoi seulement 6 plutôt que 7? Parce qu'en langage C, une chaîne de caractères se termine toujours par le caractère "NULL", qui indique la fin de la chaîne.

Lors de l'exécution du programme, la variable monTexte pourra donc contenir le mot "banane" (6 lettres, plus le caractère NULL). Elle pourrait aussi contenir un mot plus court, comme "pomme" (5 lettres, plus le caractère NULL).

Mais il faut faire très attention de ne pas tenter d'y faire entrer le mot "rutabaga" (8 lettres, plus le caractère NULL): si vous le faites, les caractères excédentaires seront quand même inscrits en mémoire, mais à un endroit qui n'a pas été réservé pour le contenu de la variable monTexte! Le comportement de votre programme sera alors totalement imprévisible.

Notez qu'il est également possible laisser au compilateur le soin de calculer lui-même le nombre d'éléments requis dans le tableau, si vous lui affectez une valeur dès sa création:

char monTexte[]="banane";

Puisqu'on n'a pas spécifié le nombre d'éléments, le compilateur va créer un tableau de 7 caractères (les 6 lettres du mot "banane", et un caractère nul à la fin).  Mais ici encore: pas question, pendant l'exécution du programme, de remplacer le mot "banane" par un mot plus long!

Lors de la création de votre tableau de caractère, vous devez donc veiller à choisir une taille suffisante pour contenir le texte que vous voudrez bien y placer (1 élément de plus que le nombre maximal de lettres). Dans la suite du programme, vous devrez vous assurer de ne jamais dépasser le maximum établi au départ.

N.B.: Dans la suite de cet article, les hyperliens mènent vers la documentation complète de chaque fonction.

Affectation d'une valeur à la chaîne de caractères

Une fois votre tableau créé, vous pouvez remplacer le texte qu'il contient grâce à la fonction strcpy() (voir, par exemple, la ligne 29 du sketch de démonstration). Attention: strcpy() ne vous offre aucune protection contre le risque d'affecter à votre tableau une chaîne de caractères plus longue que ce qu'il est capable de contenir.

Pour éviter de dépasser par mégarde la taille limite de votre tableau, il peut être plus prudent d'utiliser la fonction strncpy() (ligne 42 du sketch de démonstration) car elle permet de spécifier le nombre maximal de caractères qui seront placés dans le tableau.

Concaténation de deux chaînes de caractères

Pour concaténer (fusionner) deux chaînes de caractère, on utilise strcat() (ligne 32). On peut aussi utiliser strncat() (ligne 37) pour limiter le nombre de caractères qui seront ajoutés à la chaîne cible.

Comparaison de deux chaînes de caractères

Pour vérifier si deux chaînes sont identiques ou non, on utilise strcmp() (ligne 59). Il y a aussi strncmp() (ligne 69), pour comparer le début de deux chaînes, en spécifiant le nombre de caractères à considérer lors de la comparaison.

Connaître le nombre de caractères d'une chaîne

La fonction strlen() (ligne 48) retourne le nombre de caractères de la chaîne, situés avant le caractère nul.

Analyse caractère par caractère

On peut accéder à un caractère de la chaîne grâce à l'opérateur []. Par exemple, à la ligne 55 du sketch de démonstration, "texteRecu[3]" retourne le 4e caractère de la chaîne "texteRecu" (puisque le premier caractère porte le numéro 0).

Pour trouver la première occurrence d'un caractère dans une chaîne on peut utiliser strchr() (ligne 79), qui retourne un pointeur vers le caractère trouvé. Si vous cherchez la position de ce caractère à l'intérieur la chaîne, il s'agit de faire une soustraction de deux pointeurs (voir la ligne 84).

strrchr() (ligne 91) est similaire à strchr(), sauf que la recherche débute à la fin de la chaîne (dernière occurrence du caractère). Ici encore, la position de ce caractère sera obtenue grâce à une soustraction de pointeurs (ligne 96).

memchr() est similaire à strchr(), sauf qu'on peut restreindre la recherche aux premiers caractères de la chaîne. Voir la ligne 102 du sketch de démonstration.

À la ligne 114, j'ai utilisé strpbrk() afin de trouver la première occurrence de n'importe quel des caractères fournis (dans ce cas précis: toutes les voyelles de l'alphabet). J'ai ensuite trouvé la position de ce caractère en faisant la soustraction des deux pointeurs (ligne 119). Mais cette même position peut également être trouvée au moyen de strcspn() (ligne 143), qui retourne le nombre de caractères consécutifs au début d'un string qui ne figurent pas dans la liste fournie. strspn() (ligne 139) fait le contraire: elle retourne le nombre de caractères consécutifs au début d'un string qui figurent dans la liste fournie.

Recherche d'une chaîne à l'intérieur d'une chaîne

À la ligne 126 du sketch de démonstration, j'utilise strstr() afin de vérifier si la chaîne "ami" se trouve à l'intérieur du message reçu. Puisque la fonction retourne un pointeur vers le début de cette sous-chaîne, j'effectue ensuite une soustraction de pointeurs (ligne 131) pour connaître la position de cette "sous-chaîne", si elle a effectivement été trouvée.

Un peu n'importe quoi...

À la ligne 147, j'utilise la fonction memset() afin de remplacer les 3 premiers caractères d'une chaîne par le même caractère (dans ce cas précis: un astérisque).

À la ligne 153, grâce à strrev(), j'écris le message à l'envers, même si la pertinence de cette fonction m'échappe totalement.

Conversion d'un nombre en chaîne de caractères

Il est souvent utile de convertir un nombre en chaîne de caractères (pour afficher une mesure issue d'un capteur, par exemple).

Malgré son étrange syntaxe, la fonction sprintf() (ligne 159) est toute désignée pour intégrer un nombre entier à l'intérieur d'une chaîne de caractères. À la ligne 163, j'utilise itoa() pour parvenir au même résultat (itoa() n'est pas une fonction C standard, mais elle est supportée par Arduino)­.

En général, sprintf() permet aussi de convertir des valeurs décimales (float) au moyen d'une syntaxe du genre "sprintf(monText,"Valeur mesuree: %f volts",nombre)". Sur un Arduino AVR, toutefois, ça ne fonctionne pas, et il faut plutôt utiliser dtostrf() (ligne 170).

Conversion d'une chaîne de caractères en nombre

À la ligne 176, j'ai utilisé atoi() afin de transformer une chaîne de caractère en entier (variable de type int). Finalement, à la ligne 180, atof() m'a permis de transformer une chaîne de caractère en valeur décimale (variable de type float).

Yves Pelletier   (TwitterFacebook)

dimanche 20 octobre 2019

Texte et Arduino (1): la classe String



Deux façon possibles de procéder

Lorsque vous désirez traiter des informations sous forme de texte avec votre Arduino (demander le nom de l'utilisateur, afficher une information textuelle sur un écran ou le moniteur série, etc.), deux options s'offrent à vous: les tableaux de caractère classiques du langage C, et les objets de type String.

Les tableaux de caractères du langage C

Les tableaux de caractères terminés par "NULL" font partie intégrante du langage C depuis sa création, au début des années 1970. Ils sont souvent un peu nébuleux pour les néophytes qui abordent la programmation de l'Arduino sans avoir de connaissance préalable en langage C. Les fonctions conçues pour gérer les tableaux de caractères portent souvent des noms peu intuitifs.  Pour les connaître,  il faut consulter des sources documentaires sur le langage C car le site officiel Arduino insiste plutôt sur la classe String. Le programmeur doit également prendre soin de ne pas écrire au-delà des limites du tableau (comme, par exemple, tenter d'écrire un treizième caractère dans un tableau conçu pour en contenir douze).

Ces tableaux de caractères pourront toutefois être utilisés dans n'importe quel programme en langage C (pas seulement Arduino).

Les objets de type String

Les concepteurs du langage Arduino ont créé la classe String pour faciliter la tâche des programmeurs débutants. La syntaxe est souvent plus intuitive, les fonctions portent un nom qui permet de deviner plus facilement à quoi elles servent,  elles sont énumérées sur le site de référence officiel du langage Arduino et un tas d'exemples sont fournis avec l'IDE Arduino.



Malheureusement, cette facilité d'utilisation est accompagnée d'un inconvénient: pendant que votre programme s'exécute, les fonctions de la classe String ont tendance à causer un fractionnement de la mémoire disponible. Conséquence: avec le temps, votre programme risque de devenir instable et de planter. C'est pour cette raison que, dans les forums de discussion, vous verrez souvent des intervenants qui menacent de vous excommunier à la moindre allusion à la classe String.

Alors on fait quoi?

Si c'est très important que votre programme fonctionne sans faille pendant une longue période de temps (exemple: il gère l'alimentation en oxygène de votre scaphandre), évitez à tout prix l'utilisation de la classe String!

Par contre, si vous fabriquez par pur plaisir un gadget dont la fiabilité n'est pas essentielle (une machine qui affiche des obscénités lorsqu'on appuie sur un bouton, par exemple) et que vous détestez vous compliquer la vie, pourquoi vous priver de la classe String?

Bref, les programmeurs sérieux utilisent les tableaux de caractère à terminaison nulle, et les autres font ce qu'ils veulent.

Dans le présent article, je vais explorer l'utilisation de la classe String. Dans un prochain article, à paraître très bientôt, j'aborderai l'utilisation des tableaux de caractères à terminaison nulle.

Sketch de démonstration

Le sketch ci-dessous utilise la plupart des fonctions décrites dans le reste de cet article. Il vous demande d'écrire un message dans le moniteur série, et il fait ensuite l'analyse de ce message.

-
/*
Démonstration des principales fonctions de la classe String.
https://electroniqueamateur.blogspot.com/2019/10/texte-et-arduino-1-la-classe-string.html
*/
// Création de 3 variables de type String
String texteRecu;
String reponse;
String monTexte;
void setup ( ) {
Serial.begin (9600);
Serial.println ( "Veuillez écrire votre message" ) ;
}
void loop () {
if (Serial.available()) {
texteRecu = Serial.readString(); // récupération du texte reçu
texteRecu.trim(); // on enlève les espaces au début et à la fin du message reçu
// exemple de concaténation:
reponse = "Voici votre message: ";
reponse.concat(texteRecu);
Serial.println(reponse);
// autre façon de concaténer (opérateur "+"):
reponse = "Votre message comporte " + String(texteRecu.length()) + " caractères.";
Serial.println(reponse);
// Deux façons d'isoler un caractère
reponse = "Le 3e caractère de votre message est " + String(texteRecu.charAt(2)) + ".";
Serial.println(reponse);
reponse = "Le 4e caractère de votre message est " + String(texteRecu[3]) + ".";
Serial.println(reponse);
// recherche d'un caractère
reponse = "La première occurrence de la lettre a dans votre message est " + String(texteRecu.indexOf('a')) + ".";
Serial.println(reponse);
reponse = "La dernière occurrence de la lettre a dans votre message est " + String(texteRecu.lastIndexOf('a')) + ".";
Serial.println(reponse);
// comparaison de deux Strings:
monTexte = "Arduino";
if (texteRecu == monTexte) {
Serial.println("Votre message est Arduino");
}
else {
Serial.println("Votre message n'est pas Arduino");
}
monTexte = "Bonjour";
if (texteRecu.equals(monTexte)) {
Serial.println("Votre message est Bonjour");
}
else {
Serial.println("Votre message n'est pas Bonjour");
}
if (texteRecu.equalsIgnoreCase(monTexte)) {
Serial.println("Sans tenir compte de la case, votre message est Bonjour");
}
else {
Serial.println("Sans tenir compte de la case, votre message n'est pas Bonjour");
}
if (texteRecu.endsWith("?")) {
Serial.println("Votre message semble être une question");
}
// recherche d'un String dans un String:
Serial.print("Les 3 premiers caractères de votre message: ");
Serial.println(texteRecu.substring(0, 3));
texteRecu.toLowerCase();
reponse = "Votre message en minuscules: " + texteRecu + ".";
Serial.println(reponse);
texteRecu.toUpperCase();
reponse = "Votre message en majuscules: " + texteRecu + ".";
Serial.println(reponse);
if (texteRecu.compareTo("MERCREDI") < 0) {
Serial.println("En ordre alphabétique, votre message serait classé AVANT mercredi.");
}
else {
Serial.println("En ordre alphabétique, votre message serait classé APRÈS mercredi.");
}
if (texteRecu.indexOf('8') != -1) { // si le chiffre 8 est dans le message...
texteRecu.replace("8", " huit "); // ... on le remplace par le mot "huit"
Serial.println(texteRecu);
}
texteRecu.setCharAt(2, '*'); // on remplace le 3e caractère par "*"
Serial.println(texteRecu);
// conversion d'un float en String:
reponse = "7/3 = " + String(7.0 / 3.0, 1); // 1 décimale
Serial.println(reponse);
reponse = "7/3 = " + String(7.0 / 3.0, 5); // 5 décimales
Serial.println(reponse);
// conversion d'un String en float:
monTexte = "5.78 cm";
reponse = "5.78 * 3.3 = " + String(monTexte.toFloat() * 3.3) ;
Serial.println(reponse);
Serial.println ();
Serial.println ("Veuillez écrire votre message");
Serial.println ();
}
}
-

Voici un exemple de ce qu'affiche le moniteur série après que j'aie écrit le message "Arduino, le meilleur ami des makers":



N.B.: Dans la suite de cet article, les hyperliens mènent vers la documentation complète de chaque fonction.

Création d'une variable de type String (consructeur)

On crée une variable de type String en précédant le nom de la variable par le mot "String", comme aux lignes 8, 9 et 10 du sketch de démonstration (n'oubliez pas le "S" majuscule au début du mot).

Ce qui est chouette avec un objet de type String, c'est qu'il n'est pas nécessaire de définir sa taille au moment de sa création: pourvu que la mémoire nécessaire soit disponible, votre variable pourra tout aussi bien contenir un texte constitué d'un seul caractère qu'un long paragraphe de plusieurs lignes­.

Réception d'un String par le moniteur série

À la ligne 20 du sketch de démonstration, Serial.readString() permet de placer dans une variable de type String le texte reçu par liaison série. L'utilisation de cette fonction est toujours suivie d'un délai d'attente perceptible, car elle prend fin lorsqu'aucun nouveau caractère n'a été reçu depuis un certain temps.

Remarquez que si vous avez réglé le moniteur série pour qu'il ajoute automatiquement une fin de ligne ou un retour de chariot à la fin de chaque message, ces caractères supplémentaires feront partie du String, à moins que vous n'utilisiez la fonction trim(), qui enlève tous les espaces inutiles situés au début et à la fin d'un String (ligne 22 du sketch).

Affectation d'un String

Pour placer du texte dans une variable de type String, vous utilisez l'opérateur "=", comme pour tout autre type de variable (voir la ligne 25, par exemple).

Concaténation de deux Strings

La concaténation consiste à mettre deux Strings bout à bout. Vous pouvez utiliser la fonction concat() (ligne 26) ou l'opérateur "+" (ligne 30).

Comparaison de deux Strings

Pour vérifier si deux Strings sont identiques, vous pouvez utiliser l'opérateur "=="  (ligne 48) ou la fonction equals() (ligne 56).

Il existe également des fonctions plus spécialisées: equalsIgnoreCase() (ligne 63) compare les deux Strings sans tenir compte de la case ("Arduino" et "ARDUINO" seront donc considérés comme équivalents).

compareTo() (ligne 86) est encore un petit peu plus sophistiquée, et peut être utilisée pour trier des Strings en fonction du numéro ASCII des caractères qu'ils contiennent. On peut également trier deux Strings de façon similaire au moyen des opérateurs ">" ou "<".

Connaître le nombre de caractères d'un String

La fonction length() (ligne 30) permet de déterminer le nombre de caractères à l'intérieur du String.

Analyse caractère par caractère

On peut accéder à un caractère en particulier au moyen de la fonction charAt() (ligne 34) ou de l'opérateur [] (ligne 36). Le premier caractère porte le numéro 0.

Pour modifier un caractère en particulier, on peut utiliser setCharAt() (ligne 98).

On peut également éliminer un caractère ou plusieurs caractères grâce la fonction remove().

On peut utiliser replace() (ligne 94) pour remplacer toutes les occurrences d'un caractère par un autre caractère.

Isoler une partie d'un String

substring() (ligne 76) retourne un String constitué d'une partie d'un autre String.

Recherche à l'intérieur d'un String

indexOf() (ligne 40) et lastIndexOf() (ligne 42) permettent de trouver la position d'un caractère ou d'un String à l'intérieur d'un autre String. La valeur retournée est -1 si la recherche s'est révélée infructueuse.  Par défaut, la recherche débute au tout début ou à la toute fin du String. On peut ajouter un deuxième paramètre facultatif pour commencer la recherche à un autre caractère.

startsWith()  et endsWith() (ligne 70) permettent de vérifier si un String commence ou se termine par les caractères d'un autre String.

On peut utiliser replace() (ligne 94) pour remplacer toutes les occurrences d'un String par un autre String.

Tout réécrire en minuscules ou en majuscules

Deux fonctions permettent de réécrire le contenu du String afin qu'il ne contienne que des majuscules, ou que des minuscules: toLowerCase() et  toUpperCase() (lignes 78 et 82). Dans les deux cas, le contenu du String est immédiatement remplacé par le nouveau texte.

Conversion d'un nombre en String

Lorsque vous désirez afficher une valeur numérique mesurée par un capteur, il peut être utile de convertir un nombre en String. La conversion de type s'effectue avec la même syntaxe que pour les autres types de variable en langage C:  String(nombre). Si le nombre à convertir est de type "float" ou "double", un deuxième paramètre (facultatif) permet de déterminer le nombre de décimales à conserver (lignes 103 et 106 du sketch de démonstration).

Conversion d'un String en nombre

Un String qui débute par un nombre peut être converti en variable de type double, float ou int grâce aux fonctions toDouble()toFloat() et toInt() (ligne 111). Le String doit débuter par un nombre, mais ce nombre peut être suivi de caractères non-numériques, qui seront simplement ignorés lors de la conversion. La valeur retournée est zéro lorsque la conversion échoue.

Prochain article: les chaînes de caractères classiques du langage C (terminées par NULL).

Yves Pelletier   (TwitterFacebook)

mardi 1 octobre 2019

Bluetooth (série) avec l'ESP32


Une des nombreuses caractéristiques épatantes de l'ESP32, c'est qu'il peut communiquer en Bluetooth. Dans cet article, voyons un peu comment il est possible d'utiliser la bibliothèque BluetoothSerial pour établir une communication entre l'ESP32 et un smart phone.

Si vous savez comment utiliser la classe Serial pour établir une communication série de type UART, il n'est pas exagéré d'affirmer que vous savez déjà comment utiliser la bibliothèque BluetoothSerial: les méthodes begin, read, write, print, println et available sont disponibles et accomplissent la même fonction que dans leur équivalent série.

Réglages du smart phone ou de la tablette

Il existe de nombreuses applications Android permettant la communication bluetooth. Pour cette expérience, j'ai utilisé Serial Bluetooth Terminal par Kai Morich. Une autre option consiste à utiliser l'application réalisée dans cet article au moyen de MIT App Inventor.

Si vous n'avez pas l'habitude d'effectuer l'appairage d'un périphérique bluetooth, vous pouvez vous référer à la partie intitulée "Couplage avec un appareil hôte" de cet article.

Envoi d'informations de l'ESP32 vers le smart phone

Le sketch ci-dessous envoie un nombre croissant par bluetooth. C'est une façon simple et rapide de vérifier que la communication fonctionne correctement.


-
/*******************************************************
Communication bluetooth avec un ESP32.
L'ESP32 publie un nombre croissant.
https://electroniqueamateur.blogspot.com/2019/10/bluetooth-serie-avec-lesp32.html
********************************************************/
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;
int compteur = 0;
void setup() {
SerialBT.begin("ESP32"); //"ESP32" est le nom qu'on donne à notre client
void loop() {
compteur++;
SerialBT.print("Valeur du compteur: ");
SerialBT.println(compteur);
delay(500);
}
-

Contrôle de l'ESP32 par le smart phone

Dans ce deuxième exemple, deux LEDs reliées à l'ESP32 sont contrôlées à partir du smart phone.
  • La commande 'a' allume la première LED
  • La commande 'b' allume la deuxième LED
  • La commande 'c' allume les deux LEDs
  • La commande 'd' éteint les deux LEDs
  • La commande 'e' modifie l'état des deux LEDs


Une des LEDs est branchée à la broche GPIO 4, alors que l'autre est branchée à GPIO 5.


-
/**************************************************
Contrôle de deux LEDs par bluetooth (ESP32)
https://electroniqueamateur.blogspot.com/2019/10/bluetooth-serie-avec-lesp32.html
***************************************************/
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;
#define LEDpin1 4 // 1ere LED branchée à GPIO 4
#define LEDpin2 5 // 2e LED branchée à GPIO 5
char instruction; // le message recu par bluetooth;
int etatLED1 = 0, etatLED2 = 0;
void setup()
{
SerialBT.begin("ESP32"); // notre client Bluetooth s'appelera "ESP32"
pinMode(LEDpin1, OUTPUT);
pinMode(LEDpin2, OUTPUT);
}
void loop()
{
char message;
if (SerialBT.available()) // réception d'un message
{
message = SerialBT.read(); // lecture du message reçu
}
if (message != instruction) { // alors il faut modifier l'état des LEDs
instruction = message;
if (instruction == 'a')
{
digitalWrite(LEDpin1, HIGH);
digitalWrite(LEDpin2, LOW);
etatLED1 = 1;
etatLED2 = 0;
SerialBT.println("LED 1 allumee, LED 2 eteinte");
}
else if (instruction == 'b')
{
digitalWrite(LEDpin1, LOW);
digitalWrite(LEDpin2, HIGH);
etatLED1 = 0;
etatLED2 = 1;
SerialBT.println("LED 1 eteinte, LED 2 allumee");
}
else if (instruction == 'c')
{
digitalWrite(LEDpin1, HIGH);
digitalWrite(LEDpin2, HIGH);
etatLED1 = 1;
etatLED2 = 1;
SerialBT.println("Les 2 LEDs allumees");
}
else if (instruction == 'd')
{
digitalWrite(LEDpin1, LOW);
digitalWrite(LEDpin2, LOW);
etatLED1 = 0;
etatLED2 = 0;
SerialBT.println("Les 2 LEDs eteinte");
}
else if (instruction == 'e')
{
digitalWrite(LEDpin1, !etatLED1);
digitalWrite(LEDpin2, !etatLED2);
etatLED1 = !etatLED1;
etatLED2 = !etatLED2;
SerialBT.println("Les 2 LEDs ont change d'etat");
}
delay(500);
}
}
-

Articles similaires

Grâce au module HC-06, nous avons eu l'occasion d'utiliser Bluetooth avec Arduino, Raspberry Pi, STM32 Nucleo, MPLAB Xpress (PIC) et MSP430 Launchpad. Nous avons aussi utilisé MIT App Inventor 2 pour programmer une appli Android qui communique en Bluetooth.

En ce qui concerne l'ESP32, de nombreux autres articles sont disponibles ici.

Yves Pelletier   (TwitterFacebook)