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