Publié le 14 avril 2026 - par

Dompter le CrowPanel 2.1″ Rotary ESP32-S3 : Configuration matérielle et affichage

La société Elecrow m’a récemment fait parvenir l’une de ses dernières interfaces IHM pour évaluation matérielle : le CrowPanel 2.1″ Rotary. Ce module très intégré s’articule autour d’un microcontrôleur ESP32-S3 (Dual-Core, Wi-Fi et BLE) couplé à une superbe dalle IPS circulaire de 2,1 pouces. Les spécifications sont ambitieuses : une résolution native de 480×480 pixels pilotée par un véritable bus vidéo RGB 16 bits parallèle, exigeant une configuration stricte de la mémoire PSRAM. Sa véritable singularité réside dans sa couronne rotative physique entourant l’écran, offrant une navigation tactile et mécanique digne des équipements industriels. L’idée de cet article est de décortiquer cette plateforme avec vous, d’en dompter les exigences de synchronisation vidéo, et d’explorer les vastes possibilités qu’elle ouvre pour nos futures interfaces embarquées.

Exploration du CrowPanel 2.1″ Rotary : Une interface tactile et mécanique pour vos projets embarqués

Déballage et tour du propriétaire

 

La boîte expédiée par Elecrow est un simple carton craft, estampillé d’une étiquette précisant la référence « ESP32 Display-2.1 inch ». À l’intérieur, le module est bien calé, accompagné d’un câble USB fourni pour l’alimentation et la programmation, une attention toujours appréciable pour démarrer immédiatement.

Une fois la protection retirée, l’interface se dévoile. Le module est encerclé par une couronne rotative d’un noir mat, dotée d’une texture légèrement granuleuse qui assure une excellente préhension. Au centre, l’écran tactile circulaire est recouvert d’un film protecteur qu’il conviendra de retirer. La qualité de fabrication est au rendez-vous, l’ensemble est dense et inspire confiance.

En retournant le module on découvre la carte interface de la bête sur un PCB. La connectique est généreuse pour un format aussi compact :

  • Un port USB-C (marqué USB-5V-IN) pour l’alimentation principale et le flashage.
  • Des connecteurs UART et I2C (au pas de 1.25mm), idéaux pour venir greffer des capteurs environnementaux externes.
  • Les inévitables boutons BOOT et RESET pour la gestion du micrologiciel.

L’intégration semble très soignée.

Caractéristiques

Catégorie Caractéristique Spécification
Cœur du Système Puce Principale ESP32-S3R8
Processeur Double cœur Xtensa 32-bit LX7 haute performance, jusqu’à 240 MHz
Mémoire Système (RAM) 512 Ko SRAM, 8 Mo PSRAM
Stockage 16 Mo Flash
Affichage Taille 2.1 pouces (Circulaire)
Dalle IPS
Tactile Capacitif
Résolution 480 x 480 pixels
Connectivité Bluetooth Bluetooth Low Energy (BLE) et Bluetooth 5.0
Wi-Fi 802.11 a/b/g/n, 2.4 GHz
Matériel & Interfaces UART 1x UART0, 1x UART1 (Connecteurs ZX-MX 1.25-4P)
I2C 1x bus I2C (Connecteur ZX-MX 1.25-4P)
Connecteur FPC 12 broches (Alimentation et flashage)
Boutons physiques RESET, BOOT, Bouton de validation (Clic de la couronne)
Éclairage LED Indicateur d’alimentation, LED d’ambiance
Physique & Alimentation Alimentation (Entrée) 5V / 1A (DC)
Tension Logique 3.3V (Puce ESP32)
Température (Fonctionnement) -20 °C à +65 °C
Dimensions 79 x 79 x 30 mm
Matériaux du boîtier Alliage d’aluminium, plastique et acrylique
Poids Net 80 g

 

Première mise sous tension : La démo d’usine

Rien de tel qu’une démonstration « out of the box » pour juger des capacités matérielles. Il suffit de brancher le câble USB fourni sur le port USB-C de l’écran et de le relier à votre ordinateur ou à un chargeur. L’ESP32 s’éveille instantanément sur un micrologiciel de test préinstallé par Elecrow.

Une dalle IPS qui flatte la rétine

Dès les premières secondes, une animation de cercles concentriques multicolores vient valider la qualité de la dalle IPS. Les noirs sont profonds, le contraste est percutant et les couleurs sont vibrantes, même sous des angles de vision très prononcés. La résolution de 480×480 pixels sur un diamètre de 2,1 pouces offre une densité de pixels excellente : les polices et les graphiques sont lisses, sans effet de crénelage (aliasing) visible à l’œil nu.

Tactile et couronne : le duo gagnant

En tournant physiquement l’anneau noir autour de l’écran, on fait varier la valeur à l’écran. La jauge se remplit ou se vide en temps réel, de manière parfaitement synchronisée avec la rotation de votre main. Le retour haptique de la couronne est excellent, avec de légers crans mécaniques très discrets qui permettent un réglage de précision.

L’opération est identique dans le menu « Volume ». Ce couplage entre une dalle tactile moderne et un encodeur physique robuste ouvre des perspectives ergonomiques intéressantes pour la création de panneaux de contrôle (domotique, machines-outils, audio…).

Du Matériel au Logiciel : Relever le Défi Technique avec PlatformIO

Après cette mise en bouche impressionnante avec le firmware d’usine, le défi est maintenant de taille : reproduire ce niveau de performance avec notre propre code. Piloter une dalle IPS circulaire via un bus vidéo RGB 16 bits parallèle ne s’improvise pas. La moindre désynchronisation matérielle se traduit par des déchirements d’image (tearing), des artefacts visuels (DMA underflow) ou des couleurs fantômes. Nous allons configurer l’ESP32-S3 et le contrôleur ST7701S avec une rigueur absolue.

1. Le piège de la mémoire et la configuration PlatformIO

Une matrice de 480×480 pixels en 16 bits (RGB565) exige un tampon vidéo (framebuffer) de plus de 460 Ko. La RAM interne de l’ESP32-S3 est insuffisante pour allouer ce flux d’un seul bloc continu. Si vous tentez de générer le signal vidéo sans activer la PSRAM externe en mode Octal (très haute vitesse), vous irez droit au crash silencieux ou obtiendrez un écran strié et illisible.

Dans votre projet PlatformIO, remplacez intégralement le contenu de votre fichier platformio.ini par celui-ci. Notez l’activation critique des drapeaux -DBOARD_HAS_PSRAM et le type de mémoire qio_opi indispensable pour la PSRAM Octal.


[env:esp32-s3-crowpanel]
platform = espressif32 @ 6.4.0
board = esp32-s3-devkitc-1
framework = arduino

upload_port = COM10 ; À adapter selon votre port série
monitor_port = COM10
monitor_speed = 115200

; Paramètres de mémoire critiques
board_build.arduino.memory_type = qio_opi 
board_build.f_flash = 80000000L
board_build.f_cpu = 240000000L

build_flags = 
    -DARDUINO_USB_MODE=1
    -DARDUINO_USB_CDC_ON_BOOT=1
    -DBOARD_HAS_PSRAM
    -mfix-esp32-psram-cache-issue

lib_deps = 
    moononournation/GFX Library for Arduino @ 1.4.7

2. Le Code de validation : Synchronisation vidéo parfaite

Le contrôleur d’affichage ST7701S exige une initialisation très spécifique. Nous devons séparer le bus de configuration (SPI logiciel sur les broches 16, 2, 1) du bus vidéo (RGB parallèle sur une multitude de broches).

La clé pour éliminer toute diaphonie (crosstalk) électromagnétique sur la nappe et obtenir des textes parfaitement lisses consiste à calibrer l’horloge pixel (PCLK). Nous l’avons abaissée à 12 MHz, avec une lecture de donnée sur le front montant, ce qui assure une stabilité absolue du signal électrique. La séquence I2C au début du programme (sur l’adresse 0x21) permet de dialoguer avec l’extenseur de ports PCF8574 pour lancer le reset matériel de l’écran.

Copiez ce code dans votre fichier src/main.cpp :


#include <Arduino.h>
#include <Wire.h>
#include <Arduino_GFX_Library.h>

#define I2C_SDA_PIN 38
#define I2C_SCL_PIN 39
#define BACKLIGHT_PIN 6

// 1. Bus Vidéo RGB - Calibré pour éliminer le bruit de phase
Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
    40, 7, 15, 41,          // DE, VSYNC, HSYNC, PCLK
    46, 3, 8, 18, 17,       // R0-R4
    14, 13, 12, 11, 10, 9,  // G0-G5
    5, 45, 48, 47, 21,      // B0-B4
    1, 10, 8, 50,           // hsync
    1, 10, 8, 20,           // vsync
    0,                      // Phase stabilisée (lecture sur front montant)
    12000000UL              // Fréquence abaissée à 12 MHz pour contrer la diaphonie
);

// 2. Bus SPI Logiciel pour configurer la dalle ST7701S
Arduino_DataBus *bus = new Arduino_SWSPI(
    GFX_NOT_DEFINED, 16, 2, 1
);

// 3. Écran avec initialisation circulaire spécifique (Type 7)
Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
    480, 480, rgbpanel, 0, true,
    bus, GFX_NOT_DEFINED, st7701_type7_init_operations, sizeof(st7701_type7_init_operations)
);

void setup() {
    Serial.begin(115200);
    delay(2000);
    Serial.println("--- DÉMARRAGE CROWPANEL : CALIBRATION ---");

    // Allumage du rétroéclairage (GPIO 6)
    pinMode(BACKLIGHT_PIN, OUTPUT);
    digitalWrite(BACKLIGHT_PIN, HIGH);

    // Séquence I2C (Alimentation & Reset matériel via extenseur PCF8574 à l'adresse 0x21)
    Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
    
    // P3 (LCD Power) = ON, P4 (LCD Reset) = INACTIF
    Wire.beginTransmission(0x21); Wire.write(0xFF); Wire.endTransmission(); 
    delay(150);

    // P4 à LOW : On force le Reset matériel de la dalle
    Wire.beginTransmission(0x21); Wire.write(0xEF); Wire.endTransmission(); 
    delay(150);

    // P4 à HIGH : Fin du Reset
    Wire.beginTransmission(0x21); Wire.write(0xFF); Wire.endTransmission(); 
    delay(150); 

    // Lancement du moteur graphique
    if (!gfx->begin()) {
        Serial.println("ERREUR GFX : Échec de l'initialisation.");
    } else {
        Serial.println("GFX OK - Affichage du texte en cours...");
        
        // Test de lisibilité pur : Fond blanc, Texte noir
        gfx->fillScreen(WHITE);
        gfx->setTextColor(BLACK);
        gfx->setTextSize(4); 
        gfx->setCursor(90, 220); // Centrage horizontal approximatif
        gfx->println("Framboise314");
    }
}

void loop() {
    delay(2000);
}

Compilez et téléversez. Si vous avez rigoureusement suivi ces étapes, la magie devrait opérer : la dalle s’illumine d’un blanc pur et le texte « Framboise314 » s’affiche avec une netteté absolue, sans la moindre strie ni aucun artefact coloré.

On a maintenant un programme d’affichage qui fonctionne, et on peut passer à la prise en compte du bouton rotatif.

Interaction : Lire la couronne et animer l’écran

Maintenant que notre affichage est parfaitement stable, il est temps de donner vie à cette fameuse couronne rotative physique. Contrairement au signal de Reset de l’écran, Elecrow a fait l’excellent choix de câbler les deux phases de l’encodeur directement sur les broches de l’ESP32-S3 (GPIO 42 pour la Phase A, et GPIO 4 pour la Phase B). C’est une excellente nouvelle : nous allons pouvoir lire l’encodeur à la vitesse de l’éclair, sans être limités par le bus I2C.

Source : http://sti2d-erembert.fr/system/codeurs_rotatifs/Codeurs_rotatifs.htm

La stratégie : Anti-rebond et gestion des demi-pas

La lecture d’un contacteur mécanique pose deux défis majeurs en électronique embarquée :

  1. Les rebonds mécaniques : Lorsqu’un cran s’enclenche, le contact métallique « rebondit » de manière microscopique. Sans filtrage, le microcontrôleur lit 50 changements d’état en une milliseconde et sature. Nous utilisons ici un filtre logiciel basique basé sur millis() pour ignorer tout signal survenant moins de 5 millisecondes après le précédent.
  2. Le phénomène de « demi-pas » (Half-Step) : Sur ce modèle d’encodeur, il suffit d’écouter chaque changement d’état (front montant ET front descendant) de la Phase A pour incrémenter notre compteur.

Pour rendre la démonstration visuelle, chaque cran de la couronne modifiera l’index d’un tableau contenant 6 couleurs (Rouge, Bleu, Vert, Jaune, Rose, Violet). Le texte « Framboise314 » restera affiché au centre.

Voici le code source complet . Il fusionne notre moteur graphique et la lecture de l’encodeur :


#include <Arduino.h>
#include <Wire.h>
#include <Arduino_GFX_Library.h>

// --- CONFIGURATION MATÉRIELLE ---
#define I2C_SDA_PIN 38
#define I2C_SCL_PIN 39
#define BACKLIGHT_PIN 6
#define PCF8574_ADDR 0x21

// Les vraies broches de la couronne rotative
#define ENCODER_PIN_A 42
#define ENCODER_PIN_B 4

// --- CONFIGURATION VIDÉO (Calibrée) ---
Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
    40, 7, 15, 41,          // DE, VSYNC, HSYNC, PCLK
    46, 3, 8, 18, 17,       // R0-R4
    14, 13, 12, 11, 10, 9,  // G0-G5
    5, 45, 48, 47, 21,      // B0-B4
    1, 10, 8, 50,           // hsync
    1, 10, 8, 20,           // vsync
    0,                      // Phase 0 (Front montant)
    12000000UL              // Fréquence 12 MHz anti-diaphonie
);

Arduino_DataBus *bus = new Arduino_SWSPI(GFX_NOT_DEFINED, 16, 2, 1);
Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
    480, 480, rgbpanel, 0, true, 
    bus, GFX_NOT_DEFINED, st7701_type7_init_operations, sizeof(st7701_type7_init_operations)
);

// --- VARIABLES GLOBALES ---
uint16_t palette[] = { RED, BLUE, GREEN, YELLOW, MAGENTA, 0x780F };
int indexCouleur = 0;

int dernierEtatA = HIGH;
unsigned long dernierTempsMouvement = 0; // Chronomètre pour l'anti-rebond

// --- FONCTION D'AFFICHAGE ---
void rafraichirEcran() {
    gfx->fillScreen(palette[indexCouleur]);
    
    // Adaptation de la couleur du texte si le fond est jaune
    if (palette[indexCouleur] == YELLOW) {
        gfx->setTextColor(BLACK);
    } else {
        gfx->setTextColor(WHITE);
    }
    
    gfx->setTextSize(5); 
    gfx->setCursor(80, 220); 
    gfx->println("Framboise314");
}

// --- INITIALISATION ---
void setup() {
    Serial.begin(115200);
    delay(2000);

    // 1. Allumage de l'écran
    pinMode(BACKLIGHT_PIN, OUTPUT);
    digitalWrite(BACKLIGHT_PIN, HIGH);

    // 2. Initialisation des broches de l'encodeur avec Pull-up
    pinMode(ENCODER_PIN_A, INPUT_PULLUP);
    pinMode(ENCODER_PIN_B, INPUT_PULLUP);
    dernierEtatA = digitalRead(ENCODER_PIN_A);

    // 3. Séquence I2C pour le Reset Matériel de l'écran (Rigueur : 150ms)
    Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
    Wire.beginTransmission(PCF8574_ADDR); Wire.write(0xFF); Wire.endTransmission(); 
    delay(150);
    Wire.beginTransmission(PCF8574_ADDR); Wire.write(0xEF); Wire.endTransmission(); 
    delay(150);
    Wire.beginTransmission(PCF8574_ADDR); Wire.write(0xFF); Wire.endTransmission(); 
    delay(150); 

    // 4. Lancement du moteur graphique
    if (gfx->begin()) {
        rafraichirEcran(); // Premier affichage
    } else {
        Serial.println("Erreur d'initialisation GFX");
    }
}

// --- BOUCLE PRINCIPALE ---
void loop() {
    int etatA = digitalRead(ENCODER_PIN_A);

    // Si on détecte un changement (front montant OU descendant)
    if (etatA != dernierEtatA) {
        
        // Anti-rebond logiciel : on ignore les parasites inférieurs à 5ms
        if (millis() - dernierTempsMouvement > 5) {
            
            int etatB = digitalRead(ENCODER_PIN_B);
            
            // On compare les deux broches pour déduire le sens sur n'importe quel front
            if (etatA != etatB) {
                indexCouleur++; // Rotation horaire
            } else {
                indexCouleur--; // Rotation anti-horaire
            }

            // Bouclage du tableau (de 0 à 5)
            if (indexCouleur > 5) indexCouleur = 0;
            if (indexCouleur < 0) indexCouleur = 5;

            // Déclenchement de la mise à jour visuelle
            rafraichirEcran(); 
            
            // Mise à jour du chronomètre anti-rebond
            dernierTempsMouvement = millis();
        }
    }
    
    // Mémorisation de l'état pour la prochaine boucle
    dernierEtatA = etatA; 
}

Compilez et téléversez. Posez le doigt sur la couronne et tournez.

La fluidité du rafraîchissement d’un écran de 480×480 pixels via un ESP32 est excellente, et l’encodeur réagit au quart de tour (et à chaque cran physique !).

L’interaction complète : Rotation et Clic Central

Nous touchons au but. La dernière subtilité matérielle de ce CrowPanel 2.1″ réside dans sa conception hybride. Si la rotation de la couronne attaque directement le silicium de l’ESP32 (GPIO 42 et 4), le clic central du bouton rotatif passe, quant à lui, par l’extenseur I2C (le fameux PCF8574) sur la broche P5.

Pour gérer cette interface complète sans saturer le microcontrôleur, notre boucle principale va utiliser une double stratégie :

  • Une écoute ultra-rapide des broches de rotation, avec un filtre anti-rebond logiciel de 5 ms et une détection sur les deux fronts pour contrer les encodeurs à « demi-pas ».
  • Une interrogation (polling) du bus I2C toutes les 50 ms pour écouter le clic central. Ce délai agit naturellement comme un excellent filtre anti-rebond pour le bouton mécanique.

Pour la démonstration, chaque rotation changera la couleur de fond, tandis qu’une pression sur la couronne basculera le texte affiché entre « Framboise314 » et « CLIC ! ».

Voici le bloc monolithique définitif. C’est le code source complet et robuste de notre application. Remplacez l’intégralité de votre fichier src/main.cpp par ce programme :


#include <Arduino.h>
#include <Wire.h>
#include <Arduino_GFX_Library.h>

// --- CONFIGURATION MATÉRIELLE ---
#define I2C_SDA_PIN 38
#define I2C_SCL_PIN 39
#define BACKLIGHT_PIN 6
#define PCF8574_ADDR 0x21

// Broches de la couronne rotative (Rotation directe ESP32)
#define ENCODER_PIN_A 42
#define ENCODER_PIN_B 4
// Note : Le bouton de clic est sur l'extenseur I2C (PCF8574) broche P5

// --- CONFIGURATION VIDÉO (Calibrée à 12MHz, Phase 0) ---
Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
    40, 7, 15, 41,          // DE, VSYNC, HSYNC, PCLK
    46, 3, 8, 18, 17,       // R0-R4
    14, 13, 12, 11, 10, 9,  // G0-G5
    5, 45, 48, 47, 21,      // B0-B4
    1, 10, 8, 50,           // hsync
    1, 10, 8, 20,           // vsync
    0,                      // Phase 0
    12000000UL              // Fréquence 12 MHz
);

Arduino_DataBus *bus = new Arduino_SWSPI(GFX_NOT_DEFINED, 16, 2, 1);
Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
    480, 480, rgbpanel, 0, true, 
    bus, GFX_NOT_DEFINED, st7701_type7_init_operations, sizeof(st7701_type7_init_operations)
);

// --- VARIABLES GLOBALES ---
uint16_t palette[] = { RED, BLUE, GREEN, YELLOW, MAGENTA, 0x780F };
int indexCouleur = 0;

// Variables pour la rotation
int dernierEtatA = HIGH;
unsigned long dernierTempsMouvement = 0; 

// Variables pour le clic
bool dernierEtatBouton = false;
unsigned long dernierTempsBouton = 0;
bool modeClicActif = false; 

// --- FONCTION D'AFFICHAGE ---
void rafraichirEcran() {
    gfx->fillScreen(palette[indexCouleur]);
    
    if (palette[indexCouleur] == YELLOW) {
        gfx->setTextColor(BLACK);
    } else {
        gfx->setTextColor(WHITE);
    }
    
    gfx->setTextSize(5); 
    
    // Affichage dynamique selon l'état du bouton
    if (modeClicActif) {
        gfx->setCursor(150, 220); 
        gfx->println("CLIC !");
    } else {
        gfx->setCursor(80, 220); 
        gfx->println("Framboise314");
    }
}

// --- INITIALISATION ---
void setup() {
    Serial.begin(115200);
    delay(2000);

    // 1. Rétroéclairage
    pinMode(BACKLIGHT_PIN, OUTPUT);
    digitalWrite(BACKLIGHT_PIN, HIGH);

    // 2. Initialisation des broches de rotation (Pull-up interne)
    pinMode(ENCODER_PIN_A, INPUT_PULLUP);
    pinMode(ENCODER_PIN_B, INPUT_PULLUP);
    dernierEtatA = digitalRead(ENCODER_PIN_A);

    // 3. Séquence I2C pour le Reset de l'écran (Rigueur : 150ms)
    Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
    Wire.beginTransmission(PCF8574_ADDR); Wire.write(0xFF); Wire.endTransmission(); delay(150);
    Wire.beginTransmission(PCF8574_ADDR); Wire.write(0xEF); Wire.endTransmission(); delay(150);
    Wire.beginTransmission(PCF8574_ADDR); Wire.write(0xFF); Wire.endTransmission(); delay(150); 

    // 4. Démarrage de l'affichage
    if (gfx->begin()) {
        rafraichirEcran(); 
    }
}

// --- BOUCLE PRINCIPALE ---
void loop() {
    
    // ==========================================
    // 1. LECTURE DE LA ROTATION (Matériel direct)
    // ==========================================
    int etatA = digitalRead(ENCODER_PIN_A);
    if (etatA != dernierEtatA) {
        // Filtre anti-rebond de 5ms
        if (millis() - dernierTempsMouvement > 5) {
            int etatB = digitalRead(ENCODER_PIN_B);
            
            if (etatA != etatB) {
                indexCouleur++;
            } else {
                indexCouleur--;
            }
            
            if (indexCouleur > 5) indexCouleur = 0;
            if (indexCouleur < 0) indexCouleur = 5;

            rafraichirEcran(); 
            dernierTempsMouvement = millis();
        }
    }
    dernierEtatA = etatA; 

    // ==========================================
    // 2. LECTURE DU CLIC (Via bus I2C)
    // ==========================================
    // On poll l'extenseur toutes les 50ms pour ne pas saturer l'ESP32
    if (millis() - dernierTempsBouton > 50) {
        
        Wire.requestFrom(PCF8574_ADDR, 1);
        if (Wire.available()) {
            uint8_t etatPcf = Wire.read();
            
            // Le bouton est sur P5 (bit 5). Tirage haut logique : 0 = pressé, 1 = relâché.
            bool etatBouton = ((etatPcf & 0x20) == 0); 
            
            // Si l'état du bouton a changé
            if (etatBouton != dernierEtatBouton) {
                
                // Si on vient de l'enfoncer (front descendant du clic)
                if (etatBouton) { 
                    modeClicActif = !modeClicActif; // Bascule l'affichage
                    rafraichirEcran();
                }
                
                dernierEtatBouton = etatBouton;
            }
        }
        dernierTempsBouton = millis();
    }
}

Flashez ce programme et manipulez la couronne : la réactivité est immédiate, un cran physique correspond à un changement de couleur, et le clic central répond parfaitement.

Vous avez désormais la maîtrise totale de cette superbe interface IHM, prête à propulser vos projets de domotique ou de contrôle-commande !

La touche finale : Intégrer une image (Logo Framboise314)

Pour couronner cette interface, il serait dommage de se limiter à du texte et des fonds unis. Nous allons donc afficher un véritable logo au centre de l’écran. Cependant, en électronique embarquée rigoureuse, on ne demande pas à un petit microcontrôleur de décoder « à la volée » un fichier PNG ou JPG compressé ; cela consommerait inutilement des ressources critiques.

La méthode industrielle : La conversion en RGB565

La solution la plus performante consiste à pré-calculer l’image. L’écran ST7701S utilise le format de couleur RGB565 (16 bits par pixel : 5 pour le rouge, 6 pour le vert, 5 pour le bleu). Nous allons donc utiliser un outil de conversion (comme le célèbre image2cpp ou LCD Image Converter) pour transformer notre image carrée de 480×480 pixels en un immense tableau de valeurs hexadécimales.

Ce tableau, qui contient 230 400 valeurs (soit environ 460 Ko), sera placé dans un fichier d’en-tête séparé (par exemple F314_logo_480px.h). L’utilisation impérative de la directive PROGMEM garantit que ce lourd tableau sera stocké dans la mémoire Flash de l’ESP32-S3, préservant ainsi la précieuse mémoire vive (RAM) pour l’exécution du programme.

Le logo de F314 au format cpp : Cliquez pour télécharger le lofo F314 

Une fois le fichier d’en-tête inclus, il suffit d’une seule ligne d’instruction dans notre moteur graphique (draw16bitRGBBitmap) pour injecter instantanément l’image sur la dalle.

Voici le code principal mis à jour pour gérer la bascule entre le logo et notre interface de couleurs lors d’un clic sur la couronne :


#include <Arduino.h>
#include <Wire.h>
#include <Arduino_GFX_Library.h>
#include "F314_logo_480px.h" // Ton fichier de données image

// --- CONFIGURATION MATÉRIELLE ---
#define I2C_SDA_PIN 38
#define I2C_SCL_PIN 39
#define BACKLIGHT_PIN 6
#define PCF8574_ADDR 0x21

#define ENCODER_PIN_A 42
#define ENCODER_PIN_B 4

// --- CONFIGURATION VIDÉO ---
Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
    40, 7, 15, 41,          // DE, VSYNC, HSYNC, PCLK
    46, 3, 8, 18, 17,       // R0-R4
    14, 13, 12, 11, 10, 9,  // G0-G5
    5, 45, 48, 47, 21,      // B0-B4
    1, 10, 8, 50,           // hsync
    1, 10, 8, 20,           // vsync
    0,                      // Phase 0
    12000000UL              // 12 MHz
);

Arduino_DataBus *bus = new Arduino_SWSPI(GFX_NOT_DEFINED, 16, 2, 1);
Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
    480, 480, rgbpanel, 0, true, 
    bus, GFX_NOT_DEFINED, st7701_type7_init_operations, sizeof(st7701_type7_init_operations)
);

// --- VARIABLES ---
uint16_t palette[] = { RED, BLUE, GREEN, YELLOW, MAGENTA, 0x780F };
int indexCouleur = 0;
int dernierEtatA = HIGH;
unsigned long dernierTempsMouvement = 0; 

bool dernierEtatBouton = false;
unsigned long dernierTempsBouton = 0;
bool modeLogoActif = true; // On commence par afficher le logo

void rafraichirEcran() {
    if (modeLogoActif) {
        // Affiche le logo plein écran
        gfx->draw16bitRGBBitmap(0, 0, (uint16_t *)F314_logo_480px_565, 480, 480);
    } else {
        // Affiche le fond de couleur et le texte
        gfx->fillScreen(palette[indexCouleur]);
        gfx->setTextColor(palette[indexCouleur] == YELLOW ? BLACK : WHITE);
        gfx->setTextSize(5); 
        gfx->setCursor(80, 220); 
        gfx->println("Framboise314");
    }
}

void setup() {
    Serial.begin(115200);
    pinMode(BACKLIGHT_PIN, OUTPUT);
    digitalWrite(BACKLIGHT_PIN, HIGH);

    pinMode(ENCODER_PIN_A, INPUT_PULLUP);
    pinMode(ENCODER_PIN_B, INPUT_PULLUP);
    dernierEtatA = digitalRead(ENCODER_PIN_A);

    Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
    Wire.beginTransmission(PCF8574_ADDR); Wire.write(0xFF); Wire.endTransmission(); delay(150);
    Wire.beginTransmission(PCF8574_ADDR); Wire.write(0xEF); Wire.endTransmission(); delay(150);
    Wire.beginTransmission(PCF8574_ADDR); Wire.write(0xFF); Wire.endTransmission(); delay(150); 

    if (gfx->begin()) {
        rafraichirEcran(); 
    }
}

void loop() {
    // 1. Rotation (uniquement si on n'est pas en mode logo)
    int etatA = digitalRead(ENCODER_PIN_A);
    if (etatA != dernierEtatA && !modeLogoActif) {
        if (millis() - dernierTempsMouvement > 5) {
            int etatB = digitalRead(ENCODER_PIN_B);
            if (etatA != etatB) indexCouleur++; else indexCouleur--;
            if (indexCouleur > 5) indexCouleur = 0;
            if (indexCouleur < 0) indexCouleur = 5; rafraichirEcran(); dernierTempsMouvement = millis(); } } dernierEtatA = etatA; // 2. Clic pour basculer entre Logo et Couleurs if (millis() - dernierTempsBouton > 50) {
        Wire.requestFrom(PCF8574_ADDR, 1);
        if (Wire.available()) {
            bool etatBouton = ((Wire.read() & 0x20) == 0); 
            if (etatBouton != dernierEtatBouton) {
                if (etatBouton) { 
                    modeLogoActif = !modeLogoActif;
                    rafraichirEcran();
                }
                dernierEtatBouton = etatBouton;
            }
        }
        dernierTempsBouton = millis();
    }
}

 

Conclusion

Avec ce CrowPanel 2.1″, vous disposez désormais d’un bloc IHM (Interface Homme-Machine) de très haut niveau. La combinaison d’un écran IPS ultra-net et d’une couronne rotative traitée par des interruptions matérielles offre une fluidité digne des équipements industriels professionnels.

Mais au-delà de la performance technique, maîtriser ce type d’architecture de A à Z revêt une importance fondamentale. En concevant nos propres modules, parfaitement documentés, réparables et évolutifs grâce à des composants standards, nous construisons une véritable ligne de défense contre l’obsolescence programmée. C’est en reprenant le contrôle de la technique à l’échelle du composant que le mouvement Maker propose une alternative concrète et durable au modèle économique du tout-jetable. À vos fers à souder, et bonnes créations !

🤖 Note de l’auteur : Un copilote sur l’établi
Vous l’aurez peut-être remarqué, la structure du code de cet article est particulièrement optimisée. Pour cette session de développement (et de débogage de l’encodeur !), j’ai fait équipe avec un assistant virtuel : Gemini, l’intelligence artificielle de Google. Les programmes présentés ici sont le fruit d’un véritable travail en « pair-programming » entre l’électronicien à la manœuvre et l’IA en renfort. Une belle démonstration que ces nouveaux outils, loin de remplacer la réflexion matérielle, trouvent parfaitement leur place dans l’atelier du Maker pour gagner en rigueur et en temps !

Sources

Acheter l’écran rond Elecrow (lien affilié)

Wiki de l’écran

https://www.image2cpp.com/

Makers Guide

 

À propos François MOCQ

Électronicien d'origine, devenu informaticien, et passionné de nouvelles technologies, formateur en maintenance informatique puis en Réseau et Télécommunications. Dès son arrivée sur le marché, le potentiel offert par Raspberry Pi m’a enthousiasmé j'ai rapidement créé un blog dédié à ce nano-ordinateur (www.framboise314.fr) pour partager cette passion. Auteur de plusieurs livres sur le Raspberry Pi publiés aux Editions ENI.

5 réflexions au sujet de « Dompter le CrowPanel 2.1″ Rotary ESP32-S3 : Configuration matérielle et affichage »

  1. sophie

    Merci François pour ce chouette article.
    J’y rajouterais un petit haut parleur pour émettre un ‘click’ à chaque fois que l’on tourne d’un cran !

    Encore bravo pour toutes vos publications que je trouve de plus en plus détaillées et instructives.

    Que des bonnes choses

    Répondre
  2. Franck R.

    Bonjour,
    Je suis intéressé par ce module mais ayant eu a chaque fois des problèmes de douanes avec Electrow qu’est-ce que vous conseiller pour le choix du transporteur pour juste un module : CDP (FedEx, ShenZhenDHL, UPS) ou DDP ?
    Bonne journée.
    Franck

    Répondre

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.