Gestures are slow perceiving, only detecting one of 5-10 tries

You have a lot of good points… The main reason for using platformio, is that I have used it for many, many years, with a lot of different projects and processors, so I am very familiar with it. I like programming in C++, however C is also no problem.

I started many years ago with the Arduino framework in Platformio, due to the huge amount of libraries for sensors, but had way too many issues with it. After switching to espidf framework (in Platformio) life became a lot easier, and now I have written most of the sensor libraries that I use from scratch.

Components I use, I have downloaded from Espressif. ST7789, CST816s and then esp_lvgl_port. It worked out of the box - well - except for the crashes that it still going on, even after ajusting the buffers. And strangely, it can run for hours, and then it crashes - and its not a heap runaway, cause I monitor that.

So I will follow your advice, and start with a clean project, and getting LVGL up running as the first goal - without esp_lvgl_port. If you can help me with a rough code to get me started on the right foot, I would really apreciate it.

I can also understand, that you strontly suggest using the espressif solution in stead of Platformio. Maybe I should try downloading it, and take a look :slight_smile: Changing horse is always with some considerations - however I am not blind, if there is a better solution, even though there will be a learning experience.

Designing the UI, I have used Squareline Studio. Whats your opinion on that?

First good point… You have trouble, but CST816 isnt part LVG or any other hw driver parts. Too your idea use latest versions is little out of reality. I work some time with ESP and IDF or Arduino or any other is under continuos development , then any version you choice need debug and handle issues.

And your gesture issue, maybe good start point is turn on log and see how events is generated when gesture works and not…

Hi Marian.

You are right, ST7789, CST816 and esp_lvgl_port components are all from the Espressif component registry.

This is my first project with a real, high resolution (but small), color display. And its my first project using LVGL.

Is there some original LVGL drivers, or a better way? Writing those from scratch, I would like to avoid, if possible, eventhough I have already modified the CST816 driver, reading the swipes directly from the chip, before passing the data to LVGL. But thats a silly (but working) workaround, if LVGL is able to handle swipes responsively.

I am pretty sure your issue is due to memory fragmentation. That can happen easily on an ESP with tight memory constraints. It takes a lot of careful planning to not have that occur. the first and foremost is a reduction in the amount of memory being used. wrapper code is nice to use to make things easy but it does come at a cost. the LVGL component you are using uses quite a but of additional memory for it to run than just using vanilla LVGL. This is the reason why I am suggesting to use the component for LVGL that doesn’t have the wrapper code. You have more control that way.

Thank you kdschlosser. I will try this weekend, to set up a clean enviroment - in Platformio for now I think - and install LVGL component. What about display and touch drivers, what approach would you take on that? If you happen to have some rough code example, i would be happy :slight_smile:

Hi kdschlosser.

I have started from scratch, and looking into a hello world thing.

I have installed LVGL as a component in Platformio, so its clean LVGL. I have selected the ST7789 display in the menuconfig, I have made a tick task to update the tick every 2 ms, and now I am looking into the configuration. Do you have any advise for me :slight_smile:

br. Frank.

change the tick task to 10 milliseconds. there is no need to update LVGL at 2millisecond intervals as the default timeout for the refresh is 33 milliseconds so it just ends up consuming resources having to switch task contexts that often. If you are running on a dual core ESP32 I suggest pinning the tick task to one core and have the application run on the other core.

Just remember that LVGL is NOT thread safe so everything that is done to LVGL needs to be done in the same task, the only thing you can interact with in LVGL from a different task is setting the tick time.

give me 24 hours and I will hammer out an example on how to do it without using platform IO. just using the ESP-IDF Build system.

I need to know some things in order to do that.

  • Display IC
  • ESP32 Model
  • Amount of SPIRAM
  • Amount of flash
  • Type of SPIRAM (quad or octal)
  • Type of flash (quad or octal)
  • Touch IC
  • Type of connection to the display (SPI, RGB, I8080)
  • Type of connection to the touch (SPI, I2C)

Super :slight_smile:

  • ST7789 display IC
  • ESP32-S3 with 16mb flash 8mb quad spiram
  • CST816s touch IC I2C
  • 240x280 display SPI

This is what I have done so far. No craches, but no test on the display…

#include "lvgl.h"
#include "drivers/display/st7789/lv_st7789.h"

#include "esp_log.h"
#include "esp_timer.h"
#include <stdio.h>
#include <string.h>
#include "nvs_flash.h"

#include "esp_check.h"
#include "driver/gpio.h"

#include <freertos/FreeRTOS.h>
#include "driver/spi_master.h"

static const char *TAG = "MyDisplay";

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

/* LCD settings */
#define DISP_DRAW_BUFF_HEIGHT 50

/* LCD pins */
#define DISP_SPI_NUM         SPI3_HOST
#define DISP_GPIO_SCLK       GPIO_NUM_41 // GPIO_NUM_6
#define DISP_GPIO_MOSI       GPIO_NUM_40 // GPIO_NUM_7
#define DISP_GPIO_RST        GPIO_NUM_39 // GPIO_NUM_8
#define DISP_GPIO_DC         GPIO_NUM_44 // GPIO_NUM_4
#define DISP_GPIO_CS         GPIO_NUM_42 // GPIO_NUM_5
#define DISP_GPIO_BL         GPIO_NUM_1  // GPIO_NUM_15

/* Touch settings */
#define EXAMPLE_TOUCH_I2C_NUM       I2C_NUM_1
#define EXAMPLE_TOUCH_I2C_CLK_HZ    400000     // 400000

/* LCD touch pins */
#define TOUCH_I2C_SCL       GPIO_NUM_21  // GPIO_NUM_21   9
#define TOUCH_I2C_SDA       GPIO_NUM_14  // GPIO_NUM_14  10
#define TOUCH_GPIO_INT      GPIO_NUM_38  // GPIO_NUM_38
#define TOUCH_GPIO_RST      GPIO_NUM_11  // GPIO_NUM_11 // dummy

// SPI device handle
spi_device_handle_t spi;

esp_err_t lvgl_init()
{
    /* LCD backlight */
    gpio_config_t bk_gpio_config = { };
        bk_gpio_config.mode = GPIO_MODE_OUTPUT;
        bk_gpio_config.pin_bit_mask = 1ULL << DISP_GPIO_BL;
    ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));

    gpio_set_level(DISP_GPIO_BL, 1);

    /* 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 = 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");

    // SPI device configuration
    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 10 * 1000 * 1000, // Clock out at 10 MHz
        .mode = 0,                          // SPI mode 0
        .spics_io_num = DISP_GPIO_CS,       // CS pin
        .queue_size = 7,
        .flags = SPI_DEVICE_HALFDUPLEX
    };
    ESP_ERROR_CHECK(spi_bus_add_device(DISP_SPI_NUM, &devcfg, &spi));

    return ESP_OK;
}

void display_flush(lv_display_t *disp, const lv_area_t *area, lv_color_t *color_p) {

    uint32_t size = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1);

    // Set the address window manually (adjust based on your ST7789 controller's requirements)
    // Example: sending a command to set column address range
    uint8_t cmd_column[] = {0x2A, 0x00, area->x1, 0x00, area->x2};
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length = 8 * sizeof(cmd_column); // 8 bits per byte
    t.tx_buffer = cmd_column;
    t.user = (void *)0; // D/C line low for command
    ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t));

    // Example: sending a command to set row address range
    uint8_t cmd_row[] = {0x2B, 0x00, area->y1, 0x00, area->y2};
    memset(&t, 0, sizeof(t));
    t.length = 8 * sizeof(cmd_row); // 8 bits per byte
    t.tx_buffer = cmd_row;
    t.user = (void *)0; // D/C line low for command
    ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t));

    // Example: sending a command to write RAM
    uint8_t cmd_write[] = {0x2C};
    memset(&t, 0, sizeof(t));
    t.length = 8 * sizeof(cmd_write); // 8 bits per byte
    t.tx_buffer = cmd_write;
    t.user = (void *)0; // D/C line low for command
    ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t));

    // Transmit data via SPI
    t.length = size * 8; // 8 bits per pixel (16-bit color depth)
    t.tx_buffer = (uint8_t *)color_p;
    t.user = (void *)1; // D/C line high for data
    ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t));

    // Tell LVGL that flushing is done
    lv_disp_flush_ready(disp);

    printf("Flushing\n");
}

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

    printf("#");
}

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

    /* LVGL init */
    lv_init();
    /* LVGL driver init */
    lvgl_init();

// Display resolution
    uint32_t hor_res = 240;
    uint32_t ver_res = 280;

    // Initialize display buffer
    static lv_color_t buf1[240 * 10]; // LV_HOR_RES_MAX
    static lv_color_t buf2[240 * 10]; // LV_HOR_RES_MAX

    // Initialize display driver
    lv_disp_t *disp = lv_display_create(hor_res, ver_res);
    lv_display_set_flush_cb(disp, &display_flush);
    lv_display_set_buffers(disp, &buf1, &buf2, sizeof(buf1), LV_DISP_RENDER_MODE_PARTIAL);

    /* Tick init */
    if (ESP_OK == lvgl_tick_init()) printf("Timer tick init OK");

    // Example: Create a simple label
    lv_obj_t *label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "Hello, LVGL!");
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

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

        printf("*");
    }
}

void app_main() {

    vTaskDelay(5000/portTICK_PERIOD_MS);

    TaskHandle_t taskHandle = NULL;
    BaseType_t res = xTaskCreatePinnedToCore(lvgl_task, "LVGL task", 8192, NULL, 4, &taskHandle, 0); // stack, params, prio, handle, core

    while(true) {

        printf("Running\n");

        vTaskDelay(100/portTICK_PERIOD_MS);
    }

}

are you sure that your ESP has quad SPIRAM?? I am not sif if one is made in quad SPI. I thought the 16mb flash and 8mb PSRAM is octal for both.

also just out of question have you seen the MicroPython Binding I have been working on?

It’s very easy to compile and get up and running. Both the touch and the display are supported. Single command to compile it with all the needed drivers built onto it and very minimum code needed to get the display up and running.

No, you are right. its octal. Its this model: ESP32S3WRM1N16R8

Hi again. I just glanced at your binding. That looks really impressive. Maybe thats just what I need. I my case, the challenge should not be the display, but the other code. The display should just work and be stable :slight_smile:

But I do like to understand, the basics of how it works…

LVGL have a built in driver for ST7789, as I see it. But I do not think, that the flush function, that i have found, is correct. The documentation for the latest LVGL looks different.

I have also looked into the esp_lvgl_port code, but it seems like they just wrap around a generic mipi driver. That can not be the best solution, if there is an actual dedicated driver model.

In the code example I posted, I can see the tasks runing, and flushing occurs, but no text on the display. I suspect the flush to be incorrect.

Tadaa :slight_smile: Dont know how optimal this code is, but I get an output

#include "lvgl.h"
#include "drivers/display/st7789/lv_st7789.h"

#include "esp_log.h"
#include "esp_timer.h"
#include <stdio.h>
#include <string.h>
#include "nvs_flash.h"

#include "esp_check.h"
#include "driver/gpio.h"

#include <freertos/FreeRTOS.h>
#include "driver/spi_master.h"

static const char *TAG = "MyDisplay";

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

/* LCD settings */
#define DISP_DRAW_BUFF_HEIGHT 50

/* LCD pins */
#define DISP_SPI_NUM         SPI3_HOST
#define DISP_GPIO_SCLK       GPIO_NUM_41 // GPIO_NUM_6
#define DISP_GPIO_MOSI       GPIO_NUM_40 // GPIO_NUM_7
#define DISP_GPIO_RST        GPIO_NUM_39 // GPIO_NUM_8
#define DISP_GPIO_DC         GPIO_NUM_44 // GPIO_NUM_4
#define DISP_GPIO_CS         GPIO_NUM_42 // GPIO_NUM_5
#define DISP_GPIO_BL         GPIO_NUM_1  // GPIO_NUM_15

/* Touch settings */
#define DISP_TOUCH_I2C_NUM       I2C_NUM_1
#define DISP_TOUCH_I2C_CLK_HZ    400000     // 400000

/* LCD touch pins */
#define TOUCH_I2C_SCL       GPIO_NUM_21  // GPIO_NUM_21   9
#define TOUCH_I2C_SDA       GPIO_NUM_14  // GPIO_NUM_14  10
#define TOUCH_GPIO_INT      GPIO_NUM_38  // GPIO_NUM_38
#define TOUCH_GPIO_RST      GPIO_NUM_11  // GPIO_NUM_11 // dummy

// SPI device handle
spi_device_handle_t spi;

esp_err_t lvgl_init()
{
    /* 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 = 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");

    // SPI device configuration
    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 10 * 1000 * 1000, // Clock out at 10 MHz
        .mode = 0,                          // SPI mode 0
        .spics_io_num = DISP_GPIO_CS,       // CS pin
        .queue_size = 7,
        .flags = SPI_DEVICE_HALFDUPLEX
    };
    ESP_ERROR_CHECK(spi_bus_add_device(DISP_SPI_NUM, &devcfg, &spi));

    return ESP_OK;
}

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

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
}

/* Send short command to the LCD. This function shall wait until the transaction finishes. */
int32_t my_lcd_send_cmd(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, const uint8_t *param, size_t param_size)
{
    // Send the command
    if (cmd && cmd_size > 0) {
        spi_transaction_t trans = {
            .length = cmd_size * 8, // Command size in bits
            .tx_buffer = cmd,
        };
        gpio_set_level(DISP_GPIO_DC, 0); // DC low for command
        spi_device_transmit(spi, &trans); // Transmit command

        printf("command cmd:");
        for (int i=0; i<cmd_size; i++) {
            printf(" %02X", cmd[i]);
        }
        printf("\n");
    }

    // Send parameters (if any)
    if (param && param_size > 0) {

        spi_transaction_t trans = {
            .length = param_size * 8, // Parameters size in bits
            .tx_buffer = param,
        };

        gpio_set_level(DISP_GPIO_DC, 1); // DC high for data
        spi_device_transmit(spi, &trans); // Transmit parameters

        printf("command data:");
        for (int i=0; i<param_size; i++) {
            printf(" %02X", param[i]);
        }
        printf("\n");
    }

    return 0;
}

int32_t my_lcd_send_color(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, uint8_t *param, size_t param_size)
{
    // Send the command (if required for color data)
    if (cmd && cmd_size > 0) {
        spi_transaction_t trans = {
            .length = cmd_size * 8, // Command size in bits
            .tx_buffer = cmd,
        };
        gpio_set_level(DISP_GPIO_DC, 0); // DC low for command
        spi_device_transmit(spi, &trans); // Transmit command
    }

    // Send color data
    if (param && param_size > 0) {

        uint16_t *pixel_data = (uint16_t *)param;
        size_t pixel_count = param_size / 2; // Each pixel is 2 bytes (RGB565)

        // Perform byte-swapping
        for (size_t i = 0; i < pixel_count; i++) {
            pixel_data[i] = (pixel_data[i] >> 8) | (pixel_data[i] << 8);
        }

        spi_transaction_t trans = {
            .length = param_size * 8, // Color data size in bits
            .tx_buffer = param,
        };
        gpio_set_level(DISP_GPIO_DC, 1); // DC high for data
        spi_device_transmit(spi, &trans); // Transmit color data
    }

    lv_disp_flush_ready(disp);
    return 0;
}

static void lvgl_task(void *arg) {

    /* LVGL init */
    lv_init();

    /* LVGL driver init */
    lvgl_init();

/* Tick init */
    if (ESP_OK == lvgl_tick_init()) printf("Timer tick init OK");

    static lv_color_t buf1[DISP_HOR_RES * 10]; // Buffer for 10 rows
    static lv_color_t buf2[DISP_HOR_RES * 10]; // Double buffering

    lv_disp_t *disp = lv_st7789_create(DISP_HOR_RES, DISP_VER_RES, 0, my_lcd_send_cmd, my_lcd_send_color);
    if (!disp) {
        ESP_LOGE(TAG, "Failed to initialize lv_st7789");
    }
    lv_display_set_buffers(disp, &buf1, &buf2, sizeof(buf1), LV_DISP_RENDER_MODE_PARTIAL);

    // Create a simple label
    lv_obj_t *label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "Hello, LVGL!");
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

    long curtime = esp_timer_get_time()/1000;
    int counter = 0;

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

        if (esp_timer_get_time()/1000 - curtime > 1000) {
            curtime = esp_timer_get_time()/1000;

            char textlabel[20];
            sprintf(textlabel, "Running: %u\n", counter);
            printf(textlabel);
            lv_label_set_text(label, textlabel);
            counter++;
        }
    }
}

void app_main() {

    vTaskDelay(5000/portTICK_PERIOD_MS);

    TaskHandle_t taskHandle = NULL;
    BaseType_t res = xTaskCreatePinnedToCore(lvgl_task, "LVGL task", 8192, NULL, 4, &taskHandle, 0); // stack, params, prio, handle, core

    while(true) {

        vTaskDelay(100/portTICK_PERIOD_MS);
    }

}

This code will work with the latest master branch of the ESP-IDF and also the latest master branch of LVGL…

This is pseudo code and has not been tested. There are bound to be some small errors and typos that will need to be corrected. It gives a general idea of how to use the internal mechanics of the ESP-IDF to achieve the best possible performance. This enables DMA transfers and double buffering which will greatly improve the speed of your application.

#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   240
#define DISP_VER_RES   320

/* LCD settings */
#define DISP_DRAW_BUFF_HEIGHT 50


/* LCD pins */
#define DISP_SPI_NUM         SPI3_HOST
#define DISP_GPIO_SCLK       GPIO_NUM_41 // GPIO_NUM_6
#define DISP_GPIO_MOSI       GPIO_NUM_40 // GPIO_NUM_7
#define DISP_GPIO_RST        GPIO_NUM_39 // GPIO_NUM_8
#define DISP_GPIO_DC         GPIO_NUM_44 // GPIO_NUM_4
#define DISP_GPIO_CS         GPIO_NUM_42 // GPIO_NUM_5
#define DISP_GPIO_BL         GPIO_NUM_1  // GPIO_NUM_15

/* Touch settings */
#define DISP_TOUCH_I2C_NUM       I2C_NUM_1
#define DISP_TOUCH_I2C_CLK_HZ    400000     // 400000

/* LCD touch pins */
#define TOUCH_I2C_SCL       GPIO_NUM_21  // GPIO_NUM_21   9
#define TOUCH_I2C_SDA       GPIO_NUM_14  // GPIO_NUM_14  10
#define TOUCH_GPIO_INT      GPIO_NUM_38  // GPIO_NUM_38
#define TOUCH_GPIO_RST      GPIO_NUM_11  // GPIO_NUM_11 // dummy


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


// 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);
    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;
    int y2 = area->y2;
    
    // uncomment the following line if the colors are wrong
    // lv_draw_sw_rgb565_swap(px_map, (x2 + 1 - x1) * (y2 + 1 - y1));
    
    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 void lvgl_tick_increment(void *arg)
{
    // Tell LVGL how many milliseconds have elapsed
    lv_tick_inc(2); // tjek
}


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 esp_err_t lvgl_init(void)
{
    lv_init();

    display = lv_display_create(DISP_HOR_RES, DISP_VER_RES);

    buf1 = (void *)spi_bus_dma_memory_alloc(DISP_SPI_NUM, BUFFER_SIZE, 0);
    buf2 = (void *)spi_bus_dma_memory_alloc(DISP_SPI_NUM, BUFFER_SIZE, 0);

    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_ERROR_CHECK(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, display));

    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),
        .mode = GPIO_MODE_OUTPUT,
    };
    ESP_ERROR_CHECK(gpio_config(&gpio_cfg));

    /* LCD initialization */
    ESP_LOGD(TAG, "Initialize SPI bus");
    spi_bus_config_t buscfg = {
        .sclk_io_num = DISP_GPIO_SCLK,
        .mosi_io_num = DISP_GPIO_MOSI,
        .miso_io_num = GPIO_NUM_NC,
        .quadwp_io_num = GPIO_NUM_NC,
        .quadhd_io_num = GPIO_NUM_NC,
        .max_transfer_sz = BUFFER_SIZE
    };
    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((esp_lcd_spi_bus_handle_t)DISP_SPI_NUM, &io_config, &io_handle), "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,
    };

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

    return ESP_OK;
}


void app_main() {

    vTaskDelay(5000/portTICK_PERIOD_MS);

    esp_err_t ret = display_init(void);

    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "ST7796 failed to initilize");
        while (1);
    }
    ret = lvgl_init(void);
    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 simple label
    lv_obj_t *label = lv_label_create(lv_screen_active());
    lv_label_set_text(label, "Hello, LVGL!");
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

    gpio_set_level(DISP_GPIO_BL, 1); // Turn on backlight

    long curtime = esp_timer_get_time()/1000;
    int counter = 0;

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

        if (esp_timer_get_time() / 1000 - curtime > 1000) {
            curtime = esp_timer_get_time() / 1000;

            char textlabel[20];
            sprintf(textlabel, "Running: %u\n", counter);
            printf(textlabel);
            lv_label_set_text(label, textlabel);
            counter++;
        }
    }
}

I have tried the code, and it compiles after som minor changes, but display is black.
I will have a closer look this afternoon.

I had to replace this

//    buf1 = (void *)spi_bus_dma_memory_alloc(DISP_SPI_NUM, BUFFER_SIZE, 0);
//    buf2 = (void *)spi_bus_dma_memory_alloc(DISP_SPI_NUM, BUFFER_SIZE, 0);

    buf1 = heap_caps_malloc(BUFFER_SIZE, MALLOC_CAP_DMA);
    buf2 = heap_caps_malloc(BUFFER_SIZE, MALLOC_CAP_DMA);

since spi_bus_dms_memory_alloc does not seem to be available in 5.3.0

static void flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)

is called OK, but

static bool notify_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)

is also called, and panel_io and user_ctx seems to be valid

This is really strange. I do get a valid flush area, and the px_map data also looks correctly. I think, that

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

somehow is not outputting the data correctly on the SPI bus… Even if I manipulate with the area and data, display is still blank. I have also tried to lower the SPI bus to 10MHz. Display totally blank…

And setting

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

nothing seems to fail. To mee it all seems legit, but somehow there is not output.