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

Here are the functions that are available that you can perform on the “panel” which is the hardware display.

esp_err_t esp_lcd_panel_reset(esp_lcd_panel_handle_t panel);

esp_err_t esp_lcd_panel_init(esp_lcd_panel_handle_t panel);

esp_err_t esp_lcd_panel_del(esp_lcd_panel_handle_t panel);

esp_err_t esp_lcd_panel_draw_bitmap(esp_lcd_panel_handle_t panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);

esp_err_t esp_lcd_panel_mirror(esp_lcd_panel_handle_t panel, bool mirror_x, bool mirror_y);

esp_err_t esp_lcd_panel_swap_xy(esp_lcd_panel_handle_t panel, bool swap_axes);

esp_err_t esp_lcd_panel_set_gap(esp_lcd_panel_handle_t panel, int x_gap, int y_gap);

esp_err_t esp_lcd_panel_invert_color(esp_lcd_panel_handle_t panel, bool invert_color_data);

esp_err_t esp_lcd_panel_disp_on_off(esp_lcd_panel_handle_t panel, bool on_off);

esp_err_t esp_lcd_panel_disp_sleep(esp_lcd_panel_handle_t panel, bool sleep);

Thank you.

Hardware rotation DO work, I just need to do it after displayinit. I think, that doing it this way, no software manipulation is needed, and must be faster.

Then this is the finished code. I will move all my display handling into the lvgl_task, and avoid sephamaphoes, as you pointed out.

I really apreciate your help, I will mark this as the solution. If you note any improvements, please let me know.

And for the sake of anyone else reading this, here is the working 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 // 320
#define DISP_VER_RES   240 // 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 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);  // 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;
    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)); // 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);

    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) | (1ULL << DISP_GPIO_RST),
        .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
    gpio_set_level(DISP_GPIO_RST, 1); // Default to high

    // 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(SPI3_HOST, &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(SPI3_HOST, &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));

    // 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 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(1000/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);
    }
}

glad you have it up and running. Hopefully I was able to give you information that will provide better performance and more stability. If you have any additional questions ask away.

Hi Kevin.

The solution runs stable. And load on the core seems to have fallen from around 25% to 11%.

I have implemented the touch. But maybe you can have a look, in order to see, if it makes sense?? And is efficient.

First I init I2c, and then this:

esp_err_t MyTouch::_init(lv_display_t* disp) {

    esp_log_level_set(TAG, ESP_LOG_VERBOSE);

    // Initialize touch HW
    touch_gesture_queue = xQueueCreate(10, sizeof(uint8_t));
    touch_queue = xQueueCreate(100, sizeof(touch_event_t));

    esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_CST816S_CONFIG();

    esp_lcd_touch_config_t tp_cfg = { };
        tp_cfg.x_max = DISP_VER_RES; // rotated
        tp_cfg.y_max = DISP_HOR_RES; // rotated
        tp_cfg.rst_gpio_num = TOUCH_GPIO_RST; // GPIO_NUM_39
        tp_cfg.int_gpio_num = TOUCH_GPIO_INT;
//        tp_cfg.interrupt_callback = touch_callback;
        tp_cfg.levels = {
            .reset = 0,
            .interrupt = 0,
        };
        tp_cfg.flags = {
            .swap_xy = 0,
            .mirror_x = 0,
            .mirror_y = 0,
        };

    esp_lcd_panel_io_handle_t tp_io_handle = NULL;

    ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_i2c_v1((esp_lcd_i2c_bus_handle_t)TOUCH_I2C_NUM, &tp_io_config, &tp_io_handle), TAG, "");

    esp_lcd_touch_new_i2c_cst816s(tp_io_handle, &tp_cfg, &tp_handle);

    // Add touch input (for selected screen)
    const touch_cfg_t touch_cfg = {
        .disp = disp,
        .handle = tp_handle,
    };

    esp_err_t ret = ESP_OK;
    lv_indev_t *indev = NULL;
    assert(touch_cfg.disp != NULL);
    assert(touch_cfg.handle != NULL);

    /* Touch context */
    touch_ctx_t* touch_ctx = (touch_ctx_t*)malloc(sizeof(touch_ctx_t));
    if (touch_ctx == NULL) {
        ESP_LOGE(TAG, "Not enough memory for touch context allocation!");
        return 0;
    }
    touch_ctx->handle = touch_cfg.handle;

    if (touch_ctx->handle->config.int_gpio_num != GPIO_NUM_NC) {
        /* Register touch interrupt callback */
        ret = esp_lcd_touch_register_interrupt_callback_with_data(touch_ctx->handle, touch_interrupt_cb, touch_ctx);
        ESP_GOTO_ON_ERROR(ret, err, TAG, "Error in register touch interrupt.");
    }

    /* Register a touchpad input device */
    indev = lv_indev_create();
    lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
    /* Event mode can be set only, when touch interrupt enabled */
    if (touch_ctx->handle->config.int_gpio_num != GPIO_NUM_NC) {
        lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT);
    }
    lv_indev_set_read_cb(indev, touch_read_cb);
    lv_indev_set_disp(indev, touch_cfg.disp);
    lv_indev_set_user_data(indev, touch_ctx);
    touch_ctx->indev = indev;

err:
    if (ret != ESP_OK) {
        if (touch_ctx) {
            free(touch_ctx);
        }
        printf("Fejl after init 1\n");
    }
    else {
        printf("OK after init 1\n");
    }

    return 0;
}

I wait for interupt from CST816

void IRAM_ATTR MyTouch::touch_interrupt_cb(esp_lcd_touch_handle_t tp)
{
    touch_ctx_t *touch_ctx = (touch_ctx_t *) tp->config.user_data;

//    /* Wake LVGL task, if needed */
    task_wake(LVGL_PORT_EVENT_TOUCH, touch_ctx->indev);
}

esp_err_t MyTouch::task_wake(lvgl_port_event_type_t event, void *param)
{
    if (!touch_queue) {
        return ESP_ERR_INVALID_STATE;
    }

    touch_event_t ev = {
        .type = event,
        .param = param,
    };

    if (xPortInIsrContext() == pdTRUE) {
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        xQueueSendFromISR(touch_queue, &ev, &xHigherPriorityTaskWoken);
        if (xHigherPriorityTaskWoken) {
            portYIELD_FROM_ISR( );
        }
    } else {
        xQueueSend(touch_queue, &ev, 0);
    }

    return ESP_OK;
}

And then I poll like this, from same thread where I call lv_task_handler();, in order to avoid semephores.

bool MyTouch::poll_Touch() {
    
    if (xQueueReceive(touch_queue, &event, 0)) {

        printf("touch poll\n");

        if (event.type == LVGL_PORT_EVENT_TOUCH) {

            if (event.param != NULL) {
                lv_indev_read((lv_indev_t*)event.param);
            } else {
                indev = lv_indev_get_next(NULL);
                while (indev != NULL) {
                    lv_indev_read(indev);
                    indev = lv_indev_get_next(indev);
                }
            }

        }
        return true;
    }
    return false;
}

you can let LVGL handle the actual polling. create the indev driver and then register the callback function to read the input from the touch panel. Easy peazy.

Not so easy… using lv_indev_set_read_cb(indev, touch_read_cb); ?
I tried that aproach, but my read was not triggered. Do you have an example :slight_smile:

Ok, that was actually quiet easy, and close to my first attempt. Its working.

I think I know, why I did not get any callback in the first place. Its the nature of CST816 touch driver, if its not adressed the right way, it gets into sleep, and only wakes op upon touch.

Thank you again.

A lot easier that way and far less code on your side of things.

Yes. And I can confirm, that my original problem, with geastures slow perceiving, is also solved. Now I can swipe, and it gets detected correctly :slight_smile:
Thank you again.

Do you have any idea, what causes this??

It happens sometimes, processor dont restart, but once its happened, display is unresponsive, and it repeats every few seconds

E (1242187) task_wdt: Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:
E (1242187) task_wdt:  - IDLE1 (CPU 1)
E (1242187) task_wdt: Tasks currently running:
E (1242187) task_wdt: CPU 0: IDLE0
E (1242187) task_wdt: CPU 1: lvglDraw
E (1242187) task_wdt: Print CPU 1 backtrace


Backtrace: 0x403776FE:0x3FC9A260 0x4037A581:0x3FC9A280 0x42011F81:0x3FCBFD70 0x4201262B:0x3FCBFDD0 0x42031131:0x3FCBFEF0 0x4202E5D6:0x3FCBFF10 0x4202E62A:0x3FCBFF30 0x4202E6B4:0x3FCBFF50 0x4201AAE3:0x3FCBFF70 0x4037EE8D:0x3FCBFF90
  #0  0x403776FE in esp_crosscore_isr at C:\Users\Frank\.platformio\packages\framework-espidf\components\esp_system/crosscore_int.c:73
  #1  0x3FC9A260 in _xt_exception_table at ??:?
  #2  0x4037A581 in _xt_lowint1 at C:\Users\Frank\.platformio\packages\framework-espidf\components\xtensa/xtensa_vectors.S:1240
  #3  0x3FC9A280 in _xt_exception_table at C:\Users\Frank\.platformio\packages\framework-espidf\components\freertos\FreeRTOS-Kernel\portable\xtensa/port.c:67
  #4  0x42011F81 in draw_letter at components\lvgl\src\draw/lv_draw_label.c:538 (discriminator 1)
  #5  0x4201262B in lv_draw_label_iterate_characters at components\lvgl\src\draw/lv_draw_label.c:434
  #6  0x42031131 in lv_draw_sw_label at components\lvgl\src\draw\sw/lv_draw_sw_letter.c:59
  #7  0x4202E5D6 in execute_drawing at components\lvgl\src\draw\sw/lv_draw_sw.c:268
  #8  0x4202E62A in execute_drawing_unit at components\lvgl\src\draw\sw/lv_draw_sw.c:132
  #9  0x4202E6B4 in render_thread_cb at components\lvgl\src\draw\sw/lv_draw_sw.c:243
  #10 0x4201AAE3 in prvRunThread at components\lvgl\src\osal/lv_freertos.c:433

you are going to want to open an issue on LVGL’s Github repo for this. There is something getting stuck and it looks like it’s in LVGL’s code.

OK, I will do that. Its similar to the fault I had before, just much more rare. I wonder, if the various memory settings in menuconfig can cause this, if they are not set optimal? Or one of the various buffers is too small.

it shouldn’t cause that issue.

I just noticed, that lvglDraw is running on core 1. LVGL main task is running on core 0. Is that an issue? Can/should I pin lvglDraw to core 0?

I don’t know what your entire setup code looks like. specifically the code in your lv_conf.h file. If you have LV_USE_OS set then yes it would be normal to see LVGL using 2 different cores. If it is set to NONE then there is a problem somewhere because4 LVGL should not be using 2 cores.

I dont have a lv_conf.h file, I use the built in menuconfig thing. Operating system I have actually set to FREERTOS. I will try and set this to none. Basically I use the default settings.

Report the error to the LVGL GitHub repo. It’s important that you do that. Let them know that you have the OS set to FreeRTOS and it crashed on ya. Tell them the model of ESP32 you are using as well. They cannot fix it if they don’t know about it.

@kisvegabor

There is an issue with LVGL with USE_OS set to FreeRTOS. couple posts back is the error and back trace. You should take a look at it and see what is going on.

OK, will do that. I have now set to none, and testing with that. I dont need LVGL to run at multiple cores. I will check, that all my settings in menuconfig are same defaults as in lv_conf.h. I know, that I have tried tuning the memorysettings in there, but i should have set all back to default (except OS, that I aparently forgot).