Le CrowPanel 1.28-inch HMI ESP32 Rotary Display par Elecrow est un module compact et rond qui intègre un microcontrôleur ESP32-S3, un écran tactile circulaire IPS de 1,28 pouce avec une résolution de 240×240, un anneau de LED RGB, et un encodeur rotatif avec bouton-poussoir dans une seule unité.
Dans ce tutoriel, nous allons parcourir les étapes essentielles pour commencer avec le CrowPanel ; de la configuration initiale au test de ses capacités d’affichage et d’entrée. Vous apprendrez à contrôler les LED RGB, à lire les données de l’encodeur rotatif pour une saisie utilisateur fluide, et à capturer les événements tactiles de l’écran circulaire à l’aide d’exemples de code simples.
Pièces requises
Vous aurez seulement besoin d’une carte CrowPanel 1.28-inch HMI ESP32 Rotary Display. Le module d’affichage est livré avec un câble USB et un connecteur GPIO, aucun matériel supplémentaire n’est nécessaire.

CrowPanel 1.28inch-HMI ESP32 Rotary Display
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
Matériel du CrowPanel 1.28inch-HMI ESP32 Rotary Display
Le CrowPanel 1.28-inch HMI ESP32 Rotary Displayest un module matériel intégré qui combine un écran IPS rond haute résolution, un écran tactile capacitif, un anneau de LED RGB et un encodeur rotatif avec fonction bouton-poussoir, le tout piloté par un microcontrôleur ESP32-S3.
Le module est conçu pour servir d’interface homme-machine compacte (HMI) pour des projets embarqués et IoT nécessitant un retour visuel et un contrôle interactif dans un encombrement réduit. L’image ci-dessous montre la face avant et le côté du module avec ses dimensions :

Microcontrôleur
Au cœur, le CrowPanel utilise l’ESP32-S3R8, un processeur Xtensa LX7 double cœur fonctionnant jusqu’à 240 MHz. Le microcontrôleur intègre le Wi-Fi (802.11 b/g/n) et le Bluetooth 5.0 LE, offrant des options de connectivité locale et distante. Il comprend 8 Mo de PSRAM et 16 Mo de mémoire flash.
Écran
L’écran est un panneau IPS circulaire de 1,28 pouce avec une résolution de 240 × 240 pixels et un support complet de 65 K couleurs. L’écran utilise la communication SPI pour le transfert de données et offre un large angle de vue jusqu’à 178 degrés. Un contrôleur tactile capacitif est intégré sur le même module, connecté via l’interface I²C, permettant une détection tactile multipoint précise et la prise en charge des gestes.
Pour l’entrée physique, le module inclut un encodeur rotatif avec un bouton-poussoir intégré. L’encodeur fournit un retour de position incrémental adapté à la navigation dans les menus, au réglage des paramètres ou aux contrôles de sélection rotative.
Bande de LED RGB
Le module comprend également une bande de LED RGB WS2812 intégrée composée de cinq LED adressables individuellement. Les LED sont connectées en série et peuvent être pilotées avec des bibliothèques courantes telles que Adafruit NeoPixel ou FastLED.
Chaque LED WS2812 fonctionne sous une alimentation de 5 V et consomme typiquement jusqu’à 60 mA à pleine luminosité lorsque tous les canaux de couleur (rouge, vert et bleu) sont activés à pleine intensité. Ainsi, lorsque les cinq LED sont allumées à pleine luminosité, la consommation totale peut atteindre environ 300 mA (5 × 60 mA).
Interfaces
La carte est alimentée via un connecteur USB-C 5 V et inclut un régulateur de tension 3,3 V embarqué pour un fonctionnement stable de l’ESP32 et des composants périphériques. Des interfaces supplémentaires telles que UART, I²C, SPI, FPC et headers GPIO sont disponibles pour l’extension externe, permettant aux développeurs de connecter capteurs, actionneurs ou autres dispositifs de contrôle.
Le module comprend aussi un bouton reset, un bouton boot pour le flashage du firmware, et une LED de statut pour un retour visuel pendant l’utilisation. L’image ci-dessous montre l’arrière du module avec les boutons et diverses interfaces :

Broches
Le tableau suivant montre quelles broches GPIO de l’ESP32-S3 sont connectées à quels composants du module. Vous aurez besoin de ces informations si vous souhaitez écrire du code pour le module.
| Composant | Interface / Fonction | Affectations des broches |
|---|---|---|
| Écran (GC9A01) | SPI | SCLK = 10, MOSI = 11, MISO = -1, DC = 3, CS = 9, RST = 14 |
| Rétroéclairage de l’écran | GPIO | SCREEN_BACKLIGHT_PIN = 46 |
| Écran tactile (CST816D) | I²C | SDA = 6, SCL = 7, INT = 5, RST = 13 |
| OLED (SSD1306) | I²C | SDA = 38, SCL = 39 |
| LED RGB (WS2812) | Sortie numérique | LED_PIN = 48, LED_NUM = 5 |
| Encodeur rotatif | GPIO | ENCODER_A_PIN = 45, ENCODER_B_PIN = 42, SWITCH_PIN = 41 |
| Indicateur d’alimentation | GPIO | POWER_LIGHT_PIN = 40 |
| Test E/S | GPIO | IO4, IO12 |
Spécifications techniques
Enfin, les spécifications techniques du module d’affichage CrowPanel sont résumées dans le tableau ci-dessous.
| Spécification | Détails |
|---|---|
| Microcontrôleur | ESP32-S3R8 double cœur Xtensa LX7 @ 240 MHz |
| Mémoire | 8 Mo PSRAM, 16 Mo Flash |
| Type d’écran | TFT IPS rond 1,28 pouce |
| Résolution | 240 × 240 pixels |
| Profondeur de couleur | 65 K couleurs |
| Angle de vue | 178 degrés |
| Interface tactile | Capacitive, communication I²C |
| Entrée rotative | Encodeur incrémental avec bouton-poussoir |
| Interfaces de communication | SPI, I²C, UART, GPIO |
| Connectivité | Wi-Fi 802.11 b/g/n, Bluetooth 5.0 LE |
| Alimentation | 5 V via USB-C (régulateur 3,3 V embarqué) |
| Interface de programmation | USB-C (compatible Arduino, ESP-IDF) |
| Fonctionnalités supplémentaires | Boutons Reset et Boot, bande de LED RGB |
Installation du Core ESP32
Si c’est votre premier projet avec une carte de la série ESP32, vous devrez d’abord installer le core ESP32. Si les cartes ESP32 sont déjà installées dans votre Arduino IDE, vous pouvez passer cette section.
Commencez par ouvrir la boîte de dialogue Préférences en sélectionnant “Preferences…” dans le menu “File”. Cela ouvrira la boîte de dialogue Préférences affichée ci-dessous.
Sous l’onglet Settings, vous trouverez un champ de saisie en bas de la boîte de dialogue intitulé “Additional boards manager URLs“ :

Dans ce champ, copiez l’URL suivante :
https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json
Cela permettra à l’Arduino IDE de savoir où trouver les bibliothèques du core ESP32. Ensuite, nous installerons les cartes ESP32 via le Boards Manager.
Ouvrez le Boards Manager via « Tools -> Boards -> Board Manager ». Le gestionnaire de cartes apparaîtra dans la barre latérale gauche. Tapez « ESP32 » dans le champ de recherche en haut et vous verrez deux types de cartes ESP32 ; les « Arduino ESP32 Boards » et les cartes « esp32 by Espressif ». Nous voulons les « esp32 libraries by Espressif ». Cliquez sur le bouton INSTALL et attendez la fin du téléchargement et de l’installation.

Sélection de la carte
Enfin, nous devons sélectionner une carte ESP32. Pour le CrowPanel 1.28inch-HMI ESP32 Rotary Display, choisissez le module générique « ESP32S3 Dev Module ». Pour cela, cliquez sur le menu déroulant puis sur « Select other board and port… »:

Cela ouvrira une boîte de dialogue où vous pouvez taper « esp32s3 dev » dans la barre de recherche. Vous verrez la carte « ESP32S3 Dev Module » sous Boards. Cliquez dessus, sélectionnez le port COM pour l’activer, puis cliquez sur OK :

Notez que vous devez connecter la carte via le câble USB à votre ordinateur avant de pouvoir sélectionner un port COM. Le module CrowPanel Display dispose de deux ports, un port USB et un port UART. Vous pouvez utiliser les deux pour programmer la carte, mais pour le port UART, vous aurez besoin d’un convertisseur USB-to-TTL, voir ci-dessous :

La méthode la plus simple est de connecter le câble USB fourni avec le CrowPanel Display Module directement au port marqué « USB » comme montré ci-dessous. Dans ce cas, vous n’avez pas besoin de convertisseur USB-to-TTL.

Paramètres de l’outil
Voici les paramètres que vous devez utiliser avec la carte. Vous les trouverez dans le menu Tools de votre Arduino IDE.

Le plus important pour les exemples de code dans les sections suivantes est de régler USB CDC on Boot sur « Enabled » et aussi de définir le mode USB sur « Hardware CDC and JTAG ». Cela vous permet d’envoyer ou de recevoir des données via le port série, ce qui est nécessaire pour le débogage.
Exemple de code : Interface série
Nous commençons par tester la communication série. Ouvrez votre Arduino IDE, saisissez le code suivant et téléversez-le sur le CrowPanel Display Module.
void setup() {
Serial.begin(115200);
}
void loop() {
Serial.println("Makerguides");
delay(2000);
}
Puis ouvrez le Moniteur Série et vous devriez voir le texte « Makerguides » s’afficher toutes les deux secondes. Sinon, vérifiez que vos paramètres Tools et le débit en bauds (115200) sont corrects.
Exemple de code : bande de LED RGB
Ensuite, essayons la LED RGB intégrée. Cependant, vous devrez d’abord installer la Adafruit_NeoPixel bibliothèque. Ouvrez le LIBRARY MANAGER dans votre Arduino IDE, tapez « Adafruit NeoPixel » dans la barre de recherche et installez la bibliothèque Adafruit NeoPixel par Adafruit :

Le CrowPanel Display Module possède une bande de LED RGB WS2812 intégrée avec cinq LED. L’exemple suivant montre comment initialiser la bande, définir une couleur et augmenter progressivement sa luminosité en utilisant la bibliothèque Adafruit NeoPixel.
#include <Adafruit_NeoPixel.h>
#define LED_PIN 48
#define LED_NUM 5
Adafruit_NeoPixel strip = Adafruit_NeoPixel(LED_NUM, LED_PIN, NEO_GRB + NEO_KHZ800);
void updateBrightness(int brightness) {
strip.setBrightness(brightness);
strip.show();
delay(100);
}
void initLEDs(uint8_t r, uint8_t g, uint8_t b) {
strip.begin();
strip.setBrightness(100);
for (int i = 0; i < LED_NUM; i++) {
strip.setPixelColor(i, strip.Color(r, g, b));
}
strip.show();
}
void setup() {
Serial.begin(115200);
initLEDs(255, 255, 50); // Yellow
}
void loop() {
for(int brightness=5; brightness<255; brightness+=5) {
updateBrightness(brightness);
Serial.printf("Brightness: %d\n", brightness);
}
}
Imports
Le programme commence par inclure la bibliothèque Adafruit_NeoPixel, qui fournit une interface simple pour contrôler les LED WS2812. Cette bibliothèque gère le timing strict requis par le protocole de communication à un fil des LED, vous permettant de vous concentrer sur la logique des couleurs et des animations plutôt que sur les détails bas niveau du timing.
#include <Adafruit_NeoPixel.h>
Constantes
Deux constantes sont définies pour spécifier le nombre de LED connectées et la broche GPIO utilisée pour les contrôler. Le CrowPanel connecte la bande WS2812 à la GPIO 48, et la bande contient cinq LED.
#define LED_PIN 48 #define LED_NUM 5
Création d’objet
Ensuite, un objet bande NeoPixel est créé. Le constructeur prend trois paramètres : le nombre de LED, la broche de contrôle, et la configuration du type de pixel. Le drapeau NEO_GRB + NEO_KHZ800 spécifie que chaque LED utilise l’ordre de couleur GRB (vert, rouge, bleu) et fonctionne à 800 kHz, fréquence standard pour les LED WS2812.
Adafruit_NeoPixel strip = Adafruit_NeoPixel(LED_NUM, LED_PIN, NEO_GRB + NEO_KHZ800);
Cet objet sera utilisé tout au long du programme pour contrôler les LED — définir les couleurs, ajuster la luminosité et envoyer les mises à jour de données.
La updateBrightness() fonction
Cette fonction auxiliaire ajuste dynamiquement la luminosité de la bande LED. La méthode setBrightness() accepte une valeur entre 0 (éteint) et 255 (luminosité maximale). Après avoir réglé la luminosité, la fonction show() doit être appelée pour envoyer les données mises à jour aux LED.
void updateBrightness(int brightness) {
strip.setBrightness(brightness);
strip.show();
delay(100);
}
Le court délai assure une transition fluide et visible entre les niveaux de luminosité, créant un effet de fondu progressif.
La initLEDs() fonction
Avant de pouvoir contrôler les LED, elles doivent être initialisées. La fonction begin() prépare la broche de données pour la communication, et setBrightness(100) définit un niveau de luminosité initial pour éviter de démarrer à pleine intensité.
La boucle for itère ensuite sur chacune des cinq LED, leur assignant une couleur via setPixelColor(). La méthode strip.Color(r, g, b) convertit les valeurs individuelles rouge, vert et bleu en un format couleur 24 bits requis par les LED.
void initLEDs(uint8_t r, uint8_t g, uint8_t b) {
strip.begin();
strip.setBrightness(100);
for (int i = 0; i < LED_NUM; i++) {
strip.setPixelColor(i, strip.Color(r, g, b));
}
strip.show();
}
Enfin, show() transmet les données de couleur à toutes les LED, les illuminant simultanément dans la couleur sélectionnée. Dans notre cas, ce sera jaune.
Setup
La fonction setup() s’exécute une fois lorsque l’ESP32 est alimenté ou réinitialisé. Elle commence par initialiser la communication série à 115200 bauds, permettant d’afficher des messages dans le moniteur série. Elle appelle ensuite initLEDs(255, 255, 50), qui allume les cinq LED en jaune doux.
void setup() {
Serial.begin(115200);
initLEDs(255, 255, 50); // Yellow
}
Loop
La fonction loop() s’exécute en continu après la configuration. Dans cet exemple, elle augmente progressivement la luminosité de 5 à 255 par pas de 5, appelant updateBrightness() à chaque fois. Après chaque ajustement, elle affiche la valeur de luminosité actuelle dans le moniteur série.
void loop() {
for(int brightness=5; brightness<255; brightness+=5) {
updateBrightness(brightness);
Serial.printf("Brightness: %d\n", brightness);
}
}
Cela crée une animation de fondu fluide qui se répète en boucle. Veillez à ne pas régler la luminosité à zéro. La bande LED restera éteinte, même si vous modifiez la luminosité ensuite. Il faudra alors redéfinir les couleurs des LED.
Exemple de code : Encodeur rotatif
Ce sketch s’appuie sur la base NeoPixel de l’exemple précédent et ajoute un contrôle en temps réel de la luminosité via un encodeur rotatif mécanique. Les LED utilisent toujours la bibliothèque Adafruit NeoPixel pour la gestion du timing et des couleurs, tandis que l’encodeur est lu via une routine d’interruption pour une interaction réactive et sans rebonds. Le résultat est un bouton de réglage de luminosité fluide et matériel pour la bande WS2812 à cinq LED du CrowPanel.
#include <Adafruit_NeoPixel.h>
#define LED_PIN 48
#define LED_NUM 5
#define ENCODER_CLK 45 // A
#define ENCODER_DT 42 // B
Adafruit_NeoPixel strip = Adafruit_NeoPixel(LED_NUM, LED_PIN, NEO_GRB + NEO_KHZ800);
int brightness = 50; // 0..255
int enc_state = 0;
int old_state = -1;
bool has_changed = true;
void IRAM_ATTR encoder_irq() {
enc_state = digitalRead(ENCODER_CLK);
if (enc_state != old_state) {
brightness += (digitalRead(ENCODER_DT) == enc_state) ? -5 : +5;
brightness = constrain(brightness, 5, 255);
old_state = enc_state;
has_changed = true;
}
}
void updateBrightness() {
strip.setBrightness(brightness);
strip.show();
delay(200);
}
void initLEDs(uint8_t r, uint8_t g, uint8_t b) {
strip.begin();
for (int i = 0; i < LED_NUM; i++) {
strip.setPixelColor(i, strip.Color(r, g, b));
}
updateBrightness();
}
void initEncoder() {
pinMode(ENCODER_CLK, INPUT_PULLUP);
pinMode(ENCODER_DT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), encoder_irq, CHANGE);
}
void setup() {
Serial.begin(115200);
initEncoder();
initLEDs(255, 255, 50);
has_changed = true;
}
void loop() {
if (has_changed) {
has_changed = false;
updateBrightness();
Serial.printf("Brightness: %d\n", brightness);
}
delay(1);
}
Imports
Le programme réutilise la même bibliothèque NeoPixel utilisée précédemment, assurant la gestion complète du signal critique en timing pour les LED WS2812, vous permettant de vous concentrer sur la logique d’entrée et l’animation.
#include <Adafruit_NeoPixel.h>
Constantes
La bande LED reste connectée à la GPIO 48 et contient cinq pixels comme avant. Deux constantes supplémentaires définissent les canaux A et B de l’encodeur rotatif sur GPIO 45 et GPIO 42.
#define LED_PIN 48 #define LED_NUM 5 #define ENCODER_CLK 45 // A #define ENCODER_DT 42 // B
Objets
L’objet NeoPixel est configuré avec les mêmes paramètres expliqués dans l’exemple précédent. L’ordre de couleur GRB et la fréquence 800 kHz correspondent aux exigences des WS2812.
Adafruit_NeoPixel strip = Adafruit_NeoPixel(LED_NUM, LED_PIN, NEO_GRB + NEO_KHZ800);
Variables d’état
Le sketch maintient une valeur globale de luminosité dans la plage 0 à 255 et suit les états instantané et précédent de l’encodeur. Un drapeau booléen signale à la boucle principale qu’une mise à jour de luminosité est nécessaire, ce qui déclenche l’ajustement dans la boucle principale.
int brightness = 50; // 0..255 int enc_state = 0; int old_state = -1; bool has_changed = true;
La variable brightness est initialisée intentionnellement à un niveau modéré pour éviter que les LED ne s’allument à pleine puissance. Les états de l’encodeur sont initialisés pour que la première transition soit détectée correctement, et has_changed est à vrai pour forcer un rendu initial.
Routine d’interruption : encoder_irq
L’encodeur est décodé dans un contexte d’interruption pour maintenir une faible latence UI. La routine s’exécute sur les deux fronts du canal CLK et compare le canal DT pour déterminer la direction de rotation. Si DT est égal à CLK, le mouvement est traité dans une direction et soustrait cinq pas de luminosité ; sinon, il ajoute cinq. La taille du pas de cinq est un compromis pratique entre contrôle granulaire et réactivité.
void IRAM_ATTR encoder_irq() {
enc_state = digitalRead(ENCODER_CLK);
if (enc_state != old_state) {
brightness += (digitalRead(ENCODER_DT) == enc_state) ? -5 : +5;
brightness = constrain(brightness, 5, 255);
old_state = enc_state;
has_changed = true;
}
}
L’attribut IRAM_ATTR garantit que la routine est placée dans la RAM d’instruction, évitant les temps d’attente flash sur l’ESP32 pendant les interruptions. L’appel constrain impose un minimum de 5 pour éviter une sortie complètement sombre et un maximum de 255 pour plafonner la luminosité. Le drapeau has_changed reporte la mise à jour réelle des LED au thread principal, gardant l’ISR courte et déterministe.
Mise à jour de la luminosité : updateBrightness
Les mises à jour réelles des LED sont centralisées ici. La fonction applique la luminosité actuelle à la bande et appelle show pour transmettre les données tamponnées des pixels. Un court délai est utilisé pour rendre visibles les changements successifs et éviter de saturer le taux de mise à jour des LED.
void updateBrightness() {
strip.setBrightness(brightness);
strip.show();
delay(200);
}
Comme la luminosité sur NeoPixel est un multiplicateur global appliqué au moment de la transmission, cette approche met à jour l’intensité sans réécrire les couleurs individuelles des pixels.
Initialisation des LED : initLEDs
La logique d’initialisation reflète l’exemple précédent mais reporte la luminosité à updateBrightness pour que le même chemin de code soit utilisé au démarrage et lors des rotations du bouton. Les cinq pixels reçoivent la même couleur initiale, que vous pouvez modifier pour un retour d’état.
void initLEDs(uint8_t r, uint8_t g, uint8_t b) {
strip.begin();
for (int i = 0; i < LED_NUM; i++) {
strip.setPixelColor(i, strip.Color(r, g, b));
}
updateBrightness();
}
Appeler updateBrightness à la fin garantit que la couleur initiale apparaît au niveau de luminosité global actuel.
Initialisation de l’encodeur : initEncoder
Les canaux de l’encodeur sont configurés en entrées avec résistances pull-up internes car de nombreux encodeurs de panneau sont de type collecteur ouvert ou interrupteurs mécaniques. L’interruption est attachée à la ligne CLK sur les deux fronts pour que chaque cran produise une transition d’état que l’ISR peut décoder.
void initEncoder() {
pinMode(ENCODER_CLK, INPUT_PULLUP);
pinMode(ENCODER_DT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), encoder_irq, CHANGE);
}
Utiliser des interruptions sur la ligne CLK, plutôt que du polling dans la boucle, évite les pas manqués à haute vitesse de rotation et réduit l’utilisation CPU. Si votre encodeur est particulièrement bruyant, vous pouvez ajouter un simple anti-rebond en ignorant les changements dans une fenêtre d’une microseconde ou en échantillonnant les deux canaux à nouveau après un court délai.
Setup
La sortie série est initialisée pour la visibilité, l’encodeur est armé en premier pour capturer immédiatement les entrées utilisateur, et les LED sont allumées avec une teinte jaune. Le drapeau de changement est activé pour assurer que la première image est envoyée à la bande même si l’encodeur n’a pas encore bougé.
void setup() {
Serial.begin(115200);
initEncoder();
initLEDs(255, 255, 50);
has_changed = true;
}
Loop
La boucle principale est volontairement minimale. Elle vérifie si l’ISR a signalé un changement de luminosité et, si oui, applique le nouveau niveau et le rapporte via la liaison série.
void loop() {
if (has_changed) {
has_changed = false;
updateBrightness();
Serial.printf("Brightness: %d\n", brightness);
}
delay(1);
}
Cette structure garde les tâches critiques et courtes dans l’interruption et laisse les opérations plus lourdes d’E/S LED au premier plan, ce qui est un modèle robuste pour un code UI réactif sur ESP32.
Exemple de code : Affichage
Dans cet exemple suivant, nous contrôlerons la luminosité de l’écran à l’aide de l’encodeur rotatif. L’image ci-dessous montre trois valeurs de luminosité différentes (10 %, 50 %, 100 %) pour l’écran :

D’abord, vous devrez installer la bibliothèque Arduino_GFX par moononournation pour pouvoir contrôler l’écran. Ouvrez le LIBRARY MANAGER, tapez « GFX Library for Arduino » dans la barre de recherche, trouvez « GFX Library for Arduino » par Moon, et cliquez sur le bouton INSTALL :

Le sketch suivant connecte l’encodeur rotatif à l’écran TFT GC9A01 en utilisant la bibliothèque Arduino_GFX et ajoute un contrôle PWM du rétroéclairage. Il étend les concepts d’encodeur des exemples précédents et vous permettra de contrôler la luminosité de l’écran avec un bouton-poussoir pour basculer le rétroéclairage.
#include <Arduino_GFX_Library.h>
#define TFT_SCLK 10
#define TFT_MOSI 11
#define TFT_DC 3
#define TFT_CS 9
#define TFT_RES 14
#define TFT_BLK 46
#define LCD_PWR_EN1 1 // LCD_3V3 rail
#define LCD_PWR_EN2 2 // LEDA_3V3 rail
#define ENCODER_CLK 45 // A
#define ENCODER_DT 42 // B
#define ENCODER_BTN 41 // SW (active-LOW)
int brightness = 50; // 0..100 %
bool btn_display = true;
int enc_state = 0;
int old_state = -1;
bool has_changed = true;
Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(
TFT_DC, TFT_CS, TFT_SCLK, TFT_MOSI, GFX_NOT_DEFINED, FSPI, true
);
Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RES, 0 /* rotation */, true /* IPS */);
void IRAM_ATTR encoder_irq() {
enc_state = digitalRead(ENCODER_CLK);
if (enc_state != old_state) {
brightness += (digitalRead(ENCODER_DT) == enc_state) ? 1 : -1;
brightness = constrain(brightness, 0, 100);
old_state = enc_state;
has_changed = true;
}
}
void IRAM_ATTR button_irq() {
if (!digitalRead(ENCODER_BTN)) {
btn_display = !btn_display;
has_changed = true;
}
}
void drawText() {
gfx->setTextSize(2);
gfx->setCursor(60, 55);
gfx->println(F("Makerguides"));
}
void updateBrightness() {
gfx->fillRect(65, 95, 120, 65, WHITE);
gfx->setTextSize(6);
gfx->setCursor(65, 100);
gfx->printf("%3d", brightness);
int duty = map(brightness, 0, 100, 0, 255);
analogWrite(TFT_BLK, btn_display ? duty : 0);
}
void initPower() {
pinMode(LCD_PWR_EN1, OUTPUT);
pinMode(LCD_PWR_EN2, OUTPUT);
digitalWrite(LCD_PWR_EN1, HIGH); // must be HIGH
digitalWrite(LCD_PWR_EN2, HIGH); // must be HIGH
pinMode(TFT_BLK, OUTPUT);
analogWrite(TFT_BLK, 50);
}
void initDisplay() {
delay(20);
gfx->begin();
gfx->fillScreen(WHITE);
gfx->setTextColor(BLACK);
}
void initEncoder() {
pinMode(ENCODER_BTN, INPUT_PULLUP);
pinMode(ENCODER_CLK, INPUT_PULLUP);
pinMode(ENCODER_DT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), encoder_irq, CHANGE);
attachInterrupt(digitalPinToInterrupt(ENCODER_BTN), button_irq, CHANGE);
}
void setup() {
Serial.begin(115200);
initPower();
initEncoder();
initDisplay();
drawText();
has_changed = true;
}
void loop() {
if (has_changed) {
has_changed = false;
updateBrightness();
Serial.printf("Brightness: %d%% (display %s)\n",
brightness, btn_display ? "ON" : "OFF");
}
delay(1);
}
Imports
Le programme s’appuie sur Arduino_GFX pour des primitives de dessin SPI rapides et un pilote de panneau GC9A01. La bibliothèque masque les commandes spécifiques au contrôleur et fournit une API cohérente pour l’initialisation, le remplissage de couleur et le rendu de texte.
#include <Arduino_GFX_Library.h>
Constantes
Les broches définissent la connexion SPI au TFT rond, les rails d’alimentation pour le panneau et son anode LED, la broche de rétroéclairage pilotée en PWM, ainsi que les canaux et le bouton de l’encodeur rotatif. La luminosité est exprimée en pourcentage pour que le retour utilisateur puisse être affiché directement de 0 à 100 %.
#define TFT_SCLK 10 #define TFT_MOSI 11 #define TFT_DC 3 #define TFT_CS 9 #define TFT_RES 14 #define TFT_BLK 46 #define LCD_PWR_EN1 1 // LCD_3V3 rail #define LCD_PWR_EN2 2 // LEDA_3V3 rail #define ENCODER_CLK 45 // A #define ENCODER_DT 42 // B #define ENCODER_BTN 41 // SW (active-LOW)
Variables d’état
L’état d’exécution suit le pourcentage de luminosité actuel, si le rétroéclairage est activé, l’état instantané de l’encodeur, et un drapeau de changement qui permet à l’ISR de planifier du travail pour le thread principal. Comme avant, garder les ISR courtes et différer les E/S au premier plan évite les problèmes de timing.
int brightness = 50; // 0..100 % bool btn_display = true; int enc_state = 0; int old_state = -1; bool has_changed = true;
Objets bus et pilote d’affichage
La pile Arduino_GFX est divisée en un objet bus et un objet panneau. Le bus enveloppe l’hôte FSPI de l’ESP32 avec DC, CS, SCLK et MOSI. L’objet panneau cible le contrôleur GC9A01 et sait initialiser un panneau IPS rond. La rotation est réglée à zéro et le dernier booléen marque le panneau comme IPS pour sélectionner l’inversion de couleur et la gamma correctes.
Arduino_ESP32SPI *bus = new Arduino_ESP32SPI( TFT_DC, TFT_CS, TFT_SCLK, TFT_MOSI, GFX_NOT_DEFINED, FSPI, true ); Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RES, 0 /* rotation */, true /* IPS */);
Routine d’interruption de l’encodeur
L’ISR de l’encodeur reflète la logique précédente, mais la taille du pas est d’un pour cent par front pour un contrôle fin. La routine échantillonne le canal CLK pour détecter un front, compare DT pour déterminer la direction, met à jour le pourcentage, contraint le résultat, et inverse le drapeau de changement.
void IRAM_ATTR encoder_irq() {
enc_state = digitalRead(ENCODER_CLK);
if (enc_state != old_state) {
brightness += (digitalRead(ENCODER_DT) == enc_state) ? 1 : -1;
brightness = constrain(brightness, 0, 100);
old_state = enc_state;
has_changed = true;
}
}
Routine d’interruption du bouton
Le bouton-poussoir de l’encodeur est actif bas. L’ISR lit la broche et bascule un booléen représentant l’état du rétroéclairage. Le dessin et la mise à jour PWM sont différés à la boucle principale en activant le drapeau de changement.
void IRAM_ATTR button_irq() {
if (!digitalRead(ENCODER_BTN)) {
btn_display = !btn_display;
has_changed = true;
}
}
Dessin de texte statique
Un petit helper affiche une étiquette de site une fois au démarrage. La taille du texte et le curseur sont positionnés pour un écran rond 240×240. Notez qu’Arduino_GFX utilise un curseur basé sur les pixels qui avance avec les impressions.
void drawText() {
gfx->setTextSize(2);
gfx->setCursor(60, 55);
gfx->println(F("Makerguides"));
}
Mise à jour de la luminosité et du rétroéclairage
Cette fonction rafraîchit la valeur numérique à l’écran et applique le PWM à la broche de rétroéclairage. La zone rectangulaire derrière les chiffres est effacée en blanc pour éviter les images fantômes lors du changement des chiffres. Le pourcentage est mappé à un cycle de service 8 bits, que analogWrite traduit en PWM matériel via le LEDC de l’ESP32. Lorsque le rétroéclairage est désactivé, le cycle est forcé à zéro.
void updateBrightness() {
gfx->fillRect(65, 95, 120, 65, WHITE);
gfx->setTextSize(6);
gfx->setCursor(65, 100);
gfx->printf("%3d", brightness);
int duty = map(brightness, 0, 100, 0, 255);
analogWrite(TFT_BLK, btn_display ? duty : 0);
}
Initialisation de l’alimentation
Les rails logiques du panneau et de l’anode LED sont activés avant l’envoi de commandes d’affichage. Les deux lignes d’activation sont mises à l’état haut. La broche de rétroéclairage est configurée pour le PWM et réglée sur une luminosité faible (50), ce qui rend le texte visible sans être trop lumineux.
void initPower() {
pinMode(LCD_PWR_EN1, OUTPUT);
pinMode(LCD_PWR_EN2, OUTPUT);
digitalWrite(LCD_PWR_EN1, HIGH); // must be HIGH
digitalWrite(LCD_PWR_EN2, HIGH); // must be HIGH
pinMode(TFT_BLK, OUTPUT);
analogWrite(TFT_BLK, 50);
}
Initialisation de l’affichage
Après un court délai de stabilisation des rails d’alimentation, le pilote GC9A01 est démarré, l’écran est effacé en blanc, et la couleur du texte est réglée en noir. À partir de là, le sketch peut dessiner des primitives et du texte à volonté.
void initDisplay() {
delay(20);
gfx->begin();
gfx->fillScreen(WHITE);
gfx->setTextColor(BLACK);
}
Initialisation de l’encodeur
Les canaux et le bouton de l’encodeur utilisent des pull-ups internes, adaptés aux encodeurs à contact ouvert typiques. Les interruptions sont attachées au canal CLK pour le décodage quadrature et au bouton pour le basculement. L’utilisation de CHANGE capture les fronts de pression et de relâchement ; l’ISR vérifie elle-même la condition active bas.
void initEncoder() {
pinMode(ENCODER_BTN, INPUT_PULLUP);
pinMode(ENCODER_CLK, INPUT_PULLUP);
pinMode(ENCODER_DT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), encoder_irq, CHANGE);
attachInterrupt(digitalPinToInterrupt(ENCODER_BTN), button_irq, CHANGE);
}
Setup
Le système alimente le panneau, arme les interruptions de l’encodeur, initialise l’affichage, affiche le titre statique, et active le drapeau de changement pour que la première image de luminosité et la mise à jour PWM se produisent immédiatement. La journalisation série est activée pour la visibilité pendant les tests.
void setup() {
Serial.begin(115200);
initPower();
initEncoder();
initDisplay();
drawText();
has_changed = true;
}
Loop
Le premier plan vérifie si les ISR ont demandé une mise à jour. Lorsqu’elle est activée, il redessine la luminosité numérique, règle la luminosité du rétroéclairage, et affiche une ligne de statut montrant le pourcentage et si le rétroéclairage est activé.
void loop() {
if (has_changed) {
has_changed = false;
updateBrightness();
Serial.printf("Brightness: %d%% (display %s)\n",
brightness, btn_display ? "ON" : "OFF");
}
delay(1);
}
Exemple de code : écran tactile
Ce dernier exemple montre comment lire les données tactiles du contrôleur capacitif CST816D intégré dans le CrowPanel 1.28-inch HMI ESP32 Rotary Display. Il dessine des points rouges partout où l’utilisateur touche l’écran. Voir la photo d’exemple ci-dessous :

Le code suivant réutilise la même chaîne d’affichage Arduino_GFX introduite plus tôt, ajoute la bibliothèque Wire pour les lectures I²C bas niveau, et décode en temps réel les registres bruts du contrôleur tactile CST816D en coordonnées écran.
#include <Wire.h>
#include <Arduino_GFX_Library.h>
#define TFT_SCLK 10
#define TFT_MOSI 11
#define TFT_MISO -1
#define TFT_DC 3
#define TFT_CS 9
#define TFT_RES 14
#define TFT_BLK 46
#define LCD_PWR_EN1 1 // LCD_3V3 rail
#define LCD_PWR_EN2 2 // LEDA_3V3 rail
#define TOUCH_INT 5
#define TOUCH_SDA 6
#define TOUCH_SCL 7
#define TOUCH_RST 13
Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(
TFT_DC, TFT_CS, TFT_SCLK, TFT_MOSI, GFX_NOT_DEFINED, FSPI, true
);
Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RES, 0 /* rotation */, true /* IPS */);
int i2cRead(uint16_t addr, uint8_t reg_addr, uint8_t *reg_data, uint32_t length) {
Wire.beginTransmission(addr);
Wire.write(reg_addr);
if (Wire.endTransmission(true)) return -1;
Wire.requestFrom((int)addr, (int)length, (int)true);
for (uint32_t i = 0; i < length && Wire.available(); i++) {
*reg_data++ = Wire.read();
}
return 0;
}
int readTouch(int *x, int *y) {
// CST816D @ 0x15, coordinates start at register 0x02
uint8_t data_raw[7] = {0};
if (i2cRead(0x15, 0x02, data_raw, 7) != 0) return 0;
// data_raw[1] bits 7..6 = event (0:down,1:up,2:contact), low nibble high bits of X
int event = data_raw[1] >> 6;
if (event == 2 || event == 0) { // treat contact or down as a valid touch
*x = (int)data_raw[2] + (int)(data_raw[1] & 0x0F) * 256;
*y = (int)data_raw[4] + (int)(data_raw[3] & 0x0F) * 256;
return 1;
}
return 0;
}
void initPins() {
// Power rails for the LCD/backlight
pinMode(LCD_PWR_EN1, OUTPUT);
pinMode(LCD_PWR_EN2, OUTPUT);
digitalWrite(LCD_PWR_EN1, HIGH);
digitalWrite(LCD_PWR_EN2, HIGH);
// Backlight
pinMode(TFT_BLK, OUTPUT);
analogWrite (TFT_BLK, 100);
// Touch
pinMode(TOUCH_INT, INPUT_PULLUP);
pinMode(TOUCH_RST, OUTPUT);
// Reset the CST816D
digitalWrite(TOUCH_RST, LOW);
delay(5);
digitalWrite(TOUCH_RST, HIGH);
delay(50);
Wire.begin(TOUCH_SDA, TOUCH_SCL);
}
void initDisplay() {
delay(20);
gfx->begin();
gfx->fillScreen(WHITE);
gfx->setTextColor(BLACK);
gfx->setTextSize(3);
gfx->setCursor(80, 105);
gfx->print(F("Touch"));
}
void setup(void) {
Serial.begin(115200);
initPins();
initDisplay();
}
void loop() {
static int x, y;
if (readTouch(&x, &y) == 1) {
gfx->fillCircle(x, y, 5, RED);
}
}
Imports
Deux bibliothèques sont incluses en haut du programme.Wire.hgère la communication sur le bus I²C, utilisé pour communiquer avec le contrôleur tactile.Arduino_GFX_Library.hgère la communication SPI avec l’écran TFT rond GC9A01, permettant de dessiner facilement du texte et des formes.
#include <Wire.h> #include <Arduino_GFX_Library.h>
Définitions des broches
Cette section définit toutes les affectations de broches pour l’écran, le contrôleur tactile et les rails d’alimentation.
Les broches SPI connectent l’ESP32 au contrôleur TFT GC9A01, tandis que les broches I²C (TOUCH_SDA et TOUCH_SCL) communiquent avec la puce tactile CST816D. Des broches d’activation séparées contrôlent les rails 3,3 V alimentant la logique LCD (LCD_PWR_EN1) et le rétroéclairage LED (LCD_PWR_EN2).
#define TFT_SCLK 10 #define TFT_MOSI 11 #define TFT_MISO -1 #define TFT_DC 3 #define TFT_CS 9 #define TFT_RES 14 #define TFT_BLK 46 #define LCD_PWR_EN1 1 // LCD_3V3 rail #define LCD_PWR_EN2 2 // LEDA_3V3 rail #define TOUCH_INT 5 #define TOUCH_SDA 6 #define TOUCH_SCL 7 #define TOUCH_RST 13
Objets bus et pilote d’affichage
La bibliothèque Arduino_GFX sépare le pilote d’affichage en deux objets. Arduino_ESP32SPI définit la configuration du bus SPI pour le périphérique FSPI de l’ESP32, incluant les broches de données et de contrôle. Arduino_GC9A01 représente le contrôleur d’affichage spécifique et gère les commandes comme l’initialisation, le remplissage de couleur et le rendu de texte.
Arduino_ESP32SPI *bus = new Arduino_ESP32SPI( TFT_DC, TFT_CS, TFT_SCLK, TFT_MOSI, GFX_NOT_DEFINED, FSPI, true ); Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RES, 0 /* rotation */, true /* IPS */);
Cette configuration correspond à l’écran IPS rond 240×240 du CrowPanel.
La i2cRead() fonction auxiliaire
Cette fonction fournit un moyen général de lire un ou plusieurs registres de n’importe quel périphérique I²C.
Elle démarre la communication avec l’adresse du périphérique spécifiée, écrit le registre à lire, puis demande un nombre d’octets au périphérique. Chaque octet est stocké dans le tampon passé via reg_data. Si une étape échoue, elle retourne -1 pour signaler une erreur.
int i2cRead(uint16_t addr, uint8_t reg_addr, uint8_t *reg_data, uint32_t length) {
Wire.beginTransmission(addr);
Wire.write(reg_addr);
if (Wire.endTransmission(true)) return -1;
Wire.requestFrom((int)addr, (int)length, (int)true);
for (uint32_t i = 0; i < length && Wire.available(); i++) {
*reg_data++ = Wire.read();
}
return 0;
}
Cette abstraction garde la fonction readTouch() plus propre et concentrée sur l’interprétation des données tactiles plutôt que sur la gestion des transactions bus.
Lecture des données tactiles avec readTouch()
Le contrôleur tactile CST816D fournit les événements tactiles et les données de coordonnées via I²C à l’adresse 0x15.
Cette fonction lit sept octets à partir du registre 0x02, qui contiennent des informations sur l’état tactile actuel et les coordonnées.
Si le code d’événement indique un contact ou un appui (valeurs 0 ou 2), la fonction décode les positions X et Y en combinant les bits hauts et bas des registres de coordonnées.
Les coordonnées valides sont retournées via les pointeurs x et y, et la fonction retourne 1 pour signaler un contact valide.
int readTouch(int *x, int *y) {
uint8_t data_raw[7] = {0};
if (i2cRead(0x15, 0x02, data_raw, 7) != 0) return 0;
int event = data_raw[1] >> 6;
if (event == 2 || event == 0) {
*x = (int)data_raw[2] + (int)(data_raw[1] & 0x0F) * 256;
*y = (int)data_raw[4] + (int)(data_raw[3] & 0x0F) * 256;
return 1;
}
return 0;
}
Cette analyse bas niveau des registres suit la datasheet CST816D, où les bits supérieurs de chaque octet de coordonnées sont stockés dans le nibble inférieur de l’octet précédent.
Initialisation de l’alimentation et des E/S avec initPins()
Avant que l’écran ou le contrôleur tactile ne puissent fonctionner, les GPIO appropriées doivent être configurées.
Cette fonction configure les rails d’alimentation LCD, le PWM du rétroéclairage, et les broches d’interface tactile. Les deux lignes d’activation LCD sont mises à l’état haut pour alimenter la logique d’affichage et les LED en 3,3 V. La broche de rétroéclairage (TFT_BLK) est configurée pour le contrôle PWM, initialisée à un cycle de service modéré de 100 pour une luminosité visible.
La ligne d’interruption tactile est configurée en entrée avec une résistance pull-up interne. La broche reset du CST816D est mise à l’état bas pendant quelques millisecondes, puis relâchée à l’état haut pour redémarrer correctement le contrôleur tactile. Enfin, le bus I²C est initialisé sur les broches SDA et SCL spécifiées.
void initPins() {
pinMode(LCD_PWR_EN1, OUTPUT);
pinMode(LCD_PWR_EN2, OUTPUT);
digitalWrite(LCD_PWR_EN1, HIGH);
digitalWrite(LCD_PWR_EN2, HIGH);
pinMode(TFT_BLK, OUTPUT);
analogWrite(TFT_BLK, 100);
pinMode(TOUCH_INT, INPUT_PULLUP);
pinMode(TOUCH_RST, OUTPUT);
digitalWrite(TOUCH_RST, LOW);
delay(5);
digitalWrite(TOUCH_RST, HIGH);
delay(50);
Wire.begin(TOUCH_SDA, TOUCH_SCL);
}
Initialisation de l’affichage
Après un court délai pour assurer la stabilité de l’alimentation, l’écran GC9A01 est initialisé. L’écran est effacé en blanc, la couleur du texte est réglée en noir, et une étiquette “Touch” est dessinée au centre pour indiquer la disponibilité.
void initDisplay() {
delay(20);
gfx->begin();
gfx->fillScreen(WHITE);
gfx->setTextColor(BLACK);
gfx->setTextSize(3);
gfx->setCursor(80, 105);
gfx->print(F("Touch"));
}
Cette configuration crée une toile claire pour dessiner les indicateurs tactiles plus tard dans la boucle principale.
Setup
La fonction setup() est simple. Elle initialise l’interface série pour le débogage, puis appelle initPins() et initDisplay() pour préparer le matériel.
void setup(void) {
Serial.begin(115200);
initPins();
initDisplay();
}
À ce stade, l’écran est actif et le contrôleur tactile prêt à rapporter les coordonnées.
Loop
La boucle principale vérifie en continu la présence d’une entrée tactile avec la fonction readTouch(). Si un contact valide est détecté, elle dessine un petit cercle rouge aux coordonnées rapportées en utilisant la fonction fillCircle() de la bibliothèque Arduino_GFX. Chaque contact crée un nouveau point, permettant à l’utilisateur de “dessiner” sur l’écran.
void loop() {
static int x, y;
if (readTouch(&x, &y) == 1) {
gfx->fillCircle(x, y, 5, RED);
}
}
Cette boucle minimale montre la réactivité du CST816D via I²C et illustre la facilité avec laquelle un retour graphique peut être rendu sur l’écran GC9A01.
Conclusions
Ce tutoriel vous a fourni des exemples de code pour débuter avec le CrowPanel 1.28inch-HMI ESP32 Rotary Display.
Pour plus d’exemples, consultez Elecrows’s Github repo for the 1.28inch-HMI ESP32 Rotary Display et le Wiki Page. Notez cependant que beaucoup d’exemples là-bas utilisent les bibliothèques ui et lvgl, que j’ai évité ici pour garder la complexité faible.
Si vous cherchez un module d’affichage similaire avec un anneau d’encodeur rotatif, jetez un œil au Matouch 1.28″ ToolSet_Controller. Il ne possède pas l’anneau de LED RGB mais un Real-Time-Clock (RTC) et un moteur à vibration pour un retour haptique à la place. Et si vous avez seulement besoin d’un écran rond (sans l’anneau d’encodeur rotatif), le tutoriel Digital Clock on CrowPanel 1.28″ Round Display pourrait vous être utile.
Aussi, si vous souhaitez en savoir plus sur les encodeurs rotatifs, consultez notre tutoriel How To Interface A Quadrature Rotary Encoder.
Si vous avez des questions, n’hésitez pas à les laisser dans la section commentaires.
Bon bricolage 😉

