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

Popular posts from this blog

480x320 TFT/ILI9488 SPI wih EP32C3 (arduino-esp32) using Arduino_GFX Library

Drive 320x240 ILI9341 SPI TFT using ESP32-S3 (NodeMCU ESP-S3-12K-Kit) using TFT_eSPI library, in Arduino Framework.