LVGL (Light and Versatile Graphics Library) è una libreria grafica opensource per sistemi embedded e microcontrollers come l’ESP32.

Introduzione a LVGL

“LVGL is an open-source graphics library providing everything you need to create embedded GUI with easy-to-use graphical elements, beautiful visual effects and low memory footprint.” (lvgl.io Official Website).

LVGL (Light and Versatile Graphics Library) è una libreria grafica opensource per sistemi embedded e microcontrollers come l’ESP32. LVGL permette di disegnare interfacce grafiche accattivanti e moderne per mezzo dei numerosi widget ed effetti già pronti che mette a disposizione.

Se questa immagine animata vi ha colpito, vi consiglio subito di leggere l’introduzione della documentazione ufficiale con tutte le key features, ma soprattutto date uno sguardo al numero infinito di esempi funzionanti nel browser! :open_mouth: :scream:

Si, gli esempi funzionano nel browser perchè lgvl è una libreria grafica generica, dalla guida:

Fondamentalmente, ogni controller moderno in grado di pilotare un display è adatto per eseguire LVGL.

Il rovescio della medaglia, se cosi vogliamo chiamarlo, è che siamo noi a doverci sobbarcare il lavoro di basso livello specifico per l’hardware per il quale stiamo sviluppando. :sweat_smile:

Sta a noi la gestione del driver video, del buffer dei frame, degli input (touchscreen, pulsanti, encoders, tastiere, mouse, ecc).

Progetti su GitHub

Io sono l’esatto contrario di un grafico, la negazione totale per quanto riguarda il design di un’interfaccia grafica :rofl: Ma avendo la necessità di realizzare menù, pulsanti, form, grafici, ecc, ho giocato abbastanza con LVGL e nel corso del tempo ho creato un progetto base per ESP32 ed LVGL (ovviamente con Platform.IO) dal quale partire per integrare una GUI nel nostro software in poco tempo.

Ho cercato di separare il più possibile la parte riguardante la GUI dal resto del software, che viene eseguita in un processo separato e parallelo al main loop, grazie ad alcune funzoni FreeRTOS.

I progetti descritti in questo articolo sono nel repository che ho preparato su GitHub. Informazioni dettagliate su come clonare un repository remoto le trovi nell’apposito articolo.

Official demos

Con il progetto Official-demos saremo in grado di eseguire sulla mmb le demo ufficiali presenti sul sito di LVGL, come quello nella gif animata all’inizio dell’articolo. Ovviamente è sempre possibile eseguire il progetto su breadboard in assenza di una mmb.

Andiamo ad analizzare il contenuto del file di configurazione platformio.ini. Come abbiamo già visto in precedenza su altri articoli, anche in questo caso andiamo a definire diverse sezioni:

  • [env] Configurazioni comuni
  • [env:ILI9341]Configurazioni specifiche per ILI9341
  • [env:ILI9488]Configurazioni specifiche per ILI9488

Maggiori informazioni sull’utilizzo degli environments di Platform.IO le trovi in questo articolo.

Le librerie che andiamo ad utilizzare sono 3 e le includiamo nel env comune:

lib_deps =
          TFT_eSPI ; utilizzata come driver del display
          lvgl ; la libreria LVGL
          https://github.com/lvgl/lv_demos.git ; gli esempi ufficiali

Prestiamo ora attenzione alle build_flags dove, oltre ai pin del display, andiamo a scegliere la demo che vogliamo eseguire, il tema e la luminosità del display:

build_flags   =
  ; set 1 to enable a demo all others to 0:
  -D LV_USE_DEMO_WIDGETS=1
  -D LV_USE_DEMO_MUSIC=0
  -D LV_USE_DEMO_STRESS=0
  -D LV_USE_DEMO_BENCHMARK=0

  ; 0: Light mode; 1: Dark mode
  -D LV_THEME_DEFAULT_DARK=1

  ; set brightness
  -D TFT_BRIGHTNESS=255

Possiamo dunque compilare e flashare il progetto per eseguire la demo scelta sull’ESP32.

Entriamo ora nel dettaglio del codice sorgente, e lo facciamo analizzando il secondo progetto LVGL Base Project che trovi sempre nel repository, e che è molto simile alle official demos, con la differenza che ora siamo noi a disegnare la nostra interfaccia grafica :grinning:

LVGL Base Project

Il file di configurazione platformio.ini è pressocchè identico a quello appena visto, con l’unica differenza che ora tra le build_flags non andiamo a selezionare la demo da eseguire, ma uno dei layout che abbiamo realizzato nella directory src/gui_layouts.

build_flags   =
  ; set 1 to enable a layout in src/gui_layouts all others to 0:
  -D LAYOUT_TABVIEW=1
  -D LAYOUT_BUTTON_MATRIX=0

Nel progetto ho infatti aggiunto due semplicissimi esempi di layout su cui puoi iniziare a smanettare: src\gui_layouts\tabview.h e src\gui_layouts\button_matrix.h che puoi switchare da platformio.ini.

Come ho già detto più su, ho cercato di tenere il codice relativo alla GUI separato dal resto dell’applicazione e questo puoi notarlo subito dal file src/main.cpp che è praticamente vuoto:

  1. Includiamo il file gui_setup.h (tutte le funzioni di setup)
  2. Creiamo nel setup un task parallelo per la gestione della gui (guiTask su gui_setup.h)

xTaskCreate è una funzione FreeRTOS che ci permette di creare task paralleli al main loop, che non vanno ad interferire con il programma principale.

#include <Arduino.h>
#include "gui_setup.h"

void setup() {
  Serial.begin(115200);
  xTaskCreate(guiTask, "gui", 4096 * 2, NULL, 1, NULL);
}

void loop() {
}

Impostando il codice in questo modo, è semplicissimo integrare un’interfaccia grafica anche su un progetto già esistente senza stravolgere troppo il codice: se prendiamo ad esempio un progetto che legge i valori di un sensore e li riporta nella console, in poco tempo riusciremo a trasferirli sul display :wink:

Veniamo ora al cuore della nostra GUI, il file src/gui_setup.h: qui troviamo le funzioni che si occupano del lavoro sporco di cui parlavo all’inizio dell’articolo: la gestione del buffer, del display, dell’input (in questo caso il touchscreen), del tema, ecc.

Proprio perchè questo file si occupa del lavoro di basso livello, nella stragrande maggioranza dei casi non è necessario modificarlo. Ho cercato comunque di commentarlo il più possibile direttamente nel codice per chi volesse approfondire questa parte.

Qui voglio sottolineare solo lo switch del layout che avviene all’inizio del file, in base alla nostra scelta su platformio.ini:

#if LAYOUT_TABVIEW
  #include "gui_layouts/tabview.h"
#endif
#if LAYOUT_BUTTON_MATRIX
  #include "gui_layouts/button_matrix.h"
#endif

andiamo ad includere quindi il file che contiene il layout vero e proprio da disegnare nella funzione lvLayoutSetup().

Nel caso del tabwiev abbiamo quindi:

// possible tab position: LV_DIR_TOP - LV_DIR_BOTTOM - LV_DIR_LEFT - LV_DIR_RIGHT
#define TAB_DIR LV_DIR_TOP

// width size for left and right - height for top and bottom
#define TAB_SIZE 50

void lvLayoutSetup(void)
{
    /*Create a Tab view object*/
    lv_obj_t *tabview;
    tabview = lv_tabview_create(lv_scr_act(), TAB_DIR, TAB_SIZE);

    /*Add 3 tabs (the tabs are page (lv_page) and can be scrolled*/
    lv_obj_t *tab1 = lv_tabview_add_tab(tabview, "Tab 1");
    lv_obj_t *tab2 = lv_tabview_add_tab(tabview, "Tab 2");
    lv_obj_t *tab3 = lv_tabview_add_tab(tabview, "Tab 3");

    /*Add content to the tabs*/
    lv_obj_t * label = lv_label_create(tab1);
    lv_label_set_text(label, "ESP32 + LVGL Base Project\n"
                             "By ZioTester Lab\n\n"
                             "https://ziotester.github.io");

    label = lv_label_create(tab2);
    lv_label_set_text(label, "Second tab\n\n"
                             "If the content\n"
                             "of a tab\n"
                             "becomes too\n"
                             "longer\n"
                             "than the\n"
                             "container\n"
                             "then it\n"
                             "automatically\n"
                             "becomes\n"
                             "scrollable.\n"
                             "\n"
                             "\n"
                             "\n"
                             "Can you see it?");

    label = lv_label_create(tab3);
    lv_label_set_text(label, "Third tab");
}

Nella funzione lvLayoutSetup() possiamo sbizzarrirci inserendo e combinando tra loro tutti gli esempi presenti sul sito ufficiale. Una risorsa molto importante è la documentazione ufficiale che entra nei dettagli di ogni singolo widget.

Da poco tempo gli stessi sviluppatori di LVGL hanno realizzato un editor drag&drop per la realizzazione di interfacce grafiche LVGL che permette di esportare il codice sorgente da includere nei nostri progetti: Squareline Studio. Non ho ancora approfondito adeguatamente, ma il codice generato è perfettamente compatibile con il nostro progetto base. Approfondiremo Squareline in un altro articolo!

Web installer

Per questo progetto sono disponibili anche alcuni web installers nell’area dedicata. E’ possibile installare il firmware direttamente dalla pagina web.