How to improve UI speed to remove flickering / tearing on my display. DMA and two buffers?

what is causing the issue is the display is being updated in chunks when using a partial buffer.So if the area that needs to be updated is larger that what a single buffer is able to hold then 2 or more buffers gets used. With you UI there seems to be some extended processing time to render the buffer data so what is happening is one piece is being written and before the next piece is written the display IC is updating the display. this is why you are seeing an issue.

If a widget needs to be updated the entire widget gets redrawn not just a tiny piece of it. by changing the buffer size you are altering where that seam is between buffers. so you can move the seam to an area of the widget where the data being written is static so you don’t see the issue.

I can tell you that your UI is exceedingly complex in how it operates. Frankly I really don’t understand what is happening in it because you are jumping through a crap load of functions just to make a simple update to a widget.

I suggest writing a single widget test like a slider and have only that running and see if the issue is still there.

When doing very large updates like updating the entire screen as you are doing you are going to see the update seams. There is a way around it but it is going to come at the cost of using additional RAM. How much RAM does your device have available?

1 Like

I see, thanks for the explanation.

I made a simple slider manually, removing all the UI editor generated code:

#include "esp_log.h"

#include <stdio.h>
#include <unistd.h>
#include <sys/lock.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.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_lcd_panel_st7789.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_check.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lvgl.h"

static const char *TAG = "MyDisplay";

/* LCD size */
#define DISP_HOR_RES 320
#define DISP_VER_RES 172

/* LCD settings */
#define DISP_DRAW_BUFF_HEIGHT 50

/* LCD pins */
#define DISP_SPI_NUM SPI2_HOST
#define DISP_GPIO_SCLK GPIO_NUM_12
#define DISP_GPIO_MOSI GPIO_NUM_11
#define DISP_GPIO_RST -1 // Not connected
#define DISP_GPIO_DC GPIO_NUM_47
#define DISP_GPIO_CS GPIO_NUM_45
#define DISP_GPIO_BL GPIO_NUM_48

#define BUFFER_SIZE (DISP_HOR_RES * DISP_VER_RES * sizeof(uint16_t) / 10)

static esp_lcd_panel_handle_t panel_handle = NULL;
static esp_lcd_panel_io_handle_t io_handle = NULL;
static lv_display_t *display = NULL;

static void *buf1 = NULL;
static void *buf2 = NULL;

/* Function prototypes */

void lv_example_slider_1(void);
static void slider_anim_cb(void *var, int32_t value);

// This gets called when the DMA transfer of the buffer data has completed
static bool notify_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
    lv_display_t *disp = (lv_display_t *)user_ctx;
    lv_display_flush_ready(disp); // I have tried to change this to the global display variable, no change
    return false;
}

static void flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
    int x1 = area->x1;
    int x2 = area->x2;
    int y1 = area->y1 + 34; // Offset image to compensate for smaller 172px resolution
    int y2 = area->y2 + 34; // Offset image to compensate for smaller 172px resolution

    // uncomment the following line if the colors are wrong
    lv_draw_sw_rgb565_swap(px_map, (x2 + 1 - x1) * (y2 + 1 - y1)); // I have tried with and without this

    esp_lcd_panel_draw_bitmap((esp_lcd_panel_handle_t)lv_display_get_user_data(disp), x1, y1, x2 + 1, y2 + 1, px_map);
}

static esp_err_t lvgl_init(void)
{
    lv_init();

    display = lv_display_create(DISP_HOR_RES, DISP_VER_RES);

    buf1 = heap_caps_calloc(1, BUFFER_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
    buf2 = heap_caps_calloc(1, BUFFER_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);

    lv_display_set_buffers(display, buf1, buf2, BUFFER_SIZE, LV_DISPLAY_RENDER_MODE_PARTIAL);

    lv_display_set_user_data(display, panel_handle);
    lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);

    lv_display_set_flush_cb(display, flush_cb);

    const esp_lcd_panel_io_callbacks_t cbs = {
        .on_color_trans_done = notify_flush_ready,
    };
    /* Register done callback */
    ESP_RETURN_ON_ERROR(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, display), TAG, "esp_lcd_panel_io_register_event_callbacks error"); // I have tried to use
    ESP_RETURN_ON_ERROR(esp_lcd_panel_init(panel_handle), TAG, "esp_lcd_panel_init error");

    // Hardware rotate 90°
    uint8_t madctl = 0x60; // Rotate reg 0x00 0x60 0xC0 0xA0
    esp_lcd_panel_io_tx_param(io_handle, 0x36, &madctl, 1);

    esp_lcd_panel_io_tx_param(io_handle, 0x21, NULL, 0); // Inverted color fix (to get normal colors)

    return ESP_OK;
}

static esp_err_t display_init(void)
{
    // LCD backlight and DC
    gpio_config_t gpio_cfg = {
        .pin_bit_mask = (1ULL << DISP_GPIO_BL) | (1ULL << DISP_GPIO_DC),
        .mode = GPIO_MODE_OUTPUT,
    };
    ESP_ERROR_CHECK(gpio_config(&gpio_cfg));

    gpio_set_level(DISP_GPIO_BL, 1); // Turn on backlight
    gpio_set_level(DISP_GPIO_DC, 1); // Default to data mode

    // LCD initialization
    ESP_LOGD(TAG, "Initialize SPI bus");
    spi_bus_config_t buscfg = {};
    buscfg.sclk_io_num = DISP_GPIO_SCLK;
    buscfg.mosi_io_num = DISP_GPIO_MOSI;
    buscfg.miso_io_num = GPIO_NUM_NC;
    buscfg.quadwp_io_num = GPIO_NUM_NC;
    buscfg.quadhd_io_num = GPIO_NUM_NC;
    buscfg.max_transfer_sz = BUFFER_SIZE; // DISP_HOR_RES * DISP_DRAW_BUFF_HEIGHT * sizeof(uint16_t);
    ESP_RETURN_ON_ERROR(spi_bus_initialize(DISP_SPI_NUM, &buscfg, SPI_DMA_CH_AUTO), TAG, "SPI init failed");

    esp_lcd_panel_io_spi_config_t io_config = {
        .dc_gpio_num = DISP_GPIO_DC,
        .cs_gpio_num = DISP_GPIO_CS,
        .pclk_hz = 80 * 1000 * 1000,
        .lcd_cmd_bits = 8,
        .lcd_param_bits = 8,
        .spi_mode = 0,
        .trans_queue_depth = 10,
    };
    ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_spi(DISP_SPI_NUM, &io_config, &io_handle), TAG, "SPI init failed");

    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = DISP_GPIO_RST,
        .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
        .bits_per_pixel = 16,
        .flags = {.reset_active_high = 0}};

    ESP_RETURN_ON_ERROR(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle), TAG, "Display init failed");

    // Reset the display
    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));

    // Initialize LCD panel
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));

    // Vendor specific settings

    // Turn on the screen
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

    return ESP_OK;
}

static void lvgl_tick_increment(void *arg)
{
    // Tell LVGL how many milliseconds have elapsed
    lv_tick_inc(2);
}

static esp_err_t lvgl_tick_init(void)
{
    esp_timer_handle_t tick_timer;

    // Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
    const esp_timer_create_args_t lvgl_tick_timer_args = {
        .callback = &lvgl_tick_increment,
        .name = "LVGL tick",
    };
    ESP_RETURN_ON_ERROR(esp_timer_create(&lvgl_tick_timer_args, &tick_timer), TAG, "Creating LVGL timer filed!");
    return esp_timer_start_periodic(tick_timer, 2 * 1000); // 2 ms
}

static void lvgl_task(void *arg)
{

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    esp_log_level_set("lcd_panel", ESP_LOG_VERBOSE);
    esp_log_level_set("lcd_panel.st7789", ESP_LOG_VERBOSE);
    esp_log_level_set(TAG, ESP_LOG_VERBOSE);

    esp_err_t ret = display_init();

    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "ST7789 failed to initilize");
        while (1)
            ;
    }
    ret = lvgl_init();
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "LVGL Display failed to initialize");
        while (1)
            ;
    }

    ret = lvgl_tick_init();
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "Timer failed to initialize");
        while (1)
            ;
    }

    // Create a slider
    lv_example_slider_1();

    // Handle LVGL tasks
    while (1)
    {
        vTaskDelay(pdMS_TO_TICKS(10));
        lv_task_handler();
    }
}

void lv_example_slider_1(void)
{
    /*Create a screen*/
    lv_obj_t *screen = lv_screen_active();
    lv_obj_set_style_bg_color(screen, lv_color_black(), 0);

    /*Create a slider in the center of the display*/
    lv_obj_t *slider = lv_slider_create(lv_screen_active());
    lv_obj_center(slider);
    lv_obj_set_height(slider, 30);

    /* Change indicator (filled part) color */
    lv_obj_set_style_bg_color(slider, lv_palette_main(LV_PALETTE_GREEN), LV_PART_INDICATOR);
    lv_obj_set_style_bg_color(slider, lv_palette_main(LV_PALETTE_LIME), LV_PART_KNOB);

    lv_anim_t a;
    lv_anim_init(&a);
    lv_anim_set_var(&a, slider);
    lv_anim_set_exec_cb(&a, slider_anim_cb); // Use wrapper function
    lv_anim_set_time(&a, 1000);              // Set animation duration to 1000ms
    lv_anim_set_values(&a, 0, 100);
    lv_anim_set_delay(&a, 1000); // Start after 1000ms delay
    lv_anim_start(&a);
}

static void slider_anim_cb(void *var, int32_t value)
{
    lv_obj_t *slider = (lv_obj_t *)var;

    lv_slider_set_value(slider, value, LV_ANIM_ON);
}

void app_main()
{

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    TaskHandle_t taskHandle = NULL;
    xTaskCreatePinnedToCore(lvgl_task, "LVGL task", 8192, NULL, 4, &taskHandle, 0); // stack, params, prio, handle, core
    ESP_LOGI("CoreCheck", "Running on core %d", xPortGetCoreID());
}

PXL_20250317_003159928~4

Looks similar to what we saw earlier. If buffer is set to /10 the seam becomes visible, etc. I think it’s not bad.

In my real project I have around 80-100kB left.
I guess you want me to try LV_DISPLAY_RENDER_MODE_DIRECT, right?
Tried that with full screen sized buffers but moving objects are distorted, I suppose it’s because the flush function needs to be modified as well.

I would want you to use FULL and set both the buffers to the size of the display. so width* height * bytes per pixel You would not need to make any adjustments in your flush function at all.

do me a favor and have the widget go both up and down. One thing I am noticing is that when the slider drops to zero that would force the entire widget to get redrawn and you are not seeing the problem when that happens.

Try running this code. I move a couple of things around and changed the cores the tasks are running on and I also changed the task priorities. I got rid of the animation to set the value of the widget as that is going to have it’s own set of complexities to it that can skew the results. Doing it this way should provide the most efficient code to moving the slider.

#include "esp_log.h"

#include <stdio.h>
#include <unistd.h>
#include <sys/lock.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.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_lcd_panel_st7789.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_check.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lvgl.h"

static const char *TAG = "MyDisplay";

/* LCD size */
#define DISP_HOR_RES 320
#define DISP_VER_RES 172

/* LCD settings */
#define DISP_DRAW_BUFF_HEIGHT 50

/* LCD pins */
#define DISP_SPI_NUM SPI2_HOST
#define DISP_GPIO_SCLK GPIO_NUM_12
#define DISP_GPIO_MOSI GPIO_NUM_11
#define DISP_GPIO_RST -1 // Not connected
#define DISP_GPIO_DC GPIO_NUM_47
#define DISP_GPIO_CS GPIO_NUM_45
#define DISP_GPIO_BL GPIO_NUM_48

#define BUFFER_SIZE (DISP_HOR_RES * DISP_VER_RES * sizeof(uint16_t) / 10)

static esp_lcd_panel_handle_t panel_handle = NULL;
static esp_lcd_panel_io_handle_t io_handle = NULL;
static lv_display_t *display = NULL;

static void *buf1 = NULL;
static void *buf2 = NULL;

/* Function prototypes */

void lv_example_slider_1(void);
static void slider_anim_cb(void *var, int32_t value);

// This gets called when the DMA transfer of the buffer data has completed
static bool notify_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
    lv_display_t *disp = (lv_display_t *)user_ctx;
    lv_display_flush_ready(disp); // I have tried to change this to the global display variable, no change
    return false;
}

static void flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
    int x1 = area->x1;
    int x2 = area->x2;
    int y1 = area->y1 + 34; // Offset image to compensate for smaller 172px resolution
    int y2 = area->y2 + 34; // Offset image to compensate for smaller 172px resolution

    // uncomment the following line if the colors are wrong
    lv_draw_sw_rgb565_swap(px_map, (x2 + 1 - x1) * (y2 + 1 - y1)); // I have tried with and without this

    esp_lcd_panel_draw_bitmap((esp_lcd_panel_handle_t)lv_display_get_user_data(disp), x1, y1, x2 + 1, y2 + 1, px_map);
}

static esp_err_t lvgl_init(void)
{
    display = lv_display_create(DISP_HOR_RES, DISP_VER_RES);

    buf1 = heap_caps_calloc(1, BUFFER_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
    buf2 = heap_caps_calloc(1, BUFFER_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);

    lv_display_set_buffers(display, buf1, buf2, BUFFER_SIZE, LV_DISPLAY_RENDER_MODE_PARTIAL);

    lv_display_set_user_data(display, panel_handle);
    lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);

    lv_display_set_flush_cb(display, flush_cb);

    const esp_lcd_panel_io_callbacks_t cbs = {
        .on_color_trans_done = notify_flush_ready,
    };
    /* Register done callback */
    ESP_RETURN_ON_ERROR(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, display), TAG, "esp_lcd_panel_io_register_event_callbacks error"); // I have tried to use
    ESP_RETURN_ON_ERROR(esp_lcd_panel_init(panel_handle), TAG, "esp_lcd_panel_init error");

    // Hardware rotate 90°
    uint8_t madctl = 0x60; // Rotate reg 0x00 0x60 0xC0 0xA0
    esp_lcd_panel_io_tx_param(io_handle, 0x36, &madctl, 1);

    esp_lcd_panel_io_tx_param(io_handle, 0x21, NULL, 0); // Inverted color fix (to get normal colors)

    return ESP_OK;
}

static esp_err_t display_init(void)
{
    // LCD backlight and DC
    gpio_config_t gpio_cfg = {
        .pin_bit_mask = (1ULL << DISP_GPIO_BL) | (1ULL << DISP_GPIO_DC),
        .mode = GPIO_MODE_OUTPUT,
    };
    ESP_ERROR_CHECK(gpio_config(&gpio_cfg));

    gpio_set_level(DISP_GPIO_BL, 1); // Turn on backlight
    gpio_set_level(DISP_GPIO_DC, 1); // Default to data mode

    // LCD initialization
    ESP_LOGD(TAG, "Initialize SPI bus");
    spi_bus_config_t buscfg = {};
    buscfg.sclk_io_num = DISP_GPIO_SCLK;
    buscfg.mosi_io_num = DISP_GPIO_MOSI;
    buscfg.miso_io_num = GPIO_NUM_NC;
    buscfg.quadwp_io_num = GPIO_NUM_NC;
    buscfg.quadhd_io_num = GPIO_NUM_NC;
    buscfg.max_transfer_sz = BUFFER_SIZE; // DISP_HOR_RES * DISP_DRAW_BUFF_HEIGHT * sizeof(uint16_t);
    ESP_RETURN_ON_ERROR(spi_bus_initialize(DISP_SPI_NUM, &buscfg, SPI_DMA_CH_AUTO), TAG, "SPI init failed");

    esp_lcd_panel_io_spi_config_t io_config = {
        .dc_gpio_num = DISP_GPIO_DC,
        .cs_gpio_num = DISP_GPIO_CS,
        .pclk_hz = 80 * 1000 * 1000,
        .lcd_cmd_bits = 8,
        .lcd_param_bits = 8,
        .spi_mode = 0,
        .trans_queue_depth = 10,
    };
    ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_spi(DISP_SPI_NUM, &io_config, &io_handle), TAG, "SPI init failed");

    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = DISP_GPIO_RST,
        .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
        .bits_per_pixel = 16,
        .flags = {.reset_active_high = 0}};

    ESP_RETURN_ON_ERROR(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle), TAG, "Display init failed");

    // Reset the display
    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));

    // Initialize LCD panel
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));

    // Vendor specific settings

    // Turn on the screen
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

    return ESP_OK;
}

static void lvgl_tick_increment(void *arg)
{
    // Tell LVGL how many milliseconds have elapsed
    lv_tick_inc(2);
}

static esp_err_t lvgl_tick_init(void)
{
    esp_timer_handle_t tick_timer;

    // Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
    const esp_timer_create_args_t lvgl_tick_timer_args = {
        .callback = &lvgl_tick_increment,
        .name = "LVGL tick",
    };
    ESP_RETURN_ON_ERROR(esp_timer_create(&lvgl_tick_timer_args, &tick_timer), TAG, "Creating LVGL timer filed!");
    return esp_timer_start_periodic(tick_timer, 2 * 1000); // 2 ms
}

static void lvgl_task(void *arg)
{

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    esp_log_level_set("lcd_panel", ESP_LOG_VERBOSE);
    esp_log_level_set("lcd_panel.st7789", ESP_LOG_VERBOSE);
    esp_log_level_set(TAG, ESP_LOG_VERBOSE);

    esp_err_t ret = display_init();

    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "ST7789 failed to initilize");
        while (1)
            ;
    }
    ret = lvgl_init();
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "LVGL Display failed to initialize");
        while (1)
            ;
    }

    // Create a slider
    lv_obj_t *slider = lv_example_slider_1();

    // Handle LVGL tasks
    int32_t count = 1;
    int32_t increment = 1;
    lv_slider_set_value(slider, 1, LV_ANIM_ON);
    lv_refr_now();   

    while (1)
    {
        vTaskDelay(pdMS_TO_TICKS(10));
        if (count == 100 || count == 0) {
            increment = -increment;
        }
        count += increment;
        lv_slider_set_value(slider, count, LV_ANIM_ON);
        lv_refr_now();   
        
        lv_task_handler();
    }
}

lv_obj_t *lv_example_slider_1(void)
{
    /*Create a screen*/
    lv_obj_t *screen = lv_screen_active();
    lv_obj_set_style_bg_color(screen, lv_color_black(), 0);

    /*Create a slider in the center of the display*/
    lv_obj_t *slider = lv_slider_create(lv_screen_active());
    lv_obj_center(slider);
    lv_obj_set_height(slider, 30);

    /* Change indicator (filled part) color */
    lv_obj_set_style_bg_color(slider, lv_palette_main(LV_PALETTE_GREEN), LV_PART_INDICATOR);
    lv_obj_set_style_bg_color(slider, lv_palette_main(LV_PALETTE_LIME), LV_PART_KNOB);
    
    return slider;
    
}

static void slider_anim_cb(void *var, int32_t value)
{
    lv_obj_t *slider = (lv_obj_t *)var;

    lv_slider_set_value(slider, value, LV_ANIM_ON);
}


// we are going to have the tick run on core 0 This is so the interrupt is 
// also executed on core 0. I move the lvgl task to core 1 and bumped up 
// the priority to 22 so it will have the highest priority. 

void tick_task(void *args) 
{
    ret = lvgl_tick_init();
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "Timer failed to initialize");
        while (1)
            ;
    }
}


void app_main()
{

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    TaskHandle_t taskHandle = NULL;
    TaskHandle_t tickHandle = NULL;
    
    lv_init();

    xTaskCreatePinnedToCore(lvgl_task, "LVGL task", 8192, NULL, 22, &taskHandle, 1); // stack, params, prio, handle, core
    xTaskCreatePinnedToCore(tick_task, "LVGL tick task", 4096, NULL, 4, &tickHandle, 0); // stack, params, prio, handle, core

    ESP_LOGI("CoreCheck", "Running on core %d", xPortGetCoreID());
}

Tried that and it didn’t make a difference at all. It looks similar to LV_DISPLAY_RENDER_MODE_PARTIAL with buffer size /8

Tried compiling that code but there were a few problems with it:

  • Changed the function prototype to lv_obj_t *lv_example_slider_1(void);
  • Added the missing parameter in lv_refr_now()

Here’s the corrected code:

#include "esp_log.h"

#include <stdio.h>
#include <unistd.h>
#include <sys/lock.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.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_lcd_panel_st7789.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_check.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lvgl.h"

static const char *TAG = "MyDisplay";

/* LCD size */
#define DISP_HOR_RES 320
#define DISP_VER_RES 172

/* LCD settings */
#define DISP_DRAW_BUFF_HEIGHT 50

/* LCD pins */
#define DISP_SPI_NUM SPI2_HOST
#define DISP_GPIO_SCLK GPIO_NUM_12
#define DISP_GPIO_MOSI GPIO_NUM_11
#define DISP_GPIO_RST -1 // Not connected
#define DISP_GPIO_DC GPIO_NUM_47
#define DISP_GPIO_CS GPIO_NUM_45
#define DISP_GPIO_BL GPIO_NUM_48

#define BUFFER_SIZE (DISP_HOR_RES * DISP_VER_RES * sizeof(uint16_t) / 10)

static esp_lcd_panel_handle_t panel_handle = NULL;
static esp_lcd_panel_io_handle_t io_handle = NULL;
static lv_display_t *display = NULL;

static void *buf1 = NULL;
static void *buf2 = NULL;

/* Function prototypes */

lv_obj_t *lv_example_slider_1(void);

// This gets called when the DMA transfer of the buffer data has completed
static bool notify_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
    lv_display_t *disp = (lv_display_t *)user_ctx;
    lv_display_flush_ready(disp); // I have tried to change this to the global display variable, no change
    return false;
}

static void flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
    int x1 = area->x1;
    int x2 = area->x2;
    int y1 = area->y1 + 34; // Offset image to compensate for smaller 172px resolution
    int y2 = area->y2 + 34; // Offset image to compensate for smaller 172px resolution

    // uncomment the following line if the colors are wrong
    lv_draw_sw_rgb565_swap(px_map, (x2 + 1 - x1) * (y2 + 1 - y1)); // I have tried with and without this

    esp_lcd_panel_draw_bitmap((esp_lcd_panel_handle_t)lv_display_get_user_data(disp), x1, y1, x2 + 1, y2 + 1, px_map);
}

static esp_err_t lvgl_init(void)
{
    display = lv_display_create(DISP_HOR_RES, DISP_VER_RES);

    buf1 = heap_caps_calloc(1, BUFFER_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
    buf2 = heap_caps_calloc(1, BUFFER_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);

    lv_display_set_buffers(display, buf1, buf2, BUFFER_SIZE, LV_DISPLAY_RENDER_MODE_PARTIAL);

    lv_display_set_user_data(display, panel_handle);
    lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);

    lv_display_set_flush_cb(display, flush_cb);

    const esp_lcd_panel_io_callbacks_t cbs = {
        .on_color_trans_done = notify_flush_ready,
    };
    /* Register done callback */
    ESP_RETURN_ON_ERROR(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, display), TAG, "esp_lcd_panel_io_register_event_callbacks error"); // I have tried to use
    ESP_RETURN_ON_ERROR(esp_lcd_panel_init(panel_handle), TAG, "esp_lcd_panel_init error");

    // Hardware rotate 90°
    uint8_t madctl = 0x60; // Rotate reg 0x00 0x60 0xC0 0xA0
    esp_lcd_panel_io_tx_param(io_handle, 0x36, &madctl, 1);

    esp_lcd_panel_io_tx_param(io_handle, 0x21, NULL, 0); // Inverted color fix (to get normal colors)

    return ESP_OK;
}

static esp_err_t display_init(void)
{
    // LCD backlight and DC
    gpio_config_t gpio_cfg = {
        .pin_bit_mask = (1ULL << DISP_GPIO_BL) | (1ULL << DISP_GPIO_DC),
        .mode = GPIO_MODE_OUTPUT,
    };
    ESP_ERROR_CHECK(gpio_config(&gpio_cfg));

    gpio_set_level(DISP_GPIO_BL, 1); // Turn on backlight
    gpio_set_level(DISP_GPIO_DC, 1); // Default to data mode

    // LCD initialization
    ESP_LOGD(TAG, "Initialize SPI bus");
    spi_bus_config_t buscfg = {};
    buscfg.sclk_io_num = DISP_GPIO_SCLK;
    buscfg.mosi_io_num = DISP_GPIO_MOSI;
    buscfg.miso_io_num = GPIO_NUM_NC;
    buscfg.quadwp_io_num = GPIO_NUM_NC;
    buscfg.quadhd_io_num = GPIO_NUM_NC;
    buscfg.max_transfer_sz = BUFFER_SIZE; // DISP_HOR_RES * DISP_DRAW_BUFF_HEIGHT * sizeof(uint16_t);
    ESP_RETURN_ON_ERROR(spi_bus_initialize(DISP_SPI_NUM, &buscfg, SPI_DMA_CH_AUTO), TAG, "SPI init failed");

    esp_lcd_panel_io_spi_config_t io_config = {
        .dc_gpio_num = DISP_GPIO_DC,
        .cs_gpio_num = DISP_GPIO_CS,
        .pclk_hz = 80 * 1000 * 1000,
        .lcd_cmd_bits = 8,
        .lcd_param_bits = 8,
        .spi_mode = 0,
        .trans_queue_depth = 10,
    };
    ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_spi(DISP_SPI_NUM, &io_config, &io_handle), TAG, "SPI init failed");

    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = DISP_GPIO_RST,
        .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
        .bits_per_pixel = 16,
        .flags = {.reset_active_high = 0}};

    ESP_RETURN_ON_ERROR(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle), TAG, "Display init failed");

    // Reset the display
    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));

    // Initialize LCD panel
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));

    // Vendor specific settings

    // Turn on the screen
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

    return ESP_OK;
}

static void lvgl_tick_increment(void *arg)
{
    // Tell LVGL how many milliseconds have elapsed
    lv_tick_inc(2);
}

static esp_err_t lvgl_tick_init(void)
{
    esp_timer_handle_t tick_timer;

    // Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
    const esp_timer_create_args_t lvgl_tick_timer_args = {
        .callback = &lvgl_tick_increment,
        .name = "LVGL tick",
    };
    ESP_RETURN_ON_ERROR(esp_timer_create(&lvgl_tick_timer_args, &tick_timer), TAG, "Creating LVGL timer filed!");
    return esp_timer_start_periodic(tick_timer, 2 * 1000); // 2 ms
}

static void lvgl_task(void *arg)
{

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    esp_log_level_set("lcd_panel", ESP_LOG_VERBOSE);
    esp_log_level_set("lcd_panel.st7789", ESP_LOG_VERBOSE);
    esp_log_level_set(TAG, ESP_LOG_VERBOSE);

    esp_err_t ret = display_init();

    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "ST7789 failed to initilize");
        while (1)
            ;
    }
    ret = lvgl_init();
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "LVGL Display failed to initialize");
        while (1)
            ;
    }

    // Create a slider
    lv_obj_t *slider = lv_example_slider_1();

    // Handle LVGL tasks
    int32_t count = 1;
    int32_t increment = 1;
    lv_slider_set_value(slider, 1, LV_ANIM_ON);
    lv_refr_now(display);   

    while (1)
    {
        vTaskDelay(pdMS_TO_TICKS(10));
        if (count == 100 || count == 0) {
            increment = -increment;
        }
        count += increment;
        lv_slider_set_value(slider, count, LV_ANIM_ON);
        lv_refr_now(display);   
        
        lv_task_handler();
    }
}

lv_obj_t *lv_example_slider_1(void)
{
    /*Create a screen*/
    lv_obj_t *screen = lv_screen_active();
    lv_obj_set_style_bg_color(screen, lv_color_black(), 0);

    /*Create a slider in the center of the display*/
    lv_obj_t *slider = lv_slider_create(lv_screen_active());
    lv_obj_center(slider);
    lv_obj_set_height(slider, 30);

    /* Change indicator (filled part) color */
    lv_obj_set_style_bg_color(slider, lv_palette_main(LV_PALETTE_GREEN), LV_PART_INDICATOR);
    lv_obj_set_style_bg_color(slider, lv_palette_main(LV_PALETTE_LIME), LV_PART_KNOB);
    
    return slider;
    
}

// we are going to have the tick run on core 0 This is so the interrupt is 
// also executed on core 0. I move the lvgl task to core 1 and bumped up 
// the priority to 22 so it will have the highest priority. 

void tick_task(void *args) 
{
    esp_err_t ret = lvgl_tick_init();
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "Timer failed to initialize");
        while (1)
            ;
    }
}


void app_main()
{

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    TaskHandle_t taskHandle = NULL;
    TaskHandle_t tickHandle = NULL;
    
    lv_init();

    xTaskCreatePinnedToCore(lvgl_task, "LVGL task", 8192, NULL, 22, &taskHandle, 1); // stack, params, prio, handle, core
    xTaskCreatePinnedToCore(tick_task, "LVGL tick task", 4096, NULL, 4, &tickHandle, 0); // stack, params, prio, handle, core

    ESP_LOGI("CoreCheck", "Running on core %d", xPortGetCoreID());
}

But it’s crashing my device:

ELF file SHA256: 2e8639fc1

Rebooting...
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0xc (RTC_SW_CPU_RST),boot:0x2f (SPI_FAST_FLASH_BOOT)
Saved PC:0x4037756c
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce2810,len:0x178c
load:0x403c8700,len:0x4
load:0x403c8704,len:0xcb8
load:0x403cb700,len:0x2db0
entry 0x403c890c
␛[0;32mI (26) boot: ESP-IDF 5.3.1 2nd stage bootloader␛[0m
␛[0;32mI (26) boot: compile time Mar 17 2025 10:18:28␛[0m
␛[0;32mI (26) boot: Multicore bootloader␛[0m
␛[0;32mI (29) boot: chip revision: v0.2␛[0m
␛[0;32mI (33) boot.esp32s3: Boot SPI Speed : 80MHz␛[0m
␛[0;32mI (38) boot.esp32s3: SPI Mode       : DIO␛[0m
␛[0;32mI (43) boot.esp32s3: SPI Flash Size : 8MB␛[0m
␛[0;32mI (47) boot: Enabling RNG early entropy source...␛[0m
␛[0;32mI (53) boot: Partition Table:␛[0m
␛[0;32mI (56) boot: ## Label            Usage          Type ST Offset   Length␛[0m
␛[0;32mI (64) boot:  0 nvs              WiFi data        01 02 00009000 00006000␛[0m
␛[0;32mI (71) boot:  1 phy_init         RF data          01 01 0000f000 00001000␛[0m
␛[0;32mI (78) boot:  2 factory          factory app      00 00 00010000 00100000␛[0m
␛[0;32mI (86) boot: End of partition table␛[0m
␛[0;32mI (90) esp_image: segment 0: paddr=00010020 vaddr=3c050020 size=120fch ( 73980) map␛[0m
␛[0;32mI (112) esp_image: segment 1: paddr=00022124 vaddr=3fc94b00 size=02bach ( 11180) load␛[0m
␛[0;32mI (115) esp_image: segment 2: paddr=00024cd8 vaddr=40374000 size=0b340h ( 45888) load␛[0m
␛[0;32mI (128) esp_image: segment 3: paddr=00030020 vaddr=42000020 size=4e8d4h (321748) map␛[0m
␛[0;32mI (186) esp_image: segment 4: paddr=0007e8fc vaddr=4037f340 size=05708h ( 22280) load␛[0m
␛[0;32mI (198) boot: Loaded app from partition at offset 0x10000␛[0m
␛[0;32mI (198) boot: Disabling RNG early entropy source...␛[0m
␛[0;32mI (210) cpu_start: Multicore app␛[0m
␛[0;32mI (219) cpu_start: Pro cpu start user code␛[0m
␛[0;32mI (219) cpu_start: cpu freq: 240000000 Hz␛[0m
␛[0;32mI (220) app_init: Application information:␛[0m
␛[0;32mI (222) app_init: Project name:     espidf-lcd-test␛[0m
␛[0;32mI (228) app_init: App version:      7974892-dirty␛[0m
␛[0;32mI (233) app_init: Compile time:     Mar 17 2025 10:17:43␛[0m
␛[0;32mI (239) app_init: ELF file SHA256:  2e8639fc1...␛[0m
␛[0;32mI (245) app_init: ESP-IDF:          5.3.1␛[0m
␛[0;32mI (249) efuse_init: Min chip rev:     v0.0␛[0m
␛[0;32mI (254) efuse_init: Max chip rev:     v0.99 ␛[0m
␛[0;32mI (259) efuse_init: Chip rev:         v0.2␛[0m
␛[0;32mI (264) heap_init: Initializing. RAM available for dynamic allocation:␛[0m
␛[0;32mI (271) heap_init: At 3FCA82E8 len 00041428 (261 KiB): RAM␛[0m
␛[0;32mI (277) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM␛[0m
␛[0;32mI (283) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM␛[0m
␛[0;32mI (289) heap_init: At 600FE100 len 00001EE8 (7 KiB): RTCRAM␛[0m
␛[0;32mI (297) spi_flash: detected chip: gd␛[0m
␛[0;32mI (300) spi_flash: flash io: dio␛[0m
␛[0;32mI (304) sleep: Configure to isolate all GPIO pins in sleep state␛[0m
␛[0;32mI (311) sleep: Enable automatic switching of GPIO sleep configuration␛[0m
␛[0;32mI (318) main_task: Started on CPU0␛[0m
␛[0;32mI (328) main_task: Calling app_main()␛[0m
␛[0;31mE (1328) FreeRTOS: FreeRTOS Task "LVGL tick task" should not return, Aborting now!␛[0m

abort() was called at PC 0x4037be2b on core 0


Backtrace: 0x40377632:0x3fcb01c0 0x4037b379:0x3fcb01e0 0x40382951:0x3fcb0200 0x4037be2b:0x3fcb0270

Let me rework it.

This should work now…

#include "esp_log.h"

#include <stdio.h>
#include <unistd.h>
#include <sys/lock.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.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_lcd_panel_st7789.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_check.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lvgl.h"

static const char *TAG = "MyDisplay";

/* LCD size */
#define DISP_HOR_RES 320
#define DISP_VER_RES 172

/* LCD settings */
#define DISP_DRAW_BUFF_HEIGHT 50

/* LCD pins */
#define DISP_SPI_NUM SPI2_HOST
#define DISP_GPIO_SCLK GPIO_NUM_12
#define DISP_GPIO_MOSI GPIO_NUM_11
#define DISP_GPIO_RST -1 // Not connected
#define DISP_GPIO_DC GPIO_NUM_47
#define DISP_GPIO_CS GPIO_NUM_45
#define DISP_GPIO_BL GPIO_NUM_48

#define BUFFER_SIZE (DISP_HOR_RES * DISP_VER_RES * sizeof(uint16_t) / 10)

static esp_lcd_panel_handle_t panel_handle = NULL;
static esp_lcd_panel_io_handle_t io_handle = NULL;
static lv_display_t *display = NULL;

static void *buf1 = NULL;
static void *buf2 = NULL;

/* Function prototypes */

lv_obj_t *lv_example_slider_1(void);


// This gets called when the DMA transfer of the buffer data has completed
static bool notify_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
    lv_display_t *disp = (lv_display_t *)user_ctx;
    lv_display_flush_ready(disp); // I have tried to change this to the global display variable, no change
    return false;
}

static void flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
    int x1 = area->x1;
    int x2 = area->x2;
    int y1 = area->y1 + 34; // Offset image to compensate for smaller 172px resolution
    int y2 = area->y2 + 34; // Offset image to compensate for smaller 172px resolution

    // uncomment the following line if the colors are wrong
    lv_draw_sw_rgb565_swap(px_map, (x2 + 1 - x1) * (y2 + 1 - y1)); // I have tried with and without this

    esp_lcd_panel_draw_bitmap((esp_lcd_panel_handle_t)lv_display_get_user_data(disp), x1, y1, x2 + 1, y2 + 1, px_map);
}

static esp_err_t lvgl_init(void)
{
    display = lv_display_create(DISP_HOR_RES, DISP_VER_RES);

    buf1 = heap_caps_calloc(1, BUFFER_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
    buf2 = heap_caps_calloc(1, BUFFER_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);

    lv_display_set_buffers(display, buf1, buf2, BUFFER_SIZE, LV_DISPLAY_RENDER_MODE_PARTIAL);

    lv_display_set_user_data(display, panel_handle);
    lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);

    lv_display_set_flush_cb(display, flush_cb);

    const esp_lcd_panel_io_callbacks_t cbs = {
        .on_color_trans_done = notify_flush_ready,
    };
    /* Register done callback */
    ESP_RETURN_ON_ERROR(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, display), TAG, "esp_lcd_panel_io_register_event_callbacks error"); // I have tried to use
    ESP_RETURN_ON_ERROR(esp_lcd_panel_init(panel_handle), TAG, "esp_lcd_panel_init error");

    // Hardware rotate 90°
    uint8_t madctl = 0x60; // Rotate reg 0x00 0x60 0xC0 0xA0
    esp_lcd_panel_io_tx_param(io_handle, 0x36, &madctl, 1);

    esp_lcd_panel_io_tx_param(io_handle, 0x21, NULL, 0); // Inverted color fix (to get normal colors)

    return ESP_OK;
}

static esp_err_t display_init(void)
{
    // LCD backlight and DC
    gpio_config_t gpio_cfg = {
        .pin_bit_mask = (1ULL << DISP_GPIO_BL) | (1ULL << DISP_GPIO_DC),
        .mode = GPIO_MODE_OUTPUT,
    };
    ESP_ERROR_CHECK(gpio_config(&gpio_cfg));

    gpio_set_level(DISP_GPIO_BL, 1); // Turn on backlight
    gpio_set_level(DISP_GPIO_DC, 1); // Default to data mode

    // LCD initialization
    ESP_LOGD(TAG, "Initialize SPI bus");
    spi_bus_config_t buscfg = {};
    buscfg.sclk_io_num = DISP_GPIO_SCLK;
    buscfg.mosi_io_num = DISP_GPIO_MOSI;
    buscfg.miso_io_num = GPIO_NUM_NC;
    buscfg.quadwp_io_num = GPIO_NUM_NC;
    buscfg.quadhd_io_num = GPIO_NUM_NC;
    buscfg.max_transfer_sz = BUFFER_SIZE; // DISP_HOR_RES * DISP_DRAW_BUFF_HEIGHT * sizeof(uint16_t);
    ESP_RETURN_ON_ERROR(spi_bus_initialize(DISP_SPI_NUM, &buscfg, SPI_DMA_CH_AUTO), TAG, "SPI init failed");

    esp_lcd_panel_io_spi_config_t io_config = {
        .dc_gpio_num = DISP_GPIO_DC,
        .cs_gpio_num = DISP_GPIO_CS,
        .pclk_hz = 80 * 1000 * 1000,
        .lcd_cmd_bits = 8,
        .lcd_param_bits = 8,
        .spi_mode = 0,
        .trans_queue_depth = 10,
    };
    ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_spi(DISP_SPI_NUM, &io_config, &io_handle), TAG, "SPI init failed");

    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = DISP_GPIO_RST,
        .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
        .bits_per_pixel = 16,
        .flags = {.reset_active_high = 0}};

    ESP_RETURN_ON_ERROR(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle), TAG, "Display init failed");

    // Reset the display
    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));

    // Initialize LCD panel
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));

    // Vendor specific settings

    // Turn on the screen
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

    return ESP_OK;
}

static void lvgl_tick_increment(void *arg)
{
    // Tell LVGL how many milliseconds have elapsed
    lv_tick_inc(2);
}

static esp_err_t lvgl_tick_init(void)
{
    esp_timer_handle_t tick_timer;

    // Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
    const esp_timer_create_args_t lvgl_tick_timer_args = {
        .callback = &lvgl_tick_increment,
        .name = "LVGL tick",
    };
    ESP_RETURN_ON_ERROR(esp_timer_create(&lvgl_tick_timer_args, &tick_timer), TAG, "Creating LVGL timer filed!");
    return esp_timer_start_periodic(tick_timer, 2 * 1000); // 2 ms
}

static void lvgl_task(void *arg)
{

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    esp_log_level_set("lcd_panel", ESP_LOG_VERBOSE);
    esp_log_level_set("lcd_panel.st7789", ESP_LOG_VERBOSE);
    esp_log_level_set(TAG, ESP_LOG_VERBOSE);

    esp_err_t ret = display_init();

    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "ST7789 failed to initilize");
        while (1)
            ;
    }
    ret = lvgl_init();
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "LVGL Display failed to initialize");
        while (1)
            ;
    }

    // Create a slider
    lv_obj_t *slider = lv_example_slider_1();

    // Handle LVGL tasks
    int32_t count = 1;
    int32_t increment = 1;
    lv_slider_set_value(slider, 1, LV_ANIM_ON);
    lv_refr_now();

    while (1)
    {
        vTaskDelay(pdMS_TO_TICKS(10));
        if (count == 100 || count == 0) {
            increment = -increment;
        }
        count += increment;
        lv_slider_set_value(slider, count, LV_ANIM_ON);
        lv_refr_now(NULL);

        lv_task_handler();
    }
}

lv_obj_t *lv_example_slider_1(void)
{
    /*Create a screen*/
    lv_obj_t *screen = lv_screen_active();
    lv_obj_set_style_bg_color(screen, lv_color_black(), 0);

    /*Create a slider in the center of the display*/
    lv_obj_t *slider = lv_slider_create(lv_screen_active());
    lv_obj_center(slider);
    lv_obj_set_height(slider, 30);

    /* Change indicator (filled part) color */
    lv_obj_set_style_bg_color(slider, lv_palette_main(LV_PALETTE_GREEN), LV_PART_INDICATOR);
    lv_obj_set_style_bg_color(slider, lv_palette_main(LV_PALETTE_LIME), LV_PART_KNOB);

    return slider;

}


// we are going to have the tick run on core 0 This is so the interrupt is
// also executed on core 0. I move the lvgl task to core 1 and bumped up
// the priority to 22 so it will have the highest priority.

void tick_task(void *args)
{
    ret = lvgl_tick_init();
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "Timer failed to initialize");
        while (1)
            ;
    }
    vTaskDelete(NULL);
}


void app_main()
{

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    TaskHandle_t taskHandle = NULL;
    TaskHandle_t tickHandle = NULL;

    lv_init();

    xTaskCreatePinnedToCore(lvgl_task, "LVGL task", 8192, NULL, 22, &taskHandle, 1); // stack, params, prio, handle, core
    xTaskCreatePinnedToCore(tick_task, "LVGL tick task", 4096, NULL, 4, &tickHandle, 0); // stack, params, prio, handle, core

    ESP_LOGI("CoreCheck", "Running on core %d", xPortGetCoreID());
}

Results:

Buffer at /10:
PXL_20250317_1500188812

Buffer at /8:
PXL_20250317_1507249602

it looks a tiny bit better don’t ya think?

Give this a try. I changed the render mode to full and removed the rgb565 byte swap. I know the colors are goofed just trying to see what is causing the issue.

See if this does any better

#include "esp_log.h"

#include <stdio.h>
#include <unistd.h>
#include <sys/lock.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.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_lcd_panel_st7789.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_check.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lvgl.h"

static const char *TAG = "MyDisplay";

/* LCD size */
#define DISP_HOR_RES 320
#define DISP_VER_RES 172

/* LCD settings */
#define DISP_DRAW_BUFF_HEIGHT 50

/* LCD pins */
#define DISP_SPI_NUM SPI2_HOST
#define DISP_GPIO_SCLK GPIO_NUM_12
#define DISP_GPIO_MOSI GPIO_NUM_11
#define DISP_GPIO_RST -1 // Not connected
#define DISP_GPIO_DC GPIO_NUM_47
#define DISP_GPIO_CS GPIO_NUM_45
#define DISP_GPIO_BL GPIO_NUM_48

#define BUFFER_SIZE (DISP_HOR_RES * DISP_VER_RES * sizeof(uint16_t))

static esp_lcd_panel_handle_t panel_handle = NULL;
static esp_lcd_panel_io_handle_t io_handle = NULL;
static lv_display_t *display = NULL;

static void *buf1 = NULL;
static void *buf2 = NULL;

/* Function prototypes */

lv_obj_t *lv_example_slider_1(void);


// This gets called when the DMA transfer of the buffer data has completed
static bool notify_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
    lv_display_t *disp = (lv_display_t *)user_ctx;
    lv_display_flush_ready(disp); // I have tried to change this to the global display variable, no change
    return false;
}

static void flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
    int x1 = area->x1;
    int x2 = area->x2;
    int y1 = area->y1 + 34; // Offset image to compensate for smaller 172px resolution
    int y2 = area->y2 + 34; // Offset image to compensate for smaller 172px resolution

    // uncomment the following line if the colors are wrong
    // lv_draw_sw_rgb565_swap(px_map, (x2 + 1 - x1) * (y2 + 1 - y1)); // I have tried with and without this

    esp_lcd_panel_draw_bitmap((esp_lcd_panel_handle_t)lv_display_get_user_data(disp), x1, y1, x2 + 1, y2 + 1, px_map);
}

static esp_err_t lvgl_init(void)
{
    display = lv_display_create(DISP_HOR_RES, DISP_VER_RES);

    buf1 = heap_caps_calloc(1, BUFFER_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
    buf2 = heap_caps_calloc(1, BUFFER_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);

    lv_display_set_buffers(display, buf1, buf2, BUFFER_SIZE, LV_DISPLAY_RENDER_MODE_FULL);

    lv_display_set_user_data(display, panel_handle);
    lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);

    lv_display_set_flush_cb(display, flush_cb);

    const esp_lcd_panel_io_callbacks_t cbs = {
        .on_color_trans_done = notify_flush_ready,
    };
    /* Register done callback */
    ESP_RETURN_ON_ERROR(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, display), TAG, "esp_lcd_panel_io_register_event_callbacks error"); // I have tried to use
    ESP_RETURN_ON_ERROR(esp_lcd_panel_init(panel_handle), TAG, "esp_lcd_panel_init error");

    // Hardware rotate 90°
    uint8_t madctl = 0x60; // Rotate reg 0x00 0x60 0xC0 0xA0
    esp_lcd_panel_io_tx_param(io_handle, 0x36, &madctl, 1);

    esp_lcd_panel_io_tx_param(io_handle, 0x21, NULL, 0); // Inverted color fix (to get normal colors)

    return ESP_OK;
}

static esp_err_t display_init(void)
{
    // LCD backlight and DC
    gpio_config_t gpio_cfg = {
        .pin_bit_mask = (1ULL << DISP_GPIO_BL) | (1ULL << DISP_GPIO_DC),
        .mode = GPIO_MODE_OUTPUT,
    };
    ESP_ERROR_CHECK(gpio_config(&gpio_cfg));

    gpio_set_level(DISP_GPIO_BL, 1); // Turn on backlight
    gpio_set_level(DISP_GPIO_DC, 1); // Default to data mode

    // LCD initialization
    ESP_LOGD(TAG, "Initialize SPI bus");
    spi_bus_config_t buscfg = {};
    buscfg.sclk_io_num = DISP_GPIO_SCLK;
    buscfg.mosi_io_num = DISP_GPIO_MOSI;
    buscfg.miso_io_num = GPIO_NUM_NC;
    buscfg.quadwp_io_num = GPIO_NUM_NC;
    buscfg.quadhd_io_num = GPIO_NUM_NC;
    buscfg.max_transfer_sz = BUFFER_SIZE; // DISP_HOR_RES * DISP_DRAW_BUFF_HEIGHT * sizeof(uint16_t);
    ESP_RETURN_ON_ERROR(spi_bus_initialize(DISP_SPI_NUM, &buscfg, SPI_DMA_CH_AUTO), TAG, "SPI init failed");

    esp_lcd_panel_io_spi_config_t io_config = {
        .dc_gpio_num = DISP_GPIO_DC,
        .cs_gpio_num = DISP_GPIO_CS,
        .pclk_hz = 80 * 1000 * 1000,
        .lcd_cmd_bits = 8,
        .lcd_param_bits = 8,
        .spi_mode = 0,
        .trans_queue_depth = 10,
    };
    ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_spi(DISP_SPI_NUM, &io_config, &io_handle), TAG, "SPI init failed");

    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = DISP_GPIO_RST,
        .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
        .bits_per_pixel = 16,
        .flags = {.reset_active_high = 0}};

    ESP_RETURN_ON_ERROR(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle), TAG, "Display init failed");

    // Reset the display
    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));

    // Initialize LCD panel
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));

    // Vendor specific settings

    // Turn on the screen
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

    return ESP_OK;
}

static void lvgl_tick_increment(void *arg)
{
    // Tell LVGL how many milliseconds have elapsed
    lv_tick_inc(2);
}

static esp_err_t lvgl_tick_init(void)
{
    esp_timer_handle_t tick_timer;

    // Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
    const esp_timer_create_args_t lvgl_tick_timer_args = {
        .callback = &lvgl_tick_increment,
        .name = "LVGL tick",
    };
    ESP_RETURN_ON_ERROR(esp_timer_create(&lvgl_tick_timer_args, &tick_timer), TAG, "Creating LVGL timer filed!");
    return esp_timer_start_periodic(tick_timer, 2 * 1000); // 2 ms
}

static void lvgl_task(void *arg)
{

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    esp_log_level_set("lcd_panel", ESP_LOG_VERBOSE);
    esp_log_level_set("lcd_panel.st7789", ESP_LOG_VERBOSE);
    esp_log_level_set(TAG, ESP_LOG_VERBOSE);

    esp_err_t ret = display_init();

    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "ST7789 failed to initilize");
        while (1)
            ;
    }
    ret = lvgl_init();
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "LVGL Display failed to initialize");
        while (1)
            ;
    }

    // Create a slider
    lv_obj_t *slider = lv_example_slider_1();

    // Handle LVGL tasks
    int32_t count = 1;
    int32_t increment = 1;
    lv_slider_set_value(slider, 1, LV_ANIM_ON);
    lv_refr_now();

    while (1)
    {
        vTaskDelay(pdMS_TO_TICKS(10));
        if (count == 100 || count == 0) {
            increment = -increment;
        }
        count += increment;
        lv_slider_set_value(slider, count, LV_ANIM_ON);
        lv_refr_now();

        lv_task_handler();
    }
}

lv_obj_t *lv_example_slider_1(void)
{
    /*Create a screen*/
    lv_obj_t *screen = lv_screen_active();
    lv_obj_set_style_bg_color(screen, lv_color_black(), 0);

    /*Create a slider in the center of the display*/
    lv_obj_t *slider = lv_slider_create(lv_screen_active());
    lv_obj_center(slider);
    lv_obj_set_height(slider, 30);

    /* Change indicator (filled part) color */
    lv_obj_set_style_bg_color(slider, lv_palette_main(LV_PALETTE_GREEN), LV_PART_INDICATOR);
    lv_obj_set_style_bg_color(slider, lv_palette_main(LV_PALETTE_LIME), LV_PART_KNOB);

    return slider;

}


// we are going to have the tick run on core 0 This is so the interrupt is
// also executed on core 0. I move the lvgl task to core 1 and bumped up
// the priority to 22 so it will have the highest priority.

void tick_task(void *args)
{
    ret = lvgl_tick_init();
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "Timer failed to initialize");
        while (1)
            ;
    }
    vTaskDelete(NULL);
}


void app_main()
{

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    TaskHandle_t taskHandle = NULL;
    TaskHandle_t tickHandle = NULL;

    lv_init();

    xTaskCreatePinnedToCore(lvgl_task, "LVGL task", 8192, NULL, 22, &taskHandle, 1); // stack, params, prio, handle, core
    xTaskCreatePinnedToCore(tick_task, "LVGL tick task", 4096, NULL, 4, &tickHandle, 0); // stack, params, prio, handle, core

    ESP_LOGI("CoreCheck", "Running on core %d", xPortGetCoreID());
}

I can tell you this with 100% certainty. Your CPU use is a hell of a lot lower than it was before.

before you were hitting 100% CPU use and now the most it goes up to is 2%. That’s a sizeable difference don’t ya think? There is something in the LVGL animation code that really eats up CPU time. IDK what it is but I am going to open an issue on LVGL’s GitHub repo for it.

And that is rather odd that it is taking longer to render to the smaller buffer.

@kisvegabor You might want to check this out. A few posts back there is a video that has LVGL running and moving the slider via an animation and it’s gobbling up to 100% of the CPU where as the above examples the value is being set at each loop of the task. There must be something off in the animation code for it to eat up that much processor time.

If you compare the 2 latest videos the only difference between the 2 is the buffer size, the top one is smaller than the bottom one yet the rendering times for the smaller buffer are higher… any reason why that would occur?