Lv_obj_clean() ends in a crash

Description

Try to implement an app with multiple screens.

Before drawing the new screen i need to clean the content.

Tried both ways:

cleaning entire active screen using lv_obj_clean(lv_scr_act());

cleaning object by object using lv_obj_clean(object);

both cases it crashes mostly in lv_object_tree.c/lv_obj_clean(lv_obj_t * obj) and sometimes in lv_timer_handler()

Here is the full call stack at crash time

Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.

Core  1 register dump:
PC      : 0x400eb81a  PS      : 0x00060e30  A0      : 0x800ec8d0  A1      : 0x3fff8f50
0x400eb81a: _lv_event_mark_deleted at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_event.c:156

A2      : 0x3ffb6940  A3      : 0x3ffb6b94  A4      : 0x0000000c  A5      : 0x3ffb6940
A6      : 0x00000000  A7      : 0xff000000  A8      : 0x00000001  A9      : 0x00000000
A10     : 0x3ffb5000  A11     : 0x3ffb6b94  A12     : 0x3ffb6ba4  A13     : 0x00000000
A14     : 0x00000000  A15     : 0x00000000  SAR     : 0x0000001d  EXCCAUSE: 0x0000001c
EXCVADDR: 0x00000005  LBEG    : 0x4000c2e0  LEND    : 0x4000c2f6  LCOUNT  : 0xffffffff
0x4000c2e0: memcpy in ROM

0x4000c2f6: memcpy in ROM



Backtrace: 0x400eb817:0x3fff8f50 0x400ec8cd:0x3fff8f70 0x401b20a3:0x3fff8f90 0x401b20b1:0x3fff8fb0 0x400f1738:0x3fff8fd0 0x400f183c:0x3fff8ff0 0x400dc02c:0x3fff9010 0x400db674:0x3fff9080 0x4008f18e:0x3fff90a0
0x400eb817: _lv_event_mark_deleted at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_event.c:155

0x400ec8cd: lv_obj_destructor at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_obj.c:454

0x401b20a3: _lv_obj_destruct at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_obj_class.c:136 (discriminator 1)

0x401b20b1: _lv_obj_destruct at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_obj_class.c:143

0x400f1738: obj_del_core at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_obj_tree.c:385

0x400f183c: lv_obj_clean at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_obj_tree.c:89

0x400dc02c: do_main_screen at C:/dev/esp32/esp32wp_controller/lcd/main_screen.c:223

0x400db674: ui_task at C:/dev/esp32/esp32wp_controller/lcd/lcd.c:146

0x4008f18e: vPortTaskWrapper at C:/dev/esp32/esp-idf-v5.1.2/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:162

**0x400db674: ui_task at C:/dev/esp32/esp32wp_controller/lcd/lcd.c:146** above is my function calling lv_obj_clean()

and here is the code for one of my screens.
static void draw_main_screen(lv_disp_t *disp, int active_screen)
	{
	lv_obj_t *label;

	gpio_set_level(LCD_BK_LIGHT, LCD_BK_LIGHT_OFF_LEVEL);
	lv_obj_clean(lv_scr_act());
    lv_style_init(&watch_style);

    lv_style_set_text_color(&watch_style, lv_color_hex(0xc0c0c0));
    lv_style_set_text_font(&watch_style, &seg_black_40);

    btns[0].btn = lv_btn_create(lv_scr_act());
    lv_obj_add_style(btns[0].btn, &btn_norm, 0);
    lv_obj_add_style(btns[0].btn, &btn_sel, LV_STATE_PRESSED);

    lv_obj_set_pos(btns[0].btn, 60, 90);
    lv_obj_set_size(btns[0].btn, 200, 45);
    lv_obj_add_event_cb(btns[0].btn, bpump_event_cb, LV_EVENT_ALL, NULL);

    label = lv_label_create(btns[0].btn);
    lv_label_set_text(label, "Pump");
    lv_obj_center(label);
    lv_obj_align(label, LV_ALIGN_LEFT_MID, 0, 0);
    btns[0].state = 0;
    lv_obj_t * ledp  = lv_led_create(btns[0].btn);
    lv_obj_align(ledp, LV_ALIGN_RIGHT_MID, 0, 0);
    lv_led_set_brightness(ledp, 255);
    lv_led_set_color(ledp, lv_color_hex(0xc0c0c0));

    btns[1].btn = lv_btn_create(lv_scr_act());
    lv_obj_add_style(btns[1].btn, &btn_norm, 0);
    lv_obj_add_style(btns[1].btn, &btn_sel, LV_STATE_PRESSED);
    lv_obj_set_pos(btns[1].btn, 60, 160);
    lv_obj_set_size(btns[1].btn, 200, 45);

    lv_obj_add_event_cb(btns[1].btn, bwater_event_cb, LV_EVENT_ALL, NULL);
    label = lv_label_create(btns[1].btn);
    lv_label_set_text(label, "Watering");
    lv_obj_align(label, LV_ALIGN_LEFT_MID, 0, 0);
    lv_obj_t * ledw  = lv_led_create(btns[1].btn);
    lv_obj_align(ledw, LV_ALIGN_RIGHT_MID, 0, 0);
    lv_led_set_brightness(ledw, 255);
    lv_led_set_color(ledw, lv_color_hex(0xc0c0c0));
    btns[1].state = 1;
    if(active_screen == PUMP_SCREEN)
    	lv_obj_add_state(btns[0].btn, LV_STATE_PRESSED);
    if(active_screen == WATER_SCREEN)
    	lv_obj_add_state(btns[1].btn, LV_STATE_PRESSED);

    watch = lv_label_create(lv_scr_act());
    lv_obj_set_pos(watch, 10, 10);
    lv_obj_set_size(watch, 290, 60);
    lv_obj_add_style(watch, &watch_style, 0);
    time_t now = 0;
	char buf[24];
	struct tm timeinfo = { 0 };
	time(&now);
	localtime_r(&now, &timeinfo);
    sprintf(buf, "%02d:%02d - %02d.%02d", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_mday, (timeinfo.tm_mon + 1));
    lv_label_set_text(watch, buf);
    gpio_set_level(LCD_BK_LIGHT, LCD_BK_LIGHT_ON_LEVEL);
	}

If i comment out **lv_obj_clean(lv_scr_act())**, no crash but the content is overwritten.
Sometimes the crash show up after few transitions between screens, sometimes immediately.
As i'm new to lvgl i just took the app template provided in esp examples and try to adapt it to my application.

Are there any restrictions using lv_object_clean() function?

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

ESP32-WROOM32 ESP_IDF v5.1.2

What LVGL version are you using?

8.3.11

Hello,

For switching screens, why not just the lv_scr_load_anim function? See Displays — LVGL documentation.

This will automatically delete the previous screen after the given screen object has been loaded.

2 Likes

Thanks @Tinus for the hint!
First, you pointed me to the right approach on how to use multiple screens.
Second, your suggestion its working, however i experience random crashes, alaways in the same point
Here is the call stack:

Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.

Core  1 register dump:
PC      : 0x400ebc6e  PS      : 0x00060430  A0      : 0x800ecd24  A1      : 0x3fff73d0
0x400ebc6e: _lv_event_mark_deleted at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_event.c:156

A2      : 0x3ffb6e20  A3      : 0x3ffb6ad0  A4      : 0x00000018  A5      : 0x3ffb6e20
A6      : 0x00000400  A7      : 0x000000fe  A8      : 0xb33fffff  A9      : 0x3fff95b0
A10     : 0x3ffb5000  A11     : 0x3ffb6ad0  A12     : 0x3ffb6aec  A13     : 0x3ffb5548
A14     : 0x000000ff  A15     : 0x00000000  SAR     : 0x0000001a  EXCCAUSE: 0x0000001c
EXCVADDR: 0xb3400003  LBEG    : 0x4000c2e0  LEND    : 0x4000c2f6  LCOUNT  : 0xffffffff
0x4000c2e0: memcpy in ROM

0x4000c2f6: memcpy in ROM



Backtrace: 0x400ebc6b:0x3fff73d0 0x400ecd21:0x3fff73f0 0x401b2523:0x3fff7410 0x401b2531:0x3fff7430 0x400f1bc8:0x3fff7450 0x400f1b5d:0x3fff7470 0x400f1b5d:0x3fff7490 0x400f1c90:0x3fff74b0 0x400eb5ea:0x3fff74d0 0x400f67c9:0x3fff74f0 0x400f68e6:0x3fff7510 0x400f8399:0x3fff7530 0x400f844a:0x3fff7550 0x400db6b4:0x3fff7570 0x4008f18e:0x3fff7590
0x400ebc6b: _lv_event_mark_deleted at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_event.c:155

0x400ecd21: lv_obj_destructor at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_obj.c:454

0x401b2523: _lv_obj_destruct at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_obj_class.c:136 (discriminator 1)

0x401b2531: _lv_obj_destruct at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_obj_class.c:143

0x400f1bc8: obj_del_core at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_obj_tree.c:385

0x400f1b5d: obj_del_core at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_obj_tree.c:362

0x400f1b5d: obj_del_core at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_obj_tree.c:362

0x400f1c90: lv_obj_del at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_obj_tree.c:61

0x400eb5ea: scr_anim_ready at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/core/lv_disp.c:524 (discriminator 2)

0x400f67c9: anim_ready_handler at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/misc/lv_anim.c:437 (discriminator 1)

0x400f68e6: anim_timer at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/misc/lv_anim.c:397

0x400f8399: lv_timer_exec at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/misc/lv_timer.c:313 (discriminator 2)

0x400f844a: lv_timer_handler at C:/dev/esp32/esp32wp_controller/managed_components/lvgl__lvgl/src/misc/lv_timer.c:109

0x400db6b4: lvgl_task at C:/dev/esp32/esp32wp_controller/lcd/lcd.c:137 (discriminator 1)

0x4008f18e: vPortTaskWrapper at C:/dev/esp32/esp-idf-v5.1.2/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:162

I modified main_screen() like below

static void draw_main_screen(lv_disp_t *disp, int active_screen)
	{
	lv_obj_t *label;

	main_scr = lv_obj_create(NULL);
	lv_obj_set_style_bg_color(main_scr, lv_color_hex(0x0), LV_PART_MAIN);

    btns[0].btn = lv_btn_create(main_scr);
    lv_obj_add_style(btns[0].btn, &btn_norm, 0);
    lv_obj_add_style(btns[0].btn, &btn_sel, LV_STATE_PRESSED);

    lv_obj_set_pos(btns[0].btn, 60, 90);
    lv_obj_set_size(btns[0].btn, 200, 45);

    label = lv_label_create(btns[0].btn);
    lv_label_set_text(label, "Pump");
    lv_obj_center(label);
    lv_obj_align(label, LV_ALIGN_LEFT_MID, 0, 0);
    btns[0].state = 0;
    lv_obj_t * ledp  = lv_led_create(btns[0].btn);
    lv_obj_align(ledp, LV_ALIGN_RIGHT_MID, 0, 0);
    lv_led_set_brightness(ledp, 255);
    lv_led_set_color(ledp, lv_color_hex(0xc0c0c0));

    btns[1].btn = lv_btn_create(main_scr);
    lv_obj_add_style(btns[1].btn, &btn_norm, 0);
    lv_obj_add_style(btns[1].btn, &btn_sel, LV_STATE_PRESSED);
    lv_obj_set_pos(btns[1].btn, 60, 160);
    lv_obj_set_size(btns[1].btn, 200, 45);

    label = lv_label_create(btns[1].btn);
    lv_label_set_text(label, "Watering");
    lv_obj_align(label, LV_ALIGN_LEFT_MID, 0, 0);
    lv_obj_t * ledw  = lv_led_create(btns[1].btn);
    lv_obj_align(ledw, LV_ALIGN_RIGHT_MID, 0, 0);
    lv_led_set_brightness(ledw, 255);
    lv_led_set_color(ledw, lv_color_hex(0xc0c0c0));

    if(active_screen == PUMP_SCREEN)
    	{
    	lv_obj_add_state(btns[0].btn, LV_STATE_PRESSED);
    	btns[0].state = 1;
    	}
    if(active_screen == WATER_SCREEN)
    	{
    	lv_obj_add_state(btns[1].btn, LV_STATE_PRESSED);
    	btns[1].state = 1;
    	}

    watch = lv_label_create(main_scr);
    lv_obj_set_pos(watch, 10, 10);
    lv_obj_set_size(watch, 290, 60);
    lv_obj_set_style_text_font(watch, &seg_black_40, 0);
    lv_obj_set_style_text_color(watch, lv_color_hex(0xc0c0c0), 0);

    time_t now = 0;
	char buf[24];
	struct tm timeinfo = { 0 };
	time(&now);
	localtime_r(&now, &timeinfo);
    sprintf(buf, "%02d:%02d - %02d.%02d", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_mday, (timeinfo.tm_mon + 1));
    lv_label_set_text(watch, buf);

    lv_scr_load_anim(main_scr, LV_SCR_LOAD_ANIM_MOVE_LEFT, 300, 0, true);
	}

Any more suggestions why i face this crash?

Hello,

When exactly does the crash happen?

Right now I am not sure about a fix. Perhaps start off with no objects on either screen except a button to switch between the two? I suspect this part

    if(active_screen == PUMP_SCREEN)
    	{
    	lv_obj_add_state(btns[0].btn, LV_STATE_PRESSED);
    	btns[0].state = 1;
    	}
    if(active_screen == WATER_SCREEN)
    	{
    	lv_obj_add_state(btns[1].btn, LV_STATE_PRESSED);
    	btns[1].state = 1;
    	}

may be an issue. But for now just try switching with nothing on both screens.

With empty screens i went to a different crash, but i guess i found a workaround.

My input device is a rotary enc knob and the screen switch is triggered by the key pressed event.
I have a debounce timer on key pressed set to 100 msec, so the minimum interval between 2 consecutive key events can be 100 msec.

The transition time in lv_scr_load_anim(); I set it to 300 msec

As far as i coudl observe the crash happens (with or without empty screens) if the time between 2 key events is less than 300 msec.

So i made 2 more experimets:

  • reduce the time in lv_scr_load_anim() to 100 msec and keep the debounce timer to 100 msec
  • keep the time in lv_scr_load_anim() to 300 msec and increase the debounce timer to 300 msec

In both cases no crash happens!

As far as i could see looking into lv_scr_load_anim() code, the function returns after animation is triggered not after animation is completed. Am I right?

I this is the case then the workaround above is no longer a warkaround but the solution.

Can you comment?

Good day,

As far as I understand lv_scr_load_anim() returns after triggering the animation, but the previous screen only gets deleted after completing said animation. I suspect that switching screens (pressing the button) while this animation is still happening causes this crash.
For testing you could set the animation time and delay to 0 and the animation to NONE. This should switch screens and immediately delete the previous.

But indeed if you want to use the rotary encoder directly for switching screens, this may be the best solution. I think in a real application this screen switch would probably be triggered by a LVGL button which is then pressed by using the rotary encoder.

So it looks we have the same understanding :slightly_smiling_face:

That means here, IMHO, its a bad behavior of lvl engine.

Either the events shall be processed in sequential order, only after previous event processing is complete, or overlapped event should be discarded. In any case should not end in a crash.

I guess dumb users like me are plenty.

thanks again @Tinus for taking your time to look into this issue.

Hi, I did not fully read all of this thread, but it seems you need to have an event when the animation ended, but you only get one when the animation starts That’s what I also needed.
Maybe this is helpful for you:

Not sure when its safe to delete the tab. Is it after ready_cb() returns, or inside the function?
Can you post the code for ready_cb() function?

also the ready_cb member of the lv_anim_t structure has a confusing definition

lv_anim_ready_cb_t ready_cb;         /**< Call it when the animation is ready*/

ready means what? ready to start, ready because is completed…?!?

Hello,

Yes, it’s confusing indeed. In LVGL V9 this member was renamed from lv_anim_ready_cb_t to lv_anim_completed_cb_t. This callback is triggered when the animation has been completed. If I understand correctly you may delete this tab inside that ready_cb callback.

Sorry, I’m just reading this now. Unfortunately you have to wait longer than tabview_animation_ready_cb

This is the code I am using:

both lvgl 8 and 9

void your_function_to_delete_tab(int oldTabID, int newTabID) {
   ...
}

static bool waitBeforeActionAfterSlidingAnimationEnded = false;
static unsigned long waitBeforeActionAfterSlidingAnimationEnded_timerStart;
// This is the callback when the animation of the tab sliding ended
static void tabview_animation_ready_cb(lv_anim_t* a) {
  // Unfortunately, the animation has not completely ended here. We cannot do the recreation of the tabs here.
  // We have to wait some more milliseconds or at least one cycle of lv_timer_handler();
  // calling lv_timer_handler(); here does not help
  // lv_timer_handler();
  // your_function_to_delete_tab(oldTabID, currentTabID);

  waitBeforeActionAfterSlidingAnimationEnded = true;
  waitBeforeActionAfterSlidingAnimationEnded_timerStart = millis();
}


static bool waitOneLoop = false;
void gui_loop(void) {
  // after the sliding animation ended, we have to wait one cycle of gui_loop() before we can do the recreation of the tabs
  if (waitBeforeActionAfterSlidingAnimationEnded) {
    waitOneLoop = true;
    waitBeforeActionAfterSlidingAnimationEnded = false;
  } else if (waitOneLoop) {
    waitOneLoop = false;
    your_function_to_delete_tab(oldTabID, currentTabID);
  };
  // // as alternative, we could wait some milliseconds. But one cycle of gui_loop() works well.
  // if (waitBeforeActionAfterSlidingAnimationEnded) {
  //   if (millis() - waitBeforeActionAfterSlidingAnimationEnded_timerStart >= 5) {
  //     your_function_to_delete_tab(oldTabID, currentTabID);
  //     waitBeforeActionAfterSlidingAnimationEnded = false;
  //   }
  // }

  lv_timer_handler();
}

only lvgl 8

// Update currentTabID when a new tab is selected
// this is a callback if the tabview is changed (LV_EVENT_VALUE_CHANGED)
// Sent when a new tab is selected by sliding or clicking the tab button. lv_tabview_get_tab_act(tabview) returns the zero based index of the current tab.
void tabview_tab_changed_event_cb(lv_event_t* e) {
  if (lv_event_get_code(e) == LV_EVENT_VALUE_CHANGED) {
    
    oldTabID = currentTabID;
    currentTabID = lv_tabview_get_tab_act(lv_event_get_target(e));

    // Wait until the animation ended, then call "your_function_to_delete_tab(oldTabID, currentTabID);"
    // https://forum.lvgl.io/t/delete-a-tab-after-the-tabview-scroll-animation-is-complete/3155/4
    lv_obj_t* myTabview = lv_event_get_target(e);
    lv_obj_t* tabContainer = lv_tabview_get_content(myTabview);
    lv_anim_t* anim = lv_anim_get(tabContainer, NULL); // (lv_anim_exec_xcb_t) lv_obj_set_x);
    if(anim) {
      // Swipe is not yet complete. User released the touch screen, an animation will bring it to the end.
      // That's the normal (and only?) case for the tft touchscreen
      lv_anim_set_ready_cb(anim, tabview_animation_ready_cb);
    } else {
      // Swipe is complete, no additional animation is needed. Most likely only possible in simulator
      Serial.println("Change of tab detected, without animation at the end. Will directly do my job after sliding.");
      your_function_to_delete_tab(oldTabID, currentTabID);
    }
  }
}

lvgl 9

// Update currentTabID when a new tab is selected
// this is a callback if the tabview is changed (LV_EVENT_VALUE_CHANGED)
// Sent when a new tab is selected by sliding or clicking the tab button. lv_tabview_get_tab_act(tabview) returns the zero based index of the current tab.
void tabview_tab_changed_event_cb(lv_event_t* e) {
  if (lv_event_get_code(e) == LV_EVENT_VALUE_CHANGED) {
    
    oldTabID = currentTabID;
    currentTabID = lv_tabview_get_tab_active((lv_obj_t*)lv_event_get_target(e));

    // Wait until the animation ended, then call "your_function_to_delete_tab(oldTabID, currentTabID);"
    // https://forum.lvgl.io/t/delete-a-tab-after-the-tabview-scroll-animation-is-complete/3155/4
    lv_obj_t* myTabview = (lv_obj_t*)lv_event_get_target(e);
    lv_obj_t* tabContainer = lv_tabview_get_content(myTabview);
    lv_anim_t* anim = lv_anim_get(tabContainer, NULL); // (lv_anim_exec_xcb_t) lv_obj_set_x);
    if(anim) {
      // Swipe is not yet complete. User released the touch screen, an animation will bring it to the end.
      // That's the normal (and only?) case for the tft touchscreen
      lv_anim_set_completed_cb(anim, tabview_animation_ready_cb);
    } else {
      // Swipe is complete, no additional animation is needed. Most likely only possible in simulator
      Serial.println("Change of tab detected, without animation at the end. Will directly do my job after sliding.");
      your_function_to_delete_tab(oldTabID, currentTabID);
    }
  }
}