Apprenez à utiliser ESP-NOW avec la carte ESP32 CYD (affichage jaune bon marché) pour contrôler les GPIO de plusieurs cartes ESP32. La carte CYD agit comme une télécommande pour envoyer des commandes à d’autres cartes pour contrôler leurs GPIO. Nous contrôlerons deux cartes ESP32 et deux GPIO de chacune. Cela peut être facilement étendu pour contrôler plus de cartes ESP32 ou plus de GPIO.

Nouveau sur l’écran jaune bon marché ESP32 ? Commencez ici : Premiers pas avec le tableau d’affichage jaune bon marché ESP32 – CYD (ESP32-2432S028R).
Table des matières
Aperçu du projet
Voici un aperçu du projet que nous allons construire.

- Dans ce projet, la carte ESP32 CYD contrôlera deux GPIO de deux autres cartes ESP32.
- La carte ESP32 CYD agira comme un émetteur ESP-NOW qui enverra des données pour contrôler les GPIO des autres cartes ESP32. Nous contrôlerons les GPIO 2 et 4 sur ESP32 #1, et les GPIO 20 et 21 sur ESP32 #2.
- La carte CYD agit comme un émetteur ESP-NOW et les autres cartes agissent comme des récepteurs ESP-NOW.
- L’expéditeur CYD (carte contrôleur) envoie des messages à une carte spécifique en se référant à son adresse MAC.
- Pour contrôler les cartes, l’interface CYD affiche deux onglets, un pour chaque carte ESP32. Chaque onglet dispose de deux interrupteurs à bascule pour contrôler deux GPIO de chaque carte.
Vous débutez avec le protocole de communication ESP-NOW ? Lisez ce guide : Premiers pas avec ESP-NOW (ESP32 avec Arduino IDE).
Conditions préalables
Avant de continuer, assurez-vous de suivre les conditions préalables suivantes. Vous devez suivre toutes les étapes, sinon votre projet ne fonctionnera pas.
1) Pièces requises
Pour ce projet, vous avez besoin des pièces suivantes :
Vous pouvez utiliser les liens précédents ou vous rendre directement sur MakerAdvisor.com/tools pour trouver toutes les pièces pour vos projets au meilleur prix !

2) Installez les cartes ESP32 dans l’IDE Arduino

Nous allons programmer l’ESP32 à l’aide de l’IDE Arduino. Assurez-vous que les cartes ESP32 sont installées. Suivez le tutoriel suivant :
3) Familiarisez-vous avec l’écran jaune bon marché ESP32
La carte de développement ESP32-2432S028R est connue dans la communauté des fabricants sous le nom de « Cheap Yellow Display » ou CYD en abrégé. Cette carte de développement, dont la puce principale est un module ESP32-WROOM-32, est livrée avec un écran tactile LCD TFT de 2,8 pouces, une interface de carte microSD, une LED RVB et tous les circuits nécessaires pour programmer et alimenter la carte.

Si c’est la première fois que vous utilisez l’écran jaune bon marché ESP32, assurez-vous de suivre notre guide de démarrage :
4) Installez les bibliothèques TFT et LVGL
LVGL (Light and Versatile Graphics Library) est une bibliothèque graphique gratuite et open source qui fournit une large gamme d’éléments graphiques faciles à utiliser pour vos projets de microcontrôleurs nécessitant une interface utilisateur graphique (GUI).

Suivez le didacticiel suivant pour installer et configurer les bibliothèques requises pour utiliser LVGL pour l’écran jaune bon marché ESP32 à l’aide de l’IDE Arduino.
1) Préparation des cartes réceptrices
Suivez cette procédure pour chacune de vos cartes réceptrices ESP32. Dans cet exemple, nous utilisons deux cartes réceptrices ESP32, mais vous ne pouvez en utiliser qu’une, trois ou plus.

Obtenir l’adresse MAC des cartes ESP32
Pour communiquer via ESP-NOW, vous devez connaître l’adresse MAC de vos cartes réceptrices. Pour obtenir l’adresse MAC de vos cartes, téléchargez le code suivant sur chacune de vos cartes.
/*
Rui Santos & Sara Santos - Raspberryme.com
Complete project details at https://Raspberryme.com/get-change-esp32-esp8266-mac-address-arduino/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include
#include
void readMacAddress(){
uint8_t baseMac[6];
esp_err_t ret = esp_wifi_get_mac(WIFI_IF_STA, baseMac);
if (ret == ESP_OK) {
Serial.printf("%02x:%02x:%02x:%02x:%02x:%02x\n",
baseMac[0], baseMac[1], baseMac[2],
baseMac[3], baseMac[4], baseMac[5]);
} else {
Serial.println("Failed to read MAC address");
}
}
void setup(){
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.STA.begin();
Serial.print("[DEFAULT] ESP32 Board MAC Address: ");
readMacAddress();
}
void loop(){
}
Afficher le code brut
Après le téléchargement, ouvrez le moniteur série et réinitialisez l’ESP32. Son adresse MAC sera imprimée. Enregistrez-le car vous en aurez besoin sur le code de l’expéditeur ESP32 CYD.
Obtenez l’adresse MAC de toutes vos cartes réceptrices.
Par exemple, dans mon cas, j’obtiens :
- Carte récepteur ESP32 1: 30:ae:a4:f6:7d:4c
- Carte récepteur ESP32 2 : 34:85:18:40:2f:cc

Câblage du circuit
Nous contrôlerons deux GPIO sur chaque carte ESP32. Nous contrôlerons les GPIO 2 et 4 sur notre carte ESP32 DOIT, et les GPIO 20 et 21 sur notre ESP32S3. Vous pouvez contrôler n’importe quel autre GPIO.
Nous connecterons une LED à chaque GPIO que nous souhaitons contrôler.
| ESP32 #1 | ESP32 #2 |
| GPIO2 | GPIO20 |
| GPIO4 | GPIO21 |
Voici un exemple de schéma montrant comment connecter deux LED à votre carte.

Code – Cartes réceptrices ESP-NOW ESP32
Téléchargez le code suivant sur chacune de vos cartes réceptrices. Ce code fonctionnera pour tous les GPIO que vous souhaitez contrôler.
/*********
Rui Santos & Sara Santos - Raspberryme.com
Complete project details at https://Raspberryme.com/esp32-cyd-esp-now-control-multiple-boards/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*********/
#include
#include
// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
int gpio;
bool state;
} struct_message;
// Create a struct_message called myData
struct_message myData;
// callback function that will be executed when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
memcpy(&myData, incomingData, sizeof(myData));
Serial.print("Bytes received: ");
Serial.println(len);
Serial.print("Setting GPIO: ");
Serial.println(myData.gpio);
Serial.print("state to: ");
Serial.println(myData.state);
// Control GPIO
pinMode(myData.gpio, OUTPUT);
digitalWrite(myData.gpio, myData.state);
Serial.println();
}
void setup() {
// Initialize Serial Monitor
Serial.begin(115200);
// Initialize WIFI STA interface
WiFi.mode(WIFI_STA);
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Once ESPNow is successfully Init, we will register for recv CB to
// get recv packer info
esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));
}
void loop() {
}
Afficher le code brut
Comment fonctionne le code ?
Jetons un coup d’œil rapide au fonctionnement du code du récepteur. Vous pouvez également passer à la section suivante.
Inclure les bibliothèques
Tout d’abord, incluez les bibliothèques esp_now.h et WiFi.h (requises pour initialiser l’interface de la station Wi-Fi afin d’utiliser ESP-NOW).
#include
#include
Structure de données pour recevoir des données
Créez une structure pour recevoir les données. Nous l’appelons struct_message. Cette structure doit être la même que celle définie dans le schéma de l’expéditeur. La structure contiendra les données reçues via ESP-NOW. Les données contiennent le numéro GPIO que nous voulons contrôler (int gpio) et son état (bool state).
typedef struct struct_message {
int gpio;
bool state;
} struct_message;
Après avoir créé la structure, créez une variable struct_message appelée myData, où nous stockerons les données reçues via ESP-NOW.
struct_message myData;
Fonction de rappel de réception ESP-NOW
Créez une fonction de rappel qui est appelée lorsque l’ESP32 reçoit les données via ESP-NOW. La fonction s’appelle onDataRecv() et doit accepter plusieurs paramètres : l’adresse MAC de l’expéditeur, le message entrant (structure des données) et la longueur du message.
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
Cette ligne copie le contenu de la variable de données incomingData dans la variable de structure myData.
memcpy(&myData, incomingData, sizeof(myData));
Désormais, la structure myData contient deux variables avec les valeurs envoyées par la carte CYD émettrice. Vous pouvez accéder à la variable gpio avec myData.gpio et accéder à l’état avec myData.state. Nous imprimons ces données dans le Serial Monitor.
Serial.print("Bytes received: ");
Serial.println(len);
Serial.print("Setting GPIO: ");
Serial.println(myData.gpio);
Serial.print("state to: ");
Serial.println(myData.state);
Après avoir reçu les données, nous contrôlons le GPIO spécifié avec l’état reçu. Tout d’abord, nous définissons le GPIO comme SORTIE.
pinMode(myData.gpio, OUTPUT);
Et enfin, on le contrôle à l’aide de la fonction digitalWrite() et en passant en argument le GPIO (myData.gpio) et l’état (myData.state)
digitalWrite(myData.gpio, myData.state);
installation()
Dans setup(), initialisez le Serial Monitor.
Serial.begin(115200);
Configurez l’appareil comme station Wi-Fi.
WiFi.mode(WIFI_STA);
Initialisez ESP-NOW :
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
Enregistrez la fonction de rappel qui sera appelée lorsque les données seront reçues. Dans ce cas, nous nous inscrivons à la fonction OnDataRecv() créée précédemment.
esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));
boucle()
Puisque nous nous sommes inscrits à une fonction de rappel lors de la réception de données, l’ESP32 écoutera les paquets ESP-NOW et exécutera automatiquement la fonction de rappel lorsqu’un paquet est reçu.
Dans ce cas, nous n’avons rien besoin d’ajouter à la boucle(). Il est vide, mais vous pouvez ajouter n’importe quel code dont vous avez besoin pour exécuter votre carte.
void loop() {
}
2) Carte contrôleur/expéditeur ESP32 CYD ESP-NOW
La carte CYD agira comme un contrôleur ESP-NOW (expéditeur) qui enverra des données pour contrôler les GPIO de deux autres cartes ESP32.
Chaque GPIO aura un commutateur pour contrôler son état. Nous aurons deux onglets différents, un pour chaque tableau. Contrôler plus de tableaux est aussi simple que de créer plus d’onglets.

Avant de télécharger le code suivant sur votre carte CYD, vous devez insérer l’adresse MAC des cartes réceptrices dans les lignes suivantes :
// REPLACE WITH YOUR RECEIVER'S MAC Address
uint8_t board1Address[] = {0x30, 0xAE, 0xA4, 0xF6, 0x7D, 0x4C};
uint8_t board2Address[] = {0x34, 0x85, 0x18, 0x40, 0x2F, 0xCC};
/* Rui Santos & Sara Santos - Raspberryme.com - https://Raspberryme.com/esp32-cyd-esp-now-control-multiple-boards/
THIS EXAMPLE WAS TESTED WITH THE FOLLOWING HARDWARE:
1) ESP32-2432S028R 2.8 inch 240×320 also known as the Cheap Yellow Display (CYD): https://makeradvisor.com/tools/cyd-cheap-yellow-display-esp32-2432s028r/
SET UP INSTRUCTIONS: https://Raspberryme.com/cyd-lvgl/
2) REGULAR ESP32 Dev Board + 2.8 inch 240x320 TFT Display: https://makeradvisor.com/tools/2-8-inch-ili9341-tft-240x320/ and https://makeradvisor.com/tools/esp32-dev-board-wi-fi-bluetooth/
SET UP INSTRUCTIONS: https://Raspberryme.com/esp32-tft-lvgl/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
/* Install the "lvgl" library version 9.X by kisvegabor to interface with the TFT Display - https://lvgl.io/
*** IMPORTANT: lv_conf.h available on the internet will probably NOT work with the examples available at Raspberryme.com ***
*** YOU MUST USE THE lv_conf.h FILE PROVIDED IN THE LINK BELOW IN ORDER TO USE THE EXAMPLES FROM RANDOM NERD TUTORIALS ***
FULL INSTRUCTIONS AVAILABLE ON HOW CONFIGURE THE LIBRARY: https://Raspberryme.com/cyd-lvgl/ or https://Raspberryme.com/esp32-tft-lvgl/ */
#include
/* Install the "TFT_eSPI" library by Bodmer to interface with the TFT Display - https://github.com/Bodmer/TFT_eSPI
*** IMPORTANT: User_Setup.h available on the internet will probably NOT work with the examples available at Raspberryme.com ***
*** YOU MUST USE THE User_Setup.h FILE PROVIDED IN THE LINK BELOW IN ORDER TO USE THE EXAMPLES FROM RANDOM NERD TUTORIALS ***
FULL INSTRUCTIONS AVAILABLE ON HOW CONFIGURE THE LIBRARY: https://Raspberryme.com/cyd-lvgl/ or https://Raspberryme.com/esp32-tft-lvgl/ */
#include
// Install the "XPT2046_Touchscreen" library by Paul Stoffregen to use the Touchscreen - https://github.com/PaulStoffregen/XPT2046_Touchscreen - Note: this library doesn't require further configuration
#include
#include
#include
// Touchscreen pins
#define XPT2046_IRQ 36 // T_IRQ
#define XPT2046_MOSI 32 // T_DIN
#define XPT2046_MISO 39 // T_OUT
#define XPT2046_CLK 25 // T_CLK
#define XPT2046_CS 33 // T_CS
SPIClass touchscreenSPI = SPIClass(VSPI);
XPT2046_Touchscreen touchscreen(XPT2046_CS, XPT2046_IRQ);
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
// Touchscreen coordinates: (x, y) and pressure (z)
int x, y, z;
#define DRAW_BUF_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];
// YOU MUST REPLACE WITH YOUR RECEIVER'S MAC Address
uint8_t board1Address[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint8_t board2Address[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
// Structure example to send data
// Must match the receiver structure
typedef struct struct_message {
int gpio;
bool state;
} struct_message;
// Create a struct_message for each board
struct_message board1Data;
struct_message board2Data;
esp_now_peer_info_t peerInfo;
// User data structure for switches
typedef struct {
int gpio;
struct_message* data;
uint8_t* targetAddress;
} SwitchUserData;
// Callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
char macStr[18];
Serial.print("Packet to: ");
// Copies the sender mac address to a string
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.print(macStr);
Serial.print(" send status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
// Function to send messages via ESP-NOW
void sendEspNowMessage(const uint8_t *boardMacAddress, const struct_message &message) {
size_t messageSize = sizeof(message);
esp_err_t result = esp_now_send(boardMacAddress, (const uint8_t *) &message, messageSize);
if (result == ESP_OK) {
Serial.println("Sent with success");
}
else if (result == ESP_ERR_ESPNOW_NOT_INIT) {
Serial.println("Error: ESP-NOW not initialized");
}
else if (result == ESP_ERR_ESPNOW_NOT_FOUND) {
Serial.println("Error: Peer not found");
}
else {
Serial.printf("Error sending the data: %d\n", result);
}
}
// If logging is enabled, it will inform the user about what is happening in the library
void log_print(lv_log_level_t level, const char * buf) {
LV_UNUSED(level);
Serial.println(buf);
Serial.flush();
}
// Get the Touchscreen data
void touchscreen_read(lv_indev_t * indev, lv_indev_data_t * data) {
// Checks if Touchscreen was touched, and prints X, Y and Pressure (Z)
if(touchscreen.tirqTouched() && touchscreen.touched()) {
// Get Touchscreen points
TS_Point p = touchscreen.getPoint();
// Advanced Touchscreen calibration, LEARN MORE » https://Raspberryme.com/touchscreen-calibration/
float alpha_x, beta_x, alpha_y, beta_y, delta_x, delta_y;
// REPLACE WITH YOUR OWN CALIBRATION VALUES » https://Raspberryme.com/touchscreen-calibration/
alpha_x = -0.000;
beta_x = 0.090;
delta_x = -33.771;
alpha_y = 0.066;
beta_y = 0.000;
delta_y = -14.632;
x = alpha_y * p.x + beta_y * p.y + delta_y;
// clamp x between 0 and SCREEN_WIDTH - 1
x = max(0, x);
x = min(SCREEN_WIDTH - 1, x);
y = alpha_x * p.x + beta_x * p.y + delta_x;
// clamp y between 0 and SCREEN_HEIGHT - 1
y = max(0, y);
y = min(SCREEN_HEIGHT - 1, y);
// Basic Touchscreen calibration points with map function to the correct width and height
//x = map(p.x, 200, 3700, 1, SCREEN_WIDTH);
//y = map(p.y, 240, 3800, 1, SCREEN_HEIGHT);
z = p.z;
data->state = LV_INDEV_STATE_PRESSED;
// Set the coordinates
data->point.x = x;
data->point.y = y;
// Print Touchscreen info about X, Y and Pressure (Z) on the Serial Monitor
Serial.print("X = ");
Serial.print(x);
Serial.print(" | Y = ");
Serial.print(y);
Serial.print(" | Pressure = ");
Serial.print(z);
Serial.println();
}
else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
// Callback that is triggered when any toggle switch changes state
static void toggle_switch_event_handler(lv_event_t * e) {
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_VALUE_CHANGED) {
lv_obj_t * toggle_switch = (lv_obj_t*) lv_event_get_target(e);
void* user_data = lv_obj_get_user_data(toggle_switch);
if (user_data == NULL) return;
SwitchUserData* sw_data = (SwitchUserData*)user_data;
bool is_on = lv_obj_has_state(toggle_switch, LV_STATE_CHECKED);
LV_LOG_USER("GPIO %d State: %s", sw_data->gpio, is_on ? "On" : "Off");
sw_data->data->gpio = sw_data->gpio;
sw_data->data->state = is_on;
sendEspNowMessage(sw_data->targetAddress, *(sw_data->data));
}
}
void lv_create_main_gui(void) {
// Create a Tab view object
lv_obj_t * tabview = lv_tabview_create(lv_screen_active());
lv_tabview_set_tab_bar_size(tabview, 40);
// Add 2 tabs
lv_obj_t * tab1 = lv_tabview_add_tab(tabview, "Board #1");
lv_obj_t * tab2 = lv_tabview_add_tab(tabview, "Board #2");
// TAB 1 - Board #1 - Add 2 Toggle Switches
lv_obj_t * switch_label1 = lv_label_create(tab1);
lv_label_set_text(switch_label1, "GPIO 2");
lv_obj_align(switch_label1, LV_ALIGN_CENTER, 0, -80);
lv_obj_t * switch1 = lv_switch_create(tab1);
lv_obj_align(switch1, LV_ALIGN_CENTER, 0, -40);
static SwitchUserData switch1_data = {2, &board1Data, board1Address};
lv_obj_set_user_data(switch1, &switch1_data);
lv_obj_add_event_cb(switch1, toggle_switch_event_handler, LV_EVENT_ALL, NULL);
lv_obj_t * switch_label2 = lv_label_create(tab1);
lv_label_set_text(switch_label2, "GPIO 4");
lv_obj_align(switch_label2, LV_ALIGN_CENTER, 0, 10);
lv_obj_t * switch2 = lv_switch_create(tab1);
lv_obj_align(switch2, LV_ALIGN_CENTER, 0, 50);
static SwitchUserData switch2_data = {4, &board1Data, board1Address};
lv_obj_set_user_data(switch2, &switch2_data);
lv_obj_add_event_cb(switch2, toggle_switch_event_handler, LV_EVENT_ALL, NULL);
// TAB 2 - Board #2 - Add 2 Toggle Switches
lv_obj_t * switch_label3 = lv_label_create(tab2);
lv_label_set_text(switch_label3, "GPIO 20");
lv_obj_align(switch_label3, LV_ALIGN_CENTER, 0, -80);
lv_obj_t * switch3 = lv_switch_create(tab2);
lv_obj_align(switch3, LV_ALIGN_CENTER, 0, -40);
static SwitchUserData switch3_data = {20, &board2Data, board2Address};
lv_obj_set_user_data(switch3, &switch3_data);
lv_obj_add_event_cb(switch3, toggle_switch_event_handler, LV_EVENT_ALL, NULL);
lv_obj_t * switch_label4 = lv_label_create(tab2);
lv_label_set_text(switch_label4, "GPIO 21");
lv_obj_align(switch_label4, LV_ALIGN_CENTER, 0, 10);
lv_obj_t * switch4 = lv_switch_create(tab2);
lv_obj_align(switch4, LV_ALIGN_CENTER, 0, 50);
static SwitchUserData switch4_data = {21, &board2Data, board2Address};
lv_obj_set_user_data(switch4, &switch4_data);
lv_obj_add_event_cb(switch4, toggle_switch_event_handler, LV_EVENT_ALL, NULL);
}
void setup() {
String LVGL_Arduino = String("LVGL Library Version: ") + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.begin(115200);
Serial.println(LVGL_Arduino);
// Set device as a Wi-Fi Station
WiFi.mode(WIFI_STA);
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Once ESPNow is successfully Init, we will register for Send CB to
// get the status of Trasnmitted packet
esp_now_register_send_cb(esp_now_send_cb_t(OnDataSent));
// Register peers
peerInfo.channel = 0;
peerInfo.encrypt = false;
// Register first peer
memcpy(peerInfo.peer_addr, board1Address, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
// Register second peer
memcpy(peerInfo.peer_addr, board2Address, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
// Start LVGL
lv_init();
// Register print function for debugging
lv_log_register_print_cb(log_print);
// Start the SPI for the touchscreen and init the touchscreen
touchscreenSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
touchscreen.begin(touchscreenSPI);
// Set the Touchscreen rotation in landscape mode
// Note: in some displays, the touchscreen might be upside down, so you might need to set the rotation to 0: touchscreen.setRotation(0);
touchscreen.setRotation(2);
// Create a display object
lv_display_t * disp;
// Initialize the TFT display using the TFT_eSPI library
disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270);
// Initialize an LVGL input device object (Touchscreen)
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
// Set the callback function to read Touchscreen input
lv_indev_set_read_cb(indev, touchscreen_read);
// Function to draw the GUI
lv_create_main_gui();
}
void loop() {
lv_task_handler(); // let the GUI do its work
lv_tick_inc(5); // tell LVGL how much time has passed
delay(5); // let this time pass
}
Afficher le code brut
Nous expliquons comment créer des onglets, des champs de texte, des boutons et bien plus encore dans notre LVGL pour l’eBook ESP32. Découvrez-le ici : Apprenez LVGL : Créez des interfaces graphiques pour les projets ESP32 (eBook).
Comment fonctionne le code ?
Voyons maintenant rapidement comment fonctionne le code ou passons à la section de démonstration.
Y compris les bibliothèques
Dans tous vos croquis LVGL, vous devez inclure les bibliothèques lvgl.h et TFT_eSPI.h à afficher à l’écran.
#include
#include
Nous utilisons également l’écran tactile pour choisir entre les onglets et cliquer sur les commutateurs. Nous devons donc inclure la bibliothèque d’écrans tactiles.
#include
Vous devez inclure les bibliothèques WiFi et esp_now pour utiliser ESP-NOW.
#include
#include
Initialiser l’écran tactile
Les lignes suivantes définissent le brochage de l’écran tactile :
#define XPT2046_IRQ 36
#define XPT2046_MOSI 32
#define XPT2046_MISO 39
#define XPT2046_CLK 25
#define XPT2046_CS 33
Créez un touchscreenSPI et des instances d’écran tactile :
SPIClass touchscreenSPI = SPIClass(VSPI);
XPT2046_Touchscreen touchscreen(XPT2046_CS, XPT2046_IRQ);
Définir la largeur et la hauteur de l’affichage
Vous devez définir la largeur et la hauteur d’affichage dans tous vos croquis qui utilisent LVGL. Si vous utilisez l’affichage recommandé, la taille est de 240 × 320.
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
Variables de l’écran tactile X, Y et Z
Définissez les variables pour contenir les coordonnées tactiles et la pression tactile.
// Touchscreen coordinates: (x, y) and pressure (z)
int x, y, z;
Création d’un tampon de dessin
Vous devez créer un tampon pour dessiner sur l’écran comme suit :
#define DRAW_BUF_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];
Adresse MAC des cartes réceptrices
Modifiez les lignes suivantes avec l’adresse MAC de vos cartes réceptrices. Par exemple, dans mon cas :
- Carte récepteur ESP32 1: 30:ae:a4:f6:7d:4c
- Carte récepteur ESP32 2 : 34:85:18:40:2f:cc
Donc, ce sera comme ça :
// REPLACE WITH YOUR RECEIVER'S MAC Address
uint8_t board1Address[] = {0x30, 0xAE, 0xA4, 0xF6, 0x7D, 0x4C};
uint8_t board2Address[] = {0x34, 0x85, 0x18, 0x40, 0x2F, 0xCC};
Structure de données pour envoyer des données
Créez une structure de données pour envoyer des données aux autres cartes ESP32 afin de contrôler leurs GPIO. Il doit s’agir de la même structure que celle définie dans les cartes réceptrices. La structure contient le GPIO à contrôler (int gpio) et l’état (bool state), soit ON (true) soit OFF (false).
// Structure example to send data
// Must match the receiver structure
typedef struct struct_message {
int gpio;
bool state;
} struct_message;
Maintenant, créez deux variables de type struct_message, une pour chaque carte : board1Data et board2Data.
struct_message board1Data;
struct_message board2Data;
Informations sur les pairs ESP-NOW
Créez une variable de type esp_now_peer_info qui sera utilisée pour contenir des données sur les pairs ESP-NOW (les cartes réceptrices).
esp_now_peer_info_t peerInfo;
Structure des commutateurs LVGL
Créez une structure de données pour contenir les données sur les commutateurs appelée SwitchUserData. Ce type de structure de données contiendra le GPIO que nous voulons contrôler (afin que nous sachions quel commutateur a été basculé), une structure de type struct_message que nous avons créée précédemment et l’adresse MAC de la carte réceptrice (afin que nous sachions à quelle carte appartient ce commutateur).
// User data structure for switches
typedef struct {
int gpio;
struct_message* data;
uint8_t* targetAddress;
} SwitchUserData;
Pour mieux comprendre cette structure, voici un schéma simple.

onDatSent() Fonction de rappel ESP-NOW
La fonction onDataSent() s’exécutera lorsque la carte enverra un message via ESP-NOW. Il affiche simplement un message de débogage.
// Callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
char macStr[18];
Serial.print("Packet to: ");
// Copies the sender mac address to a string
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.print(macStr);
Serial.print(" send status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
Fonction pour envoyer des messages ESP-NOW
La fonction sendEspNowMessage() envoie un message via ESP-NOW. Il accepte comme arguments l’adresse MAC de la carte réceptrice et les données à envoyer (une variable de type struct_message).
// Function to send messages via ESP-NOW
void sendEspNowMessage(const uint8_t *boardMacAddress, const struct_message &message) {
size_t messageSize = sizeof(message);
esp_err_t result = esp_now_send(boardMacAddress, (const uint8_t *) &message, messageSize);
if (result == ESP_OK) {
Serial.println("Sent with success");
}
else if (result == ESP_ERR_ESPNOW_NOT_INIT) {
Serial.println("Error: ESP-NOW not initialized");
}
else if (result == ESP_ERR_ESPNOW_NOT_FOUND) {
Serial.println("Error: Peer not found");
}
else {
Serial.printf("Error sending the data: %d\n", result);
}
}
Fonction de débogage
Pour le débogage avec la bibliothèque LVGL, vous devez utiliser la fonction log_print(). Il est défini ci-dessous. Incluez-le dans tous vos croquis avant le setup().
// If logging is enabled, it will inform the user about what is happening in the library
void log_print(lv_log_level_t level, const char * buf) {
LV_UNUSED(level);
Serial.println(buf);
Serial.flush();
}
Fonction de rappel sur écran tactile
La fonction de rappel touchscreen_read() sera appelée lorsqu’un toucher est détecté sur l’écran tactile.
// Get the Touchscreen data
void touchscreen_read(lv_indev_t * indev, lv_indev_data_t * data) {
// Checks if Touchscreen was touched, and prints X, Y and Pressure (Z)
if(touchscreen.tirqTouched() && touchscreen.touched()) {
// Get Touchscreen points
TS_Point p = touchscreen.getPoint();
(....)
}
}
Fonction principale de LVGL – Dessiner l’interface graphique
La fonction lv_create_main_gui() dessinera l’interface graphique sur l’écran. Cette fonction sera appelée plus tard dans setup().
void lv_create_main_gui(void) {
Tout d’abord, nous créons une vue d’onglets dans laquelle nous ajouterons nos onglets.
// Create a Tab view object
lv_obj_t * tabview;
tabview = lv_tabview_create(lv_screen_active());
lv_tabview_set_tab_bar_size(tabview, 40);
Ensuite, nous créons les onglets que nous souhaitons afficher et les ajoutons à la vue des onglets. Nous créons deux onglets, un pour chaque tableau.
lv_obj_t * tab1 = lv_tabview_add_tab(tabview, "BOARD #1");
lv_obj_t * tab2 = lv_tabview_add_tab(tabview, "BOARD #2");
Créer les interrupteurs à bascule
Ensuite, nous créons une étiquette pour le premier commutateur et l’ajoutons à tab1.
lv_obj_t * switch_label1 = lv_label_create(tab1);
Nous définissons l’étiquette de texte sur GPIO 2.
lv_label_set_text(switch_label1, "GPIO 2");
Et enfin, on l’aligne sur l’écran.
lv_obj_align(switch_label1, LV_ALIGN_CENTER, 0, -80);
Ensuite, nous créons le commutateur réel sur tab1 et l’alignons sur l’écran sous l’étiquette.
lv_obj_t * switch1 = lv_switch_create(tab1);
lv_obj_align(switch1, LV_ALIGN_CENTER, 0, -40);
Ensuite, nous créons une structure SwitchUserData (définie précédemment) appelée switch1_data contenant le numéro GPIO du commutateur, la structure de données à envoyer et l’adresse MAC de la carte réceptrice contrôlée par ce commutateur.
static SwitchUserData switch1_data = {2, &board1Data, board1Address};
Nous pouvons attacher des données supplémentaires au commutateur afin qu’elles soient accessibles dans la fonction de rappel qui s’exécute lorsque le commutateur est basculé. Dans ce cas, nous attachons la structure switch1_data, qui stocke le GPIO que le commutateur contrôle, la structure de données à envoyer et l’adresse MAC de la carte réceptrice. Cela se fait à l’aide de la fonction lv_obj_set_user_data().
lv_obj_set_user_data(switch1, &switch1_data);
Enfin, nous attachons la fonction de rappel au commutateur en utilisant lv_obj_add_event_cb().
lv_obj_add_event_cb(switch1, toggle_switch_event_handler, LV_EVENT_ALL, NULL);
Nous suivons une procédure similaire pour l’autre commutateur. Cet extrait contrôle GPIO 4.
lv_obj_t * switch_label2 = lv_label_create(tab1);
lv_label_set_text(switch_label2, "GPIO 4");
lv_obj_align(switch_label2, LV_ALIGN_CENTER, 0, 10);
lv_obj_t * switch2 = lv_switch_create(tab1);
lv_obj_align(switch2, LV_ALIGN_CENTER, 0, 50);
static SwitchUserData switch2_data = {4, &board1Data, board1Address};
lv_obj_set_user_data(switch2, &switch2_data);
lv_obj_add_event_cb(switch2, toggle_switch_event_handler, LV_EVENT_ALL, NULL);
Ensuite, nous créons un autre onglet avec deux autres interrupteurs à bascule. Ceux-ci contrôleront les GPIO 20 et 21 de l’autre carte réceptrice ESP32 (ESP32 #2).
// TAB 2 - Board #2 - Add 2 Toggle Switches
lv_obj_t * switch_label3 = lv_label_create(tab2);
lv_label_set_text(switch_label3, "GPIO 20");
lv_obj_align(switch_label3, LV_ALIGN_CENTER, 0, -80);
lv_obj_t * switch3 = lv_switch_create(tab2);
lv_obj_align(switch3, LV_ALIGN_CENTER, 0, -40);
static SwitchUserData switch3_data = {20, &board2Data, board2Address};
lv_obj_set_user_data(switch3, &switch3_data);
lv_obj_add_event_cb(switch3, toggle_switch_event_handler, LV_EVENT_ALL, NULL);
lv_obj_t * switch_label4 = lv_label_create(tab2);
lv_label_set_text(switch_label4, "GPIO 21");
lv_obj_align(switch_label4, LV_ALIGN_CENTER, 0, 10);
lv_obj_t * switch4 = lv_switch_create(tab2);
lv_obj_align(switch4, LV_ALIGN_CENTER, 0, 50);
static SwitchUserData switch4_data = {21, &board2Data, board2Address};
lv_obj_set_user_data(switch4, &switch4_data);
lv_obj_add_event_cb(switch4, toggle_switch_event_handler, LV_EVENT_ALL, NULL);
Nous gérons les événements de commutateur à bascule dans la fonction toggle_switch_event_handler(). Tous les interrupteurs à bascule déclenchent la même fonction, mais attachent des données différentes au commutateur (différentes variables de structure de type SwitchUserData). Alternativement, nous aurions pu créer une fonction différente pour chaque commutateur.
toggle_switch_event_handler – Fonction de rappel des commutateurs à bascule
Lorsqu’un événement se produit sur le commutateur, la fonction toggle_switch_event_handler() s’exécute.
static void toggle_switch_event_handler(lv_event_t * e) {
Dans les lignes suivantes, nous obtenons l’événement qui a déclenché la fonction de rappel. Il est enregistré dans la variable code.
lv_event_code_t code = lv_event_get_code(e);
Nous vérifions si l’événement LV_EVENT_VALUE_CHANGED s’est produit (si la valeur du commutateur à bascule a changé).
if(code == LV_EVENT_VALUE_CHANGED) {
Nous récupérons l’objet qui a déclenché l’événement, afin de savoir de quel commutateur il s’agissait.
lv_obj_t * toggle_switch = (lv_obj_t*) lv_event_get_target(e);
Plus d’informations sur la création d’interrupteurs à bascule dans la documentation LVGL.
Nous avons accès aux données personnalisées que nous avons précédemment attachées au commutateur. Nous l’enregistrons dans la variable de structure sw_data.
void* user_data = lv_obj_get_user_data(toggle_switch);
if (user_data == NULL) return;
SwitchUserData* sw_data = (SwitchUserData*)user_data;
Nous vérifions si l’interrupteur à bascule est ON ou OFF.
bool is_on = lv_obj_has_state(toggle_switch, LV_STATE_CHECKED);
Si LV_STATE_CHECKED est vrai (l’interrupteur à bascule est coché), il renvoie vrai. Sinon, il renvoie faux.
Maintenant, nous connaissons l’état du commutateur à bascule et quel état nous devons envoyer à la carte réceptrice pour contrôler le GPIO. L’état est désormais enregistré dans la variable is_on.
Maintenant, nous pouvons mettre à jour notre structure de données qui se trouve à l’intérieur de la structure sw_data avec le GPIO que nous voulons contrôler et l’état :
sw_data->data->gpio = sw_data->gpio;
sw_data->data->state = is_on;
Maintenant, nous pouvons enfin appeler sendEspNowMessage() pour envoyer des données aux autres cartes. Passez en arguments l’adresse de la carte et les données que nous voulons envoyer. Nous voulons envoyer la structure de données qui se trouve à l’intérieur du sw_data.
sendEspNowMessage(sw_data->targetAddress, *(sw_data->data));
installation()
Dans setup(), incluez les lignes suivantes pour le débogage. Ceux-ci imprimeront la version de LVGL que vous utilisez. Vous devez utiliser la version 9.
String LVGL_Arduino = String("LVGL Library Version: ") + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.begin(115200);
Serial.println(LVGL_Arduino);
Initialiser ESP-NOW
Activez l’interface Wi-Fi et initialisez ESP-NOW.
// Set device as a Wi-Fi Station
WiFi.mode(WIFI_STA);
// Initialize ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
Fonction de rappel ESP-NOW
Inscrivez-vous à une fonction de rappel d’envoi ESP-NOW. Dans ce cas, il s’agit du OnDataSent que nous avons vu précédemment.
esp_now_register_send_cb(esp_now_send_cb_t(OnDataSent));
Enregistrez les cartes réceptrices en tant que pairs ESP-NOW.
// Register first peer
memcpy(peerInfo.peer_addr, board1Address, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
// Register second peer
memcpy(peerInfo.peer_addr, board2Address, 6);
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
Initialiser la bibliothèque LVGL
Initialisez la bibliothèque LVGL en appelant la fonction lv_init() dans setup().
// Start LVGL
lv_init();
Fonction de débogage d’enregistrement
Enregistrez votre fonction log_print() déclarée précédemment comme fonction associée au débogage LVGL.
// Register print function for debugging
lv_log_register_print_cb(log_print);
Démarrer l’écran tactile
Démarrez le SPI pour l’écran tactile et initialisez l’écran tactile.
touchscreenSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
touchscreen.begin(touchscreenSPI);
touchscreen.setRotation(2);
Remarque : sur certains écrans, l’écran tactile peut être à l’envers, vous devrez donc peut-être définir la rotation sur 0 : touchscreen.setRotation(0);
Création d’un objet d’affichage
Créez l’objet d’affichage et initialisez l’affichage TFT à l’aide de la bibliothèque TFT_eSPI.
// Create a display object
lv_display_t * disp;
// Initialize the TFT display using the TFT_eSPI library
disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270);
Initialisez un objet de périphérique d’entrée LVGL (écran tactile) et définissez la fonction de rappel qui sera déclenchée lorsque vous cliquez sur l’écran tactile. C’est la fonction touchscreen_read() que nous avons créée précédemment.
// Initialize an LVGL input device object (Touchscreen)
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
// Set the callback function to read Touchscreen input
lv_indev_set_read_cb(indev, touchscreen_read);
Dessiner l’interface graphique
Appelez la fonction lv_create_main_gui() pour dessiner l’interface graphique de votre écran tactile :
lv_create_main_gui();
boucle()
Gardez LVGL en cours d’exécution en appelant les fonctions suivantes dans la boucle().
void loop() {
lv_task_handler(); // let the GUI do its work
lv_tick_inc(5); // tell LVGL how much time has passed
delay(5); // let this time pass
}
3) Démonstration
Téléchargez le code sur votre tableau CYD. Accédez à Outils > Carte et sélectionnez ESP32 > Module de développement ESP32. Ensuite, sélectionnez le bon port COM dans Outils > Port.
Accédez à Outils > Schéma de partition > choisissez tout ce qui contient plus de 1,4 Mo d’application, par exemple : « Énorme application (3 Mo sans OTA/1 Mo SPIFFS »).

Enfin, cliquez sur le bouton de téléchargement.

Avec les cartes réceptrices ESP32 exécutant le code du récepteur ESP-NOW, vous pouvez désormais utiliser l’écran tactile CYD pour contrôler leurs GPIO. Vous pouvez changer d’onglet en utilisant la navigation en haut de l’écran pour basculer entre le tableau n°1 et le tableau n°2.

Vous pouvez regarder la démonstration vidéo rapide suivante.
Conclusion
Dans ce didacticiel, vous avez appris à contrôler plusieurs sorties de cartes ESP32 avec la carte CYD à l’aide du protocole de communication ESP-NOW.
Nous avons un autre tutoriel ESP-NOW avec la carte CYD qui reçoit les données de plusieurs cartes ESP32 et les affiche sur différentes tables :
Nous espérons que vous avez trouvé ce tutoriel utile. Si vous souhaitez en savoir plus sur la création d’interfaces utilisateur graphiques à l’aide de la bibliothèque LVGL avec l’ESP32, consultez notre eBook :
Autres guides que vous aimeriez lire :
Pour en savoir plus sur l’ESP32, assurez-vous de consulter nos ressources :
Cette vidéo vous emmène dans l’histoire de Raspberry Pi :

-
ELEGOO 3PCS Carte de Développement ESP-32 Type-C, 2,4 GHz WiFi + Bluetooth
-
Ulegqin ESP32 NodeMCU mit CH340 Cartes de développement, ESP32 30 Pins ESP ESP-32-WROOM-32D Module 2.4 GHz WiFi + Bluetooth 2-in-1 Faible consommation Typ-C Interface Compatible avec Arduino(3 pcs)
