TFT_eSPI + LVGL on Xiao ESP32S3 + ST7789 SPI IPS with FT6236 cap touch.
To implement TFT_eSPI + LVGL on Xiao ESP32S3 + ST7789 SPI IPS with FT6236 cap touch.
First, follow previous exercise Xiao ESP32S3 display on 240*240 ST7789 SPI LCD/FT6236U cap. touch using TFT_eSPI in Arduino Framework, make sure both ST7789 and FT6236 works using TFT_eSPI library.
Install LVGL library in Arduino IDE Library Manager. Follow the video to prepare lv_conf.h file.
Then you can try LVGL example:
My example codes:
XS3_ST7789_TFT_eSPI_lvgl.ino
Create a button to toggle onboard LED.
XS3_ST7789_TFT_eSPI_lv_slider.ino
Create a slider to control onboard LED brightness.
Related:
~ Using Arduino_GFX_Library with LVGL, instead of TFT_eSPI.
First, follow previous exercise Xiao ESP32S3 display on 240*240 ST7789 SPI LCD/FT6236U cap. touch using TFT_eSPI in Arduino Framework, make sure both ST7789 and FT6236 works using TFT_eSPI library.
Install LVGL library in Arduino IDE Library Manager. Follow the video to prepare lv_conf.h file.
Then you can try LVGL example:
> File > Examples > lvgl > arduino > LVGL_Arduino
XS3_ST7789_TFT_eSPI_lvgl.ino
Create a button to toggle onboard LED.
/*
TFT_eSPI + LVGL exercise
run on Xiao ESP32S3 + 1.54" 240x240 ST7789V2 SPI IPS with FT6236 cap touch
Create a button to toggle onboard LED.
https://coxxect.blogspot.com/2025/10/tftespi-lvgl-on-xiao-esp32s3-st7789-spi.html
modified from Examples > lvgl > arduino > LVGL_Arduino
Library needed:
- TFT_eSPI
- LVGL
Preparation:
- Prepare TFT_eSPI in Arduino IDE Library Manager,
follow previous exercise https://coxxect.blogspot.com/2025/08/xiao-esp32s3-display-on-240240-st7789.html,
make sure both ST7789 and FT6236 works.
- Install LVGL library in Arduino IDE Library Manager.
- Go to lvgl library directory.
Copy lv_conf_template.h as lv_conf.h into the Arduino Libraries directory.
- To enable the content of the file:
change the first #if 0 to #if 1
- To use with TFT_eSPI, change the line:
#define LV_USE_TFT_ESPI 1 //0
To make it simple, haven't handle touch point adjustment for screen rotation.
It work on rotation=0 only.
*/
#include <lvgl.h>
#include <TFT_eSPI.h>
#include <Wire.h>
#define CTP_INT 43
#define CTP_RST 44
#define FT6236_ADDR 0x38
/*Set to your screen resolution and rotation*/
#define TFT_HOR_RES 240
#define TFT_VER_RES 240
#define TFT_ROTATION LV_DISPLAY_ROTATION_0
/*LVGL draw into this buffer, 1/10 screen size usually works well. The size is in bytes*/
#define DRAW_BUF_SIZE (TFT_HOR_RES * TFT_VER_RES / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];
/* LVGL calls it when a rendered image needs to copied to the display*/
void my_disp_flush( lv_display_t *disp, const lv_area_t *area, uint8_t * px_map)
{
/*Call it to tell LVGL you are ready*/
lv_display_flush_ready(disp);
}
/*Read the touchpad*/
void my_touchpad_read( lv_indev_t * indev, lv_indev_data_t * data )
{
if (get_touch_count()>0){
data->state = LV_INDEV_STATE_PRESSED;
int x, y;
Wire.beginTransmission(FT6236_ADDR);
Wire.write(0x03);
Wire.endTransmission();
Wire.requestFrom(FT6236_ADDR, 2);
x = (Wire.read() & 0x0f) << 8 | Wire.read();
x = TFT_HOR_RES - x;
Wire.beginTransmission(FT6236_ADDR);
Wire.write(0x05);
Wire.endTransmission();
Wire.requestFrom(FT6236_ADDR, 2);
y = (Wire.read() & 0x0f) << 8 | Wire.read();
y = TFT_VER_RES - y;
data->point.x = x;
data->point.y = y;
}else{
data->state = LV_INDEV_STATE_RELEASED;
}
}
int get_touch_count(){
// Register 0x02;
// Touch count, max 2
Wire.beginTransmission(FT6236_ADDR);
Wire.write(0x02);
Wire.endTransmission();
Wire.requestFrom(FT6236_ADDR, 1);
return(Wire.read());
}
/*use Arduinos millis() as tick source*/
static uint32_t my_tick(void)
{
return millis();
}
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH); //OFF
String LVGL_Arduino = "Hello Arduino! ";
LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.begin( 115200 );
Serial.println( LVGL_Arduino );
// Init I2C for FT6236 and CTR_RST
Wire.begin(); // I2C
delay(100);
pinMode(CTP_RST, OUTPUT);
digitalWrite(CTP_RST, HIGH);
delay(100);
digitalWrite(CTP_RST, LOW);
delay(100);
digitalWrite(CTP_RST, HIGH);
delay(100);
lv_init();
/*Set a tick source so that LVGL will know how much time elapsed. */
lv_tick_set_cb(my_tick);
lv_display_t * disp;
//Use TFT_eSPI interface
/*TFT_eSPI can be enabled lv_conf.h to initialize the display in a simple way*/
disp = lv_tft_espi_create(TFT_HOR_RES, TFT_VER_RES, draw_buf, sizeof(draw_buf));
lv_display_set_rotation(disp, TFT_ROTATION);
/*Initialize the (dummy) input device driver*/
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); /*Touchpad should have POINTER type*/
lv_indev_set_read_cb(indev, my_touchpad_read);
/* Create LVGL content */
lv_obj_t *label_top = lv_label_create(lv_scr_act());
String version = "LVGL (V" + String(LVGL_VERSION_MAJOR) + "." + String(LVGL_VERSION_MINOR) + "." + String(LVGL_VERSION_PATCH) + ")";
lv_label_set_text(label_top, version.c_str());
lv_obj_align(label_top, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t *label_bottom = lv_label_create(lv_screen_active());
lv_label_set_text(label_bottom, "coXXect.blogspot.com");
lv_obj_align(label_bottom, LV_ALIGN_BOTTOM_MID, 0, 0);
// create a big toggle button that occupies the entire middle area
lv_obj_t *btn = lv_btn_create(lv_scr_act()); // Create a button
lv_obj_set_size(btn, LV_HOR_RES-100, LV_VER_RES - 100 - 30); // Adjust size dynamically
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0); // Align to the center
lv_obj_t *btn_label = lv_label_create(btn); // Create label inside button
lv_label_set_text(btn_label, "Toggle Me"); // Set label text
lv_obj_center(btn_label); // Center label inside button
// Add event to toggle button text
lv_obj_add_event_cb(btn, [](lv_event_t * e) {
lv_obj_t * label = lv_obj_get_child((lv_obj_t *)lv_event_get_target(e), 0);
const char * text = lv_label_get_text(label);
//lv_label_set_text(label, strcmp(text, "ON") == 0 ? "OFF" : "ON");
if (strcmp(text, "ON") == 0){
lv_label_set_text(label, "OFF");
digitalWrite(LED_BUILTIN, HIGH);
}else{
lv_label_set_text(label, "ON");
digitalWrite(LED_BUILTIN, LOW);
}
}, LV_EVENT_CLICKED, NULL);
Serial.println( "Setup done" );
}
void loop()
{
lv_timer_handler(); /* let the GUI do its work */
delay(5); /* let this time pass */
}
XS3_ST7789_TFT_eSPI_lv_slider.ino
Create a slider to control onboard LED brightness.
/*
TFT_eSPI + LVGL lv_slider & lv_led exercise
run on Xiao ESP32S3 + 1.54" 240x240 ST7789V2 SPI IPS with FT6236 cap touch
Create a slider to control onboard LED brightness.
https://coxxect.blogspot.com/2025/10/tftespi-lvgl-on-xiao-esp32s3-st7789-spi.html
Library needed:
- TFT_eSPI
- LVGL
Preparation:
- Prepare TFT_eSPI in Arduino IDE Library Manager,
follow previous exercise https://coxxect.blogspot.com/2025/08/xiao-esp32s3-display-on-240240-st7789.html,
make sure both ST7789 and FT6236 works.
- Install LVGL library in Arduino IDE Library Manager.
- Go to lvgl library directory.
Copy lv_conf_template.h as lv_conf.h into the Arduino Libraries directory.
- To enable the content of the file:
change the first #if 0 to #if 1
- To use with TFT_eSPI, change the line:
#define LV_USE_TFT_ESPI 1 //0
To make it simple, haven't handle touch point adjustment for screen rotation.
It work on rotation=0 only.
*/
#include <lvgl.h>
#include <TFT_eSPI.h>
#include <Wire.h>
#define CTP_INT 43
#define CTP_RST 44
#define FT6236_ADDR 0x38
/*Set to your screen resolution and rotation*/
#define TFT_HOR_RES 240
#define TFT_VER_RES 240
#define TFT_ROTATION LV_DISPLAY_ROTATION_0
/*LVGL draw into this buffer, 1/10 screen size usually works well. The size is in bytes*/
#define DRAW_BUF_SIZE (TFT_HOR_RES * TFT_VER_RES / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];
String LVGL_Arduino;
/* LVGL calls it when a rendered image needs to copied to the display*/
void my_disp_flush( lv_display_t *disp, const lv_area_t *area, uint8_t * px_map)
{
/*Call it to tell LVGL you are ready*/
lv_display_flush_ready(disp);
}
/*Read the touchpad*/
void my_touchpad_read( lv_indev_t * indev, lv_indev_data_t * data )
{
if (get_touch_count()>0){
data->state = LV_INDEV_STATE_PRESSED;
int x, y;
Wire.beginTransmission(FT6236_ADDR);
Wire.write(0x03);
Wire.endTransmission();
Wire.requestFrom(FT6236_ADDR, 2);
x = (Wire.read() & 0x0f) << 8 | Wire.read();
x = TFT_HOR_RES - x;
Wire.beginTransmission(FT6236_ADDR);
Wire.write(0x05);
Wire.endTransmission();
Wire.requestFrom(FT6236_ADDR, 2);
y = (Wire.read() & 0x0f) << 8 | Wire.read();
y = TFT_VER_RES - y;
data->point.x = x;
data->point.y = y;
}else{
data->state = LV_INDEV_STATE_RELEASED;
}
}
int get_touch_count(){
// Register 0x02;
// Touch count, max 2
Wire.beginTransmission(FT6236_ADDR);
Wire.write(0x02);
Wire.endTransmission();
Wire.requestFrom(FT6236_ADDR, 1);
return(Wire.read());
}
/*use Arduinos millis() as tick source*/
static uint32_t my_tick(void)
{
return millis();
}
void setup()
{
pinMode(LED_BUILTIN, OUTPUT); // Set pin as output
analogWrite(LED_BUILTIN, 255); // OFF
LVGL_Arduino = "LVGL ";
LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.begin( 115200 );
Serial.println( LVGL_Arduino );
// Init I2C for FT6236 and CTR_RST
Wire.begin(); // I2C
delay(100);
pinMode(CTP_RST, OUTPUT);
digitalWrite(CTP_RST, HIGH);
delay(100);
digitalWrite(CTP_RST, LOW);
delay(100);
digitalWrite(CTP_RST, HIGH);
delay(100);
lv_init();
/*Set a tick source so that LVGL will know how much time elapsed. */
lv_tick_set_cb(my_tick);
lv_display_t * disp;
//Use TFT_eSPI interface
/*TFT_eSPI can be enabled lv_conf.h to initialize the display in a simple way*/
disp = lv_tft_espi_create(TFT_HOR_RES, TFT_VER_RES, draw_buf, sizeof(draw_buf));
lv_display_set_rotation(disp, TFT_ROTATION);
/*Initialize the (dummy) input device driver*/
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); /*Touchpad should have POINTER type*/
lv_indev_set_read_cb(indev, my_touchpad_read);
/* Create LVGL content */
lv_example_slider_1();
Serial.println( "Setup done" );
}
void loop()
{
lv_timer_handler(); /* let the GUI do its work */
delay(5); /* let this time pass */
}
/*
Modified examples from
LVGL Slider (lv_slider):
https://docs.lvgl.io/master/details/widgets/slider.html
LED (lv_led):
https://docs.lvgl.io/master/details/widgets/led.html
*/
static void slider_event_cb(lv_event_t * e);
static lv_obj_t * slider_label;
lv_obj_t * led;
/**
* A default slider with a label displaying the current value
*/
void lv_example_slider_1(void)
{
/*Create a container with COLUMN flex direction*/
lv_obj_t * cont_main = lv_obj_create(lv_screen_active());
lv_obj_set_size(cont_main, TFT_HOR_RES-10, TFT_VER_RES-50);
lv_obj_align(cont_main, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_flex_flow(cont_main, LV_FLEX_FLOW_COLUMN);
lv_obj_t *label_top = lv_label_create(lv_screen_active());
lv_label_set_text(label_top, "Arduino LVGL Exercise on ESP32S3");
lv_obj_align(label_top, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t *label_bottom = lv_label_create(lv_screen_active());
lv_label_set_text(label_bottom, "coXXect.blogspot.com");
lv_obj_align(label_bottom, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_t *label_lvgl = lv_label_create(cont_main);
lv_label_set_text(label_lvgl, LVGL_Arduino.c_str());
/* Create LED*/
led = lv_led_create(cont_main);
lv_obj_align(led, LV_ALIGN_CENTER, 0, 0);
lv_led_set_color(led, lv_color_hex(0xffa500));
lv_led_set_brightness(led, 0);
/*Create a slider in the center of the display*/
lv_obj_t * slider = lv_slider_create(cont_main);
lv_obj_set_width(slider, 200);
lv_obj_center(slider);
lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
lv_obj_set_style_anim_duration(slider, 2000, 0);
/*Create a label below the slider*/
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);
}
static void slider_event_cb(lv_event_t * e)
{
lv_obj_t * slider = lv_event_get_target_obj(e);
char buf[8];
int slider_value = (int)lv_slider_get_value(slider);
lv_snprintf(buf, sizeof(buf), "%d%%", slider_value);
lv_label_set_text(slider_label, buf);
lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
int led_value = map(slider_value, 0, 100, 255, 0);
analogWrite(LED_BUILTIN, led_value);
int brightness_value = map(slider_value, 0, 100, 0, 255);
lv_led_set_brightness(led, brightness_value);
}
Related:
~ Using Arduino_GFX_Library with LVGL, instead of TFT_eSPI.
Comments
Post a Comment