I recently acquired a 3.2-inch LCD TFT with an ILI9488 driver to be used with an Esp32, where the graphical part was entirely done through Squareline Studio, which, by the way, pleasantly surprised me. However, an unsolvable bug, which neither I nor my professors in my university’s electrical engineering department could resolve, and this issue, which seems to be simple, burned not only my neurons in the last month but also those of various other software engineers and programmers across forums and around the world.
The issue can be summarized as a screen freeze occurring when using Squareline Studio software with Wi-Fi functions, such as automatic reconnection when the internet is down or Google Firebase connections (And yes, I have used multiple graphic libraries and Firebase as a test, and the error persisted). At first, you might think, well, the solution is to implement asynchronous tasks using FreeRTOS threads for LVGL and move Wi-Fi activities to core 0 and the graphical part, like lv_task_handler, to core 1 to prevent freezes or interruptions. However, that’s not the case; the bug persists. Mutex cannot be used either due to the screen’s frame rate update rate; it introduces a delay. After a thorough debug, I found an issue with the esp_wifi_set_config() instance when it’s called, and WiFi Setup is accessing the on-chip flash memory, causing the DMA to stall when updating the LCD.
Of course, it might not be this, but after all, won’t I be able to use Squareline Studio for design on one of the best-selling devices worldwide (Esp32/Arduino/STM32)? Please, help!
Here is the code in a generic form; I hope it helps the entire community. `#include <WiFi.h> // Include the WiFi library
#include <IOXhop_FirebaseESP32.h> // Include the FirebaseESP32 library
#include <ArduinoJson.h> // Include the ArduinoJson library
#include <Arduino.h>
#include <lvgl.h>
#include <LovyanGFX.hpp>
#include <ui.h>
#define WIFI_SSID “" // Replace with your WiFi SSID
#define WIFI_PASSWORD "” // Replace with your WiFi password
#define FIREBASE_HOST “" // Replace with your Firebase project’s host
#define FIREBASE_AUTH "” // Replace with your Firebase authentication token
// Variables
int WiFiStatus, PreviousWiFiStatus;
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_ILI9488 _panel_instance;
lgfx::Bus_SPI _bus_instance; // SPI bus instance
lgfx::Light_PWM _light_instance;
lgfx::Touch_XPT2046 _touch_instance;
public:
LGFX(void)
{
{ // Bus control configuration
auto cfg = _bus_instance.config();
// SPI bus configuration
cfg.spi_host = VSPI_HOST;
// Select the SPI to be used: ESP32-S2, C3: SPI2_HOST or SPI3_HOST / ESP32: VSPI_HOST or HSPI_HOST
// ※ With ESP-IDF updates, writing VSPI_HOST and HSPI_HOST is obsolete. If errors occur, use SPI2_HOST or SPI3_HOST.
cfg.spi_mode = 0; // Set SPI communication mode (0 ~ 3)
cfg.freq_write = 40000000; // SPI clock frequency during transmission (max 80 MHz)
cfg.freq_read = 16000000; // SPI clock frequency during reception
cfg.spi_3wire = true; // Configuration to receive on the MOSI pin
cfg.use_lock = true; // Set transaction lock usage
cfg.dma_channel = SPI_DMA_CH_AUTO; // Set the DMA channel to be used (0=no DMA / 1=1ch / 2=ch / SPI_DMA_CH_AUTO=auto)
// ※ With ESP-IDF updates, it is recommended to use SPI_DMA_CH_AUTO (auto). Options 1ch and 2ch are obsolete.
cfg.pin_sclk = 18; // Set the SCLK pin of SPI
cfg.pin_mosi = 23; // Set the MOSI pin of SPI
cfg.pin_miso = 19; // Set the MISO pin of SPI (-1 = disabled)
cfg.pin_dc = 2; // Set the D/C pin of SPI (-1 = disabled)
_bus_instance.config(cfg); // Apply the bus configuration
_panel_instance.setBus(&_bus_instance); // Associate the bus with the panel instance
}
{ // Display panel control configuration
auto cfg = _panel_instance.config();
cfg.pin_cs = 15; // Panel CS pin (-1 = disabled)
cfg.pin_rst = 4; // Panel RST pin (-1 = disabled)
cfg.pin_busy = -1; // Panel BUSY pin (-1 = disabled)
cfg.panel_width = 320; // Actual display area width
cfg.panel_height = 480; // Actual display area height
cfg.offset_x = 0; // Panel X offset
cfg.offset_y = 0; // Panel Y offset
cfg.offset_rotation = 0; // Panel rotation value (0~7; 4~7 mirror vertically)
cfg.dummy_read_pixel = 8; // Number of dummy read bits before actual pixel read
cfg.dummy_read_bits = 1; // Number of dummy read bits before non-pixel data read
cfg.readable = true; // Set if data read is possible
cfg.invert = false; // Invert panel colors if true
cfg.rgb_order = false; // Swap color channels if true
cfg.dlen_16bit = false; // Set to true for panels that send data in 16-bit units
cfg.bus_shared = true; // Set to true if the bus is shared with an SD card
_panel_instance.config(cfg); // Apply the panel configuration
}
// Backlight control settings (optional)
{
auto cfg = _light_instance.config();
cfg.pin_bl = 21; // Backlight connection pin
cfg.invert = false; // Invert backlight brightness if true
cfg.freq = 44100; // Backlight PWM frequency
cfg.pwm_channel = 7; // PWM channel number to be used
_light_instance.config(cfg); // Apply backlight settings
_panel_instance.setLight(&_light_instance); // Associate backlight with the panel instance
}
// Touch screen control settings (optional)
{
auto cfg = _touch_instance.config();
cfg.x_min = 0; // Minimum X value of the touch screen (raw value)
cfg.x_max = 319; // Maximum X value of the touch screen (raw value)
cfg.y_min = 0; // Minimum Y value of the touch screen (raw value)
cfg.y_max = 479; // Maximum Y value of the touch screen (raw value)
cfg.pin_int = -1; // Touch screen INT pin
cfg.bus_shared = true; // Set to true if the touch screen shares the bus with the display
cfg.offset_rotation = 0; // Adjustment for matching display and touch screen orientation (0~7)
// SPI connection settings
cfg.spi_host = VSPI_HOST; // Select the SPI to be used (HSPI_HOST or VSPI_HOST)
cfg.freq = 1000000; // SPI clock frequency
cfg.pin_sclk = 18; // SPI SCLK pin
cfg.pin_mosi = 23; // SPI MOSI pin
cfg.pin_miso = 19; // SPI MISO pin
cfg.pin_cs = 17; // SPI CS pin
_touch_instance.config(cfg); // Apply touch screen control settings
_panel_instance.setTouch(&_touch_instance); // Associate touch screen control with the panel instance
}
setPanel(&_panel_instance); // Set the panel to be used
}
};
// Create an instance of the prepared class
LGFX tft;
static const uint16_t screenWidth = 480;
static const uint16_t screenHeight = 320;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * screenHeight / 10];
// Screen update
void my_screen_update(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
tft.startWrite();
tft.setAddrWindow(area->x1, area->y1, w, h);
tft.writePixels((lgfx::rgb565_t *)&color_p->full, w * h);
tft.endWrite();
lv_disp_flush_ready(disp_drv);
}
// Touchpad reading
void my_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
uint16_t touchX, touchY;
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("X Data ");
Serial.println(touchX);
Serial.print("Y Data ");
Serial.println(touchY);
}
}
void WiFiInit()
{
if ((WiFi.status() != WL_CONNECTED))
{
while (WiFi.status() != WL_CONNECTED)
{
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print(“Connecting to Wi-Fi”);
Serial.print(“.”);
delay(500);
}
Serial.println();
Serial.print("Connected with IP: ");
Serial.println(WiFi.localIP());
Serial.println();
Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH); // Initialize communication with Firebase as defined earlier
Serial.print("WiFi Status: ");
Serial.println(WiFi.status());
Serial.println(“Connected!”);
delay(1000);
}
}
// Test connection with Firebase
void Display(void *pvParameters)
{
while (1)
{
WiFiInit();
int x;
x = random(0, 100);
Firebase.setInt(“/test/a”, x);
Serial.print("Value sent: ");
Serial.println(x);
Serial.print("WiFi variable value: ");
Serial.println(WiFiStatus);
if (Firebase.failed())
{
Serial.print("setting /number failed:");
return;
}
vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait for 300 milliseconds
}
}
void gui_task(void *pvParameters)
{
// Initialize GUI
tft.begin();
tft.setRotation(1); // Landscape mode, mirrored
// Set touchscreen calibration data
uint16_t calData[] = {234, 3878, 226, 227, 3786, 3910, 3757, 325};
tft.setTouchCalibrate(calData);
tft.setBrightness(255);
lv_init();
lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10);
// Initialize the display
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
// Change the line below to match your display resolution
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
disp_drv.flush_cb = my_screen_update;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
// Initialize the input device driver (dummy)
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_touchpad_read;
lv_indev_drv_register(&indev_drv);
ui_init();
while (1)
{
lv_task_handler();
vTaskDelay(pdMS_TO_TICKS(4));
}
}
void setup()
{
WiFiInit();
Serial.begin(115200);
xTaskCreatePinnedToCore(Display, “Display”, 10000, NULL, 1, NULL, 1);
xTaskCreatePinnedToCore(gui_task, “GUI”, 10000, NULL, 1, NULL, 0);
}
void loop()
{
WiFiInit();
}
`