ESP32S3 crashes when accessing lvgl objects from outside of class

Hi everyone,

I’ve been working on a little project. Sort of a monitoring device for sensors and smart power relays using MQTT. Think, Home Assistant, but way worse, in every way.

I’ve tried to provide as much info as I could, but if there is anything missing, let me know.

Description

I’ve created a separate class for every device I use, each containing every every object used when creating the panel itself

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

I am using this touch display panel and the code is based on the LVGL Widgets demo provided with it. It is compiled with the Arduino IDE

Video of the issue

Code to reproduce

This is an shortened example of what the class looks like
Essentially, it contains all all LVGL objects, getters, setters and the function to generate the initial panel itself

class TestClass {
  private:
    lv_style_t style;
    lv_obj_t * Panel;
    lv_obj_t * DeviceName;

    String test = "test";
  public:
    //GETTERS
    lv_obj_t * getPanel() {
      return Panel;
    }
    lv_obj_t * getDeviceName() {
      return DeviceName;
    }
    //SETTERS
    void setdeviceName(const char* newContent) {
      //lv_label_set_text_fmt(DeviceName , "%s", device["DeviceName"].as<const char*>());
      lv_label_set_text(DeviceName, newContent);
    }
    void updateDevice() {
      lv_label_set_text_fmt(DeviceName, "%s",test);
    }

    void createTestPanel(lv_obj_t * parent, JsonVariant device) {

      static lv_style_t style;
      lv_style_init(&style);
      lv_style_set_flex_flow(&style, LV_FLEX_FLOW_ROW_WRAP);
      lv_style_set_flex_main_place(&style, LV_FLEX_ALIGN_SPACE_EVENLY);
      lv_style_set_layout(&style, LV_LAYOUT_FLEX);

      //create panel
      lv_obj_t * panel1 = lv_obj_create(parent);
      lv_obj_set_size(panel1, lv_pct(48), LV_SIZE_CONTENT);
      lv_obj_set_style_pad_bottom(panel1, 30, 0);
      lv_obj_add_style(panel1, &style, 0);

      //Device name
      lv_obj_t * DeviceName = lv_label_create(panel1);
      lv_obj_set_size(DeviceName, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
      lv_label_set_text(DeviceName, device["DeviceName"].as<const char*>());
      lv_obj_add_style(DeviceName, &style_title, 0);

      if (disp_size == DISP_LARGE) {
        static lv_coord_t grid1_col_dsc[] = {LV_GRID_FR(1), 20,  LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};
        static lv_coord_t grid1_row_dsc[] = {LV_GRID_FR(1), 20, LV_GRID_CONTENT, LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};

        lv_obj_set_grid_dsc_array(panel1, grid1_col_dsc, grid1_row_dsc);
        lv_obj_set_grid_cell(DeviceName, LV_GRID_ALIGN_START, 0, 1, LV_GRID_ALIGN_START, 0, 1);

      }

    }
};

This is the code handling MQTT.
Essentially when a message arrives, a new Panel is created using the decoded JSON from the message and is then stored in a Dictionary with DeviceID as the key.
Or, if it already is in the dictionary, it finds the panel by the DeviceID key, and updates the necessary labels and indicators, which should be accessible by the get functions of the class.

And now, the problem itself.
When recieving the first message, the panel is created without any issues. It is also stored in the dictionary.
On the second message, the Panel is found, however, the device always crashes, when accessing the lv_obj_t * label.
I’ve tried updating it via the setter function, outside of the class via the getter function, and so far it has always crashed
I’ve also tried using it without the dictionary, but it still crashed.
I’ve also trying access to other String variables, but that worked fine.
So, I’ve tried to set a private String variable, and then use it to set the label text, which crashed.
I am starting to run out of ideas here.

Dictionary<String, TestClass> TestDict = Dictionary<String, TestClass>();
void onConnectionEstablished() {

  client.subscribe("test/+", [] (const String & payload)  {
    deserializeJson(JSONData, payload);
    JsonArray arr = JSONData.as<JsonArray>();
    
    for (JsonVariant value : arr) {
      Serial.println(value["DeviceType"].as<const char*>());
      String DeviceID = value["DeviceID"].as<String>();

      if (value["DeviceType"].as<String>().equals("Test")) {
        if (!TestDict.isInDictionary(value["DeviceID"].as<const char*>())) {
          testClass testPanel;
          testPanel.createTestPanel(t1, value);
          TestDict.set(DeviceID, testPanel);
        }
        else {
          Serial.println(TestDict.length());
          TestClass UpdatingTest = TestDict.get(DeviceID);
          UpdatingTest.setDeviceName(value["DeviceName"].as<const char*>());
          //does not work
          //lv_label_set_text(UpdatingTest.getDeviceName(), value["DeviceName"].as<const char*>());
          //lv_label_set_text(TestDict.get(DeviceID).getDeviceName(), value["DeviceName"].as<const char*>());
          //UpdatingTest.updateDevice();
          }
      }
    }

  });

}

Crash dump to serial

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

Core  1 register dump:
PC      : 0x420adf97  PS      : 0x00060730  A0      : 0x82008927  A1      : 0x3fcebbb0  
A2      : 0x06ffffff  A3      : 0x3d800938  A4      : 0x000000ff  A5      : 0x0000ff00  
A6      : 0x00ff0000  A7      : 0xff000000  A8      : 0x00656d61  A9      : 0x00efffe3  
A10     : 0x00000003  A11     : 0x018c1abc  A12     : 0x000000ff  A13     : 0x0000ff00  
A14     : 0x00ff0000  A15     : 0xff000000  SAR     : 0x00000014  EXCCAUSE: 0x0000001c  
EXCVADDR: 0x07000007  LBEG    : 0x400554b9  LEND    : 0x400554dd  LCOUNT  : 0x82001e7f  


Backtrace: 0x420adf94:0x3fcebbb0 0x42008924:0x3fcebbd0 0x420257bd:0x3fcebc00 0x420046ce:0x3fcebc20 0x420047f5:0x3fcebdb0 0x4202b2f9:0x3fcebdd0 0x4202b362:0x3fcebe30 0x4202f115:0x3fcebe50 0x4202b3c8:0x3fcebe90 0x4202b6a6:0x3fcebeb0 0x42002245:0x3fcebed0 0x42033f69:0x3fcebef0

I do not know how you setup everthing, and I do not know whether you use a preemtive multitasking system,
and you call lvgl functions from different threads/tasks.

lvgl is not thread safe. See the lvlg docs.
In this case you have to use a mutex/semaphore.
With a mutex/semaphore the appropriate (e.g. FreeRTOS) tasks can gain exclusive access to lvgl.
BTW the same is true if using lvgl functions from ISR.

Thanks for the reply.
I am not very familiar with FreeRTOS. As far as I know, I havent used it.
How would that look like?

Enable backtrace analyser plugin for serial monitor and check callstack.
And in createTestPanel your

lv_obj_t * panel1 = lv_obj_create(parent);

is local variable , not exist outside …

lv_obj_t * Panel;

create nothing.


Well, that is one of the dumber mistakes I ever made.

Sure enough, all of them were declared in as private variables of the class, and the function created them again, with a slightly different name, so the compiler didn’t protest, and I didn’t notice.
Probably leftovers from some careless copy pasting.

Anyways, thanks for assitance.