Changing label too fast?

Description

I’ve setup LVGL 9 in a small test project.
I’ve a label, that shows no text at the beginning.
Connected is a tact switch. When ever I press the switch, I want to toggle between two texts and styles.
This is working, but when I press the switch too fast, it either panics or results in this message [Error] (4.177, +4177) _lv_inv_area: Asserted at expression: !disp->rendering_in_progress (Invalidate area is not allowed during rendering.) lv_refr.c:257
I can’t recover from that state. A switch press won’t change anything now …

What MCU/Processor/Board and compiler are you using?

ESP32 S3 Dev Kit C with PlatformIO and Arduino Framework

What LVGL version are you using?

LVGL 9 and OneButton lib

What I tried

I tried adding the lv_mutex_t, but I think it’s not running on two cores currently.
The problem is still the same with mutex …

Code to reproduce

OneButton upperRight = OneButton(UPPER_RIGHT_PIN, false);

void pressed()
{
  nhd.change();
}

void setup()
{
  Serial.begin(9600);

  nhd.begin();
  upperRight.attachClick(pressed);
}

NHD.cpp

void NHD::begin()
{
    lv_init();

    display = lv_display_create(TFT_HOR_RES, TFT_VER_RES);
    TFT_eSPI *tft = new TFT_eSPI(TFT_HOR_RES, TFT_VER_RES);
    tft->begin();        /* TFT init */
    tft->setRotation(1); /* Landscape orientation, flipped */
    tft->initDMA();
    lv_display_set_driver_data(display, (void *)tft);
    lv_display_set_flush_cb(display, flush_cb);
    lv_display_set_buffers(display, (void *)draw_buf, NULL, sizeof(draw_buf), LV_DISPLAY_RENDER_MODE_PARTIAL);

    label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "");

    xTaskCreate(tick, "tick", 3000, NULL, 1, NULL);
    xTaskCreate(timer, "handle", 4000, NULL, 1, NULL);
}

uint i = 0;
void NHD::change()
{
    lv_mutex_lock(&lvgl_mutex);
    if (i++ == 0)
    {
        lv_label_set_text(label, "This is a extremely long text, that needs scrolling to be visible at all");
        lv_obj_remove_style_all(label);
        lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR);
        lv_obj_align(label, LV_ALIGN_LEFT_MID, 5, 0);
        lv_obj_set_width(label, 100);
    }
    else
    {
        lv_label_set_text_fmt(label, "%d%%", 70);
        lv_obj_align(label, LV_ALIGN_LEFT_MID, 5, 0);
        lv_obj_set_style_border_color(label, lv_palette_main(LV_PALETTE_BLUE), LV_PART_MAIN);
        lv_obj_set_style_border_width(label, 5, LV_PART_MAIN);
        i = 0;
    }
    lv_mutex_unlock(&lvgl_mutex);
}
1 Like

You completely dont show code relevant to problem, and used multithread is unsafe .

Sorry, I accidentally missed these two functions

void timer(void *ctx)
{
    while (1)
    {
        lv_timer_handler();
        vTaskDelay(20 / portTICK_PERIOD_MS);
    }
}

void tick(void *ctx)
{
    while (1)
    {
        lv_tick_inc(1);
        vTaskDelay(1 / portTICK_PERIOD_MS);
    }
}

You’re saying, that running lv_tick_inc and lv_timer_handler in main loop will solve the problem, as multithreading causing the problem?

No i say , that any system have docu and lvgl too. For example lv_tick_inc is designed for lvgl get real time. Use task for this get only aproximate unprecise time.
And lv_timer_handler is simpler place in loop in same thread where lv is updated.

sounds like you are trying to change the label from an ISR context instead of in the main loop. LVGL is not thread safe and not ISR safe. The only portion of LVGL that is safe to use in an ISR context is lv_tick_inc and that’s about it.

So keep it simple and get rid of the ISR and check the state of the pin during each loop of the program before you call lv_lask_handler set the label to what it needs to be set to when you check the pin state.

OneButton upperRight = OneButton(UPPER_RIGHT_PIN, false);

void pressed()
{
  nhd.change();
}

void setup()
{
  Serial.begin(9600);

  nhd.begin();
  upperRight.attachClick(pressed);   <<<< this looks like you are binding an ISR callback
}

I do not see any code in the main loop of the program to check the pin state for upperRight that pretty much means it’s in an ISR context. You cannot allocate ANY memory in an ISR context. if you do the program will crash.

Your program needs to be something along the lines of


#include "tft_espi.h"
#include "OneButton.h"
#include "lvgl.h"


lv_disp_t *display;
TFT_eSPI *tft;

void begin()
{
    lv_init();

    display = lv_display_create(TFT_HOR_RES, TFT_VER_RES);
    tft = new TFT_eSPI(TFT_HOR_RES, TFT_VER_RES);
    tft->begin();        /* TFT init */
    tft->setRotation(1); /* Landscape orientation, flipped */
    tft->initDMA();
    lv_display_set_driver_data(display, (void *)tft);
    lv_display_set_flush_cb(display, flush_cb);
    lv_display_set_buffers(display, (void *)draw_buf, NULL, sizeof(draw_buf), LV_DISPLAY_RENDER_MODE_PARTIAL);

    label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "");
}

uint i = 0;

void change()
{
    if (i++ == 0)
    {
        lv_label_set_text(label, "This is a extremely long text, that needs scrolling to be visible at all");
        lv_obj_remove_style_all(label);
        lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR);
        lv_obj_align(label, LV_ALIGN_LEFT_MID, 5, 0);
        lv_obj_set_width(label, 100);
    }
    else
    {
        lv_label_set_text_fmt(label, "%d%%", 70);
        lv_obj_align(label, LV_ALIGN_LEFT_MID, 5, 0);
        lv_obj_set_style_border_color(label, lv_palette_main(LV_PALETTE_BLUE), LV_PART_MAIN);
        lv_obj_set_style_border_width(label, 5, LV_PART_MAIN);
        i = 0;
    }
}


OneButton upperRight = OneButton(UPPER_RIGHT_PIN, false);


void setup()
{
  Serial.begin(9600);
  begin();
  upperRight.attachClick(change);
}


// loop checks the button pin each time,
// and will send serial if it is pressed

void loop() 
{
  delay(1);
  upperRight.tick();
  lv_tick_inc(1);
  lv_timer_handler();
}

The code is incomplete because you never pasted all of your code that is being used. But it gives you the basic idea of what needs to be done.

Thank @Marian_M and @kdschlosser for your answers.
You were right. The timer loop in a separate thread, was the reason for the problem.
I moved the lv_task_handler to the Arduino main loop and it’s working fine now.

@kdschlosser
From what I understand, the attachClick is not binding an ISR callback.
Sorry, I just saw, that I didn’t post the code.
I think it’s running in the same thread, as I call upperRight.tick() in the main loop. This function determines if the switch was pressed and if so executes the attached callback.

I understood from @Marian_M answer, that the lv_tick_inc function is all about time and I should provide the time to lvgl as precise as possible.
As I said, I am running Arduino on ESP32 and afaik under the hood it’s still freeRTOS.
I tried to use the vApplicationTickHook hook to call lv_tick_inc, but I didn’t get it working.

I added this code to the top of my main.cpp

#define CONFIG_FREERTOS_USE_TICK_HOOK 1
extern "C"
{
  void esp_vApplicationTickHook()
  {
    lv_tick_inc(1);
  }
}

But I do get a compilation error:

/Users/user/.platformio/packages/toolchain-xtensa-esp32s3/bin/../lib/gcc/xtensa-esp32s3-elf/8.4.0/../../../../xtensa-esp32s3-elf/bin/ld: /Users/user/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32s3/qio_qspi/libesp_system.a(freertos_hooks.c.obj): in function `esp_vApplicationTickHook':
/Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/freertos_hooks.c:36: multiple definition of `esp_vApplicationTickHook'; .pio/build/esp32dev/src/main.cpp.o:/Users/users/project/src/main.h:59: first defined here
collect2: error: ld returned 1 exit status
*** [.pio/build/esp32dev/firmware.elf] Error 1

Could you give me a hint, what’s wrong?
Is it sufficient to call lv_tick_inc in a xTask or is it heavily recommend using the tickHook?

for LVGL 9 you require esp timer ISR use for tick_inc

Hi caldicot,

your first attempt to fix the problem with using a mutex was just half the way.
Within your NHD::change you did use a lv_mutex_lock and a lv_mutex_unlock.
But you forgot to use the lv_mutex_lock and lv_mutex_unlock when you call lv_timer_handler within the timer thread.

Thanks for pointing this out. This helps me better understand how lvgl works. :slight_smile:
So, this would also have fixed the problem:

void timer(void *ctx)
{
    while (1)
    {
        lv_mutex_lock(&lvgl_mutex);
        lv_timer_handler();
        lv_mutex_unlock(&lvgl_mutex);
        vTaskDelay(20 / portTICK_PERIOD_MS);
    }
}

In addition to

uint i = 0;
void NHD::change()
{
    lv_mutex_lock(&lvgl_mutex);
    if (i++ == 0)
    {
        lv_label_set_text(label, "This is a extremely long text, that needs scrolling to be visible at all");
        lv_obj_remove_style_all(label);
        lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR);
        lv_obj_align(label, LV_ALIGN_LEFT_MID, 5, 0);
        lv_obj_set_width(label, 100);
    }
    else
    {
        lv_label_set_text_fmt(label, "%d%%", 70);
        lv_obj_align(label, LV_ALIGN_LEFT_MID, 5, 0);
        lv_obj_set_style_border_color(label, lv_palette_main(LV_PALETTE_BLUE), LV_PART_MAIN);
        lv_obj_set_style_border_width(label, 5, LV_PART_MAIN);
        i = 0;
    }
    lv_mutex_unlock(&lvgl_mutex);
}

I am fine with calling the lv_timer_handler from the main loop, as it keeps things simpler.
But I really appreciate your answer as my understanding growth :slight_smile:

Operating system and interrupts — LVGL documentation

I’ve added a timer for the lv_tick_inc call as suggested by @Marian_M.
The code looks like this now:

hw_timer_t *timer = NULL;
void ARDUINO_ISR_ATTR onTimer()
{
    lv_tick_inc(3);
}

void NHD::begin()
{
    lv_init();
    // lv_log_register_print_cb(log);

    display = lv_display_create(TFT_HOR_RES, TFT_VER_RES);
    TFT_eSPI *tft = new TFT_eSPI(TFT_HOR_RES, TFT_VER_RES);
    tft->begin();        /* TFT init */
    tft->setRotation(1); /* Landscape orientation, flipped */
    tft->initDMA();
    lv_display_set_driver_data(display, (void *)tft);
    lv_display_set_flush_cb(display, flush_cb);
    lv_display_set_buffers(display, (void *)draw_buf, NULL, sizeof(draw_buf), LV_DISPLAY_RENDER_MODE_PARTIAL);

    label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "");

    timer = timerBegin(0, 80, true);
    timerAttachInterrupt(timer, &onTimer, true);
    timerAlarmWrite(timer, 3000, true);
    timerAlarmEnable(timer);
}

// this is called from the main loop
void NHD::update()
{
    lv_timer_handler();
}

uint i = 0;
void NHD::change()
{
    if (i++ == 0)
    {
        lv_label_set_text(label, "This is a extremely long text, that needs scrolling to be visible at all");
        lv_obj_remove_style_all(label);
        lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR);
        lv_obj_align(label, LV_ALIGN_LEFT_MID, 5, 0);
        lv_obj_set_width(label, 100);
    }
    else
    {
        lv_label_set_text_fmt(label, "%d%%", 70);
        lv_obj_align(label, LV_ALIGN_LEFT_MID, 5, 0);
        lv_obj_set_style_border_color(label, lv_palette_main(LV_PALETTE_BLUE), LV_PART_MAIN);
        lv_obj_set_style_border_width(label, 5, LV_PART_MAIN);
        i = 0;
    }
}

It is working like a charm now :slight_smile:
Thanks everyone for their help :+1: