Arduino_GFX_Library + LVGL on ESP32S3 + ST7789 SPI IPS with FT6236 cap touch, create button to toggle onboard LED
Last exercise on
Xiao ESP32S3 Sense, to display on 240x240 ST7789V2 SPI IPS using
Arduino_GFX_Library, and interface FT6236 cap touch using Wire I2C, in
Arduino framework. This exercise add function of LVGL.
Connection, following in last exercise.
Preparation:
- Install GFX Library for Arduino in Arduino IDE Library Manager,
follow last exercise, make sure both ST7789 and FT6236 works.
- Install LVGL library in Arduino IDE Library Manager.
Using LVGL with Arduino requires some extra steps:
*Be sure to read the docs here: https://docs.lvgl.io/master/get-started/platforms/arduino.html
Copy lv_conf_template.h in lvgl library directory, rename as lv_conf.h and save into the Arduino Libraries directory.
To enable the content of the file, change the first #if 0 to #if 1.
#if 1 //0 /*Set it to "1" to enable content*/
Exercise code:
XIAOS3_ST7789_FT6236_LVGL_Arduino_v9.ino, my exercise to
create a button to toggle the onboard LED.
/*
GFX Library for Arduino + LVGL exercise
run on Xiao ESP32S3 + 1.54" 240x240 ST7789V2 SPI IPS with FT6236 cap touch
Create a button to toggle onboard LED.
modified from Examples > GFX Library for Arduino > LVGL > LVGL_arduino_v9
Library needed:
- GFX Library for Arduino
- LVGL
Preparation:
- Install GFX Library for Arduino in Arduino IDE Library Manager,
follow previous exercise https://coxxect.blogspot.com/2025/05/240x240-st7789v2-spi-ips-with-ft6236.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 make it simple, haven't handle touch point adjustment for screen rotation.
It work on rotation=0 only.
*/
/*Using LVGL with Arduino requires some extra steps:
*Be sure to read the docs here: https://docs.lvgl.io/master/get-started/platforms/arduino.html */
#include <lvgl.h>
// #define DIRECT_RENDER_MODE // Uncomment to enable full frame buffer
/*******************************************************************************
* Start of Arduino_GFX setting
******************************************************************************/
#include <Arduino_GFX_Library.h>
//=== Custom to match my connection ===========================================
#define PIN_BLK 4
#define PIN_CS 3
#define PIN_DC 2
#define PIN_RES 1
#define PIN_SDA 9
#define PIN_SCL 7
#define GFX_BL PIN_BLK
#define CTP_INT 43
#define CTP_RST 44
#define FT6236_ADDR 0x38
#define DISP_WIDTH 240
#define DISP_HEIGHT 240
Arduino_DataBus *bus = new Arduino_HWSPI(PIN_DC, PIN_CS);
Arduino_GFX *gfx = new Arduino_ST7789(bus, PIN_RES, 0 /* rotation */, true /* IPS */, DISP_WIDTH, DISP_HEIGHT);
/*******************************************************************************
* End of Arduino_GFX setting
******************************************************************************/
// for FT6236 cap touch
#include <Wire.h>
uint32_t screenWidth;
uint32_t screenHeight;
uint32_t bufSize;
lv_display_t *disp;
lv_color_t *disp_draw_buf;
#if LV_USE_LOG != 0
void my_print(lv_log_level_t level, const char *buf)
{
LV_UNUSED(level);
Serial.println(buf);
Serial.flush();
}
#endif
uint32_t millis_cb(void)
{
return millis();
}
/* 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)
{
#ifndef DIRECT_RENDER_MODE
uint32_t w = lv_area_get_width(area);
uint32_t h = lv_area_get_height(area);
gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)px_map, w, h);
#endif // #ifndef DIRECT_RENDER_MODE
/*Call it to tell LVGL you are ready*/
lv_disp_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 = DISP_WIDTH - x;
Wire.beginTransmission(FT6236_ADDR);
Wire.write(0x05);
Wire.endTransmission();
Wire.requestFrom(FT6236_ADDR, 2);
y = (Wire.read() & 0x0f) << 8 | Wire.read();
y = DISP_HEIGHT - 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());
}
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH); //OFF
#ifdef DEV_DEVICE_INIT
DEV_DEVICE_INIT();
#endif
Serial.begin(115200);
// Serial.setDebugOutput(true);
// while(!Serial);
Serial.println("Arduino_GFX LVGL_Arduino_v9 example ");
String LVGL_Arduino = String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.println(LVGL_Arduino);
// Init Display
if (!gfx->begin())
{
Serial.println("gfx->begin() failed!");
}
gfx->fillScreen(RGB565_BLACK);
#ifdef GFX_BL
pinMode(GFX_BL, OUTPUT);
digitalWrite(GFX_BL, HIGH);
#endif
// To make it simple, haven't handle touch point adjustment for screen rotation,
// so no touch_init() implemented.
// Init touch device
// touch_init(gfx->width(), gfx->height(), gfx->getRotation());
// 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(millis_cb);
/* register print function for debugging */
#if LV_USE_LOG != 0
lv_log_register_print_cb(my_print);
#endif
screenWidth = gfx->width();
screenHeight = gfx->height();
#ifdef DIRECT_RENDER_MODE
bufSize = screenWidth * screenHeight;
#else
bufSize = screenWidth * 40;
#endif
#ifdef ESP32
#if defined(DIRECT_RENDER_MODE) && (defined(CANVAS) || defined(RGB_PANEL) || defined(DSI_PANEL))
disp_draw_buf = (lv_color_t *)gfx->getFramebuffer();
#else // !(defined(DIRECT_RENDER_MODE) && (defined(CANVAS) || defined(RGB_PANEL) || defined(DSI_PANEL)))
disp_draw_buf = (lv_color_t *)heap_caps_malloc(bufSize * 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (!disp_draw_buf)
{
// remove MALLOC_CAP_INTERNAL flag try again
disp_draw_buf = (lv_color_t *)heap_caps_malloc(bufSize * 2, MALLOC_CAP_8BIT);
}
#endif // !(defined(DIRECT_RENDER_MODE) && (defined(CANVAS) || defined(RGB_PANEL) || defined(DSI_PANEL)))
#else // !ESP32
Serial.println("LVGL disp_draw_buf heap_caps_malloc failed! malloc again...");
disp_draw_buf = (lv_color_t *)malloc(bufSize * 2);
#endif // !ESP32
if (!disp_draw_buf)
{
Serial.println("LVGL disp_draw_buf allocate failed!");
}
else
{
disp = lv_display_create(screenWidth, screenHeight);
lv_display_set_flush_cb(disp, my_disp_flush);
#ifdef DIRECT_RENDER_MODE
lv_display_set_buffers(disp, disp_draw_buf, NULL, bufSize * 2, LV_DISPLAY_RENDER_MODE_DIRECT);
#else
lv_display_set_buffers(disp, disp_draw_buf, NULL, bufSize * 2, LV_DISPLAY_RENDER_MODE_PARTIAL);
#endif
/*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());
lv_label_set_text(label_top, "LVGL (V" GFX_STR(LVGL_VERSION_MAJOR) "." GFX_STR(LVGL_VERSION_MINOR) "." GFX_STR(LVGL_VERSION_PATCH) ")");
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_task_handler(); /* let the GUI do its work */
#ifdef DIRECT_RENDER_MODE
#if defined(CANVAS) || defined(RGB_PANEL) || defined(DSI_PANEL)
gfx->flush();
#else // !(defined(CANVAS) || defined(RGB_PANEL) || defined(DSI_PANEL))
gfx->draw16bitRGBBitmap(0, 0, (uint16_t *)disp_draw_buf, screenWidth, screenHeight);
#endif // !(defined(CANVAS) || defined(RGB_PANEL) || defined(DSI_PANEL))
#else // !DIRECT_RENDER_MODE
#ifdef CANVAS
gfx->flush();
#endif
#endif // !DIRECT_RENDER_MODE
delay(5);
}
More:
~ List files in SD images_240 directory on LVGL list
~ ESP32S3 (Arduino framework) decode jpg and display on ST7789 SPI Display.
Comments
Post a Comment