LVGL v9 exercise run on Raspberry Pi Pico 2 + Waveshare 1.9inch Touch LCD (170x320 ST7789V2 SPI LCD and CST816 I2C capacitive touch).

LVGL v9 exercise run on Raspberry Pi Pico 2 + Waveshare 1.9inch Touch LCD (170x320 ST7789V2 SPI LCD and CST816 I2C capacitive touch)



Basic setup and connection, refer to last exercise: Raspberry Pi Pico 2 + Waveshare 1.9inch Touch LCD, using Arduino_GFX_Library in Arduino Framework.

To add LVGL function:
- 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
/* clang-format off */
#if 1 //0 /* Set this to "1" to enable content */
Exercise Code:

rpPico2_ST7789_CST816_lvgl_button.ino, Create a button to control onboard LED.
/*
  LVGL v9 exercise run on Raspberry Pi Pico 2 + Waveshare 1.9inch Touch LCD 
  (170x320 ST7789V2 SPI LCD and CST816 I2C capacitive touch).
  Create a button to control onboard LED.

  https://coxxect.blogspot.com/2025/12/lvgl-v9-exercise-run-on-raspberry-pi.html

  Using board of Raspberry Pi Pico/RP2040/RP2350 by Earle F. Philhower III in Boards Manager.
  https://github.com/earlephilhower/arduino-pico

  Install Earle Philhower Raspberry Pi Pico Arduino core in Arduino IDE
  https://coxxect.blogspot.com/2023/08/install-earle-philhower-raspberry-pi.html
*/
/*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>

/*******************************************************************************
 * Start of Arduino_GFX setting
 * Raspberry Pi Pico dev board : CS: 17, DC: 22, RST: 21, BL: 20, SCK: 18, MOSI: 19, MISO: 16
 ******************************************************************************/
#include <Arduino_GFX_Library.h>
#include <Wire.h>

#define SPI_MOSI  19
#define SPI_SCK   18
#define TFT_CS    17
#define TFT_DC    22
#define TFT_RST   21
#define TFT_BL    20

#define TP_SDA 4  // default I2C SDA
#define TP_SCL 5  // default I2C SCL
#define TP_RST 3
#define TP_IRQ 2

#define TFT_ROTATION 0
#define TFT_WIDTH 170
#define TFT_HEIGHT 320
#define TFT_COLSTART 35
#define TFT_ROWSTART 0

#define CST816_ADDR 0x15

/* More data bus class: https://github.com/moononournation/Arduino_GFX/wiki/Data-Bus-Class */
Arduino_DataBus *bus = new Arduino_RPiPicoSPI(TFT_DC, TFT_CS, SPI_SCK, SPI_MOSI, GFX_NOT_DEFINED /* MISO */);
//Arduino_DataBus *bus = new Arduino_HWSPI(TFT_DC, TFT_CS);
//Arduino_DataBus *bus = new Arduino_SWSPI(TFT_DC, TFT_CS, SPI_SCK, SPI_MOSI, GFX_NOT_DEFINED /* MISO */);

/* More display class: https://github.com/moononournation/Arduino_GFX/wiki/Display-Class */
Arduino_GFX *gfx = new Arduino_ST7789(bus, TFT_RST, TFT_ROTATION, true /* IPS */,
                                      TFT_WIDTH, TFT_HEIGHT,
                                      TFT_COLSTART, TFT_ROWSTART);

/*******************************************************************************
 * End of Arduino_GFX setting
 ******************************************************************************/

uint32_t screenWidth;
uint32_t screenHeight;
uint32_t bufSize;
lv_display_t *disp;
lv_color_t *disp_draw_buf;

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)
{
  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);

  /*Call it to tell LVGL you are ready*/
  lv_disp_flush_ready(disp);
}

// ==== CST816 data structure ====
struct TouchData {
  uint8_t gesture;
  uint8_t fingers;
  uint16_t x;
  uint16_t y;
};
TouchData currentData = {0,0,0,0};

/*Read the touchpad*/
void my_touchpad_read(lv_indev_t *indev, lv_indev_data_t *data)
{
  Wire.beginTransmission(CST816_ADDR);
  Wire.write(0x01);
  Wire.endTransmission(false);
  Wire.requestFrom(CST816_ADDR, 6);

  if (Wire.available() == 6) {
    currentData.gesture = Wire.read();
    currentData.fingers = Wire.read();
    currentData.x = (Wire.read() & 0x0F) << 8 | Wire.read();
    currentData.y = (Wire.read() & 0x0F) << 8 | Wire.read();

    if (currentData.fingers > 0) {
      data->state = LV_INDEV_STATE_PRESSED;
      data->point.x = currentData.x;
      data->point.y = currentData.y;
    } else {
      data->state = LV_INDEV_STATE_RELEASED;
    }
  }
}

void setup()
{
  delay(2000);
  Serial.begin(115200);
  delay(500);
  Serial.println("--- Start ---");

  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);

  Wire.begin();
    // Send a Reset to CST816
  pinMode(TP_RST, OUTPUT);
  digitalWrite(TP_RST, HIGH);
  delay(50);
  digitalWrite(TP_RST, LOW);
  delay(30);
  digitalWrite(TP_RST, HIGH);
  delay(50);

  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);

  // Turn ON back-light
  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, LOW);

  lv_init();

  /*Set a tick source so that LVGL will know how much time elapsed. */
  lv_tick_set_cb(millis_cb);

  screenWidth = gfx->width();
  screenHeight = gfx->height();
  bufSize = screenWidth * 40;

  Serial.println("LVGL disp_draw_buf heap_caps_malloc failed! malloc again...");
  disp_draw_buf = (lv_color_t *)malloc(bufSize * 2);

  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);
    lv_display_set_buffers(disp, disp_draw_buf, NULL, bufSize * 2, LV_DISPLAY_RENDER_MODE_PARTIAL);

    /*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, "\nLVGL! (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\n");
    lv_obj_align(label_bottom, LV_ALIGN_BOTTOM_MID, 0, 0);

    // create a ON/OFF button
    lv_obj_t *btn_ONOFF = lv_btn_create(lv_screen_active());  // Create a button
    lv_obj_set_size(btn_ONOFF, 100, 100);
    lv_obj_align(btn_ONOFF, LV_ALIGN_CENTER, 0, 0);

    lv_obj_t *btn_label_ONOFF = lv_label_create(btn_ONOFF);  // Create label inside button
    lv_label_set_text(btn_label_ONOFF, "ON");  // Set label text
    lv_obj_center(btn_label_ONOFF);  // Center label inside button

    // Add event to toggle button text
    lv_obj_add_event_cb(btn_ONOFF, [](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);

      if (strcmp(text, "ON") == 0){
        // LED ON
        lv_label_set_text(label, "OFF");
        digitalWrite(LED_BUILTIN, HIGH);  // Turn ON LED
      }else{
        // LED OFF
        lv_label_set_text(label, "ON");
        digitalWrite(LED_BUILTIN, LOW);   // Turn OFF LED
      }
    }, LV_EVENT_CLICKED, NULL);
  }
  Serial.println("Setup done");
}

void loop()
{
  lv_task_handler(); /* let the GUI do its work */

  delay(5);
}


rpPico2_ST7789_CST816_lvgl_button_2.ino, Create a button and slider to control onboard LED.
/*
  LVGL v9 exercise run on Raspberry Pi Pico 2 + Waveshare 1.9inch Touch LCD 
  (170x320 ST7789V2 SPI LCD and CST816 I2C capacitive touch).
  Create a button and slider to control onboard LED.

  https://coxxect.blogspot.com/2025/12/lvgl-v9-exercise-run-on-raspberry-pi.html

  Using board of Raspberry Pi Pico/RP2040/RP2350 by Earle F. Philhower III in Boards Manager.
  https://github.com/earlephilhower/arduino-pico

  Install Earle Philhower Raspberry Pi Pico Arduino core in Arduino IDE
  https://coxxect.blogspot.com/2023/08/install-earle-philhower-raspberry-pi.html
*/
/*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>

/*******************************************************************************
 * Start of Arduino_GFX setting
 * Raspberry Pi Pico dev board : CS: 17, DC: 22, RST: 21, BL: 20, SCK: 18, MOSI: 19, MISO: 16
 ******************************************************************************/
#include <Arduino_GFX_Library.h>
#include <Wire.h>

#define SPI_MOSI  19
#define SPI_SCK   18
#define TFT_CS    17
#define TFT_DC    22
#define TFT_RST   21
#define TFT_BL    20

#define TP_SDA 4  // default I2C SDA
#define TP_SCL 5  // default I2C SCL
#define TP_RST 3
#define TP_IRQ 2

#define TFT_ROTATION 0
#define TFT_WIDTH 170
#define TFT_HEIGHT 320
#define TFT_COLSTART 35
#define TFT_ROWSTART 0

#define CST816_ADDR 0x15

/* More data bus class: https://github.com/moononournation/Arduino_GFX/wiki/Data-Bus-Class */
Arduino_DataBus *bus = new Arduino_RPiPicoSPI(TFT_DC, TFT_CS, SPI_SCK, SPI_MOSI, GFX_NOT_DEFINED /* MISO */);
//Arduino_DataBus *bus = new Arduino_HWSPI(TFT_DC, TFT_CS);
//Arduino_DataBus *bus = new Arduino_SWSPI(TFT_DC, TFT_CS, SPI_SCK, SPI_MOSI, GFX_NOT_DEFINED /* MISO */);

/* More display class: https://github.com/moononournation/Arduino_GFX/wiki/Display-Class */
Arduino_GFX *gfx = new Arduino_ST7789(bus, TFT_RST, TFT_ROTATION, true /* IPS */,
                                      TFT_WIDTH, TFT_HEIGHT,
                                      TFT_COLSTART, TFT_ROWSTART);

/*******************************************************************************
 * End of Arduino_GFX setting
 ******************************************************************************/

uint32_t screenWidth;
uint32_t screenHeight;
uint32_t bufSize;
lv_display_t *disp;
lv_color_t *disp_draw_buf;

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)
{
  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);

  /*Call it to tell LVGL you are ready*/
  lv_disp_flush_ready(disp);
}

// ==== CST816 data structure ====
struct TouchData {
  uint8_t gesture;
  uint8_t fingers;
  uint16_t x;
  uint16_t y;
};
TouchData currentData = {0,0,0,0};

/*Read the touchpad*/
void my_touchpad_read(lv_indev_t *indev, lv_indev_data_t *data)
{
  Wire.beginTransmission(CST816_ADDR);
  Wire.write(0x01);
  Wire.endTransmission(false);
  Wire.requestFrom(CST816_ADDR, 6);

  if (Wire.available() == 6) {
    currentData.gesture = Wire.read();
    currentData.fingers = Wire.read();
    currentData.x = (Wire.read() & 0x0F) << 8 | Wire.read();
    currentData.y = (Wire.read() & 0x0F) << 8 | Wire.read();

    if (currentData.fingers > 0) {
      data->state = LV_INDEV_STATE_PRESSED;
      data->point.x = currentData.x;
      data->point.y = currentData.y;
    } else {
      data->state = LV_INDEV_STATE_RELEASED;
    }
  }
}

int settingBrightness = 50;
void setBrightness(){
  analogWrite(LED_BUILTIN, map(settingBrightness, 0, 100, 0, 255));
}

void setup()
{
  delay(2000);
  Serial.begin(115200);
  delay(500);
  Serial.println("--- Start ---");

  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
  setBrightness();

  Wire.begin();
    // Send a Reset to CST816
  pinMode(TP_RST, OUTPUT);
  digitalWrite(TP_RST, HIGH);
  delay(50);
  digitalWrite(TP_RST, LOW);
  delay(30);
  digitalWrite(TP_RST, HIGH);
  delay(50);

  Serial.println("Arduino_GFX LVGL_Arduino_v9 Exercise ");
  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);

  // Turn ON back-light
  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, LOW);

  lv_init();

  /*Set a tick source so that LVGL will know how much time elapsed. */
  lv_tick_set_cb(millis_cb);

  screenWidth = gfx->width();
  screenHeight = gfx->height();
  bufSize = screenWidth * 40;

  Serial.println("LVGL disp_draw_buf heap_caps_malloc failed! malloc again...");
  disp_draw_buf = (lv_color_t *)malloc(bufSize * 2);

  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);
    lv_display_set_buffers(disp, disp_draw_buf, NULL, bufSize * 2, LV_DISPLAY_RENDER_MODE_PARTIAL);

    /*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 */
    // Top Label
    lv_obj_t *label_top = lv_label_create(lv_scr_act());
    lv_label_set_text(label_top, "\nLVGL! (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);

    // Bottom Label
    lv_obj_t *label_bottom = lv_label_create(lv_screen_active());
    lv_label_set_text(label_bottom, "coXXect.blogspot.com\n");
    lv_obj_align(label_bottom, LV_ALIGN_BOTTOM_MID, 0, 0);

    // Create a container
    lv_obj_t *container = lv_obj_create(lv_scr_act());
    lv_obj_set_size(container, screenWidth - 10, screenHeight - 80);
    lv_obj_align(container, LV_ALIGN_CENTER, 0, 0);

    // Setup Flex Flow
    lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_style_pad_row(container, 10, 0);

    //
    lv_obj_t *label_brightness = lv_label_create(container);
    lv_label_set_text(label_brightness, "Brightness");

    lv_obj_t *slider_brightness = lv_slider_create(container);
    lv_obj_set_width(slider_brightness, screenWidth - 40);
    lv_slider_set_range(slider_brightness, 0, 100);
    lv_slider_set_value(slider_brightness, settingBrightness, LV_ANIM_OFF); // default 50%

    lv_obj_t *slider_label = lv_label_create(container);
    lv_label_set_text_fmt(slider_label, "%d %%", lv_slider_get_value(slider_brightness));

    // Slider event: update label + LED brightness
    lv_obj_add_event_cb(slider_brightness, [](lv_event_t * e) {
      lv_obj_t * slider = (lv_obj_t *) lv_event_get_target(e);
      settingBrightness = lv_slider_get_value(slider);

      lv_obj_t * label = (lv_obj_t *)lv_event_get_user_data(e);
      lv_label_set_text_fmt(label, "%d %%", settingBrightness);

      setBrightness();
    }, LV_EVENT_VALUE_CHANGED, slider_label);

    // create a ON/OFF button
    lv_obj_t *btn_ONOFF = lv_btn_create(container);  // Create a button
    lv_obj_set_size(btn_ONOFF, 100, 100);

    lv_obj_t *btn_label_ONOFF = lv_label_create(btn_ONOFF);  // Create label inside button
    lv_label_set_text(btn_label_ONOFF, "OFF");  // Set label text
    lv_obj_center(btn_label_ONOFF);  // Center label inside button

    // Add event to toggle button text
    lv_obj_add_event_cb(btn_ONOFF, [](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);

      // Get the slider from user_data
      lv_obj_t * slider = static_cast<lv_obj_t *>(lv_event_get_user_data(e));

      if (strcmp(text, "ON") == 0) {
          // LED ON
          lv_label_set_text(label, "OFF");
          setBrightness();

          // Enable slider
          lv_obj_clear_state(slider, LV_STATE_DISABLED);  // Enable Slider
          lv_obj_clear_flag(slider, LV_OBJ_FLAG_HIDDEN); // show
        } else {
          // LED OFF
          lv_label_set_text(label, "ON");
          analogWrite(LED_BUILTIN, 0);

          // Disable slider
          lv_obj_add_state(slider, LV_STATE_DISABLED);    // Disable Slider
          lv_obj_add_flag(slider, LV_OBJ_FLAG_HIDDEN);   // hide
        }
    }, LV_EVENT_CLICKED, slider_brightness);
  }
  Serial.println("Setup done");
}

void loop()
{
  lv_task_handler(); /* let the GUI do its work */

  delay(5);
}



Comments

Popular posts from this blog

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

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