LVGL 9.5.0 Invalidate area is not allowed during rendering Assertion on STM32H7 + FreeRTOS

I am repeatedly hitting the following assertion in LVGL 9.5.0:

LV_ASSERT_MSG(!disp->rendering_in_progress,
              "Invalidate area is not allowed during rendering.");

The assertion occurs from inside my dedicated LVGL thread/task.


Enviroment

  • Version: 9.5.0
  • Color depth: 32-bit
  • Doubled buffered
  • Forked from Riverdies Repository
  • Screen Generated with Squareline Studio

Hardware

  • MCU: STM32H7
  • Resolution: 1024x600
  • Display: Riverdi RVT70HSSNWC00

Software Stack

  • RTOS: FreeRTOS
  • HAL: STM32 HAL
  • Compiler: arm-none-eabi-gcc
  • IDE: STM32CubeIDE

Problem Statement

I have a dedicated LVGL task that periodically calls:

lv_timer_handler();

The same task also updates several labels/widgets using functions like:

DashboardUI_SetSpeed(speed);
DashboardUI_SetRPM(rpm);
DashboardUI_SetBrake(brake);

Eventually the system hits:

LV_ASSERT_MSG(!disp->rendering_in_progress,
              "Invalidate area is not allowed during rendering.");

From debugging, it appears the invalidation originates from inside the LVGL task itself.

Stack Trace

lv_inv_area() at lv_refr.c:286 0x80183ca 
invalidate_area_core() at lv_obj_pos.c:1,448 0x8013ea0 
lv_obj_invalidate_area() at lv_obj_pos.c:945 0x8013eec 
lv_obj_invalidate() at lv_obj_pos.c:975 0x8013f74 
lv_label_mark_need_refr_text() at lv_label.c:1,070 0x8044fd0 
lv_label_set_text_vfmt() at lv_label.c:181 0x8045272 
lv_label_set_text_fmt() at lv_label.c:153 0x8045294 
DashboardUI_SetSpeed() at dashboard_ui.c:22 0x804c8b4 
LVGLTimer() at freertos.c:316 0x8001392 0x800ee10

The assertion occurs while lv_label_set_text_fmt() attempts to invalidate the label after updating text.


Current LVGL thread

void LVGLTimer(void *argument)
{
    TickType_t lastWake = xTaskGetTickCount();
    for (;;)
    {
        uint16_t speed = VehicleState_GetSpeed();
        uint16_t rpm = VehicleState_GetRPM();
        ...
        lv_lock();
        DashboardUI_SetSpeed(speed);
        DashboardUI_SetRPM(rpm);
        ...
        lv_unlock();
        lv_lock();
        lv_timer_handler();
        lv_unlock();
        vTaskDelayUntil(&lastWake, pdMS_TO_TICKS(66));
    }
}

Additional Notes

I’ve also experimented with adding lv_lock()/lv_unlock() calls inside functions such as:

void DashboardUI_SetSpeed(uint16_t speed_kmh)
{
    lv_lock();

    lv_label_set_text_fmt(
        ui_SpeedBaseTag,
        "%u",
        speed_kmh
    );

    lv_unlock();
}

However, this did not change the behavior.


Additional Symptoms

Once the assertions occurs, the system becomes unstable and other subsystems appear affected as well. for example:

  • CAN interrupts eventually stop firing
  • The debug UI element still seems to be updating despite the assertion being triggered.

Questions

  1. What should my next debugging steps be?
  2. Is my LVGL thread wrong? I believed I was following the correct structure with having only that thread touch the lv objects
  3. Has anyone else encountered this issue before?

Hi @Mason-Pronger, you enabled the FreeRTOS inside lv_conf.h? You can check this project and try running on PC first. Also it has a main.c for FreeRTOS that can help you understand the FreeRTOS structure.

Regarding the lv_lock and lv_unlock functions, this doc is good way to understand how they work, you are on the right track but there are some minor things.

When this is the case, lv_timer_handler calls lv_lock and lv_unlock internally, so you do not have to bracket your calls to lv_timer_handler with them.

You have to use the mutex calls in others threads.

Is my LVGL thread wrong? I believed I was following the correct structure with having only that thread touch the lv objects

This is the wrong train of thought. It is not just on a per-thread basis but also just using LVGL functions outside of designated timers or callbacks set up for LVGL.

I recommend moving those dashboardUI_... functions to an LVGL timer instead. Timer (lv_timer) - LVGL 9.5 documentation

You will also need to call lv_tick_inc(...) somewhere. Preferably a hardware timer interrupt (I suppose that is what this LVGLTimer is) you can move lv_timer_handler() to your thread’s main loop instead.

Thank you @bruno and @Tinus for your feedback, I’ve looked into both of your suggestions and believe I’m close to figuring out how to fix my issues.

It seems I had a problem with a separate CAN interrupt that was causing an assert to be triggered. I’ve since addressed that issue and was able to see a corrupted LV image being rendered.

I’ve also read the docs on lv_timer , FreeRTOS integration, and STM32 chip support, and I think I’ve cleaned up my code structure. I added a new lv_timer with its accompanying callback function, and used lv_tick_set_cb(...) to increment the LVGL tick. I believe I now have the correct architecture to work with the LVGL library.

void MX_FREERTOS_Init(void) {
  /* USER CODE BEGIN Init */
	g_can1 = can_port_create(1);
	if (g_can1 != NULL) {
		can_init(g_can1);
		can_start(g_can1);
	}

	g_can2 = can_port_create(2);
	if (g_can2 != NULL) {
		can_init(g_can2);
		can_start(g_can2);
	}

    /* initialize LVGL framework */
    lv_init();

    /* initialize display and touchscreen */
    lvgl_display_init();
    lvgl_touchscreen_init();

    lv_tick_set_cb(HAL_GetTick);

    ui_init(); // Squareline generated ui

    /* Create LVGL timer to update dashboard UI */
    lv_timer_create(ui_update_cb, 20, NULL); 
  /* USER CODE END Init */

With an accompanying callback function:

 static void ui_update_cb(lv_timer_t *timer)
{
    uint16_t speed   = VehicleState_GetSpeed();
    uint16_t rpm     = VehicleState_GetRPM();
    uint8_t  brake   = VehicleState_GetBrake();
    uint16_t power   = VehicleState_GetWattage();
    uint8_t  throttle= VehicleState_GetThrottle();
    uint8_t  soc     = VehicleState_GetSOC();

    DashboardUI_SetSpeed(speed);
    DashboardUI_SetRPM(rpm);
    DashboardUI_SetBrake(brake);
    DashboardUI_SetPower(power);
    DashboardUI_SetThrottle(throttle);
    DashboardUI_SetSOC(soc);
}

And I’ve cut down the lv_timer_handler() thread to match that in the pc freertos port that Bruno suggested earlier.

void LVGLTimer(void *argument)
{

    for (;;)
    {
        lv_timer_handler();

        osDelay(5);
    }
}

I’ll update this thread once I’ve flashed the new version of the firmware on my board and can verify that my issues are resolved.