Usually, the LVGL buffers are located to PSRAM, and this is a bottleneck especially on ESP32-S3 and RGB displays.
If you perform OTA update (at least I tried the Arduino OTA implementation and not tested the ESP-IDF implementation yet, like, esp_ota_begin/esp_ota_end), you can see stripes on the top of interface, which looks like crosstalks and other image artifacts.
I suppose that the problem is related to non-cached Flash access during OTA update and blocking between Flash and PSRAM, hence, ESP LCD module cannot get the buffer access in time.
Therefore, I think of making LVGL switching between PSRAM buffer (full buffer) and little buffer that can be fit into SRAM. I suppose to use a little buffer to show the OTA update progress.
How do I switch buffers for LVGL/esp_lvgl_port?
Here is the my example for LVGL, display and touch init
Code: Select all
#include <stdio.h>
#include "display_ui_init.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "driver/i2c_master.h"
#include "driver/spi_master.h"
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_check.h"
#include "esp_event.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_lvgl_port.h"
#include "esp_timer.h"
#include "lvgl.h"
#include "lv_demos.h"
#include "esp_lcd_nv3041a.h"
#include "esp_lcd_touch_gt911.h"
#include "ui.h"
#include "screens.h"
#include "actions.h"
static const char *TAG = "example";
static SemaphoreHandle_t lvgl_mux = NULL;
#define LCD_HOST SPI2_HOST
#define TOUCH_HOST I2C_NUM_1
#define LCD_BIT_PER_PIXEL (16)
#define EXAMPLE_LCD_H_RES 480
#define EXAMPLE_LCD_V_RES 272
#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL 1
#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL
#define EXAMPLE_PIN_NUM_LCD_CS (GPIO_NUM_45)
#define EXAMPLE_PIN_NUM_LCD_PCLK (GPIO_NUM_47)
#define EXAMPLE_PIN_NUM_LCD_DATA0 (GPIO_NUM_21)
#define EXAMPLE_PIN_NUM_LCD_DATA1 (GPIO_NUM_48)
#define EXAMPLE_PIN_NUM_LCD_DATA2 (GPIO_NUM_40)
#define EXAMPLE_PIN_NUM_LCD_DATA3 (GPIO_NUM_39)
#define EXAMPLE_PIN_NUM_LCD_RST (-1)
#define EXAMPLE_PIN_NUM_BK_LIGHT (GPIO_NUM_1)
#define EXAMPLE_PIN_NUM_TOUCH_SCL (GPIO_NUM_4)
#define EXAMPLE_PIN_NUM_TOUCH_SDA (GPIO_NUM_8)
#define EXAMPLE_PIN_NUM_TOUCH_RST (GPIO_NUM_38)
#define EXAMPLE_PIN_NUM_TOUCH_INT (GPIO_NUM_3)
esp_lcd_touch_handle_t tp = NULL;
static esp_lcd_panel_handle_t lcd_panel = NULL;
static i2c_master_bus_handle_t my_bus = NULL;
static esp_lcd_panel_io_handle_t touch_io_handle = NULL;
static esp_lcd_touch_handle_t touch_handle = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
/* LVGL display and touch */
static lv_display_t * lvgl_disp = NULL;
static lv_indev_t * lvgl_touch_indev = NULL;
static bool _display_ui_initialized = false;
static bool _display_bsp_initialized = false;
SemaphoreHandle_t xGuiSemaphore;
static esp_timer_handle_t eez_tick_timer_handle;
typedef enum {
UI_SCREEN_MAIN,
UI_SCREEN_OFF_CHARGER,
UI_SCREEN_SLEEP,
UI_SCREEN_SHUTDOWN,
UI_SCREEN_REBOOT
} ui_screen_t;
static ui_screen_t pending_screen;
static bool screen_change_pending = false;
static void (*_on_display_touched)() = NULL;
#define EXAMPLE_LVGL_TICK_PERIOD_MS 10
#define EXAMPLE_LVGL_TASK_MAX_DELAY_MS 500
#define EXAMPLE_LVGL_TASK_MIN_DELAY_MS 1
#define EXAMPLE_LVGL_TASK_STACK_SIZE (4 * 1024)
#define EXAMPLE_LVGL_TASK_PRIORITY 2
#if LVGL_VERSION_MAJOR >= 9
#define SEND_BUF_SIZE ((EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES \
* LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565)) / 8)
#else
#define SEND_BUF_SIZE ((EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES \
* 2) / 10)
#endif
static bool _show_notification = false;
bool get_var_show_notification() { return _show_notification; }
void set_var_show_notification(bool value) { _show_notification = value; }
void display_set_on_display_touched_cb(void (*cb)()) {
_on_display_touched = cb;
}
void display_set_on_off_state(bool enabled) {
if (!_display_ui_initialized) {
ESP_LOGE(TAG, "display not initialized");
return;
}
if (enabled) {
esp_lcd_panel_disp_on_off(lcd_panel, true);
} else {
esp_lcd_panel_disp_on_off(lcd_panel, false);
}
}
static bool IRAM_ATTR example_notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
#if LVGL_VERSION_MAJOR >= 9
lv_display_t *disp_driver = (lv_display_t *)user_ctx;
#else
lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
#endif
lv_disp_flush_ready(disp_driver);
return false;
}
#if LVGL_VERSION_MAJOR >= 9
// static void example_lvgl_flush_cb(lv_display_t *drv, const lv_area_t *area, lv_color_t *color_map)
static void example_lvgl_flush_cb(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_user_data(drv);
#else
static void example_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
#endif
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
lv_draw_sw_rgb565_swap(color_map, lv_area_get_size(area));
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
}
void process_coordinates(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num) {
ESP_LOGD(TAG, "Touch detected at %u/%u - (%u,%u)", (*point_num), max_point_num, (*x), (*y));
if (_on_display_touched) _on_display_touched();
}
#if LVGL_VERSION_MAJOR < 9
static void example_lvgl_update_cb(lv_disp_drv_t *drv)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
switch (drv->rotated) {
case LV_DISPLAY_ROTATION_0:
esp_lcd_panel_swap_xy(panel_handle, false);
esp_lcd_panel_mirror(panel_handle, true, false);
break;
case LV_DISPLAY_ROTATION_90:
esp_lcd_panel_swap_xy(panel_handle, true);
esp_lcd_panel_mirror(panel_handle, true, true);
break;
case LV_DISPLAY_ROTATION_180:
esp_lcd_panel_swap_xy(panel_handle, false);
esp_lcd_panel_mirror(panel_handle, false, true);
break;
case LV_DISPLAY_ROTATION_270:
esp_lcd_panel_swap_xy(panel_handle, true);
esp_lcd_panel_mirror(panel_handle, false, false);
break;
}
}
#endif
static esp_err_t app_display_init(esp_lcd_panel_handle_t *lp, esp_lcd_panel_io_handle_t * io_handle)
{
esp_err_t ret = ESP_OK;
ESP_LOGI(TAG, "Turn off LCD backlight");
gpio_config_t bk_gpio_config = {
.pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT,
.mode = GPIO_MODE_OUTPUT
};
const spi_bus_config_t buscfg = NV3041A_PANEL_BUS_QSPI_CONFIG(EXAMPLE_PIN_NUM_LCD_PCLK,
EXAMPLE_PIN_NUM_LCD_DATA0,
EXAMPLE_PIN_NUM_LCD_DATA1,
EXAMPLE_PIN_NUM_LCD_DATA2,
EXAMPLE_PIN_NUM_LCD_DATA3,
EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * LCD_BIT_PER_PIXEL / 8);
const esp_lcd_panel_io_spi_config_t io_config = NV3041A_PANEL_IO_QSPI_CONFIG(EXAMPLE_PIN_NUM_LCD_CS, NULL, NULL);
nv3041a_vendor_config_t vendor_config = {
.flags = {
.use_qspi_interface = 1,
},
};
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
// .data_endian = LCD_RGB_DATA_ENDIAN_BIG,
.bits_per_pixel = LCD_BIT_PER_PIXEL,
.vendor_config = &vendor_config,
};
ESP_GOTO_ON_ERROR(
gpio_config(&bk_gpio_config),
display_init_gpio_init_failed,
TAG,
"Cannot init GPIO for display backlight!"
);
ESP_LOGI(TAG, "Initialize SPI bus");
ESP_GOTO_ON_ERROR(
spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO),
display_init_spi_init_failed,
TAG,
"Cannot init display SPI!"
);
ESP_LOGI(TAG, "Install panel IO");
ESP_GOTO_ON_ERROR(
esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, io_handle),
display_init_lcd_attach_spi_failed,
TAG,
"Cannot attach SPI to display!"
);
ESP_LOGI(TAG, "Install NV3041A panel driver");
ESP_GOTO_ON_ERROR(
esp_lcd_new_panel_nv3041a(*io_handle, &panel_config, lp),
display_init_panel_driver_init_failed,
TAG,
"Cannot init display driver!"
);
ESP_GOTO_ON_ERROR(esp_lcd_panel_reset(*lp), display_init_panel_setup_failed, TAG, "Cannot reset panel!");
ESP_GOTO_ON_ERROR(esp_lcd_panel_init(*lp), display_init_panel_setup_failed, TAG, "Cannot init panel!");
ESP_GOTO_ON_ERROR(esp_lcd_panel_disp_on_off(*lp, true), display_init_panel_setup_failed, TAG, "Cannot set panel on/off!");
ESP_LOGI(TAG, "Turn on LCD backlight");
gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);
return ESP_OK;
display_init_panel_setup_failed:
if (*lp)
esp_lcd_panel_del(*lp);
display_init_panel_driver_init_failed:
esp_lcd_panel_io_del((*io_handle));
display_init_lcd_attach_spi_failed:
spi_bus_free(LCD_HOST);
display_init_spi_init_failed:
gpio_reset_pin((gpio_num_t)EXAMPLE_PIN_NUM_BK_LIGHT);
display_init_gpio_init_failed:
return ret;
}
static esp_err_t app_touch_init(esp_lcd_panel_io_handle_t *tp_io,
esp_lcd_touch_handle_t *tp)
{
esp_err_t ret = ESP_OK;
ESP_LOGI(TAG, "Initialize I2C master bus");
i2c_master_bus_handle_t i2c_bus = NULL;
const i2c_master_bus_config_t bus_cfg = {
.i2c_port = TOUCH_HOST, // обычно I2C_NUM_0
.sda_io_num = EXAMPLE_PIN_NUM_TOUCH_SDA,
.scl_io_num = EXAMPLE_PIN_NUM_TOUCH_SCL,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.flags = {
.enable_internal_pullup = true
}
};
esp_lcd_panel_io_i2c_config_t tp_io_config =
ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
tp_io_config.scl_speed_hz = 400000;
const esp_lcd_touch_config_t tp_cfg = {
.x_max = EXAMPLE_LCD_H_RES,
.y_max = EXAMPLE_LCD_V_RES,
.rst_gpio_num = EXAMPLE_PIN_NUM_TOUCH_RST,
.int_gpio_num = EXAMPLE_PIN_NUM_TOUCH_INT,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0,
.mirror_x = 1,
.mirror_y = 1,
},
.process_coordinates = process_coordinates,
};
ESP_GOTO_ON_ERROR(
i2c_new_master_bus(&bus_cfg, &i2c_bus),
err,
TAG,
"Failed to create I2C master bus"
);
ESP_LOGI(TAG, "Create I2C panel IO for GT911");
ESP_GOTO_ON_ERROR(
esp_lcd_new_panel_io_i2c(i2c_bus, &tp_io_config, tp_io),
err,
TAG,
"Failed to create panel IO"
);
ESP_LOGI(TAG, "Initialize GT911 touch controller");
ESP_GOTO_ON_ERROR(
esp_lcd_touch_new_i2c_gt911(*tp_io, &tp_cfg, tp),
err,
TAG,
"Failed to init GT911"
);
return ESP_OK;
err:
if (*tp_io) {
esp_lcd_panel_io_del(*tp_io);
*tp_io = NULL;
}
if (i2c_bus) {
i2c_del_master_bus(i2c_bus);
}
return ret;
}
static esp_err_t app_lvgl_init(esp_lcd_panel_handle_t *lp, esp_lcd_panel_io_handle_t *io_handle, esp_lcd_touch_handle_t tp,
lv_display_t **lv_disp, lv_indev_t **lv_touch_indev) {
ESP_LOGI(TAG, "Initialize LVGL");
lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG();
lvgl_cfg.task_stack = 32768;
lvgl_cfg.task_affinity = 1;
lvgl_cfg.timer_period_ms = EXAMPLE_LVGL_TICK_PERIOD_MS;
ESP_RETURN_ON_ERROR(lvgl_port_init(&lvgl_cfg), TAG, "LVGL port initialization failed");
ESP_LOGI(TAG, "LVGL port initialized");
const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = *io_handle,
.panel_handle = *lp,
.buffer_size = SEND_BUF_SIZE,
.double_buffer = false,
.trans_size = 16384,
.hres = EXAMPLE_LCD_H_RES,
.vres = EXAMPLE_LCD_V_RES,
.monochrome = false,
// .color_format = LV_COLOR_FORMAT_RGB565,
#if LVGL_VERSION_MAJOR >= 9
.color_format = LV_COLOR_FORMAT_RGB565,
#endif
.flags = {
.buff_dma = false,
.buff_spiram = true,
#if LVGL_VERSION_MAJOR >= 9
// .swap_bytes = true,
#endif
}
};
// lvgl_port_lock(0);
*lv_disp = lvgl_port_add_disp(&disp_cfg);
// lvgl_port_unlock();
ESP_LOGI(TAG, "LVGL port display added");
// lvgl_disp = lvgl_port_add_disp(&disp_cfg);
// lvgl_port_lock(0);
// esp_lcd_panel_io_register_event_callbacks(
// *io_handle,
// &(esp_lcd_panel_io_callbacks_t) {
// example_notify_lvgl_flush_ready
// },
// *lv_disp
// );
// lvgl_port_unlock();
#if LVGL_VERSION_MAJOR >= 9
lvgl_port_lock(0);
lv_display_set_user_data(*lv_disp, *lp);
lv_display_set_flush_cb(*lv_disp, example_lvgl_flush_cb);
lvgl_port_unlock();
ESP_LOGI(TAG, "LVGL port callbacks set");
#else
// lv_obj_set_user_data(*lv_disp, *lp);
(*lv_disp)->driver->user_data = (*lp);
(*lv_disp)->driver->flush_cb = example_lvgl_flush_cb;
(*lv_disp)->driver->drv_update_cb = example_lvgl_update_cb;
#endif
lv_disp_set_rotation(*lv_disp, LV_DISPLAY_ROTATION_180);
/* Add touch input (for selected screen) */
const lvgl_port_touch_cfg_t touch_cfg = {
.disp = *lv_disp,
.handle = tp,
};
*lv_touch_indev = lvgl_port_add_touch(&touch_cfg);
assert(*lv_touch_indev != NULL);
lv_indev_enable(*lv_touch_indev, true);
ESP_LOGI(TAG, "LVGL port touch added");
esp_lcd_panel_invert_color((*lp), false);
ESP_LOGI(TAG, "LCD colors inverted");
return ESP_OK;
}
static void eez_tick_timer(void * params) {
ui_tick();
vTaskDelay(1);
if (screen_change_pending) {
screen_change_pending = false;
switch (pending_screen) {
case UI_SCREEN_MAIN:
loadScreen(SCREEN_ID_MAIN);
vTaskDelay(1);
break;
case UI_SCREEN_OFF_CHARGER:
loadScreen(SCREEN_ID_DEVICE_OFF_CHARGING);
vTaskDelay(1);
break;
case UI_SCREEN_SLEEP:
loadScreen(SCREEN_ID_DEVICE_COMING_SLEEP);
vTaskDelay(1);
break;
case UI_SCREEN_SHUTDOWN:
loadScreen(SCREEN_ID_DEVICE_SHUTTING_DOWN);
vTaskDelay(1);
break;
case UI_SCREEN_REBOOT:
loadScreen(SCREEN_ID_DEVICE_REBOOTING);
vTaskDelay(1);
break;
}
}
vTaskDelay(1);
}
void display_bsp_init() {
if (_display_bsp_initialized) {
ESP_LOGW(TAG, "Display BSP has been already initialized!");
return;
}
esp_err_t ret = ESP_OK;
xGuiSemaphore = xSemaphoreCreateMutex();
ESP_ERROR_CHECK(app_display_init(&lcd_panel, &io_handle));
assert(lcd_panel != NULL);
assert(io_handle != NULL);
ESP_ERROR_CHECK(app_touch_init(&touch_io_handle, &touch_handle));
assert(touch_handle != NULL);
assert(touch_io_handle != NULL);
_display_bsp_initialized = true;
}
void display_ui_init(void) {
if (_display_ui_initialized) {
ESP_LOGW(TAG, "display_ui_init already initialized");
return;
}
display_bsp_init();
esp_err_t ret = ESP_OK;
ESP_LOGI(TAG, "Display LVGL demos");
ESP_ERROR_CHECK(app_lvgl_init(&lcd_panel, &io_handle, touch_handle, &lvgl_disp, &lvgl_touch_indev));
ESP_LOGI(TAG, "LVGL has been initialized");
ui_set_lvgl_lock_cb(
[]() {
lvgl_port_lock(0);
}
);
ui_set_lvgl_unlock_cb(lvgl_port_unlock);
ui_init();
vTaskDelay(pdMS_TO_TICKS(100));
const esp_timer_create_args_t eez_tick_timer_args = {
.callback = &eez_tick_timer,
/* argument specified here will be passed to timer callback function */
//.arg = (void*) periodic_timer,
.name = "eez tick timer"
};
ESP_ERROR_CHECK(esp_timer_create(&eez_tick_timer_args, &eez_tick_timer_handle));
esp_timer_start_periodic(eez_tick_timer_handle, 100000);
_display_ui_initialized = true;
ESP_LOGI(TAG, "LVGL interface init passed");
}
void display_change_screen_main() {
ESP_LOGI(TAG, "Change screen to main requested");
pending_screen = UI_SCREEN_MAIN;
screen_change_pending = true;
}
void display_change_screen_off_charging() {
ESP_LOGI(TAG, "Change screen to off-charge requested");
pending_screen = UI_SCREEN_OFF_CHARGER;
screen_change_pending = true;
}
void display_change_screen_sleep() {
ESP_LOGI(TAG, "Change screen to sleep requested");
pending_screen = UI_SCREEN_SLEEP;
screen_change_pending = true;
}
void display_change_screen_shutdown() {
ESP_LOGI(TAG, "Change screen to shutdown requested");
pending_screen = UI_SCREEN_SHUTDOWN;
screen_change_pending = true;
}
void display_change_screen_rebooting() {
ESP_LOGI(TAG, "Change screen to reboot requested");
pending_screen = UI_SCREEN_REBOOT;
screen_change_pending = true;
}