SH8601 with LVGL 9 (Waveshare ESP32 S3 Touch AMOLED 1.8")

Description

I’m running into an issue with the lv_display_flush_ready getting stuck when calling from on_color_trans_done defined in my esp_lcd_panel_io_spi_config_t.

I know the lv_display_flush_ready is getting called, if I add an assert(false) the software crashes.

When I move the lv_display_flush_ready to the lv_display_set_flush_cb the screen will render normally.

From what I have read, I have a suspicion its something to do with my esp_lcd_panel_io_spi_config_t but I can’t for the life of me get this working.

What MCU/Processor/Board and compiler are you using?

ESP32-S3 1.8inch AMOLED Touch Display Development Board

What do you want to achieve?

I want the display to flush when lv_display_flush_ready is called in the on_color_trans_done

What have you tried doing?

I’ve tried messing with adding/using a mutex, no change
I’ve tried messing with .max_transfer_sz = on the esp_lcd_panel_io_spi_config_t
I’ve tried moving the lv_display_flush_ready around. (this works but I’d really like to get the interrupt working)

Code to reproduce

This was hacked together from the ESP-IDF > 05_LVGL_WITH_RAM c demo provided by Waveshare. It is using LVGL 8 and I’m attempting to convert over to c++ and LVGL 9. I’m using platformio with the arduino framework and espressif32 platform.

// hal_display.h
#pragma once

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

bool display_init(void);
void display_set_brightness(uint8_t brightness);

#ifdef __cplusplus
} /* extern "C" */
#endif
// hal_display.cpp
#include "hal_display.h"

#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_timer.h"
#include "esp_heap_caps.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_commands.h"
#include "esp_lcd_sh8601.h"
#include "lvgl.h"

#ifndef LV_BUF_HEIGHT
#  define LV_BUF_HEIGHT (4)
#endif

#ifndef LV_COLOR_DEPTH
#  define LV_COLOR_DEPTH (16)
#endif

#define SH8601_QSPI_CLK (GPIO_NUM_11)
#define SH8601_QSPI_D0 (GPIO_NUM_4)
#define SH8601_QSPI_D1 (GPIO_NUM_5)
#define SH8601_QSPI_D2 (GPIO_NUM_6)
#define SH8601_QSPI_D3 (GPIO_NUM_7)

#define SH8601_SPI_CS (GPIO_NUM_12)
#define SH8601_SPI_MODE (0)
#define SH8601_SPI_CLK_HZ (40 * 1000 * 1000)
#define SH8601_SPI_TRANS_QUEUE_SZ (10)
#define SH8601_SPI_CMD_BITS (32)
#define SH8601_SPI_PARAM_BITS (8)
#define SH8601_SPI_HOST (SPI2_HOST)
#define SH8601_RST (GPIO_NUM_NC)

#define SH8601_H_RES (368)
#define SH8601_V_RES (448)

#define LV_TICK_PERIOD_MS (2)
#define LV_TASK_MAX_DELAY_MS (500)
#define LV_TASK_MIN_DELAY_MS (1)
#define LV_TASK_STACK_SIZE (4 * 1024)
#define LV_TASK_PRIORITY (2)

#define LV_BUF_LEN (SH8601_H_RES * (SH8601_V_RES / LV_BUF_HEIGHT))

static const sh8601_lcd_init_cmd_t sh8601_init_cmds[] = {
  // {cmd, { data }, data_size, delay_ms}
  { LCD_CMD_SLPOUT,  (uint8_t[]){ 0x00 },                   0, 120 },
  { LCD_CMD_STE,     (uint8_t[]){ 0x01, 0xD1 },             2, 0   },
  { LCD_CMD_TEON,    (uint8_t[]){ 0x00 },                   1, 0   },
  { LCD_CMD_WCRTL1,  (uint8_t[]){ 0x20 },                   1, 10  },
  { LCD_CMD_CASET,   (uint8_t[]){ 0x00, 0x00, 0x01, 0x6F }, 4, 0   },
  { LCD_CMD_RASET,   (uint8_t[]){ 0x00, 0x00, 0x01, 0xBF }, 4, 0   },
  { LCD_CMD_WRDISBV, (uint8_t[]){ 0x00 },                   1, 10  },
  { LCD_CMD_DISPON,  (uint8_t[]){ 0x00 },                   0, 10  },
  { LCD_CMD_WRDISBV, (uint8_t[]){ 0xFF },                   1, 0   },
};

static bool display_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) {
  lv_display_flush_ready((lv_display_t *)user_ctx);
  return false;
}

static void display_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) {
  esp_lcd_panel_handle_t lcd_handle = (esp_lcd_panel_handle_t)lv_display_get_user_data(disp);
  assert(lcd_handle);
  lv_draw_sw_rgb565_swap(px_map, (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1));
  esp_lcd_panel_draw_bitmap(lcd_handle, area->x1, area->y1, area->x2 + 1, area->y2 + 1, px_map);
  // lv_disp_flush_ready(disp);
}

static void display_rounder_cb(lv_event_t *e) {
  lv_area_t *area = (lv_area_t *)lv_event_get_param(e);

  uint16_t x1 = area->x1;
  uint16_t x2 = area->x2;

  uint16_t y1 = area->y1;
  uint16_t y2 = area->y2;

  // round the start of coordinate down to the nearest 2M number
  area->x1 = (x1 >> 1) << 1;
  area->y1 = (y1 >> 1) << 1;
  // round the end of coordinate up to the nearest 2N+1 number
  area->x2 = ((x2 >> 1) << 1) + 1;
  area->y2 = ((y2 >> 1) << 1) + 1;
}

static void display_tick(void *arg) {
  lv_tick_inc(LV_TICK_PERIOD_MS);
}

#if LV_USE_LOG != 0
static void display_log(lv_log_level_t level, const char *buf) {
  printf("%s", buf);
}
#endif

bool display_init(void) {
  static esp_lcd_panel_handle_t lcd_panel_handle;
  static lv_display_t *lv_display_drv;

  // Initialize SPI for display
  const spi_bus_config_t buscfg = {
    .data0_io_num = SH8601_QSPI_D0,
    .data1_io_num = SH8601_QSPI_D1,
    .sclk_io_num = SH8601_QSPI_CLK,
    .data2_io_num = SH8601_QSPI_D2,
    .data3_io_num = SH8601_QSPI_D3,
    .max_transfer_sz = SH8601_H_RES * SH8601_V_RES * LV_COLOR_DEPTH / 10,
  };

  if (spi_bus_initialize(SH8601_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO) != ESP_OK) {
    printf("The QSPI initialization failed.\r\n");
    return false;
  }

  const esp_lcd_panel_io_spi_config_t io_config = {
    .cs_gpio_num = SH8601_SPI_CS,
    .dc_gpio_num = -1,
    .spi_mode = SH8601_SPI_MODE,
    .pclk_hz = SH8601_SPI_CLK_HZ,
    .trans_queue_depth = SH8601_SPI_TRANS_QUEUE_SZ,
    .on_color_trans_done = display_flush_ready,
    .user_ctx = &lv_display_drv,
    .lcd_cmd_bits = SH8601_SPI_CMD_BITS,
    .lcd_param_bits = SH8601_SPI_PARAM_BITS,
    .flags = {
              .quad_mode = true,
              },
  };

  esp_lcd_panel_io_handle_t io_handle;
  if (esp_lcd_new_panel_io_spi(SH8601_SPI_HOST, &io_config, &io_handle) != ESP_OK) {
    printf("Failed to set display communication parameters -- SPI\r\n");
    return false;
  }

  sh8601_vendor_config_t vendor_config = {
    .init_cmds = sh8601_init_cmds,
    .init_cmds_size = sizeof(sh8601_init_cmds) / sizeof(sh8601_init_cmds[0]),
    .flags = {
              .use_qspi_interface = 1,
              },
  };

  const esp_lcd_panel_dev_config_t panel_config = {
    .reset_gpio_num = SH8601_RST,
    .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
    .bits_per_pixel = LV_COLOR_DEPTH,
    .vendor_config = &vendor_config,
  };

  if (esp_lcd_new_panel_sh8601(io_handle, &panel_config, &lcd_panel_handle) != ESP_OK) {
    printf("Failed to create LCD panel.\r\n");
    return false;
  }

  if (esp_lcd_panel_reset(lcd_panel_handle) != ESP_OK) {
    printf("Failed to reset LCD panel.\r\n");
    return false;
  }

  if (esp_lcd_panel_init(lcd_panel_handle) != ESP_OK) {
    printf("Failed to initialize LCD panel.\r\n");
    return false;
  }

  if (esp_lcd_panel_disp_on_off(lcd_panel_handle, true) != ESP_OK) {
    printf("Failed to turn on LCD panel.\r\n");
    return false;
  }

  // Initialize LVGL
  lv_init();

#if LV_USE_LOG != 0
  lv_log_register_print_cb(display_log);
#endif

  lv_color_t *buf1 = (lv_color_t *)heap_caps_malloc(LV_BUF_LEN * sizeof(lv_color_t), MALLOC_CAP_SPIRAM);
  assert(buf1);

  lv_color_t *buf2 = (lv_color_t *)heap_caps_malloc(LV_BUF_LEN * sizeof(lv_color_t), MALLOC_CAP_SPIRAM);
  assert(buf2);

  lv_display_t *lv_display = lv_display_create(SH8601_H_RES, SH8601_V_RES);
  lv_display_set_user_data(lv_display, lcd_panel_handle);
  lv_display_set_flush_cb(lv_display, display_flush_cb);
  lv_display_add_event_cb(lv_display, display_rounder_cb, LV_EVENT_INVALIDATE_AREA, lv_display);
  lv_display_set_buffers(lv_display, buf1, buf2, LV_BUF_LEN, LV_DISPLAY_RENDER_MODE_PARTIAL);

  const esp_timer_create_args_t lvgl_tick_timer_args = {
    .callback = &display_tick,
    .name = "lvgl_tick"
  };

  esp_timer_handle_t lvgl_tick_timer;
  if (esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer) != ESP_OK) {
    printf("Failed to create LVGL tick timer.\r\n");
    return false;
  }

  if (esp_timer_start_periodic(lvgl_tick_timer, LV_TICK_PERIOD_MS * 1000) != ESP_OK) {
    printf("Failed to start LVGL tick timer.\r\n");
    return false;
  }

  return true;
}

void display_set_brightness(uint8_t brightness) {
  //
}

// main.cpp
#include "hal_display.h"
#include "lvgl.h"
#include "ui.h"

void setup() {
  Serial.begin(115200);
  while (!Serial) {
    delay(10); // Wait for Serial to be ready
  }

  delay(1000);

  if (!display_init()) {
    printf("Failed to initialize display.\r\n");
    return;
  }
  
  printf("Display initialized successfully.\r\n");

  ui_init(); // just a demo
}

void loop() {
  lv_timer_handler();
  delay(5);
}

Screenshot and/or video

figured it out, I was passing a pointer of a pointer to esp_lcd_panel_io_spi_config_t->user_ctx. I wasn’t event setting lv_display_drv to the lv_display_t created. After moving that around it works as expected!