Lvgl crash + boot loop

Hi, Again me with similar issue that i had a year ago.
Unfortunately i lost all of my files, and didn’t code for a year so my memory is pretty bad.
I hope someone can help.

Setup: Ili9341+ sn65hvd230+esp32
Issue: As soon as i plug in the sn65hvd230 into the obd2 ( ofc. it request can data), my splash screen loads ( I get one CAN) packet, and then the esp32 crashes. And then it loops.
If i disconnect the obd2 port from the car, esp32 works normally ( splash screen, main screen)

Code:

#include <lvgl.h>
#include <TFT_eSPI.h>
#include <ui.h>
#include <ACAN_ESP32.h>

// LVGL display setup
static const uint16_t screenWidth = 320;
static const uint16_t screenHeight = 240;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * screenHeight / 10];
TFT_eSPI tft = TFT_eSPI(screenWidth, screenHeight);

// CAN settings
ACAN_ESP32_Settings settings(500 * 1000);  // 500 kbps
unsigned long lastRequestTime = 0;
const uint8_t PID_COOLANT_TEMP = 0x05;

// State flag
bool canInitialized = false;
unsigned long canInitAttemptTime = 0;

// LVGL flush
void my_disp_flush(lv_disp_drv_t *disp, 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.pushColors((uint16_t *)&color_p->full, w * h, true);
  tft.endWrite();

  lv_disp_flush_ready(disp);
}

// Send OBD2 PID request
void sendPIDRequest(uint8_t pid) {
  CANMessage msg;
  msg.id = 0x7DF;           // functional request ID
  msg.len = 8;
  msg.data[0] = 0x02;       // number of additional bytes
  msg.data[1] = 0x01;       // service 01 - show current data
  msg.data[2] = pid;        // requested PID
  msg.data[3] = 0x55;
  msg.data[4] = 0x55;
  msg.data[5] = 0x55;
  msg.data[6] = 0x55;
  msg.data[7] = 0x55;
  ACAN_ESP32::can.tryToSend(msg);
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("ESP32 LVGL + CAN setup...");

  // TFT and LVGL init
  lv_init();
  tft.begin();
  tft.setRotation(3);

  lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10);

  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = screenWidth;
  disp_drv.ver_res = screenHeight;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.draw_buf = &draw_buf;
  lv_disp_drv_register(&disp_drv);

  // SquareLine UI init
  ui_init();

  // Set CAN TX/RX pins (TX = 17, RX = 16)
  settings.mTxPin = GPIO_NUM_17;
  settings.mRxPin = GPIO_NUM_16;

  // Start timer for delayed CAN init
  canInitAttemptTime = millis();

  Serial.println("Setup done - UI initialized");
}

void loop() {
  lv_timer_handler();  // LVGL loop
  delay(5);

  // CAN init (odgođeni pokušaji)
  if (!canInitialized && millis() - canInitAttemptTime > 3000) {
    Serial.println("Trying to initialize CAN...");
    const uint32_t errorCode = ACAN_ESP32::can.begin(settings);
    if (errorCode == 0) {
      Serial.println("CAN initialized successfully!");
      canInitialized = true;
      sendPIDRequest(PID_COOLANT_TEMP);
      lastRequestTime = millis();
    } else {
      Serial.print("CAN init failed. Error code: ");
      Serial.println(errorCode);
      canInitAttemptTime = millis(); // retry in 3s
    }
  }

  // Ako je CAN aktivan
  if (canInitialized) {
    // Primi CAN poruke
    CANMessage msg;
    while (ACAN_ESP32::can.receive(msg)) {
      if (msg.id >= 0x7E8 && msg.id <= 0x7EF && msg.len >= 4) {
        if (msg.data[1] == 0x41 && msg.data[2] == PID_COOLANT_TEMP) {
          int tempC = msg.data[3] - 40;
          lv_arc_set_value(ui_engineCoolantTempGauge, tempC);
          lv_label_set_text_fmt(ui_textECT, "%d°C", tempC);
        }
      }
    }

    // Povremeni PID zahtjev
    if (millis() - lastRequestTime > 2000) {
      sendPIDRequest(PID_COOLANT_TEMP);
      lastRequestTime = millis();
    }
  }
}


I remember that last time part of the code in void setup(), was the culprit for crashing, but now i tried to remove the code from that part.

I know that my SN65hvd230 is correctly wired in because i ran a sketch that request s data ( lcd plugged in, but without the code) and it returns correct data in serial monitor.

Any ideas how to fix this?

if you can provide the decoded backtrace data from the crash it would tell you exactly where the crash is happening.

also knowing the specific model of the MCU you are using would be helpful. things like amount is SPIRAM, knowing what the hardware is that you are using. resolutions, color depth

Knowing software versions, LVGL version TFT-eSPI version things of that nature…

Hi,
I think i have sorted this issue, but I have tested on a bench with two esp32 one acting as a obd2 car sending canbus, other connected to the screen and reciving canbus packet.

#include <lvgl.h>
#include <TFT_eSPI.h>
#include <ui.h>
#include <ACAN_ESP32.h>

// LVGL display buffer and driver
static const uint16_t screenWidth = 320;
static const uint16_t screenHeight = 240;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * screenHeight / 10];
TFT_eSPI tft = TFT_eSPI(screenWidth, screenHeight);

// CAN state tracking
bool canStarted = false;
bool mainScreenShown = false;
unsigned long lastCANRequestTime = 0;

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

// Flush callback for LVGL
void my_disp_flush(lv_disp_drv_t *disp, 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.pushColors((uint16_t *)&color_p->full, w * h, true);
  tft.endWrite();

  lv_disp_flush_ready(disp);
}

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

  // Initialize LVGL
  lv_init();
#if LV_USE_LOG != 0
  lv_log_register_print_cb(my_print);
#endif

  // TFT setup
  tft.begin();
  tft.setRotation(3);

  // LVGL buffer & driver
  lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10);
  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = screenWidth;
  disp_drv.ver_res = screenHeight;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.draw_buf = &draw_buf;
  lv_disp_drv_register(&disp_drv);

  // Load UI from SquareLine
  ui_init();

  Serial.println("✅ LVGL UI setup complete");
}

void loop() {
  lv_timer_handler();
  delay(5);

  // Check if SplashScreen has transitioned to MainScreen
  if (!mainScreenShown && lv_scr_act() == ui_MainScreen) {
    mainScreenShown = true;
    Serial.println("📺 Main screen now active");

    // Initialize CAN bus
    ACAN_ESP32_Settings settings(500 * 1000); // 500kbps
    settings.mRxPin = GPIO_NUM_16;
    settings.mTxPin = GPIO_NUM_17;

    const uint32_t err = ACAN_ESP32::can.begin(settings);
    if (err == 0) {
      canStarted = true;
      Serial.println("🚗 CAN started");
    } else {
      Serial.printf("❌ CAN init failed: %lu\n", err);
    }
  }

  // Handle CAN only after splash screen & CAN setup
  if (mainScreenShown && canStarted) {
    unsigned long now = millis();

    // Send coolant temp request every 1 second
    if (now - lastCANRequestTime > 1000) {
      lastCANRequestTime = now;

      CANMessage req;
      req.id = 0x7DF; // OBD functional request
      req.ext = false;
      req.len = 8;
      req.data[0] = 0x02;
      req.data[1] = 0x01;
      req.data[2] = 0x05;
      req.data[3] = req.data[4] = req.data[5] = req.data[6] = req.data[7] = 0;

      if (ACAN_ESP32::can.tryToSend(req)) {
        Serial.println("📤 Request sent for coolant temp (PID 05)");
      } else {
        Serial.println("❌ Failed to send request");
      }
    }

    // Check for reply
    CANMessage frame;
    if (ACAN_ESP32::can.receive(frame)) {
      if (frame.id == 0x7E8 && frame.data[1] == 0x41 && frame.data[2] == 0x05) {
        int temp = frame.data[3] - 40; // OBD-II temp = raw - 40
        Serial.printf("🌡️  Coolant Temperature: %d°C\n", temp);

        // Update UI
        lv_label_set_text_fmt(ui_engineCoolantTempGauge, "%d°C", temp);
        lv_arc_set_value(ui_textECT, temp);
      }
    }
  }
}

It seems to work. There was an issue if you start to initialize can withouth MainScreen being loaded. And it crashed.
But now i have another issue.
The screen is flickering, text values are updating smoothly, and arc is smooth. But it seems like its redrawing background every time. I think i didnt have this issue before. It’s flickering fast, like every frame and it’s noticeable since my backlight is allways ON ( not pwm controlled)

#include <lvgl.h>
#include <TFT_eSPI.h>
#include "ui.h"  // Your SquareLine Studio generated header

// Screen resolution
static const uint16_t screenWidth  = 320;
static const uint16_t screenHeight = 240;

// LVGL draw buffer and TFT instance
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * screenHeight / 10];
TFT_eSPI tft = TFT_eSPI(screenWidth, screenHeight);

// Labels for FPS and CPU
lv_obj_t *label_fps = nullptr;
lv_obj_t *label_cpu = nullptr;
uint32_t last_fps = 0;
uint32_t last_cpu = 0;

// Global buffers for last text (to reduce flicker)
char last_fps_str[16] = "";
char last_cpu_str[16] = "";

// Animation variables
int temp = -40;
bool tempIncreasing = true;

// Display flush callback for LVGL
void my_disp_flush(lv_disp_drv_t *disp, 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.pushColors((uint16_t *)&color_p->full, w * h, true);
  tft.endWrite();

  lv_disp_flush_ready(disp);
}

// Monitor callback to track FPS & CPU usage
void monitor_cb(lv_disp_drv_t *d, uint32_t time_ms, uint32_t px) {
  if (time_ms > 0) {
    last_fps = 1000 / time_ms;
  } else {
    last_fps = 0;
  }
  last_cpu = (time_ms > 16) ? 100 : (time_ms * 100) / 16;
}

// Create FPS and CPU labels on current active screen
void create_fps_cpu_labels() {
  label_fps = lv_label_create(lv_scr_act());
  lv_obj_align(label_fps, LV_ALIGN_TOP_RIGHT, -10, 5);
  lv_label_set_text(label_fps, "FPS: --");

  label_cpu = lv_label_create(lv_scr_act());
  lv_obj_align(label_cpu, LV_ALIGN_TOP_RIGHT, -10, 25);
  lv_label_set_text(label_cpu, "CPU: --%");
}

// Update FPS and CPU labels if text changed
void update_fps_cpu_labels() {
  char buf[16];

  snprintf(buf, sizeof(buf), "FPS: %lu", last_fps);
  if (strcmp(buf, last_fps_str) != 0) {
    lv_label_set_text(label_fps, buf);
    strcpy(last_fps_str, buf);
  }

  snprintf(buf, sizeof(buf), "CPU: %lu%%", last_cpu);
  if (strcmp(buf, last_cpu_str) != 0) {
    lv_label_set_text(label_cpu, buf);
    strcpy(last_cpu_str, buf);
  }
}

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

  // Initialize TFT
  tft.begin();
  tft.setRotation(3); // Landscape

  // Initialize LVGL
  lv_init();

  // Initialize draw buffer
  lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10);

  // Setup and register display driver
  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = screenWidth;
  disp_drv.ver_res = screenHeight;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.draw_buf = &draw_buf;
  disp_drv.monitor_cb = monitor_cb;
  lv_disp_drv_register(&disp_drv);

  // Initialize UI from SquareLine Studio
  ui_init();

  // Load main screen immediately (no splash)
  lv_scr_load(ui_MainScreen);
}

void loop() {
  lv_timer_handler();
  delay(5);

  static unsigned long lastUpdate = 0;
  static unsigned long mainScreenStart = 0;
  static bool fpsLabelsCreated = false;
  unsigned long now = millis();

  // Mark when main screen loaded (first time loop runs after setup)
  if (mainScreenStart == 0) {
    mainScreenStart = now;
  }

  // Animate temperature from -40 to 130 and back
  if (now - lastUpdate > 200) {
    lastUpdate = now;

    if (tempIncreasing) {
      temp++;
      if (temp >= 130) tempIncreasing = false;
    } else {
      temp--;
      if (temp <= -40) tempIncreasing = true;
    }

    lv_arc_set_value(ui_textECT, temp);
    lv_label_set_text_fmt(ui_engineCoolantTempGauge, "%d°C", temp);
  }

  // Create FPS/CPU labels only after 7 seconds on main screen
  if (!fpsLabelsCreated && (now - mainScreenStart >= 7000)) {
    create_fps_cpu_labels();
    fpsLabelsCreated = true;
  }

  // Update FPS/CPU labels every 500ms only if created
  static unsigned long lastStatsUpdate = 0;
  if (fpsLabelsCreated && (now - lastStatsUpdate > 500)) {
    lastStatsUpdate = now;
    update_fps_cpu_labels();
  }
}

Like with this simple code to test the arc and label. Every 200ms ( when the values update) i get a screen flicker (white to black)  ( white since the backlight is white) and allways on.  My frame rate is 16 that is really low when its graphing the arc. 

// This file was generated by SquareLine Studio
// SquareLine Studio version: SquareLine Studio 1.5.3
// LVGL version: 8.3.11
// Project name: SquareLine_Project

#include "ui.h"

lv_obj_t * ui_MainScreen = NULL;
lv_obj_t * ui_Image1 = NULL;
lv_obj_t * ui_textECT = NULL;
lv_obj_t * ui_Label1 = NULL;
lv_obj_t * ui_engineCoolantTempGauge = NULL;
// event funtions

// build funtions

void ui_MainScreen_screen_init(void)
{
    ui_MainScreen = lv_obj_create(NULL);
    lv_obj_clear_flag(ui_MainScreen, LV_OBJ_FLAG_SCROLLABLE);      /// Flags

    ui_Image1 = lv_img_create(ui_MainScreen);
    lv_img_set_src(ui_Image1, &ui_img_pozadina_png);
    lv_obj_set_width(ui_Image1, LV_SIZE_CONTENT);   /// 1
    lv_obj_set_height(ui_Image1, LV_SIZE_CONTENT);    /// 1
    lv_obj_set_align(ui_Image1, LV_ALIGN_CENTER);
    lv_obj_add_flag(ui_Image1, LV_OBJ_FLAG_IGNORE_LAYOUT | LV_OBJ_FLAG_OVERFLOW_VISIBLE);     /// Flags
    lv_obj_clear_flag(ui_Image1, LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE |
                      LV_OBJ_FLAG_SNAPPABLE | LV_OBJ_FLAG_SCROLLABLE);     /// Flags

    ui_textECT = lv_arc_create(ui_MainScreen);
    lv_obj_set_width(ui_textECT, 215);
    lv_obj_set_height(ui_textECT, 215);
    lv_obj_set_align(ui_textECT, LV_ALIGN_CENTER);
    lv_obj_add_flag(ui_textECT, LV_OBJ_FLAG_OVERFLOW_VISIBLE);     /// Flags
    lv_obj_clear_flag(ui_textECT, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE |
                      LV_OBJ_FLAG_GESTURE_BUBBLE | LV_OBJ_FLAG_SNAPPABLE | LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC |
                      LV_OBJ_FLAG_SCROLL_MOMENTUM | LV_OBJ_FLAG_SCROLL_CHAIN);     /// Flags
    lv_arc_set_range(ui_textECT, 0, 130);
    lv_arc_set_value(ui_textECT, 90);
    lv_obj_set_style_arc_color(ui_textECT, lv_color_hex(0x4040FF), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_arc_opa(ui_textECT, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_arc_width(ui_textECT, 25, LV_PART_MAIN | LV_STATE_DEFAULT);

    lv_obj_set_style_arc_width(ui_textECT, 220, LV_PART_INDICATOR | LV_STATE_DEFAULT);
    lv_obj_set_style_arc_rounded(ui_textECT, false, LV_PART_INDICATOR | LV_STATE_DEFAULT);
    lv_obj_set_style_arc_img_src(ui_textECT, &ui_img_krugic_png, LV_PART_INDICATOR | LV_STATE_DEFAULT);

    lv_obj_set_style_bg_color(ui_textECT, lv_color_hex(0xFFFFFF), LV_PART_KNOB | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_textECT, 0, LV_PART_KNOB | LV_STATE_DEFAULT);

    ui_Label1 = lv_label_create(ui_MainScreen);
    lv_obj_set_width(ui_Label1, LV_SIZE_CONTENT);   /// 1
    lv_obj_set_height(ui_Label1, LV_SIZE_CONTENT);    /// 1
    lv_obj_set_x(ui_Label1, 0);
    lv_obj_set_y(ui_Label1, 24);
    lv_obj_set_align(ui_Label1, LV_ALIGN_CENTER);
    lv_label_set_text(ui_Label1, "Temperatura motora");
    lv_obj_clear_flag(ui_Label1, LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE |
                      LV_OBJ_FLAG_SNAPPABLE | LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM |
                      LV_OBJ_FLAG_SCROLL_CHAIN);     /// Flags
    lv_obj_set_style_text_color(ui_Label1, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_opa(ui_Label1, 210, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_align(ui_Label1, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_font(ui_Label1, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT);

    ui_engineCoolantTempGauge = lv_label_create(ui_MainScreen);
    lv_obj_set_width(ui_engineCoolantTempGauge, LV_SIZE_CONTENT);   /// 1
    lv_obj_set_height(ui_engineCoolantTempGauge, LV_SIZE_CONTENT);    /// 1
    lv_obj_set_x(ui_engineCoolantTempGauge, 3);
    lv_obj_set_y(ui_engineCoolantTempGauge, -5);
    lv_obj_set_align(ui_engineCoolantTempGauge, LV_ALIGN_CENTER);
    lv_label_set_text(ui_engineCoolantTempGauge, "90°C");
    lv_obj_set_style_text_align(ui_engineCoolantTempGauge, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_text_font(ui_engineCoolantTempGauge, &lv_font_montserrat_40, LV_PART_MAIN | LV_STATE_DEFAULT);

}

void ui_MainScreen_screen_destroy(void)
{
    if(ui_MainScreen) lv_obj_del(ui_MainScreen);

    // NULL screen variables
    ui_MainScreen = NULL;
    ui_Image1 = NULL;
    ui_textECT = NULL;
    ui_Label1 = NULL;
    ui_engineCoolantTempGauge = NULL;

}


Mainscreen.c
what am i doing wrong?
Like when nothing is graphing (arc) i can reach 80-100 fps

I think the issue is that i dont have backlight via pwm.
-When i set white foreground and black text no flickering is visible

OK so there are some things that I am seeing. The first thing is you are using an old version of LVGL. The second thing is you are not using double buffering with DMA memory. The last thing is you are using the Aduino IDE which adds a large amount of overhead.

as far as the flickering is concerned with the display. It doesn’t have anything to do with PWM on the backlight. It could however be caused by not enough power. How are you powering the display? If you are using a wall wart (AC to DC power supply) it might not be filtering out all of the AC bounce and if the design of the power supply for the back lighting doesn’t have enough filtering on it then you could be seeing some of that AC bounce realized in the backlight for the display. If this is the case then if you set the entire display to white the flickering would be super noticeable.

You could also simply be seeing what is called tearing which is caused by how fast you are able to update chunks of the display. The best way to handle this is to use double buffering with DMA memory.

#include <lvgl.h>
#include <TFT_eSPI.h>
#include <ui.h>  // SquareLine Studio generated

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

// ===== DOUBLE BUFFERING WITH DMA =====
static lv_disp_draw_buf_t draw_buf;
// Allocate two buffers for double buffering, DMA-capable
// screenWidth * screenHeight / 10 is safe (partial buffer)
static lv_color_t *buf1 = nullptr;
static lv_color_t *buf2 = nullptr;

TFT_eSPI tft = TFT_eSPI(screenWidth, screenHeight);

#if LV_USE_LOG != 0
void my_print(const char * buf) {
    Serial.printf("%s", buf);
    Serial.flush();
}
#endif

// ===== FLUSH CALLBACK =====
void my_disp_flush(lv_disp_drv_t *disp, 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.pushColors((uint16_t *)&color_p->full, w * h, true);  // true = use DMA if available
    tft.endWrite();

    lv_disp_flush_ready(disp);
}

void setup() {
    Serial.begin(115200);
    lv_init();

#if LV_USE_LOG != 0
    lv_log_register_print_cb(my_print);
#endif

    tft.begin();
    tft.setRotation(3);  // Landscape

    // ===== Allocate DMA-CAPABLE BUFFERS =====
    buf1 = (lv_color_t *)heap_caps_malloc((screenWidth * screenHeight / 10) * sizeof(lv_color_t), MALLOC_CAP_DMA);
    buf2 = (lv_color_t *)heap_caps_malloc((screenWidth * screenHeight / 10) * sizeof(lv_color_t), MALLOC_CAP_DMA);

    if (!buf1 || !buf2) {
        Serial.println("Failed to allocate display buffers!");
        while (1);  // Halt
    }

    lv_disp_draw_buf_init(&draw_buf, buf1, buf2, screenWidth * screenHeight / 10);

    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = screenWidth;
    disp_drv.ver_res = screenHeight;
    disp_drv.flush_cb = my_disp_flush;
    disp_drv.draw_buf = &draw_buf;
    lv_disp_drv_register(&disp_drv);

    ui_init();

    Serial.println("LVGL setup complete with double buffering and DMA");
}

// --------- Animation Example ----------
int temp = -40;
int dir = 1;
unsigned long lastUpdate = 0;
const unsigned long updateInterval = 50;

void loop() {
    lv_timer_handler();

    unsigned long now = millis();
    if (now - lastUpdate > updateInterval) {
        lastUpdate = now;

        temp += dir;
        if (temp >= 130) dir = -1;
        else if (temp <= -40) dir = 1;

        lv_arc_set_value(ui_textECT, temp);
        lv_label_set_text_fmt(ui_engineCoolantTempGauge, "%d°C", temp);
    }

    delay(5);
}

Im powering the board from my laptop usb port. I ran tft_espi matrix sketch and it runs without flickering.

Is this code okay for double buffering?
Because im still getting flickering, it seems like its rendering the whole screen.
Im using a bit older version of LVGL because of squareline studio. I have added an arc ( image), and a image in background as a “gui”.

What i tried to to is to remove all the images just leave the text label, and even still then i got flickering. Like the animation of values is smooth, its just like its rendering the whole screen and with Backlight being on the flicker is more noticeable.

I cant seem to get the part of the arc to update.

the lv_disp_flush_ready(disp); call is in the wrong location. You need to register a separate callback for when the buffer is finished being transmitted. that is where you put that call. I don’t know how to do that with display driver library you are using.