ESP32 rebooting on watchdog for IDLE0

Description

  • LVGL keeps rebooting on WD error when refreshing labels.

    E (271032) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
    E (271032) task_wdt: - IDLE0 (CPU 0)
    E (271032) task_wdt: Tasks currently running:
    E (271032) task_wdt: CPU 0: wifi
    E (271032) task_wdt: CPU 1: IDLE1
    E (271032) task_wdt: Aborting.

  • All I do is refreshing and re-aligning 14 labels every 3 sec (lv_label_set_text) and realign them.

  • If I comment out the refreshing/realigning of labels, the code runs stable.

  • I think to understand, the updating of labels keeps the CPU0 busy for seconds, and therefore doesn’t let the Idle thread feed the watchdog. Why does it take that long (I think WD is 4s)? How can I feed the watchdog?

  • If I put delay(1500); in the arduino loop, the refreshing of labels works for a little while (still reboots after 30sec or so)
    With any lower delay, the reboots get more frequent, about at 200 ist never updates, only reboots.
    This delay makes of course the touchscreen useless, and also any animation impossible.
    I am aware this is wrong, it can’t take that long to update a few labels, but I cant figure anything else so far which keeps it running.

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

3.5" CYD, ESP32-035
VScode/Platformio/Arduino

What LVGL version are you using?

9.1.0

What do you want to achieve?

Change text on a label and center the label again.

What have you tried so far?

I have one ESP32S3 gathering data and sending them to this CYD.
When CYD receives data it refreshes the labels. I plan to add few functions to it once this works.

  • I tried adding delay(5) or yield() every other line, with no change.

  • I tried running it with LVGL 8.3, but couldn’t get the code working.

  • I tried arbitrarily changing board definitions in platformio.ini, but this doesn’t make any difference.

  • I tried unsing “observers” for changing the labels, but couldn’t get that running, as I didn’t understand how to implement in arduino framework. (didn’t find any examples for it either)

  • This is how I refresh one label, there’s 7 of them:

    update_labels(){
      dtostrf(sensorData.temp, 5, 1, txt5);
       strcat(txt5,"foo");
       lv_label_set_text(shadow1_label, txt5);
       lv_label_set_text(btn1_label, txt5);
       lv_obj_center(btn1_label);
       lv_obj_align_to(shadow1_label, btn1_label, LV_ALIGN_CENTER, 2, 2);
    }
    
  • The less labels I refresh, the “less bad” it works, but even with 1 label it crashes with delay(5) in loop().

  • I also tried having only one call in update_labels() (that works OK)

    update_labels(){
        lv_label_set_text(shadow1_label, "foo"); 
    
    

//this works with delay(5) in loop for a while(30-100 update cycles) before rebooting.
}

but having a few of those, already makes it reboot:
```c
update_labels(){
    lv_label_set_text(btn1_label, "foo");
    lv_label_set_text(btn2_label, "foo");
    lv_label_set_text(btn3_label, "foo");
    lv_label_set_text(btn0_label, "foo");
    lv_label_set_text(btn4_label, "foo");
    lv_label_set_text(btn5_label, "foo");
    lv_label_set_text(btn6_label, "foo");

// this crashes with delay(5) in loop, it works "for a while" with delay(100) but also eventually reboots.
}

I am for one not clear about how to implement the tick with arduino.
All instructions I can find are for v8.1 and older, which do not work for 9.1.
I tried many ways of doing it, but none worked.
For now I do this(from LVGL-Arduino example, except I need to change the delay to 1200)

static uint32_t my_tick(void)
{
    return millis();
}

setup(){
...
lv_tick_set_cb(my_tick);
...
}

loop{
  lv_task_handler();
  delay(1500); // should be 5 for the rest of the code to work smooth.
}

The updating of labels is done like that (update_labels is called when new data is received):

char txt5[5];

update_labels(){

//THIS IS DONE 7 TIMES (FOR A TOTAL OF 14 BUTTON LABELS)
   dtostrf(sensorData.temp, 5, 1, txt5);
    strcat(txt5,"foo");
    lv_label_set_text(shadow1_label, txt5);
    lv_label_set_text(btn1_label, txt5);
    lv_obj_center(btn1_label);
    lv_obj_align_to(shadow1_label, btn1_label, LV_ALIGN_CENTER, 2, 2);
}

this is my first try with C++, I did some stuff in C# so far. Never noticed that I love strings :confused:

  • Is there anything wrong with the (haphazard) way I build the char?
  • Is there any “easier” way I can concatenate a float and a string and pass it to the label?

Any help would be really appreciated, as I’m about to put LVGL into the “too difficult for me” box.

I think you may be running into an issue of the string bot being sized properly and it not being null terminated.

It is hard to know what is going on without seeing more of the code. There is also a degree of latency that gets thrown into the mix because of using the Arduino IDE due to all of the wrapper code it has to make things work.

Have you tried setting labels as string constants instead of trying to set them using a variable? See if it still hangs… If it doesn’t hang then you know the issue is not going to be in LVGL and it is instead in your code…

The other thing is LVGL doesn’t use “strings”. it uses char arrays that are typically null terminated. How you know if there needs to be a null termination is not having to pass the length.

If you are doing this 14 times…

dtostrf(sensorData.temp, 5, 1, txt5);
    strcat(txt5,"foo");
    lv_label_set_text(shadow1_label, txt5);
    lv_label_set_text(btn1_label, txt5);
    lv_obj_center(btn1_label);
    lv_obj_align_to(shadow1_label, btn1_label, LV_ALIGN_CENTER, 2, 2);

You are going to have issues because you are using the same variable. You cannot do that. You need to use a different variable for each label and the variable also needs to be a global variable so it is persistent after the function exits.

I don’t see you specifically null terminating txt5. I can see from this line…

dtostrf(sensorData.temp, 5, 1, txt5);

The problem with using the function you are using to convert the float value is it accepts a “min width”. Unless you know specifically what the largest value is going to be this is less than ideal because if you create a char array that has a size of 6 thinking that you have passed a 5 to that function this is incorrect thinking on how it works. If the float being passed to the function has a value of 9999999.9 then guess what? You just overflowed your buffer removing the null termination character. There is no protection against a buffer overrun. LVGL looks for that null termination character to know where the end is. It will read bast the boundaries of the array until it find a null termination character.

I suggest using snprintf is a better way to go because it will tell you how large the buffer needs to be for the data being placed in it. It is guarded against a memory overrun.

https://cplusplus.com/reference/cstdio/snprintf/

If you use "%.2f" as the format specifier that will give you a precision of 2 and "%.3f" is a precision of 3, etc…

example of use…

char buf[20];
int ret;

ret = snprintf (buf, 20, "%.2f", 9999.99f);

if (ret >= 0 && ret < 20) {      // check returned value
    buf[ret + 1] = "\x00";
    // now you can set the label
} else {
    // there is an encoding error is the returned value is < 0 or 
    // the buffer is not large enough if the value is > 0
}

Thanks a lot for the answer.

So it seems there are at least 2 issues (the rebooting and the formatting of the char) which are not necessarily related.

  1. the rebooting issue, you say

    Have you tried setting labels as string constants instead of trying to set them using a variable? See if it still hangs… If it doesn’t hang then you know the issue is not going to be in LVGL and it is instead in your code…

    Yes I did, the rebooting is apparently not related to the Char passed:

    update_labels(){
        lv_label_set_text(shadow1_label, "foo"); 
    }
    //this works with delay(5) in loop for a while(30-100 update cycles) before rebooting.
    
    update_labels(){
        lv_label_set_text(btn1_label, "foo");
        lv_label_set_text(btn2_label, "foo");
        lv_label_set_text(btn3_label, "foo");
        lv_label_set_text(btn0_label, "foo");
        lv_label_set_text(btn4_label, "foo");
        lv_label_set_text(btn5_label, "foo");
        lv_label_set_text(btn6_label, "foo");
    
    // this reboots every cycle with delay(5) in loop, it works "for a while" with delay(100) but also eventually reboots.
    }
    
    • I noticed after booting, I got following warning:
      [ 157][E][esp32-hal-cpu.c:110] addApbChangeCallback(): duplicate func=0x400fbd84 arg=0x3ffbdc34
      I cannot immagine it to be related to the rebooting issue, since without selling the LVGL labels, everything runs just fine.
  2. the consruction of the Char
    I tried using snprintf as you recommend, but it gives me an error when trying to add the \00
    buf[ret + 1] = "\x00";
    warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
    I do not really fathom why?

    I think to understand snprintf is automatically adding the terminating \0.
    The floats I send are with 0-2 digits precision, all max 4 characters.
    This seems to work ok.
    snprintf(buf, 20, "%4.1f°", sensorData.temp);
    It’s sure more elegant. Thanx.

I meanwhile tried following things:

  • Making 14 buffers for the 14 labels… no difference.
  • Implementing tick with IRQ… no difference
  • Disabling tWDT does also not work.
    Arbitrarily any of the tasks " lv_label_set_text", “lv_obj_center” or “lv_obj_align_to” just goes hanging (some infinite loop?). Part of the screen being updated, the rest not.
    It never stops at the “snprintf”.
  • Backtrace always says:
    Backtrace:0x4008372d:0x3ffbea7c |<-CORRUPTED
    #0 0x4008372d:0x3ffbea7c in panic_abort at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/esp_system/panic.c:402
    Unfortunately I have no clue what that is supposed to tell me :confused:

Is there anything wrong with the (haphazard) way I build the char?

Yes, as Kevin pointed out, you would be better of using snprintf as that will make sure your string fits into the defined size. C strings are just arrays of chars that are ended with a NULL-terminator '\0' character. So “foo” actually looks like

txt5[0] = 'f'
txt5[1] = 'o'
txt5[2] = 'o'
txt5[3] = '\0'
txt5[4] = undefined or '\0'

Is there any “easier” way I can concatenate a float and a string and pass it to the label?

Yes! You can use lv_label_set_text_fmt().

For instance if you wanted to show “foo [temp] C”
just use

lv_label_set_text_fmt(label, "%s %4.1f %s, "Foo", sensorData.temp, "C").

You could also use snprintf instead of the above function to manually fill a char array (string) as long as it is big enough to contain all the characters needed and then use lv_label_set_text().

Hello,

Excuse the double posting but I think I may have an idea what the issue is.

Where do you call update_labels()?
If it is outside of an LVGL function, it may not work like expected. I recommend making an lv_timer and in the callback function for that timer, just call update_labels().
Set the delay to 250ms or so, you probably will not need to update these labels the exact millisecond something has changed anyway.

If this does not crash anymore I would suggest setting some sort of flag when a value has actually changed so you only call update_labels when necessary.

See: Timers — LVGL documentation

Good luck

1 Like

IF you make an alteration to an object from a callback that is being made from that object that could also cause an issue if I an not mistaken. Depending on the event that has triggered the callback. as an example. if you have registered an event for a value changing and then inside of that callback you change the value for the very same object that could cause an issue with a never ending loop. You would need to unregister the callback change the value and then register the callback. This would keep a infinite loop from occurring.

another thing is LVGL is NOT thread safe. All changes must occur from the same cpu core using the same thread/task. Unless you specifically write in guards to keep updates from happening at the same time.

The only exception to that is you can call lv_tick_inc from say a hardware timer interrupt without any issue. calls made to lv_task_handler also need to be made from the same task/thread as the updates are happening from and not from any other task/ISR

1 Like

Bingo!

Kudos @Tinus.
Yep, calling the same “updateLabels()” method from an lv_timer works. :grinning:
I just set a bool when new data arrived and have the lv_timer checking that bool.
I didn’t really figure yet what “user_data” does, or how to setup an “lv_timer_create_basic”, but it runs flawlessly even with delay(5) in the loop() with a 500ms timer (10 in the “user data”).

Text formatting:
Also here kudos both of you, and sorry for being so innocent in these things :crazy_face:
As I mentioned, never noticed how much I like strings :joy:

So I have snprintf working with:
snprintf(buf, 9, "%4.1f°", sensorData.temp);

but I can’t get lv_label_set_text_fmt working.
I tried:
lv_label_set_text_fmt("%4.1f°", sensorData.temp)
but that only shows f°.
Reading around, I think maybe lvgl doesn’t like floats much…
Will keep trying.

Thanx both of u for the quick and competent help!

1 Like

You need to pass the object you want to set the text on as the first parameter. Just like you do when setting the label.

Yes, sorry, I did that. Only wrote it wrong in the post. e.g.:
lv_label_set_text_fmt(btn1_label, "%4.1f°", sensorData.temp);
but it only shows “f°”.

you cannot use the ° like that. instead of using that symbol try using this…

"%4.1f\xC2\xB0"

nope, still shows “f°”.
lv_label_set_text_fmt(btn1_label, "%4.1f\xC2\xB0", sensorData.temp)
I also tried casting to double, doesn’t work either.

Since snprintf perfectly works the way you first recommended, I’ll just leave it at that for now.
Thanx heaps :grinning: