LVGL with ESP32 TFT LCD Touchscreen Display – 2.8 inch ILI9341 240×320 (Arduino IDE)

In this guide, you’ll get started with the LVGL (Light and Versatile Graphics Library) using a TFT LCD Touchscreen Display wired to an ESP32 board. The LVGL is a popular free and open-source embedded graphics library to create awesome UIs for many microcontrollers and displays.

ESP32 TFT LCD Touchscreen Display 2.8 inch ILI9341 240px by 320px Arduino IDE LVGL Graphics Library

We recommend an ESP32 board that has an on-board TFT display, read our Guide for LVGL with ESP32 Cheap Yellow Display Board (ESP32-2432S028R)

Introducing the TFT LCD Touchscreen Display

The display we’re using in this guide is the 2.8. inch TFT LCD that also comes with a touchscreen. The display communicates via SPI communication protocol and uses the ILI9341 driver. You can write text, draw shapes, and display images. The touchscreen also uses the SPI communication protocol.

The TFT LCD touchscreen also comes with an SD card interface if you need to load files for your specific project. This display is also available with different screen sizes.

Parts Required

For this project, you need to wire the TFT display and touchscreen pins to the ESP32. Here’s a list of parts you need:

Wiring TFT LCD Touchscreen display to ESP32

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Wiring the TFT LCD Touchscreen Display to the ESP32 Board

Wiring the TFT LCD Touchscreen Display to the ESP32 Board

Wire the TFT LCD and touchscreen pins to the following ESP32 GPIOs (you must use these exact pins, otherwise the project will not work).

TFT LCD TouchscreenESP32
T_IRQGPIO 36
T_OUTGPIO 39
T_DINGPIO 32
T_CSGPIO 33
T_CLKGPIO 25
SDO(MISO)GPIO 12
LEDGPIO 21
SCKGPIO 14
SDI(MOSI)GPIO 13
D/CGPIO 2
RESETEN/RESET
CSGPIO 15
GNDGND
VCC5V (or 3.3V)*

* In the VCC pin, you can either use 5V or 3.3V depending if your J1 connection is open or closed (by default it’s usually open as you can see in the figure below).

VCC = 5V | J1=OPEN
VCC = 3.3V | J1=CLOSE
TFT LCD Touchscreen display J1 connection

Introducing LVGL (Light and Versatile Graphics Library)

LVGL (Light and Versatile Graphics Library) is a free and open-source graphics library that provides a wide range of easy-to-use graphical elements for your microcontroller projects that require a graphical user interface (GUI).

Logo LVGL Light and Versatile Graphics Library

Here are some of its key features:

  • Blocks: buttons, charts, lists, sliders, images, etc…
  • Advanced graphics with animations, anti-aliasing, opacity, smooth scrolling;
  • Various input devices such as touchpad, mouse, keyboard, encoder, etc…
  • Multi-language support with UTF-8 encoding;
  • Multi-display support, i.e. use multiple TFT, monochrome displays simultaneously;
  • Fully customizable graphic elements with CSS-like styles;
  • Hardware independent: use with any microcontroller or display;
  • Scalable: able to operate with little memory (64 kB Flash, 16 kB RAM);
  • Written in C for maximal compatibility (C++ compatible) and binding to MicroPython.

It also has a wide range of code examples in their documentation that you can use: text, buttons, sliders, input fields, keyboard, custom styling, images, arcs, lines, animations, menus, tabs, layouts, tables, and much more…

LVGL Library Examples

Installing Arduino Libraries

The ESP32 communicates with the TFT Display and Touchscreen using SPI communication protocol. We’ll be using the TFT_eSPI, XPT2046_Touchscreen, and LVGL 9 libraries.

Installing the TFT_eSPI Library

Open your Arduino IDE and go to Sketch Include Library > Manage Libraries. The Library Manager should open. Search for TFT_eSPI. Select the TFT_eSPI library by Bodmer and install it.

Installing TFT_eSPI library Bodmer Arduino IDE 2

Installing the XPT2046_Touchscreen Library

Open your Arduino IDE and go to Sketch Include Library > Manage Libraries. The Library Manager should open. Search for XPT2046_Touchscreen. Select the XPT2046_Touchscreen library by Paul Stoffregen and install it.

Installing XPT2046_Touchscreen Library by Paul Stoffregen Arduino IDE 2

Installing the LVGL 9 Library

Open your Arduino IDE and go to Sketch Include Library > Manage Libraries. The Library Manager should open. Search for LVGL. Select the LVGL library by kiskegabor and install version 9.2.

Installing LVGL version 9.2 Arduino IDE software

Prepare Config Files for TFT_eSPI and LVGL Library

To properly use the TFT_eSPI library, you need a configuration file called User_Setup.h with the right definitions. You also need to prepare the lv_conf.h file for the LVGL library. We’ve already prepared these two files so that you don’t have any configuration issues following our examples. You just need to download them and move them to the correct folders. Follow the next instructions to learn how to do it.

a) Preparing the Config Files – Windows PC

b) Preparing the Config Files – Mac OS

a) Preparing the Config Files – Windows PC

Having all the libraries installed (TFT_eSPI, XPT2046_Touchscreen, and LVGL), start by downloading the User_Setup.h configuration file.

User_Setup.h file for TFT_eSPI library Bodmer Arduino IDE 2

In your Arduino IDE, go to File and open the Preferences menu.

Open Arduino IDE 2 Preferences Menu Windows PC

Copy the Arduino IDE “Sketchbook location” path. In my case, it’s:

C:\Users\rui_s\Documents\Arduino
Open Sketchbook Location Arduino IDE 2 Browse

Then, in your Windows PC File Explorer tab enter the sketchbook location path to open the Arduino folder (it’s usually under the Documents folder).

Browse to Arduino libraries folder to copy lv_conf.h file Arduino IDE 2

Open the libraries folder:

Open the Arduino IDE 2 Libraries Folder Windows PC

You should see the TFT_eSPI library folder there. Open it.

Open TFT_eSPI folder libraries Arduino IDE 2

You should be in a similar folder path as shown below:

C:\Users\rui_s\Documents\Arduino\libraries\TFT_eSPI

Copy the User_Setup.h file provided earlier and replace the existing file.

Move copy User_Setup.h file to TFT_eSPI library folder Arduino IDE 2

Then, download the lv_conf.h configuration file.

lv_config.h file for LVGL library by Kisvegabor Arduino IDE 2

Open a similar folder path in your computer as shown below:

C:\Users\rui_s\Documents\Arduino\libraries

Move the lv_conf.h to the libraries folder (do NOT move it inside the lvgl folder).

Move copy lv_conf.h configuration file to Arduino libraries folder Arduino IDE 2

IMPORTANT: other User_Setup.h and lv_conf.h files available on the internet will probably NOT work with the examples available at Random Nerd Tutorials. You must use the exact files provided in this article.

Note: if you update your libraries, you’ll need to do this procedure again and place the right configuration files in the right places.

b) Preparing the Config Files – Mac OS

Having both libraries installed (TFT_eSPI and XPT2046_Touchscreen), download the User_Setup.h configuration file.

Download User_Setup.h file for TFT_eSPI library Bodmer Arduino IDE 2

In your Arduino IDE, open the Settings menu.

Open Arduino IDE 2 Preferences Menu Mac OS

Copy the Arduino IDE “Sketchbook location” path. In my case, it’s:

/Users/rui/Documents/Arduino
Open Sketchbook Location Arduino IDE 2 Browse Mac OS

In Finder, type /Users/rui/Documents/Arduino and open that directory.

Open the Arduino IDE 2 Folder Mac OS

Open the libraries folder.

Open the Arduino IDE 2 Libraries Folder Mac OS

You should see the TFT_eSPI library folder there. Open it.

Open TFT_eSPI folder libraries Arduino IDE 2 Mac OS

You should be in a similar folder path as shown below:

/Users/rui/Documents/Arduino/libraries/TFT_eSPI

Copy the User_Setup.h file provided earlier and replace the existing file.

Move copy User_Setup.h file to TFT_eSPI library folder Arduino IDE 2 Mac OS

You should now have the User_Setup.h file provided on that path.

Move User_Setup.h file to TFT_eSPI library folder Arduino IDE 2 Mac OS

Then, download the lv_conf.h configuration file.

Download lv_config.h file for LVGL library by Kisvegabor Arduino IDE 2

Open a similar folder path for the libraries in your computer as shown below:

/Users/rui/Documents/Arduino/libraries
Open the Arduino IDE 2 Libraries Folder Mac OS

Move the lv_conf.h to the libraries folder (do NOT move it inside the lvgl folder).

Move copy lv_conf.h configuration file to Arduino libraries folder Arduino IDE 2 Mac OS

IMPORTANT: other User_Setup.h and lv_conf.h files available on the internet will probably NOT work with the examples available at Random Nerd Tutorials. You must use the exact files provided in this article.

Note: if you update your libraries, you’ll need to do this procedure again and place the right configuration files in the right places.

Code – Display Text, Create Buttons and Slider

The following code displays a simple text in your TFT display and allows you to test the touchscreen using a button and slider. When you press the buttons or slider, it should trigger some events.

Copy the following code to the Arduino IDE and upload it to your board.

/*  Rui Santos & Sara Santos - Random Nerd Tutorials
    THIS EXAMPLE WAS TESTED WITH THE FOLLOWING HARDWARE:
    1) ESP32-2432S028R 2.8 inch 240×320 also known as the Cheap Yellow Display (CYD): https://makeradvisor.com/tools/cyd-cheap-yellow-display-esp32-2432s028r/
      SET UP INSTRUCTIONS: https://RandomNerdTutorials.com/cyd-lvgl/
    2) REGULAR ESP32 Dev Board + 2.8 inch 240x320 TFT Display: https://makeradvisor.com/tools/2-8-inch-ili9341-tft-240x320/ and https://makeradvisor.com/tools/esp32-dev-board-wi-fi-bluetooth/
      SET UP INSTRUCTIONS: https://RandomNerdTutorials.com/esp32-tft-lvgl/
    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/

/*  Install the "lvgl" library version 9.2 by kisvegabor to interface with the TFT Display - https://lvgl.io/
    *** IMPORTANT: lv_conf.h available on the internet will probably NOT work with the examples available at Random Nerd Tutorials ***
    *** YOU MUST USE THE lv_conf.h FILE PROVIDED IN THE LINK BELOW IN ORDER TO USE THE EXAMPLES FROM RANDOM NERD TUTORIALS ***
    FULL INSTRUCTIONS AVAILABLE ON HOW CONFIGURE THE LIBRARY: https://RandomNerdTutorials.com/cyd-lvgl/ or https://RandomNerdTutorials.com/esp32-tft-lvgl/   */
#include <lvgl.h>

/*  Install the "TFT_eSPI" library by Bodmer to interface with the TFT Display - https://github.com/Bodmer/TFT_eSPI
    *** IMPORTANT: User_Setup.h available on the internet will probably NOT work with the examples available at Random Nerd Tutorials ***
    *** YOU MUST USE THE User_Setup.h FILE PROVIDED IN THE LINK BELOW IN ORDER TO USE THE EXAMPLES FROM RANDOM NERD TUTORIALS ***
    FULL INSTRUCTIONS AVAILABLE ON HOW CONFIGURE THE LIBRARY: https://RandomNerdTutorials.com/cyd-lvgl/ or https://RandomNerdTutorials.com/esp32-tft-lvgl/   */
#include <TFT_eSPI.h>

// Install the "XPT2046_Touchscreen" library by Paul Stoffregen to use the Touchscreen - https://github.com/PaulStoffregen/XPT2046_Touchscreen - Note: this library doesn't require further configuration
#include <XPT2046_Touchscreen.h>

// Touchscreen pins
#define XPT2046_IRQ 36   // T_IRQ
#define XPT2046_MOSI 32  // T_DIN
#define XPT2046_MISO 39  // T_OUT
#define XPT2046_CLK 25   // T_CLK
#define XPT2046_CS 33    // T_CS

SPIClass touchscreenSPI = SPIClass(VSPI);
XPT2046_Touchscreen touchscreen(XPT2046_CS, XPT2046_IRQ);

#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320

// Touchscreen coordinates: (x, y) and pressure (z)
int x, y, z;

#define DRAW_BUF_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];

// If logging is enabled, it will inform the user about what is happening in the library
void log_print(lv_log_level_t level, const char * buf) {
  LV_UNUSED(level);
  Serial.println(buf);
  Serial.flush();
}

// Get the Touchscreen data
void touchscreen_read(lv_indev_t * indev, lv_indev_data_t * data) {
  // Checks if Touchscreen was touched, and prints X, Y and Pressure (Z)
  if(touchscreen.tirqTouched() && touchscreen.touched()) {
    // Get Touchscreen points
    TS_Point p = touchscreen.getPoint();
    // Calibrate Touchscreen points with map function to the correct width and height
    x = map(p.x, 200, 3700, 1, SCREEN_WIDTH);
    y = map(p.y, 240, 3800, 1, SCREEN_HEIGHT);
    z = p.z;

    data->state = LV_INDEV_STATE_PRESSED;

    // Set the coordinates
    data->point.x = x;
    data->point.y = y;

    // Print Touchscreen info about X, Y and Pressure (Z) on the Serial Monitor
    /* Serial.print("X = ");
    Serial.print(x);
    Serial.print(" | Y = ");
    Serial.print(y);
    Serial.print(" | Pressure = ");
    Serial.print(z);
    Serial.println();*/
  }
  else {
    data->state = LV_INDEV_STATE_RELEASED;
  }
}

int btn1_count = 0;
// Callback that is triggered when btn1 is clicked
static void event_handler_btn1(lv_event_t * e) {
  lv_event_code_t code = lv_event_get_code(e);
  if(code == LV_EVENT_CLICKED) {
    btn1_count++;
    LV_LOG_USER("Button clicked %d", (int)btn1_count);
  }
}

// Callback that is triggered when btn2 is clicked/toggled
static void event_handler_btn2(lv_event_t * e) {
  lv_event_code_t code = lv_event_get_code(e);
  lv_obj_t * obj = (lv_obj_t*) lv_event_get_target(e);
  if(code == LV_EVENT_VALUE_CHANGED) {
    LV_UNUSED(obj);
    LV_LOG_USER("Toggled %s", lv_obj_has_state(obj, LV_STATE_CHECKED) ? "on" : "off");
  }
}

static lv_obj_t * slider_label;
// Callback that prints the current slider value on the TFT display and Serial Monitor for debugging purposes
static void slider_event_callback(lv_event_t * e) {
  lv_obj_t * slider = (lv_obj_t*) lv_event_get_target(e);
  char buf[8];
  lv_snprintf(buf, sizeof(buf), "%d%%", (int)lv_slider_get_value(slider));
  lv_label_set_text(slider_label, buf);
  lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
  LV_LOG_USER("Slider changed to %d%%", (int)lv_slider_get_value(slider));
}

void lv_create_main_gui(void) {
  // Create a text label aligned center on top ("Hello, world!")
  lv_obj_t * text_label = lv_label_create(lv_screen_active());
  lv_label_set_long_mode(text_label, LV_LABEL_LONG_WRAP);    // Breaks the long lines
  lv_label_set_text(text_label, "Hello, world!");
  lv_obj_set_width(text_label, 150);    // Set smaller width to make the lines wrap
  lv_obj_set_style_text_align(text_label, LV_TEXT_ALIGN_CENTER, 0);
  lv_obj_align(text_label, LV_ALIGN_CENTER, 0, -90);

  lv_obj_t * btn_label;
  // Create a Button (btn1)
  lv_obj_t * btn1 = lv_button_create(lv_screen_active());
  lv_obj_add_event_cb(btn1, event_handler_btn1, LV_EVENT_ALL, NULL);
  lv_obj_align(btn1, LV_ALIGN_CENTER, 0, -50);
  lv_obj_remove_flag(btn1, LV_OBJ_FLAG_PRESS_LOCK);

  btn_label = lv_label_create(btn1);
  lv_label_set_text(btn_label, "Button");
  lv_obj_center(btn_label);

  // Create a Toggle button (btn2)
  lv_obj_t * btn2 = lv_button_create(lv_screen_active());
  lv_obj_add_event_cb(btn2, event_handler_btn2, LV_EVENT_ALL, NULL);
  lv_obj_align(btn2, LV_ALIGN_CENTER, 0, 10);
  lv_obj_add_flag(btn2, LV_OBJ_FLAG_CHECKABLE);
  lv_obj_set_height(btn2, LV_SIZE_CONTENT);

  btn_label = lv_label_create(btn2);
  lv_label_set_text(btn_label, "Toggle");
  lv_obj_center(btn_label);
  
  // Create a slider aligned in the center bottom of the TFT display
  lv_obj_t * slider = lv_slider_create(lv_screen_active());
  lv_obj_align(slider, LV_ALIGN_CENTER, 0, 60);
  lv_obj_add_event_cb(slider, slider_event_callback, LV_EVENT_VALUE_CHANGED, NULL);
  lv_slider_set_range(slider, 0, 100);
  lv_obj_set_style_anim_duration(slider, 2000, 0);

  // Create a label below the slider to display the current slider value
  slider_label = lv_label_create(lv_screen_active());
  lv_label_set_text(slider_label, "0%");
  lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
}

void setup() {
  String LVGL_Arduino = String("LVGL Library Version: ") + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
  Serial.begin(115200);
  Serial.println(LVGL_Arduino);
  
  // Start LVGL
  lv_init();
  // Register print function for debugging
  lv_log_register_print_cb(log_print);

  // Start the SPI for the touchscreen and init the touchscreen
  touchscreenSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
  touchscreen.begin(touchscreenSPI);
  // Set the Touchscreen rotation in landscape mode
  // Note: in some displays, the touchscreen might be upside down, so you might need to set the rotation to 0: touchscreen.setRotation(0);
  touchscreen.setRotation(2);

  // Create a display object
  lv_display_t * disp;
  // Initialize the TFT display using the TFT_eSPI library
  disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
  lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270);
    
  // Initialize an LVGL input device object (Touchscreen)
  lv_indev_t * indev = lv_indev_create();
  lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
  // Set the callback function to read Touchscreen input
  lv_indev_set_read_cb(indev, touchscreen_read);

  // Function to draw the GUI (text, buttons and sliders)
  lv_create_main_gui();
}

void loop() {
  lv_task_handler();  // let the GUI do its work
  lv_tick_inc(5);     // tell LVGL how much time has passed
  delay(5);           // let this time pass
}

View raw code

How the Code Works

Let’s take a quick look at the parts of the code that are relevant to this example.

Libraries

Include the lvgl, TFT_eSPI and XPT2046_Touchscreen libraries.

#include <lvgl.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>

Initialize Touchscreen

The following lines set the touchscreen pinout:

#define XPT2046_IRQ 36
#define XPT2046_MOSI 32
#define XPT2046_MISO 39
#define XPT2046_CLK 25
#define XPT2046_CS 33

Create a touchscreenSPI and touchscreen instances:

SPIClass touchscreenSPI = SPIClass(VSPI);
XPT2046_Touchscreen touchscreen(XPT2046_CS, XPT2046_IRQ);

Other Variables

Set the screen width and screen height:

#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
#define FONT_SIZE 2

Variables to store the coordinates: (x, y) and pressure (z).

int x, y, z;

setup()

Start a serial communication with the Serial Monitor at a baud rate of 115200 and print the LVGL library version that you are using:

String LVGL_Arduino = String("LVGL Library Version: ") + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.begin(115200);
Serial.println(LVGL_Arduino);

Start the LVGL and assign a callback function for debugging purposes.

lv_init();
lv_log_register_print_cb(log_print);

Start the SPI for the touchscreen and initialize the touchscreen.

touchscreenSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
touchscreen.begin(touchscreenSPI);
touchscreen.setRotation(2);

Note: in some displays, the touchscreen might be upside down, so you might need to set the rotation to 0: touchscreen.setRotation(2);

Create the display object and initialize the TFT display using the TFT_eSPI library.

lv_display_t * disp;
disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270);

Initialize an LVGL input device object (touchscreen) and set the callback function that will be triggered when you click the touchscreen.

lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, touchscreen_read);

Finally, call the lv_create_main_gui() function to draw the GUI for your touchscreen:

lv_create_main_gui();

loop()

When running an LVGL example, the loop() will usually look like this:

lv_task_handler();  // let the GUI do its work
lv_tick_inc(5);        // tell LVGL how much time has passed
delay(5);                // let this time pass

lv_create_main_gui()

The lv_create_main_gui() function draws the text label, buttons, and sliders. It’s also where you assign the event handler callback functions that will be triggered when you interact with your display.

void lv_create_main_gui(void) {
  // Create a text label aligned center on top ("Hello, world!")
  lv_obj_t * text_label = lv_label_create(lv_screen_active());
  lv_label_set_long_mode(text_label, LV_LABEL_LONG_WRAP);    // Breaks the long lines
  lv_label_set_text(text_label, "Hello, world!");
  lv_obj_set_width(text_label, 150);    // Set smaller width to make the lines wrap
  lv_obj_set_style_text_align(text_label, LV_TEXT_ALIGN_CENTER, 0);
  lv_obj_align(text_label, LV_ALIGN_CENTER, 0, -90);

  lv_obj_t * btn_label;
  // Create a Button (btn1)
  lv_obj_t * btn1 = lv_button_create(lv_screen_active());
  lv_obj_add_event_cb(btn1, event_handler_btn1, LV_EVENT_ALL, NULL);
  lv_obj_align(btn1, LV_ALIGN_CENTER, 0, -50);
  lv_obj_remove_flag(btn1, LV_OBJ_FLAG_PRESS_LOCK);

  btn_label = lv_label_create(btn1);
  lv_label_set_text(btn_label, "Button");
  lv_obj_center(btn_label);

  // Create a Toggle button (btn2)
  lv_obj_t * btn2 = lv_button_create(lv_screen_active());
  lv_obj_add_event_cb(btn2, event_handler_btn2, LV_EVENT_ALL, NULL);
  lv_obj_align(btn2, LV_ALIGN_CENTER, 0, 10);
  lv_obj_add_flag(btn2, LV_OBJ_FLAG_CHECKABLE);
  lv_obj_set_height(btn2, LV_SIZE_CONTENT);

  btn_label = lv_label_create(btn2);
  lv_label_set_text(btn_label, "Toggle");
  lv_obj_center(btn_label);
  
  // Create a slider aligned in the center bottom of the TFT display
  lv_obj_t * slider = lv_slider_create(lv_screen_active());
  lv_obj_align(slider, LV_ALIGN_CENTER, 0, 60);
  lv_obj_add_event_cb(slider, slider_event_callback, LV_EVENT_VALUE_CHANGED, NULL);
  lv_slider_set_range(slider, 0, 100);
  lv_obj_set_style_anim_duration(slider, 2000, 0);

  // Create a label below the slider to display the current slider value
  slider_label = lv_label_create(lv_screen_active());
  lv_label_set_text(slider_label, "0%");
  lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
}

event_handler_btn1()

The event_handler_btn1() function is triggered when you click the “Button” and it will also display in the Arduino IDE Serial Monitor the number of times the button has been clicked.

int btn1_count = 0;
// Callback that is triggered when btn1 is clicked
static void event_handler_btn1(lv_event_t * e) {
  lv_event_code_t code = lv_event_get_code(e);
  if(code == LV_EVENT_CLICKED) {
    btn1_count++;
    LV_LOG_USER("Button clicked %d%", (int)btn1_count);
  }
}

event_handler_btn2()

The event_handler_btn2() is triggered when you click the “Toggle” button, it stores the current button state and prints a message with the current state in the Serial Monitor.

// Callback that is triggered when btn2 is clicked/toggled
static void event_handler_btn2(lv_event_t * e) {
  lv_event_code_t code = lv_event_get_code(e);
  lv_obj_t * obj = (lv_obj_t*) lv_event_get_target(e);
  if(code == LV_EVENT_VALUE_CHANGED) {
    LV_UNUSED(obj);
    LV_LOG_USER("Toggled %s", lv_obj_has_state(obj, LV_STATE_CHECKED) ? "on" : "off");
  }
}

slider_event_callback()

The slider_event_callback() function is called when you move the slider, it will also display both in the touchscreen and Serial Monitor the latest slider value on a scale from 0 to 100%.

static lv_obj_t * slider_label;
// Callback that prints the current slider value on the TFT display and Serial Monitor for debugging purposes
static void slider_event_callback(lv_event_t * e) {
  lv_obj_t * slider = (lv_obj_t*) lv_event_get_target(e);
  char buf[8];
  lv_snprintf(buf, sizeof(buf), "%d%%", (int)lv_slider_get_value(slider));
  lv_label_set_text(slider_label, buf);
  lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
  LV_LOG_USER("Slider changed to %d%%", (int)lv_slider_get_value(slider));
}

Demonstration

Upload the code to your board. Go to Tools > Board and select ESP32 > ESP32 Dev Module. Then, select the right COM port in Tools > Port. Finally, click the upload button.

Arduino IDE 2 Upload Button

After uploading the code to your board, it should display the sample “Hello, world!” text centered at the top.

Press with your finger the first “Button”. In the Serial Monitor, it will print the coordinates of your button press, as well as a message with the number of times the button has been clicked: “Button clicked 1”, “Button clicked 2”, “Button clicked 3″…

ESP32 TFT Display Touchscreen LVGL Library Example

Then, click the “Toggle” button, this could be used to control an LED or any other output. This button type has two states (color blue when off and color red when on).

ESP32 TFT Display Touchscreen LVGL Library Example Button Toggle

Drag the slider with your finger, you’ll see the slider moving and the slider label changing when you move your finger.

ESP32 TFT Display Touchscreen LVGL Library Example Move Slider

All the events will be printed in your Arduino IDE Serial Monitor for debugging purposes:

ESP32 TFT Touchscreen Display LVGL Basic Example Text Label Button Slider

Wrapping Up

In this tutorial, you learned to get started with the LVGL library with the TFT Touchscreen wired to an ESP32 board. In the next guides, we’ll explore other features and components of the LVGL library.

If you have an ESP32 Cheap Yellow Display board, you can follow this other guide to use the LVGL with the CYD board.

We hope you found this tutorial useful. We’re preparing more guides about the LVGL library, so stay tuned.

Other guides you might like reading:

To learn more about the ESP32, make sure to take a look at our resources:



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

43 thoughts on “LVGL with ESP32 TFT LCD Touchscreen Display – 2.8 inch ILI9341 240×320 (Arduino IDE)”

  1. Nice example, but the ridiculous number of ads in the web page are very distracting.
    Please limit or eliminate most of them.

    Reply
  2. There were error messages.
    – E:\port apps\Arduino\arduino\libraries\TFT_eSPI/Processors/TFT_eSPI_ESP32.h:14:25: fatal error: hal/gpio_ll.h: No such file or directory
    this helped: https://github.com/Bodmer/TFT_eSPI/discussions/3048

    E:\port apps\Arduino\arduino\libraries\TFT_eSPI/Processors/TFT_eSPI_ESP32.h:138:18: fatal error: FS.h: No such file or directory

    E:\port apps\Arduino\arduino\libraries\TFT_eSPI/Processors/TFT_eSPI_ESP32.h:139:36: fatal error: SPIFFS.h: No such file or directory

    I fixed it, by searchig for SPIFFS.h
    probably i’ve got the wrong one, because afterwards lots of error messages appeared

    Can someone help me?

    Reply
    • Hi.
      Please double-check the instructions to install the libraries and preparing the configuration files.
      Make sure you’re placing everything in the right places.
      Regards,
      Sara

      Reply
  3. Hello! As I understand, touch and LCD and SD card holder are SPI devices. Could it possible to connect them to same SPI pins, but using different CS pin for each device?

    Reply
  4. Hello, I’m trying desperately to get your example to work but unfortunately the display on the screen doesn’t work unless I invert 240 x 320 but in that case the keys don’t work properly except for “toggle” because the screen isn’t facing the right way.

    Can you please help me?

    Reply
    • Hi.
      Check this line of code:
      // Note: in some displays, the touchscreen might be upside down, so you might need to set the rotation to 1: touchscreen.setRotation(1);
      touchscreen.setRotation(3);

      Set your rotation to 1.
      touchscreen.setRotation(1);

      Regards,
      Sara

      Reply
      • Hello, Sorry it doesn’t change anything, I can’t put the screen in “landscape” mode, but only in “portrait” mode by inverting #define SCREEN_WIDTH 240 #define SCREEN_HEIGHT 320 but in this case only “toggle” works normally even though the tests of the TFT-eSPI library are good I don’t know how to send you a photo, it would be easier thank you again.

        Reply
  5. Hi, I have already bought a couple of separate screens that are ILI9341 and 320 x 240 which look identical to those listed, including the touch screen, connectors and jumpers. However, they are clearly marked 3.2″ TFT SPI 240×320 V1.0 on the rear.
    So far I have followed the instructions carefully and everything ( except needing to change the ‘Rotation’ from 3 to 1 ) is working perfectly.
    Am I likely to run into problems later, or do you think these are just physically a little bigger than the recommended ones ?
    Many thanks – Noel

    Reply
  6. Wow, really awesome this post! I really must read the posts which you guys posted after this one. I never used the ILI9341 with the ESP32, just with Arduino, but this LVGL library seens really awesome. Once again, congratulations, you guys rock Sara and Rui!

    Reply
  7. This project was working fine. Buttons, slider, touch screen. But now, after an upload completes, the TFT does not light up. However, in the ‘Serial Monitor’ I see this scrolling on forever:-
    [Warn] (895.260, +0) indev_pointer_proc: Y is -15 which is smaller than zero lv_indev.c:654
    [Warn] (895.290, +30) indev_pointer_proc: X is -17 which is smaller than zero lv_indev.c:648
    [Warn] (895.290, +0) indev_pointer_proc: Y is -15 which is smaller than zero lv_indev.c:654
    [Warn] (895.320, +30) indev_pointer_proc: X is -17 which is smaller than zero lv_indev.c:648

    Any ideas ?

    Nothing has changed except this is the next day !

    Best regards – Noel

    Reply
    • I think I just solved my own problem.

      A couple of days ago, I was prompted to update one of my libraries, which is so common that I did not give it a second thought.

      So I took a look at the manual changes detailed for the project. I used a File Comparison program, which showed me that the User_Setup.h had been changed. I replaced it with the required, correct version and all is now fine – phew !

      Sorry for the panic, perhaps my mistake will help someone else….

      Reply
    • Hi.
      We recently changed a bit the User_setup file because there was a pin that was not being set correctly.
      The User_setup file on the link already reflects those changes. Just make sure you follow the pinout on the table. There was a change in one of the pins.

      Regards,
      Sara

      Reply
    • There were breaking changes on version 9.2.0.
      Use previous version, or change WIDTH with HEIGHT and it will display correctly but in portrait mode.
      I’m not sure why this broke with the update.
      Regards,
      sara

      Reply
  8. Greeting.
    I have a question. Example from your guide ESP32: TFT Touchscreen On/Off Button – 2.8 inch ILI9341 240×320 (Arduino IDE) works fine for me, but example from LVGL Library : LVGL with ESP32 TFT LCD Touchscreen – 2.8 inch ILI9341 240× 320 (Arduino IDE) doesn’t work at all even though I tried to follow the instructions carefully.
    The Arduino IDE gives me the following error:

    c:\Users\Korisnik\Documents\Arduino\libraries\lvgl\src\examples\styles\lv_example_style_14.c:1:10: fatal error: ../../src/themes/lv_theme_private.h: No such file or directory
    #include “../../src/themes/lv_theme_private.h”
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    compilation terminated.

    exit status 1

    Compilation error: exit status 1

    Reply
  9. Muchas gracias por compartir la información. Creo que he seguido todos los pasos, pero al compilar me da este error:
    c:\Users\fremi\OneDrive – IES Juan de la Cierva\ESP32\TFT_eSPI_ILI9486\libraries\lvgl\src\examples\styles\lv_example_style_14.c:1:10: fatal error: ../../src/themes/lv_theme_private.h: No such file or directory
    #include “../../src/themes/lv_theme_private.h”
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    compilation terminated.
    exit status 1

    Compilation error: exit status 1

    Puede ayudarme alguien

    Reply
  10. hi, I´m using a ESP32-2432S032 and i want to rotate the display from 270° to 90° with the lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_90) function… but it doesn´t change anything… it just rotate the touchtscreen but not the display itself. With the itself TFT_eSPI lib it working. Does someone know how to change that for this code?

    Reply
  11. Hi Sara,

    When I compile your program I get the following error msg:

    :\User s\User\OneDrive\Documents\Arduino\libraries\TFT_eSPI\Extensions/Touch.cpp: In member function ‘void TFT_eSPI::begin_touch_read_write()’:
    c:\Users\User\OneDrive\Documents\Arduino\libraries\TFT_eSPI\Processors/TFT_eSPI_ESP32.h:234:22: error: ‘GPIO’ was not declared in this scope
    234 | #define CS_H GPIO.out_w1ts = (1 << TFT_CS)//;GPIO.out_w1ts = (1 << TFT_CS)
    | ^~~~
    c:\Users\User\OneDrive\Documents\Arduino\libraries\TFT_eSPI\Extensions/Touch.cpp:20:3: note: in expansion of macro ‘CS_H’
    20 | CS_H; // Just in case it has been left low
    | ^~~~

    Means very little to me. Please help.

    Thanks

    Alan

    Reply
  12. Good day. Such a problem. Nothing is displayed on the screen. The following is written in the port:
    [Warn] (127.770, +0) indev_pointer_proc: Y is -26 which is smaller than zero lv_indev.c:695
    [Warn] (127.770, +0) indev_pointer_proc: Y is -26 which is smaller than zero lv_indev.c:695

    [Warn] (127.800, +30) indev_pointer_proc: X is -18 which is smaller than zero lv_indev.c:689
    [Warn] (127.800, +30) indev_pointer_proc: X is -18 which is smaller than zero lv_indev.c:689

    Reply
  13. i am constantly getting this message i even tried older versions of the libraries but still this error remains constant
    C:\Users\Dhruv Goel\AppData\Local\Temp.arduinoIDE-unsaved2024117-6388-skiaa5.sc6ef\sketch_dec7a\sketch_dec7a.ino: In function ‘void setup()’:
    C:\Users\Dhruv Goel\AppData\Local\Temp.arduinoIDE-unsaved2024117-6388-skiaa5.sc6ef\sketch_dec7a\sketch_dec7a.ino:152:3: error: ‘lv_log_register_print_cb’ was not declared in this scope
    152 | lv_log_register_print_cb(log_print);
    | ^~~~~~~~~~~~~~~~~~~~~~~~
    C:\Users\Dhruv Goel\AppData\Local\Temp.arduinoIDE-unsaved2024117-6388-skiaa5.sc6ef\sketch_dec7a\sketch_dec7a.ino:164:10: error: ‘lv_tft_espi_create’ was not declared in this scope; did you mean ‘lv_tlsf_create’?
    164 | disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
    | ^~~~~~~~~~~~~~~~~~
    | lv_tlsf_create

    exit status 1

    Compilation error: ‘lv_log_register_print_cb’ was not declared in this scope

    Reply
      • Hi,

        Here is informations I have on Serial Monitor. Then, nothing else.

        17:53:26.871 -> ets Jul 29 2019 12:21:46
        17:53:26.871 ->
        17:53:26.871 -> rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
        17:53:26.871 -> configsip: 0, SPIWP:0xee
        17:53:26.871 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
        17:53:26.871 -> mode:DIO, clock div:1
        17:53:26.871 -> load:0x3fff0030,len:1344
        17:53:26.871 -> load:0x40078000,len:13964
        17:53:26.871 -> load:0x40080400,len:3600
        17:53:26.871 -> entry 0x400805f0
        17:53:27.059 -> LVGL Library Version: 9.2.0

        Reply

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.