ESP32-S3 + petit ecran couleur a 3$ avec animations LVGL | Guide pour debutants en 10 minutes

Piloter un ecran TFT couleur ST7735S 0.96 pouce avec un ESP32-S3 et y faire tourner des animations LVGL. Du cablage au code complet, avec un guide anti-pieges inclus. Ideal pour les debutants en Arduino et en developpement embarque.

ESP32-S3 + petit ecran couleur a 3$ avec animations LVGL ! Guide pour debutants en 10 minutes (edition anti-pieges 2026)

En une phrase : ESP32-S3 + ecran TFT ST7735S 0.96 pouce + animations LVGL, avec 5 broches essentielles et un guide complet anti-pieges

Resultat final

image-20260410152138611

Un ecran 0.96 pouce, petit comme un ongle, et pourtant capable d’afficher des animations LVGL ultra-fluides. Ce guide couvre tout, du cablage au code, pour que vous n’ayez pas a galerez avec les memes pieges que nous.


Youtube:


Ce que vous allez apprendre

  1. Comment l’ESP32-S3 pilote un ecran TFT ST7735S 0.96 pouce via SPI
  2. La methode de configuration de la bibliotheque Arduino_GFX (et pourquoi on n’utilise pas TFT_eSPI)
  3. Le processus complet pour porter LVGL v9 sur un petit ecran
  4. Un exemple d’interface LVGL avec une double animation (deplacement horizontal + rebond vertical)

BOM

ComposantQuantiteRemarque
Carte de developpement ESP32-S31N’importe quelle variante S3 convient
Ecran TFT IPS ST7735S 0.96 pouce1Resolution 80x160, interface SPI, 8 broches
Cables Dupont (femelle-femelle)Quelques8 suffisent

Specifications de l’ecran

image-20260410113243742

Pas besoin de tout retenir par coeur. Concentrez-vous sur les parametres avec *, ce sont ceux qu’il faut absolument connaitre pour ecrire le code.

ParametreSpecificationRemarque
Taille0.96 pouce TFT IPSAngle de vue complet, bon rendu des couleurs
Resolution80(H) x 160(V)***** Dans le code : screenWidth=160, screenHeight=80 (mode paysage)
PiloteST7735S***** Doit correspondre au choix de bibliotheque
InterfaceSPI 4 filsJusqu’a 40 MHz (recommande de tester d’abord avec la frequence par defaut)
Tension3.3V***** Ne surtout PAS brancher en 5V !
Nombre de broches8 brochesInclut la broche de retroeclairage BLK
ParametreSpecification
Zone d’affichage10.8(H) x 21.7(V) mm
Dimensions du panneau19(H) x 24(V) x 2.7(D) mm
Pitch des pixels0.135(H) x 0.1356(V) mm
Courant de fonctionnement20mA
Type de retroeclairage1 LED
Temperature de fonctionnement-20 ~ 70C
Dimensions du PCB30.00 x 24.04 mm
Diametre des trous de fixation2 mm
Espacement des broches2.54 mm

Definition des broches :

BrocheDescription
1GNDMasse
2VCCAlimentation positive (3.3V)
3SCLSignal d’horloge SPI
4SDASignal de donnees SPI
5RESReinitialisation (niveau bas = reset)
6DCSelection registre/donnees (bas = commande, haut = donnees)
7CSChip Select (niveau bas = actif)
8BLKControle du retroeclairage (niveau haut = allume ; sinon brancher sur 3.3V)

Cablage

Broche ESP32-S3Broche ST7735SDescription
GNDGNDMasse commune
3.3VVCCStrictement interdit de brancher en 5V
GPIO 12SCLHorloge SPI
GPIO 11SDADonnees SPI (MOSI)
GPIO 21RESReinitialisation
GPIO 47DCSelection commande/donnees
GPIO 38CSChip Select
GPIO 48BLKRetroeclairage (si pas de controle logiciel, brancher directement sur 3.3V)

Precautions de cablage

  • Alimentation : Brancher uniquement sur 3.3V. Le 5V detruirait l’ecran
  • Broche BLK (retroeclairage) : Si vous n’avez pas besoin de controler le retroeclairage par logiciel, branchez-la directement sur 3.3V pour qu’il reste constamment allume
  • CS (Chip Select) : Actif au niveau bas
  • RES (Reset) : Necessite un niveau bas lors de l’initialisation au demarrage
  • Choix des broches : Les broches ci-dessus utilisent les broches par defaut du SPI2 (FSPI) de l’ESP32-S3. Si vous changez de broches, vous devrez aussi modifier les definitions dans le code

Installation des bibliotheques

Dans l’Arduino IDE, installez les deux bibliotheques suivantes :

  1. Arduino_GFX_Library — Cherchez “GFX Library for Arduino” et installez
  2. LVGL — Cherchez lvgl et installez (la version v9.x est requise)

Pourquoi Arduino_GFX plutot que TFT_eSPI ?

Pour clarifier les choses d’emblee : j’aime bien TFT_eSPI, je l’ai utilise pour piloter plein d’ecrans. Les deux bibliotheques peuvent piloter l’ecran ST7735S, mais leur approche de configuration est tres differente :

Le probleme de TFT_eSPI : il faut modifier les fichiers sources de la bibliotheque

TFT_eSPI exige que vous ouvriez le fichier User_Setup.h dans le dossier d’installation de la bibliotheque, pour y modifier manuellement les definitions de broches et le choix du pilote d’ecran. Ca implique :

  1. Trouver le chemin d’installation de la bibliotheque (different selon le systeme : Documents/Arduino/libraries/ ou .platformio/packages/)

  2. Naviguer dans un fichier de configuration de plusieurs centaines de lignes pour trouver la bonne ligne, commenter les valeurs par defaut et decommenter celles que vous voulez

  3. Si vous travaillez sur plusieurs projets avec des ecrans differents, il faut reprendre ces modifications a chaque changement

  4. Apres une mise a jour de la bibliotheque, votre configuration est ecrasee, et votre projet ne compile plus du jour au lendemain

    C’est aussi la plainte la plus courante : “J’ai suivi le tuto video mais j’ai un ecran blanc” — souvent c’est parce que User_Setup.h a ete mal modifie ou n’a pas ete pris en compte.

    L’approche d’Arduino_GFX : les broches sont definies directement dans votre code

    En comparaison, avec Arduino_GFX toute la configuration se fait dans votre propre fichier .ino :

    // Toutes les broches et parametres du pilote definis directement dans le code, sans modifier aucun fichier de bibliotheque
    Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, TFT_SCLK, TFT_MOSI, GFX_NOT_DEFINED);
    Arduino_GFX *gfx = new Arduino_ST7735(bus, TFT_RST, ROTATION, false, 80, 160, 26, 1, 26, 1);
    • Changer de broche ? Modifiez une seule ligne #define

    • Changer d’ecran ? Remplacez Arduino_ST7735 par Arduino_ILI9341 ou un autre pilote

    • Mise a jour de la bibliotheque ? Votre code n’est pas affecte

    • Plusieurs projets en parallele ? Chaque projet a sa propre configuration, sans conflit

    De plus, la compatibilite de TFT_eSPI avec l’ESP32-S3 pose deja probleme. Plusieurs issues sur GitHub signalent des echecs de compilation avec ESP32 Arduino Core 3.x. Arduino_GFX est toujours activement maintenu et offre un meilleur support pour les nouvelles puces.

Environnement de developpement

Environnement utilise pour cet exemple :

MacOS - v15.1.1

Arduino IDE - v2.3.8

Bibliotheque de carte : esp32 (by Espressif Systems) - v3.3.7

Bibliotheque de pilote d’ecran : GFX Library for Arduino (by Moon on our nation) - v1.6.5

Bibliotheque graphique : LVGL (by kisvegabor) - v9.5.0

Code complet

#include <Arduino_GFX_Library.h>
#include <lvgl.h>

// --- Broches et initialisation GFX ---
#define TFT_CS 38
#define TFT_RST 21
#define TFT_DC 47
#define TFT_MOSI 11
#define TFT_SCLK 12
#define TFT_BLK 48

#define BLACK   0x0000
#define WHITE   0xFFFF
#define ROTATION 1

Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, TFT_SCLK, TFT_MOSI, GFX_NOT_DEFINED);
Arduino_GFX *gfx = new Arduino_ST7735(bus, TFT_RST, ROTATION, false, 80, 160, 26, 1, 26, 1);

static const uint32_t screenWidth  = 160;
static const uint32_t screenHeight = 80;

void my_disp_flush(lv_display_t *display, const lv_area_t *area, uint8_t *px_map) {
  uint32_t w = lv_area_get_width(area);
  uint32_t h = lv_area_get_height(area);
  uint32_t stride = lv_draw_buf_width_to_stride(w, LV_COLOR_FORMAT_RGB565);
  uint8_t * row_ptr = px_map;

  for (uint32_t y = 0; y < h; y++) {
    gfx->draw16bitRGBBitmap(area->x1, area->y1 + y, (uint16_t *)row_ptr, w, 1);
    row_ptr += stride;
  }
  lv_display_flush_ready(display);
}

// ==========================================
// Definition des fonctions de rappel d'animation (pour recevoir les changements de valeur des animations LVGL)
// ==========================================

// Rappel : changer la coordonnee X de l'objet (deplacement horizontal)
static void anim_x_cb(void * var, int32_t v) {
  lv_obj_set_x((lv_obj_t *)var, v);
}

// Rappel : changer la coordonnee Y de l'objet (deplacement vertical)
static void anim_y_cb(void * var, int32_t v) {
  lv_obj_set_y((lv_obj_t *)var, v);
}

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

  gfx->begin();
  gfx->fillScreen(BLACK);

  lv_init();
  lv_display_t *display = lv_display_create(screenWidth, screenHeight);
  lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);

  static lv_color_t buf[screenWidth * screenHeight / 10];
  lv_display_set_buffers(display, buf, NULL, sizeof(buf), LV_DISPLAY_RENDER_MODE_PARTIAL);
  lv_display_set_flush_cb(display, my_disp_flush);

  // Definir le fond de l'ecran en blanc
  lv_obj_set_style_bg_color(lv_scr_act(), lv_color_hex(0xFFFFFF), 0);

  // ==========================================
  // Disposition avancee de l'interface : creation d'un conteneur transparent pour envelopper les elements
  // ==========================================

  // 1. Creer un conteneur transparent (taille 100x60)
  lv_obj_t * cont = lv_obj_create(lv_scr_act());
  lv_obj_set_size(cont, 100, 60);
  lv_obj_set_style_bg_opa(cont, 0, 0);             // Arriere-plan completement transparent
  lv_obj_set_style_border_width(cont, 0, 0);       // Supprimer la bordure
  lv_obj_set_style_pad_all(cont, 0, 0);            // Supprimer les marges internes
  lv_obj_align(cont, LV_ALIGN_CENTER, 0, 0);       // Centrer le conteneur

  // 2. Placer le carre vert dans le conteneur, aligne en haut au centre
  lv_obj_t *rect = lv_obj_create(cont);
  lv_obj_set_size(rect, 30, 30);
  lv_obj_set_style_bg_color(rect, lv_color_hex(0x00FF00), 0);
  lv_obj_set_style_border_width(rect, 0, 0);
  lv_obj_align(rect, LV_ALIGN_TOP_MID, 0, 0);

  // 3. Placer le texte dans le conteneur, aligne en bas au centre
  lv_obj_t * label = lv_label_create(cont);
  lv_label_set_text(label, "hello world!");
  lv_obj_set_style_text_color(label, lv_color_hex(0x000000), 0);
  lv_obj_align(label, LV_ALIGN_BOTTOM_MID, 0, 0);


  // ==========================================
  // Ajout d'une double animation (moteur d'animation LVGL v9)
  // ==========================================

  // Animation A : faire deplacer le conteneur entier (carre + texte) de gauche a droite
  lv_anim_t a_x;
  lv_anim_init(&a_x);
  lv_anim_set_var(&a_x, cont);                       // Objet lie a l'animation : le conteneur
  lv_anim_set_values(&a_x, -30, 30);                 // Se deplacer de 30 pixels vers la gauche, puis 30 vers la droite
  lv_anim_set_time(&a_x, 2000);                      // Duree d'un deplacement : 2000 ms (2 secondes)
  lv_anim_set_playback_time(&a_x, 2000);             // Le trajet retour prend aussi 2000 ms
  lv_anim_set_repeat_count(&a_x, LV_ANIM_REPEAT_INFINITE); // Boucle infinie
  lv_anim_set_path_cb(&a_x, lv_anim_path_ease_in_out);     // Courbe "ease in-out" pour un mouvement fluide et naturel
  lv_anim_set_exec_cb(&a_x, anim_x_cb);              // Lier la fonction de rappel de l'axe X definie plus haut
  lv_anim_start(&a_x);                               // Lancer l'animation !

  // Animation B : faire rebondir le carre vert de haut en bas
  lv_anim_t a_y;
  lv_anim_init(&a_y);
  lv_anim_set_var(&a_y, rect);                       // Objet lie a l'animation : uniquement le carre vert
  lv_anim_set_values(&a_y, 0, 10);                   // Decalage vertical de 0 a 10 pixels
  lv_anim_set_time(&a_y, 300);                       // Rebond rapide, 300 ms par aller
  lv_anim_set_playback_time(&a_y, 300);
  lv_anim_set_repeat_count(&a_y, LV_ANIM_REPEAT_INFINITE);
  lv_anim_set_path_cb(&a_y, lv_anim_path_ease_in_out);
  lv_anim_set_exec_cb(&a_y, anim_y_cb);              // Lier la fonction de rappel de l'axe Y definie plus haut
  lv_anim_start(&a_y);                               // Lancer l'animation !
}

// Enregistrer l'instant du dernier passage
uint32_t last_tick = 0;
void loop() {
  // 1. Calculer le temps ecoule depuis le dernier passage dans loop
  uint32_t current_tick = millis();
  uint32_t elapsed_time = current_tick - last_tick;
  last_tick = current_tick;

  // 2. Transmettre le temps ecoule a LVGL (c'est absolument crucial pour que les animations fonctionnent !)
  lv_tick_inc(elapsed_time);

  // 3. LVGL traite les animations et le rafraichissement de l'interface
  lv_timer_handler();

  // 4. Petit delai pour eviter de saturer le CPU
  delay(5);
}

Explication des lignes cles du code

Voici les passages ou les debutants font le plus d’erreurs. Comparez ligne par ligne avec votre code :

1. Les parametres de decalage dans l’initialisation GFX

Arduino_GFX *gfx = new Arduino_ST7735(bus, TFT_RST, ROTATION, false, 80, 160, 26, 1, 26, 1);

Les 4 derniers chiffres 26, 1, 26, 1 correspondent a col_offset1, row_offset1, col_offset2, row_offset2. Si l’affichage est decalé (le contenu est coin dans un angle ou avec une bordure noire), ajustez ces 4 valeurs. Les modules ST7735S de differents fabricants ont des decalages differents ; ceux-ci sont les valeurs les plus courantes.

2. Taille de l’ecran — attention a l’orientation paysage

#define ROTATION 1  // Rotation en mode paysage
static const uint32_t screenWidth  = 160;  // Apres rotation, la largeur devient 160
static const uint32_t screenHeight = 80;   // Et la hauteur devient 80

L’ecran physique fait 80x160 (mode portrait). Avec ROTATION=1, une rotation de 90 degres le transforme en 160x80. La taille du display LVGL doit correspondre a l’orientation apres rotation, sinon l’image sera corrompue.

3. Le rappel flush — le pont entre LVGL et GFX

void my_disp_flush(lv_display_t *display, const lv_area_t *area, uint8_t *px_map) {
  ...
  lv_display_flush_ready(display);  // Cette ligne ne doit pas etre oubliee !
}

lv_display_flush_ready() indique a LVGL : “Cette zone est terminee, tu peux m’envoyer la suivante”. Si vous oubliez cette ligne = l’ecran ne se mettra jamais a jour.

4. L’alimentation en temps dans la boucle loop

lv_tick_inc(elapsed_time);
lv_timer_handler();

Ces deux lignes sont le “coeur” des animations LVGL. lv_tick_inc fournit le temps ecoule, lv_timer_handler declenche le rafraichissement de l’interface. S’il en manque une seule, aucune animation ne fonctionnera.

FAQ : Problemes courants

SymptomeCause probableSolution
Ecran blanc (retroeclairage allume mais pas de contenu)Le rappel flush n’est pas correctement lie, ou lv_display_flush_ready() est absentVerifiez que my_disp_flush est bien defini comme flush_cb
Ecran parasite / blocs de couleur aleatoiresBroches SPI mal branchees ou mauvais contactReVerifier le cablage, assurez-vous que les cables Dupont sont bien enfonces
Image decalée / bordure noireLes parametres de decalage ST7735S ne correspondent pasAjustez les parametres col_offset et row_offset dans le constructeur Arduino_ST7735
Couleurs inversees (bleu au lieu de rouge, etc.)Parametrage RGB/BGR incorrectVerifiez le parametre d’ordre des couleurs dans l’initialisation GFX
Image retournee haut/basParametre de rotation incorrectEssayez de changer ROTATION a 0 ou 3
Erreur de compilation : lvgl.h introuvableBibliotheque LVGL non installee ou mauvaise versionAssurez-vous d’avoir installe LVGL v9.x (pas la v8)
Les animations ne bougent pas, interface statiqueIl manque lv_tick_inc() ou lv_timer_handler() dans la boucle loopVerifiez que les deux lignes sont bien presentes