La librairie LilyGoWatch intègre les libraries TFT_eSPI et LVGL pour construire l’affichage de l’application ESP32. La librairie TFT_eSPI est hyper simple et rapide à prendre en main, idéal pour faire un prototype. LVGL est une librairie professionnelle de haut niveau qui demande plus de temps d’apprentissage. Le rendu est juste hallucinant pour un projet bricolé soi-même.
La librairie TFT_eSPI est super simple d’utilisation et rapide à prendre en main idéal pour afficher du texte, des formes géomètriques simples. La librairie LVGL est une librairie de très haut niveau qui permet de construire des écrans complexes avec des animations, listes défilantes… destinée à des applications industrielles.
Le code source a été testé sur la T-Watch Touch 2019
Ainsi que sur la T-Watch 2020
Sommaire
- 1 Quelques articles à lire avant de commencer votre projet T-Watch
- 2 Activer la librairie LVGL dans votre projet Arduino ou PlatformIO pour T-Watch
- 3 Initialiser la librairie LVGL
- 4 Exemple 1 : page principale créée avec LVGL
- 5 Exemple 2, page principale construite avec TFT_eSPI
- 6 Remarque concernant les librairies TTGO.h et LilyGoWatch.h
- 7 Mises à jour
Quelques articles à lire avant de commencer votre projet T-Watch
Si vous débutez le développement de votre application pour votre T-Watch, voici quelques articles pour débuter
Activer la librairie LVGL dans votre projet Arduino ou PlatformIO pour T-Watch
Contrairement à la librairie TFT_eSPI, la librairie LVGL doit être activée manuellement au démarrage du projet en ajoutant la constante LILYGO_WATCH_LVGL avant de déclarer la librairie LilyGoWatch.
Dans un projet développé à l’aide de l’IDE Arduino. Dé-commenter le modèle de T-Watch utilisé
//#define LILYGO_WATCH_2019_WITH_TOUCH
//#define LILYGO_WATCH_2019_NO_TOUCH
//#define LILYGO_WATCH_BLOCK
#define LILYGO_WATCH_2020_V1
#define LILYGO_WATCH_LVGL
#include
Un exemple de fichier platformio.ini en utilisant l’option build_flags
[env:ttgo-t-watch]
platform = espressif32
board = ttgo-t-watch
framework = arduino
build_flags =
-D LILYGO_WATCH_2019_WITH_TOUCH=1
;-D LILYGO_WATCH_2019_NO_TOUCH=1
;-D LILYGO_WATCH_BLOCK=1
;-D LILYGO_WATCH_2020_V1=1
;-D LILYGO_WATCH_LVGL=1
; Important, activate LVGL support
-D LILYGO_WATCH_LVGL=1
lib_deps =
TTGO TWatch Library
upload_speed = 2000000
monitor_speed = 115200
Initialiser la librairie LVGL
Avant de pouvoir accéder à l’API (fonctions) de la librairie LVGL, il faut l’initialiser en appelant la méthode ttgo->beginlvgl() dans le setup().
Voici le code minimum à exécuter avant de pouvoir utiliser l’écran de la T-Watch.
void setup() {
ttgo = TTGOClass::getWatch();
ttgo->begin();
ttgo->lvgl_begin();
ttgo->openBL();
}
Exemple 1 : page principale créée avec LVGL
Dans ce premier exemple, nous allons apprendre comment créer un projet dont la page principale est construite avec la librairie LVGL. La seconde page est construit avec la librairie TFT_eSPI.
Créer un nouveau projet avec l’IDE Arduino ou PlatformIO et collez le code suivant. Vous pouvez également récupérer le code source directement sur GitHub.
/* Arduino IDE - dé-commenter votre T-Watch*/
//#define LILYGO_WATCH_2019_WITH_TOUCH
//#define LILYGO_WATCH_2019_NO_TOUCH
//#define LILYGO_WATCH_BLOCK
//#define LILYGO_WATCH_2020_V1
// Arduino IDE - dé-commenter pour activer LVGL
//#define LILYGO_WATCH_LVGL
/* PlatformIO -> Select your watch in platformio.ini file */
#include
#include
QueueHandle_t g_event_queue_handle = nullptr;
// Enumère les événements de la queue
enum {
Q_EVENT_DISPLAY_TFT,
};
// Déclare l'image de fond
LV_IMG_DECLARE(WALLPAPER_1_IMG);
/**************************/
/* Static variables */
/**************************/
TTGOClass *ttgo = nullptr;
static bool onAir = true;
static lv_obj_t *lvglpage = NULL;
/**************************/
/* STATIC PROTOTYPES */
/**************************/
void createLVGLPage();
static void event_handler(lv_obj_t * obj, lv_event_t event);
void hideLVGLpage(bool hide);
bool openTFT();
/**************************/
void setup() {
Serial.begin(115200);
// Créé une file d'événement que l'on va utiliser pour déclencher l'affichage de l'écran TFT_eSPI
g_event_queue_handle = xQueueCreate(20, sizeof(uint8_t));
ttgo = TTGOClass::getWatch();
ttgo->begin();
ttgo->lvgl_begin();
// Allume le rétro-éclairage
ttgo->openBL();
// et affiche l'écran principal
createLVGLPage();
}
void loop() {
uint8_t data;
if (xQueueReceive(g_event_queue_handle, &data, 5 / portTICK_RATE_MS) == pdPASS) {
switch (data) {
case Q_EVENT_DISPLAY_TFT:{
// cache l'écran LVGL
hideLVGLpage(true);
// affiche l'écran eSPI et attend que l'utilisateur touche l'écran
// Dans l'écran eSPI, le superviseur LVGL n'est pas exécuté
while ( openTFT() ) {}
// Affiche la page d'accueil LVGL
hideLVGLpage(false);
}
break;
default:
break;
}
}
// Superviseur LVGL
lv_task_handler();
}
// Créé un écran avec la librairie LVGL
void createLVGLPage(){
// Conteneur qui contient tous les élements affiché. Permet d'afficher ou masquer plus facilement la page
lvglpage = lv_cont_create( lv_scr_act(), NULL );
lv_obj_set_width( lvglpage, lv_disp_get_hor_res( NULL ) ); // résolution horizontale
lv_obj_set_height( lvglpage, lv_disp_get_ver_res( NULL ) ); // résolution verticale
// Image de fond
lv_obj_t * img1 = lv_img_create(lvglpage, NULL);
lv_img_set_src(img1, &WALLPAPER_1_IMG);
lv_obj_align(img1, NULL, LV_ALIGN_CENTER, 0, 0);
// Bouton au centre de l'écran
lv_obj_t * btn1 = lv_btn_create(lvglpage, NULL);
lv_obj_set_event_cb(btn1, event_handler);
lv_obj_align(btn1, NULL, LV_ALIGN_CENTER, 0, 0);
// Display a circuar scrolling welcome message
// Affiche un message défilant de bienvenue
lv_obj_t * welcomemessage;
welcomemessage = lv_label_create(lvglpage, NULL);
lv_label_set_long_mode(welcomemessage, LV_LABEL_LONG_SROLL_CIRC); /*Circular scroll*/
lv_obj_set_width(welcomemessage, lv_disp_get_hor_res( NULL ));
lv_label_set_text(welcomemessage, "Welcome on LVGL Demo Screen for TTGO T-Wach");
lv_obj_align(welcomemessage, btn1, LV_ALIGN_CENTER, 0, -60);
// libellé du bouton
lv_obj_t * label;
label = lv_label_create(btn1, NULL);
lv_label_set_text(label, "Go to TFT_eSPI");
}
// Déclencheur du bouton pour passer d'un écran LVGL à TFT_eSPI
static void event_handler(lv_obj_t * obj, lv_event_t event){
// Il faut toujours tester l'évènement sinon plusieurs signaux sont envoyés dans la queue ce qui entraîne l'affichage de plusieurs écrans TFT_eSPI
if (event == LV_EVENT_CLICKED) {
Serial.println("event_handler => send open TFT Screen");
uint8_t data = Q_EVENT_DISPLAY_TFT;
xQueueSend(g_event_queue_handle, &data, portMAX_DELAY);
}
}
// Masque / affiche la page LVGL
void hideLVGLpage(bool hide){
lv_obj_set_hidden(lvglpage, hide);
}
// Créé un écran avec la librairie TFT_eSPI
bool openTFT(){
Serial.println("Display TFT_eSPI screen");
onAir = true;
TTGOClass *ttgo = TTGOClass::getWatch();
TFT_eSPI *tft = ttgo->tft;
tft->fillScreen(TFT_BLACK);
tft->setTextSize(2);
tft->setTextColor(TFT_WHITE);
tft->drawString("TFT_eSPI Screen", 0,0);
// Toucher l'écran pour sortir
tft->drawString("Touch screen to exit", 0,20);
// Attend que l'utilisateur touche l'écran pour sortir
while (onAir) {
if (!onAir) return false;
int16_t x,y;
if (ttgo->getTouch(x, y)) {
while (ttgo->getTouch(x, y)) {} // Attend que l'utilisateur relaâche l'écran
Serial.println("User touch the screen");
onAir = false;
}
}
}
Un petit clic de démonstration
Explication du code
LVGL utilise un superviseur (handler) pour actualiser l’écran et détecter les actions de l’utilisateur sur l’écran (clic sur un bouton, défilement d’une liste…). Le plus facile est donc d’appeler le lv_task_handler() dans la boucle loop().
Le problème, c’est qu’il faut suspendre l’appel du superviseur le temps qu’on utilise l’écran construit avec la librairie TFT_eSPI.
Pour cela, le plus simple est d’utiliser le système d’événement de FreeRTOS. FreeRTOS est le système de base sur lequel Espressif a construit son SDK ESP-IDF pour ESP32.
Il suffit de créer un objet QueueHandle_t.
QueueHandle_t g_event_queue_handle = nullptr;
// Enumère les événements possibles
enum {
Q_EVENT_DISPLAY_TFT,
};
puis d’initialiser la fil d’attente dans le setup()
g_event_queue_handle = xQueueCreate(20, sizeof(uint8_t));
Ensuite voici ce qui se passe :
- Le superviseur du bouton event_handler() est appelé dès que l’utilisateur Touche l’écran
- Le superviseur envoi dans la pile un nouveau message Q_EVENT_DISPLAY_TFT demandant l’affichage de l’écran TFT_eSPI à l’aide de la fonction xQueueSend de FreeRTOS.
- Dès que le message est reçu dans la loop() par xQueueReceive()
- On masque la page LVGL. Comme tous les éléments sont dans un conteneur, il suffit d’appeler la méthode lv_obj_set_hiden(nom_objet,false) pour la masquer
- On appel la méthode openTFT() qui construit la librairie TFT_eSPI.
- Une boucle while() bloque l’appel de lv_task_handler(), ce qui empêche LVGL de reconstruire l’écran
- Ici on sort de la boucle while() en touchant l’écran mais on pourrait créer un bouton ou utiliser le bouton utilisateur de la T-Watch.
- En sortant de la page TFT_eSPI, on ré-active l’affichage de la page LVGL en appelant la méthode lv_obj_set_hiden(nom_objet,true). Eventuellement, on peut forcer l’actualisation de l’affichage avec
Exemple 2, page principale construite avec TFT_eSPI
Voyons maintenant l’inverse. La page principale du projet est créé avec la librairie TFT_eSPI. La page secondaire avec LVGL.
Aucun superviseur n’est nécessaire puisqu’il suffit d’utiliser la méthode ttgo->getTouch() pour détecter une action sur l’écran dans la boucle loop(). La gestion des appels est donc bien plus simple et on n’aura pas besoin d’utiliser le gestionnaire de tâche xQueue de FreeRTOS.
On construit la page LVGL comme précédemment. Pour maintenir la page ouverte et la quitter, l’astuce consiste à changer l’état d’une variable. Tant que celle-ci n’est pas vrai, on appel régulièrement le superviseur lv_task_handler(). Ici par exemple, il est appelé toutes les 20ms.
while (!KeyPressed) {lv_task_handler(); delay(20);}
Il est préférable de détruire la page LVGL lorsqu’on retourne à la page d’accueil pour libérer les ressources.
lv_obj_del(lvglpage);
L’affichage de l’écran principal est géré dans la boucle principale loop(). En sortant de la page LVGL, il suffit de renvoyer un état pour déclencher la re-construction de l’écran principal. On pourra utiliser la même stratégie pour récupérer une valeur d’une page de configuration, par exemple un mot passe…
void loop() {
int16_t x, y;
if (ttgo->getTouch(x, y)) {
while (ttgo->getTouch(x, y)) {} // wait for user to release
if ( gotoLVGLPage() ) buildTFTPage();
}
}
Voici un petit clic de démo
Et le code Arduino complet de l’exemple que vous pouvez également retrouver sur GitHub
/* Arduino IDE - dé-commenter votre T-Watch */
//#define LILYGO_WATCH_2019_WITH_TOUCH
//#define LILYGO_WATCH_2019_NO_TOUCH
//#define LILYGO_WATCH_BLOCK
//#define LILYGO_WATCH_2020_V1
#define LILYGO_WATCH_LVGL
/* PlatformIO -> Select your watch in platformio.ini file */
#include
#include
// Déclare l'image de fond
LV_IMG_DECLARE(WALLPAPER_1_IMG);
/**************************/
/* Static variables */
/**************************/
TTGOClass *ttgo = nullptr;
static lv_obj_t *lvglpage = NULL;
bool KeyPressed = false;
/**************************/
/* STATIC PROTOTYPES */
/**************************/
bool gotoLVGLPage();
static void event_handler(lv_obj_t * obj, lv_event_t event);
void buildTFTPage();
/**************************/
void setup() {
Serial.begin(115200);
ttgo = TTGOClass::getWatch();
// Initialize the hardware
ttgo->begin();
ttgo->lvgl_begin();
// Allume le rétro-éclairage
ttgo->openBL();
// créé la page principale
buildTFTPage();
}
void loop() {
int16_t x, y;
if (ttgo->getTouch(x, y)) {
while (ttgo->getTouch(x, y)) {} // wait for user to release
if ( gotoLVGLPage() ) buildTFTPage();
}
}
void buildTFTPage(){
TFT_eSPI *tft = ttgo->tft;
tft->fillScreen(TFT_BLACK);
tft->setTextSize(2);
tft->setTextColor(TFT_WHITE);
tft->drawString("TFT_eSPI Screen", 0,0);
// Toucher l'écran pour sortir
tft->drawString("Touch screen to open LVGL page", 0,20);
}
// Créé un écran avec la librairie LVGL
bool gotoLVGLPage(){
KeyPressed = false;
// Container that contain all displayed elements. Makes it easier to show or hide the page
// Conteneur qui contient tous les élements affiché. Permet d'afficher ou masquer plus facilement la page
lvglpage = lv_cont_create( lv_scr_act(), NULL );
lv_obj_set_width( lvglpage, lv_disp_get_hor_res( NULL ) ); // Horizontal resolution | résolution horizontale
lv_obj_set_height( lvglpage, lv_disp_get_ver_res( NULL ) ); // Vertical resolution | résolution verticale
// Background Image | Image de fond
lv_obj_t * img1 = lv_img_create(lvglpage, NULL);
lv_img_set_src(img1, &WALLPAPER_1_IMG);
lv_obj_align(img1, NULL, LV_ALIGN_CENTER, 0, 0);
// Bouton au centre de l'écran
lv_obj_t * btn1 = lv_btn_create(lvglpage, NULL);
lv_obj_set_event_cb(btn1, event_handler);
lv_obj_align(btn1, NULL, LV_ALIGN_CENTER, 0, 0);
// Affiche un message défilant de bienvenue
lv_obj_t * welcomemessage;
welcomemessage = lv_label_create(lvglpage, NULL);
lv_label_set_long_mode(welcomemessage, LV_LABEL_LONG_SROLL_CIRC); /*Circular scroll*/
lv_obj_set_width(welcomemessage, lv_disp_get_hor_res( NULL ));
lv_label_set_text(welcomemessage, "Welcome on LVGL Demo Screen for TTGO T-Wach");
lv_obj_align(welcomemessage, btn1, LV_ALIGN_CENTER, 0, -60);
// libellé du bouton
lv_obj_t * label;
label = lv_label_create(btn1, NULL);
lv_label_set_text(label, "Exit");
while (!KeyPressed) {lv_task_handler(); delay(20);} // Wait for touch
Serial.print("Exit LVGL page");
lv_obj_del(lvglpage);
return true;
}
// Déclencheur du bouton retourner à l'écran d'accueil
static void event_handler(lv_obj_t * obj, lv_event_t event){
if (event == LV_EVENT_CLICKED) {
Serial.println("event_handler => return main page");
KeyPressed = true;
}
}
Remarque concernant les librairies TTGO.h et LilyGoWatch.h
Vous trouverez de nombreux tutoriels pour les T-Watch sur GitHub et sur d’autres blogs faisant référence à la librairie TTGO.h. C’est tout simplement la première version de la librairie développée par LilyGo.
Le dépôt, toujours disponible sur GitHub, est déprécié.
Vous pouvez toujours vous inspirer des exemples mais il faudra faire des adaptations du code
La librairie LilyGoWatch prend la suite. Certaines fonctions ne sont plus disponibles ou les appels sont différents
Par exemple, l’API de la librairie TFT_eSPI était accessible depuis la classe eTFT.
TTGOClass *watch = TTGOClass::getWatch();
TFT_eSPI *tft = watch->eTFT;
Maintenant, c’est la classe tft que l’on doit appeler
TTGOClass *watch = TTGOClass::getWatch();
TFT_eSPI *tft = watch->tft;
Certains projets intègrent directement un version modifiée de la librairie TTGO.h dans le dossier lib (projet PlatformIO). Si vous avez pris l’habitude d’utiliser la nouvelle version, vous risquez de perdre pas mal de temps à chercher vos erreurs et les méthodes…
Mises à jour
20/11/2020 Publication de l’article
Avez-vous aimé cet article ?