LVGL Freezing when Connecting to WiFi or Checking For Updates

Description

Hi! Im trying to do an OS with LVGL, but when im connecting to wifi or checking for updates it’s freezing for a few seconds before comming back normal. I’ve read the docs and found that LVGL IS NOT THREAD-SAFE. If someone can help me by adding gateway-threads into my project will be appreciated.

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

Arduino Nano ESP32 (ESP32 S3)

What LVGL version are you using?

9.2.2

What do you want to achieve?

LVGL not freezing anymore

What have you tried so far?

MUTEX but is not working (for me)

Code to reproduce

#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <lvgl.h>
#include <ui.h>
#include <vector>

#include "time.h"
const char *ntpServer = "pool.ntp.org";
const long gmtOffset_sec = +2 * 60 * 60;  // Set your timezone here
const int daylightOffset_sec = +3 * 60 * 60;

#include <EEPROM.h>
#define EEPROM_SIZE 128
#define EEPROM_ADDR_WIFI_FLAG 0
#define EEPROM_ADDR_WIFI_CREDENTIAL 4

typedef enum {
  NONE,
  NETWORK_SEARCHING,
  NETWORK_CONNECTED_POPUP,
  NETWORK_CONNECTED,
  NETWORK_CONNECT_FAILED
} Network_Status_t;
Network_Status_t networkStatus = NONE;

const char *version_url = "https://raw.githubusercontent.com/IT-Generation-Monq/Update-Test/main/version.txt";      // Path to the version.txt file
const char *changelog_url = "https://raw.githubusercontent.com/IT-Generation-Monq/Update-Test/main/changelog.txt";  // Path to the changelog.txt file
String firmware_url = "https://raw.githubusercontent.com/IT-Generation-Monq/Update-Test/main/firmware_vX.X.X.bin";  // Path to the firmware file
String LVGL_Arduino = String("LVGL Version: ") + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();

// GitHub version and firmware URLs
String current_version = "0.0.1";  // Current firmware version
String new_version_changelog;
String new_version;

WebServer server(80);  // Web server for OTA Web Updater
WiFiClientSecure client;

#include <LovyanGFX.hpp>

class LGFX : public lgfx::LGFX_Device {
  lgfx::Panel_ILI9488 _panel_instance;
  lgfx::Bus_SPI _bus_instance;
  lgfx::Light_PWM _light_instance;
  lgfx::Touch_FT5x06 _touch_instance;

public:

  LGFX(void) {
    {
      auto cfg = _bus_instance.config();

      cfg.spi_host = SPI3_HOST;
      cfg.spi_mode = 0;
      cfg.freq_write = 40000000;
      cfg.freq_read = 16000000;
      cfg.spi_3wire = false;
      cfg.use_lock = true;
      cfg.dma_channel = SPI_DMA_CH_AUTO;
      cfg.pin_sclk = 48;
      cfg.pin_mosi = 38;
      cfg.pin_miso = 47;
      cfg.pin_dc = 2;
      // If you use a common SPI bus with the SD card, be sure to set MISO and do not omit it.

      _bus_instance.config(cfg);
      _panel_instance.setBus(&_bus_instance);
    }

    {
      auto cfg = _panel_instance.config();

      cfg.pin_cs = 14;
      cfg.pin_rst = 4;
      cfg.pin_busy = -1;

      cfg.panel_width = 320;
      cfg.panel_height = 480;
      cfg.offset_x = 0;
      cfg.offset_y = 0;
      cfg.offset_rotation = 0;
      cfg.dummy_read_pixel = 8;
      cfg.dummy_read_bits = 1;
      cfg.readable = true;
      cfg.invert = true;
      cfg.rgb_order = false;
      cfg.dlen_16bit = false;
      cfg.bus_shared = true;  // If the bus is shared with the SD card, set it to true (bus control is performed using drawJpgFile, etc.)

      _panel_instance.config(cfg);
    }

    {
      auto cfg = _light_instance.config();

      cfg.pin_bl = 13;
      cfg.invert = true;
      cfg.freq = 44100;
      cfg.pwm_channel = 7;

      _light_instance.config(cfg);
      _panel_instance.setLight(&_light_instance);
    }

    {
      auto cfg = _touch_instance.config();

      cfg.x_min = 0;
      cfg.x_max = 319;
      cfg.y_min = 0;
      cfg.y_max = 479;
      cfg.pin_int = 21;
      cfg.bus_shared = true;
      cfg.offset_rotation = 0;

      cfg.i2c_port = 1;
      cfg.i2c_addr = 0x38;
      cfg.pin_sda = 11;
      cfg.pin_scl = 12;
      cfg.freq = 400000;

      _touch_instance.config(cfg);
      _panel_instance.setTouch(&_touch_instance);
    }

    setPanel(&_panel_instance);
  }
};

LGFX tft;

static const uint16_t screenWidth = 320;
static const uint16_t screenHeight = 480;

enum { SCREENBUFFER_SIZE_PIXELS = screenWidth * screenHeight / 10 };
static lv_color_t buf[SCREENBUFFER_SIZE_PIXELS];

#if LV_USE_LOG != 0
/* Serial debugging */
void my_print(const char *buf) {
  Serial.printf(buf);
  Serial.flush();
}
#endif

/* Display flushing */
void my_disp_flush(lv_display_t *disp, const lv_area_t *area, uint8_t *pixelmap) {
  uint32_t w = (area->x2 - area->x1 + 1);
  uint32_t h = (area->y2 - area->y1 + 1);

  if (LV_COLOR_16_SWAP) {
    size_t len = lv_area_get_size(area);
    lv_draw_sw_rgb565_swap(pixelmap, len);
  }

  tft.startWrite();
  tft.setAddrWindow(area->x1, area->y1, w, h);
  tft.pushColors((uint16_t *)pixelmap, w * h, true);
  tft.endWrite();

  lv_disp_flush_ready(disp);
}

/*Read the touchpad*/
void my_touchpad_read(lv_indev_t *indev_driver, lv_indev_data_t *data) {
  uint16_t touchX = 0, touchY = 0;

  bool touched = tft.getTouch(&touchX, &touchY);

  if (!touched) {
    data->state = LV_INDEV_STATE_REL;
  } else {
    data->state = LV_INDEV_STATE_PR;

    /*Set the coordinates*/
    data->point.x = touchX;
    data->point.y = touchY;

    //Serial.print( "Data x " );
    //Serial.println( touchX );

    //Serial.print( "Data y " );
    //Serial.println( touchY );
  }
}

/*Set tick routine needed for LVGL internal timings*/
static uint32_t my_tick_get_cb(void) {
  return millis();
}

static lv_timer_t *timer;

static int foundNetworks = 0;
unsigned long networkTimeout = 10 * 1000;
String ssidName, ssidPW;

TaskHandle_t ntScanTaskHandler, ntConnectTaskHandler;
std::vector<String> foundWifiList;

void setup() {
  Serial.begin(115200);
  delay(100);

  lv_init();

#if LV_USE_LOG != 0
  lv_log_register_print_cb(my_print); /* register print function for debugging */
#endif

  tft.begin();        /* TFT init */
  tft.setRotation(0); /* Landscape orientation, flipped */
  tft.setBrightness(0);
  uint16_t calData[] = { 0, 0, 0, 479, 319, 0, 319, 479 };
  tft.setTouchCalibrate(calData);

  static lv_disp_t *disp;
  disp = lv_display_create(screenWidth, screenHeight);
  lv_display_set_buffers(disp, buf, NULL, SCREENBUFFER_SIZE_PIXELS * sizeof(lv_color_t), LV_DISPLAY_RENDER_MODE_PARTIAL);
  lv_display_set_flush_cb(disp, my_disp_flush);

  static lv_indev_t *indev;
  indev = lv_indev_create();
  lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
  lv_indev_set_read_cb(indev, my_touchpad_read);

  lv_tick_set_cb(my_tick_get_cb);

  ui_init();

  listCurrentVersion();

  //tryPreviousNetwork();

  /*WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");*/
}

void loop() {
  lv_task_handler();  // let the GUI do its work
  //delay(5);
  if (!WiFi.status() == WL_CONNECTED) {
  } else {
    server.handleClient();
  }
  //server.handleClient();  // Handle web updater requests
  //delay(1000);

  // Check for OTA updates on GitHub once at startup

  /*if (checkForUpdate(firmware_url)) {
    performOTA(firmware_url.c_str());
  }
  delay(1000);*/
}

void tryPreviousNetwork() {

  if (!EEPROM.begin(EEPROM_SIZE)) {
    delay(1000);
    ESP.restart();
  }

  loadWIFICredentialEEPROM();
}

void saveWIFICredentialEEPROM(int flag, String ssidpw) {
  EEPROM.writeInt(EEPROM_ADDR_WIFI_FLAG, flag);
  EEPROM.writeString(EEPROM_ADDR_WIFI_CREDENTIAL, flag == 1 ? ssidpw : "");
  EEPROM.commit();
}

void loadWIFICredentialEEPROM() {
  int wifiFlag = EEPROM.readInt(EEPROM_ADDR_WIFI_FLAG);
  String wifiCredential = EEPROM.readString(EEPROM_ADDR_WIFI_CREDENTIAL);

  if (wifiFlag == 1 && wifiCredential.length() != 0 && wifiCredential.indexOf(" ") != -1) {
    char preSSIDName[30], preSSIDPw[30];
    if (sscanf(wifiCredential.c_str(), "%s %s", preSSIDName, preSSIDPw) == 2) {

      lv_obj_add_state(ui_Switch1, LV_STATE_CHECKED);
      lv_obj_send_event(ui_Switch1, LV_EVENT_VALUE_CHANGED, NULL);

      ssidName = String(preSSIDName);
      ssidPW = String(preSSIDPw);
      networkConnector();
    } else {
      saveWIFICredentialEEPROM(0, "");
    }
  }
}

void ui_event_Switch1(lv_event_t *e) {
  lv_event_code_t code = lv_event_get_code(e);
  lv_obj_t *btn = (lv_obj_t *)lv_event_get_target(e);

  if (code == LV_EVENT_CLICKED) {
    if (btn == ui_Button7) {
      ssidPW = String(lv_textarea_get_text(ui_TextArea1));

      networkConnector();
      lv_obj_add_flag(ui_Panel19, LV_OBJ_FLAG_HIDDEN);
      lv_obj_remove_flag(ui_Panel20, LV_OBJ_FLAG_HIDDEN);
    } else if (btn == ui_Button6) {
      lv_obj_add_flag(ui_Panel19, LV_OBJ_FLAG_HIDDEN);
      lv_obj_add_flag(ui_Panel17, LV_OBJ_FLAG_HIDDEN);
    }

  } else if (code == LV_EVENT_VALUE_CHANGED) {
    _ui_flag_modify(ui_Label23, LV_OBJ_FLAG_HIDDEN, _UI_MODIFY_FLAG_TOGGLE);
    if (btn == ui_Switch1) {

      if (lv_obj_has_state(btn, LV_STATE_CHECKED)) {

        if (ntScanTaskHandler == NULL) {
          networkStatus = NETWORK_SEARCHING;
          networkScanner();
          timer = lv_timer_create(timerForNetwork, 1000, ui_Panel16);
          //lv_list_add_text(wfList, "WiFi: Looking for Networks...");
        }

      } else {

        if (ntScanTaskHandler != NULL) {
          networkStatus = NONE;
          vTaskDelete(ntScanTaskHandler);
          ntScanTaskHandler = NULL;
          lv_timer_del(timer);
          lv_obj_clean(ui_Panel16);
        }

        if (WiFi.status() == WL_CONNECTED) {
          WiFi.disconnect(true);
          lv_label_set_text(ui_Label12, "");
          lv_label_set_text(ui_Label17, "");
          //lv_label_set_text(timeLabel, "WiFi Not Connected!    " LV_SYMBOL_CLOSE);
        }
      }
    }
  }
}

static void timerForNetwork(lv_timer_t *timer) {
  LV_UNUSED(timer);

  switch (networkStatus) {

    case NETWORK_SEARCHING:
      showingFoundWiFiList();
      break;

    case NETWORK_CONNECTED_POPUP:
      lv_obj_remove_flag(ui_Panel20, LV_OBJ_FLAG_HIDDEN);
      lv_obj_remove_flag(ui_Panel21, LV_OBJ_FLAG_HIDDEN);
      networkStatus = NETWORK_CONNECTED;
      configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
      break;

    case NETWORK_CONNECTED:
      lv_label_set_text_fmt(ui_Label34, "%s", ssidName.c_str());
      lv_label_set_text_fmt(ui_Label47, "%s", ssidName.c_str());
      lv_label_set_text(ui_Label12, LV_SYMBOL_WIFI);
      lv_label_set_text(ui_Label17, LV_SYMBOL_WIFI);
      showingFoundWiFiList();
      updateLocalTime();
      break;

    case NETWORK_CONNECT_FAILED:
      networkStatus = NETWORK_SEARCHING;
      lv_obj_remove_flag(ui_Panel20, LV_OBJ_FLAG_HIDDEN);
      lv_obj_remove_flag(ui_Panel22, LV_OBJ_FLAG_HIDDEN);
      break;

    default:
      break;
  }
}

static void showingFoundWiFiList() {
  if (foundWifiList.size() == 0 || foundNetworks == foundWifiList.size())
    return;

  lv_obj_clean(ui_Panel16);
  //lv_list_add_text(ui_Panel16, foundWifiList.size() > 1 ? "WiFi: Found Networks" : "WiFi: Not Found!");

  for (std::vector<String>::iterator item = foundWifiList.begin(); item != foundWifiList.end(); ++item) {
    lv_obj_t *btn = lv_list_add_btn(ui_Panel16, LV_SYMBOL_WIFI, (*item).c_str());
    lv_obj_add_event_cb(btn, list_event_handler, LV_EVENT_CLICKED, NULL);
    lv_obj_set_style_text_font(btn, &lv_font_montserrat_16, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_color(btn, lv_color_hex(0x000000), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(btn, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    delay(1);
  }

  foundNetworks = foundWifiList.size();
}

static void list_event_handler(lv_event_t *e) {
  lv_event_code_t code = lv_event_get_code(e);
  lv_obj_t *obj = (lv_obj_t *)lv_event_get_target(e);


  if (code == LV_EVENT_CLICKED) {

    String selectedItem = String(lv_list_get_btn_text(ui_Panel16, obj));
    for (int i = 0; i < selectedItem.length() - 1; i++) {
      if (selectedItem.substring(i, i + 2) == " (") {
        ssidName = selectedItem.substring(0, i);
        lv_label_set_text_fmt(ui_Label26, "%s", ssidName.c_str());
        lv_obj_remove_flag(ui_Panel17, LV_OBJ_FLAG_HIDDEN);
        lv_obj_remove_flag(ui_Panel19, LV_OBJ_FLAG_HIDDEN);
        break;
      }
    }
  }
}

/*
 * NETWORK TASKS
 */

static void networkScanner() {
  xTaskCreate(scanWIFITask,
              "ScanWIFITask",
              4096,
              NULL,
              1,
              &ntScanTaskHandler);
}

static void networkConnector() {
  xTaskCreate(beginWIFITask,
              "beginWIFITask",
              2048,
              NULL,
              1,
              &ntConnectTaskHandler);
}

static void scanWIFITask(void *pvParameters) {
  while (1) {
    foundWifiList.clear();
    int n = WiFi.scanNetworks();
    vTaskDelay(10);
    for (int i = 0; i < n; ++i) {
      String item = WiFi.SSID(i) + " (" + WiFi.RSSI(i) + ") " + ((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " : "*");
      foundWifiList.push_back(item);
      vTaskDelay(10);
    }
    vTaskDelay(5000);
  }
}


void beginWIFITask(void *pvParameters) {

  unsigned long startingTime = millis();
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  vTaskDelay(100);

  WiFi.begin(ssidName.c_str(), ssidPW.c_str());
  while (WiFi.status() != WL_CONNECTED && (millis() - startingTime) < networkTimeout) {
    vTaskDelay(250);
  }

  if (WiFi.status() == WL_CONNECTED) {
    networkStatus = NETWORK_CONNECTED_POPUP;
    saveWIFICredentialEEPROM(1, ssidName + " " + ssidPW);
    if (MDNS.begin("esp32")) {
      Serial.println("MDNS responder started");
    }

    client.setInsecure();

    // Web Updater endpoints
    server.on("/", HTTP_GET, []() {
      server.sendHeader("Connection", "close");
      server.send(200, "text/html", "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>");
    });

    server.on(
      "/update", HTTP_POST, []() {
        server.sendHeader("Connection", "close");
        server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
        ESP.restart();
      },
      []() {
        HTTPUpload &upload = server.upload();
        if (upload.status == UPLOAD_FILE_START) {
          Serial.printf("Update: %s\n", upload.filename.c_str());
          if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
            Update.printError(Serial);
          }
        } else if (upload.status == UPLOAD_FILE_WRITE) {
          if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
            Update.printError(Serial);
          }
        } else if (upload.status == UPLOAD_FILE_END) {
          if (Update.end(true)) {
            Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
          } else {
            Update.printError(Serial);
          }
        }
      });

    server.begin();
  } else {
    networkStatus = NETWORK_CONNECT_FAILED;
    saveWIFICredentialEEPROM(0, "");
  }

  vTaskDelete(NULL);
}

void updateLocalTime() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    return;
  }

  char hourMin[6];
  strftime(hourMin, 6, "%H:%M", &timeinfo);
  String hourMinWithSymbol = String(hourMin);
  //hourMinWithSymbol;
  lv_label_set_text(ui_Label4, hourMinWithSymbol.c_str());
  lv_label_set_text(ui_Label40, hourMinWithSymbol.c_str());
}


// Function to check for updates
bool checkForUpdate(String &firmware_url) {
  HTTPClient http;
  http.begin(version_url);
  int httpCode = http.GET();

  if (httpCode == HTTP_CODE_OK) {
    new_version = http.getString();
    new_version.trim();

    Serial.println("Current version: " + current_version);
    Serial.println("Available version: " + new_version);

    if (new_version != current_version) {
      Serial.println("New version available. Getting changelog...");
      http.begin(changelog_url);
      int httpCode = http.GET();

      if (httpCode == HTTP_CODE_OK) {
        new_version_changelog = http.getString();
        new_version_changelog.trim();
      }
      Serial.println("Changelog: ");
      Serial.println(new_version_changelog);
      /*Serial.println("Updating. Please wait...");
      firmware_url = String("https://raw.githubusercontent.com/IT-Generation-Monq/Update-Test/main/firmware_v")+ new_version + String(".bin");
      Serial.println("Firmware URL: " + firmware_url);*/
      return true;
    } else {
      Serial.println("Already on the latest version");
      lv_obj_remove_flag(ui_Label13, LV_OBJ_FLAG_HIDDEN);
    }
  } else {
    Serial.println("Failed to check for update, HTTP code: " + String(httpCode));
  }

  http.end();
  return false;
}

void performOTA(const char *firmware_url) {
  HTTPClient http;

  http.begin(firmware_url);
  int httpCode = http.GET();

  if (httpCode == HTTP_CODE_OK) {
    int contentLength = http.getSize();
    bool canBegin = Update.begin(contentLength);

    if (canBegin) {
      size_t written = Update.writeStream(http.getStream());

      if (written == contentLength) {
        Serial.println("Written : " + String(written) + " successfully");
      } else {
        Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?");
      }

      if (Update.end()) {
        Serial.println("OTA done!");
        if (Update.isFinished()) {
          Serial.println("Update successfully completed.");
          lv_obj_remove_flag(ui_Panel8, LV_OBJ_FLAG_HIDDEN);
          //delay(300);
          //ESP.restart();
        } else {
          Serial.println("Update not finished? Something went wrong!");
        }
      } else {
        Serial.println("Error Occurred. Error #: " + String(Update.getError()));
      }
    } else {
      Serial.println("Not enough space to begin OTA");
    }
  } else {
    Serial.println("Cannot download firmware. HTTP code: " + String(httpCode));
  }

  http.end();
}

void ui_event_Button1(lv_event_t *e) {
  lv_event_code_t event_code = lv_event_get_code(e);

  if (event_code == LV_EVENT_CLICKED) {
    lv_obj_add_flag(ui_Panel9, LV_OBJ_FLAG_HIDDEN);
    lv_obj_add_flag(ui_Panel14, LV_OBJ_FLAG_HIDDEN);
    if (WiFi.status() != WL_CONNECTED) {
      lv_obj_remove_flag(ui_Panel17, LV_OBJ_FLAG_HIDDEN);
      lv_obj_remove_flag(ui_Label18, LV_OBJ_FLAG_HIDDEN);
    } else {
      lv_obj_remove_flag(ui_Panel3, LV_OBJ_FLAG_HIDDEN);
      if (checkForUpdate(firmware_url)) {
        lv_obj_remove_flag(ui_Panel9, LV_OBJ_FLAG_HIDDEN);
        lv_label_set_text_fmt(ui_Label19, "Version: %s", new_version.c_str());
        lv_label_set_text_fmt(ui_Label14, "Version: %s", new_version.c_str());
        lv_label_set_text_fmt(ui_Label16, "%s", new_version_changelog.c_str());
        lv_label_set_text_fmt(ui_Label39, "%s", new_version_changelog.c_str());
      } else {
        lv_obj_remove_flag(ui_Panel14, LV_OBJ_FLAG_HIDDEN);
      }
    }
  }
}

void listCurrentVersion() {
  lv_label_set_text_fmt(ui_Label5, "OS version: %s", current_version.c_str());
  lv_label_set_text_fmt(ui_Label21, "OS version: %s", current_version.c_str());
  lv_label_set_text_fmt(ui_Label53, "Version: %s", current_version.c_str());
  lv_label_set_text_fmt(ui_Label6, "%s", LVGL_Arduino.c_str());
}

void ui_event_Button4(lv_event_t *e) {
  lv_event_code_t event_code = lv_event_get_code(e);

  if (event_code == LV_EVENT_CLICKED) {
    _ui_screen_change(&ui_Screen2, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_Screen2_screen_init);
    Serial.println("Updating. Please wait...");
    firmware_url = String("https://raw.githubusercontent.com/IT-Generation-Monq/Update-Test/main/firmware_v") + new_version + String(".bin");
    Serial.println("Firmware URL: " + firmware_url);
    performOTA(firmware_url.c_str());
  }
}

void ui_event_Button12(lv_event_t *e) {
  lv_event_code_t event_code = lv_event_get_code(e);

  if (event_code == LV_EVENT_CLICKED) {
    ESP.restart();
  }
}

Screenshot and/or video

If possible, add screenshots and/or videos about the current state.

cant do this in one loop task. Learn create next task in setup and use it…

Common occurrence on single-threaded systems.

ESP32S3 is a dual core MCU
Create an LVGL task on one core and have WiFi run on the other core.