T-Watch. Mix the LVGL and TFT_eSPI libraries in the same ESP32 project

ttgo lilygo t-watch lvgl tft_epis esp32 project together
Share on facebook
Share on twitter
Share on linkedin
Share on pinterest
Share on email
Share on whatsapp

The LilyGoWatch library integrates the TFT_eSPI and LVGL libraries to build the display of the ESP32 application. The TFT_eSPI library is super simple and quick to learn, ideal for making a prototype. LVGL is a high level professional bookstore that takes more time to learn. The rendering is just mind blowing for a DIY project.

 

The TFT_eSPI library is super easy to use and quick to learn, ideal for displaying text, simple geometric shapes. The LVGL library is a very high level library that allows you to build complex screens with animations, scrolling lists, etc. intended for industrial applications.

The two examples offered allow you to combine the two libraries in the same ESP32 project for T-Watch with a minimum of Arduino code. Obviously there are many others. If you have other more effective solutions, feel free to share them on the GitHub repository or in the comments. Thank you 🙂

The source code has been tested on the T-Watch Touch 2019

As well as on the T-Watch 2020

T-Watch and expansion boards

Some articles to read before starting your T-Watch project

If you are new to developing your app for your T-Watch, here are some articles to get you started

Activate the LVGL library in your Arduino or PlatformIO project for T-Watch

Unlike the TFT_eSPI library, the LVGL library must be activated manually when starting the project by adding the LILYGO_WATCH_LVGL constant before declaring the LilyGoWatch library.

In a project developed using the Arduino IDE. Comment out the T-Watch model used

//#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 <LilyGoWatch.h> 

A sample platformio.ini file using the build_flags option

[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

Initialize the LVGL library

Before being able to access the API (functions) of the LVGL library, it must be initialized by calling the ttgo->beginlvgl() method in the setup().

Here is the minimum code to run before you can use the T-Watch screen.

void setup() {
  ttgo = TTGOClass::getWatch();
  ttgo->begin();
  ttgo->lvgl_begin();
  ttgo->openBL();
}

Example 1: main page created with LVGL

In this first example, we will learn how to create a project whose main page is built with the LVGL library. The second page is built with the TFT_eSPI library.

Create a new project with the Arduino or PlatformIO IDE and paste the following code. You can also grab the source code directly from GitHub.

logo github

/* Arduino IDE - uncomment your watch */
//#define LILYGO_WATCH_2019_WITH_TOUCH
//#define  LILYGO_WATCH_2019_NO_TOUCH
//#define LILYGO_WATCH_BLOCK
//#define LILYGO_WATCH_2020_V1

// Arduino IDE - uncomment to activate LVGL library
//#define LILYGO_WATCH_LVGL

/* PlatformIO -> Select your watch in platformio.ini file */
#include <Arduino.h>
#include <LilyGoWatch.h>

QueueHandle_t g_event_queue_handle = nullptr;

// Enum Queue events 
enum {
  Q_EVENT_DISPLAY_TFT,
};

// Declare background image 
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);

  // Create an event queue that we will use to trigger the display of the TFT_eSPI screen 
  g_event_queue_handle = xQueueCreate(20, sizeof(uint8_t));

  ttgo = TTGOClass::getWatch();
    // Initialize the hardware
  ttgo->begin();
  ttgo->lvgl_begin();

  // Turn on the backlight 
  ttgo->openBL();

  // and create main screen 
  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:{
        //Hide LVGL screen 
        hideLVGLpage(true);
        // Display TFT_eSPI screen and wait user touch the screen 
        // Inside the eSPI screen, LVGL handler in not executed 
        while ( openTFT() ) {}
        // Show LVGL main page | Affiche la page d'accueil LVGL
        hideLVGLpage(false);
      }
      break;
      default:
        break;
    }
  }  
  // LGVL handler | Superviseur LVGL
  lv_task_handler();
}

// Create a screen with LVGL library 
void createLVGLPage(){
  // Container that contain all displayed elements. Makes it easier to show or hide the page 
  lvglpage = lv_cont_create( lv_scr_act(), NULL );
  lv_obj_set_width( lvglpage, lv_disp_get_hor_res( NULL )  );  // Horizontal resolution 
  lv_obj_set_height( lvglpage, lv_disp_get_ver_res( NULL ) );  // Vertical resolution 

  // 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);

  // Button in center of the screen | 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 
  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);

  // Button label | libellé du bouton
  lv_obj_t * label;
  label = lv_label_create(btn1, NULL);
  lv_label_set_text(label, "Go to TFT_eSPI");
}

// Switch screen button handler | Déclencheur du bouton pour passer d'un écran LVGL à TFT_eSPI
static void event_handler(lv_obj_t * obj, lv_event_t event){
  // Important !! always test event to avoid multiple signals
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);
  }  
}

// Show / hide LVGL page L
void hideLVGLpage(bool hide){
  lv_obj_set_hidden(lvglpage, hide);
}

// Create a screen with TFT_eSPI library 
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);

    // Wait user touch the screen to exit 
    while (onAir) {

      if (!onAir) return false;
      
      int16_t x,y;

      if (ttgo->getTouch(x, y)) {
        while (ttgo->getTouch(x, y)) {}           // wait for user to release 
        Serial.println("User touch the screen");
        onAir = false;
      }     
    }  
}

A small click of demonstration

Explanation of the code

LVGL uses a supervisor (handler) to update the screen and detect user actions on the screen (click on a button, scroll through a list, etc.). The easiest way is to call the lv_task_handler() in the loop().

The problem is that you have to suspend the supervisor call while you are using the screen built with the TFT_eSPI library.

The easiest way to do this is to use the FreeRTOS event system. FreeRTOS is the base system on which Espressif built its ESP-IDF SDK for ESP32.

Just create a QueueHandle_t object.

QueueHandle_t g_event_queue_handle = nullptr;

enum {
  Q_EVENT_DISPLAY_TFT,
};

then to initialize the waiting line in the setup()

g_event_queue_handle = xQueueCreate(20, sizeof(uint8_t));

Then here is what happens:

  • The supervisor of the event_handler() button is called as soon as the user touches the screen
  • The supervisor sends a new Q_EVENT_DISPLAY_TFT message to the stack requesting the display of the TFT_eSPI screen using the xQueueSend function of FreeRTOS.
  • As soon as the message is received in the loop() by xQueueReceive()
    • We hide the LVGL page. As all the elements are in a container, just call the lv_obj_set_hiden(object_name, false) method to hide it
    • We call the openTFT() method which builds the TFT_eSPI library.
    • A while() loop blocks the call to lv_task_handler(), which prevents LVGL from rebuilding the screen
    • Here we exit the while() loop by touching the screen but we could create a button or use the T-Watch user button.
  • When exiting the TFT_eSPI page, the display of the LVGL page is re-enabled by calling the lv_obj_set_hiden(name_object, true) method. Optionally, you can force the updating of the display with

ttgo lilygo t-watch project tft_espi lvgl

Example 2, main page built with TFT_eSPI

Now let’s see the reverse. The main page of the project is created with the TFT_eSPI library. The secondary page with LVGL.

No supervisor is necessary since it suffices to use the ttgo->getTouch() method to detect an action on the screen in the loop(). Call management is therefore much simpler and we will not need to use the FreeRTOS xQueue task manager.

We build the LVGL page as before. The trick to keeping the page open and exiting it is to change the state of a variable. As long as this is not true, we regularly call the supervisor lv_task_handler(). Here for example, it is called every 20ms.

while (!KeyPressed) {lv_task_handler(); delay(20);} 

It is better to destroy the LVGL page when returning to the home page to free up resources.

lv_obj_del(lvglpage);

The display of the main screen is managed in the main loop(). When exiting the LVGL page, it suffices to return a state to trigger the re-construction of the main screen. We can use the same strategy to retrieve a value from a configuration page, for example a password …

void loop() {
  int16_t x, y;
  if (ttgo->getTouch(x, y)) {
    while (ttgo->getTouch(x, y)) {} // wait for user to release
    if ( gotoLVGLPage() ) buildTFTPage();
  }  
}

Here is a small demo click

And the full Arduino code of the example that you can also find on GitHub

logo 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 <Arduino.h>
#include <LilyGoWatch.h>

// 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();

  // Turn on the backlight 
  ttgo->openBL();
  // Build landing page 
  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);
  tft->drawString("Touch screen to open LVGL page", 0,20);
}

// Create a screen with LVGL library 
bool gotoLVGLPage(){
  KeyPressed = false;
  // Container that contain all displayed elements. Makes it easier to show or hide the page 
  lvglpage = lv_cont_create( lv_scr_act(), NULL );
  lv_obj_set_width( lvglpage, lv_disp_get_hor_res( NULL )  );  // Horizontal resolution 
  lv_obj_set_height( lvglpage, lv_disp_get_ver_res( NULL ) );  // Vertical resolution 

  // Background Image  
  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);

  // Button in center of the screen 
  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 circular scrolling welcome message 
  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);

  // Button label 
  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;
}

// Switch screen button handler 
static void event_handler(lv_obj_t * obj, lv_event_t event){
  // Important !! always test event to avoid multiple signals
  // 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 => return main page");
    KeyPressed = true;
  }  
}

Note concerning the TTGO.h and LilyGoWatch.h libraries

You will find many tutorials for T-Watch on GitHub and on other blogs referring to the TTGO.h library. It is quite simply the first version of the library developed by LilyGo.

The repository, still available on GitHub, is deprecated.

You can always take inspiration from the examples but you will have to adapt the code

The LilyGoWatch bookstore takes over. Some functions are no longer available or the calls are different

For example, the API of the TFT_eSPI library was accessible from the eTFT class.

TTGOClass *watch = TTGOClass::getWatch();
TFT_eSPI *tft = watch->eTFT;

Now it’s the tft class that we have to call

TTGOClass *watch = TTGOClass::getWatch();
TFT_eSPI *tft = watch->tft;

Some projects directly integrate a modified version of the TTGO.h library in the lib folder (PlatformIO project). If you’ve gotten into the habit of using the new version, you might waste a lot of time looking for your mistakes and methods …

Updates

2020/11/20 Publication of the article

Version française

 

Click to rate this post!
[Total: 0 Average: 0]

Are you having a problem with this topic?

Maybe someone has already found the solution, visit the forum before asking your question
Ask your question

Share on facebook
Share on twitter
Share on linkedin
Share on pinterest
Share on email
Share on whatsapp

Did you like this project ? Don't miss any more projects by subscribing to our weekly newsletter!

We will be happy to hear your thoughts

      Leave a Reply

      DIY Projects