How to solve the problem of flickering display

Description

Hello everyone, i am developing a project using LVGL 9.2 and 4D-ESP32-GEN4-70-CLB from 4D-Systems. I am using a chart to visualize a signal. The chart is flickering very much.
i attached a video link.

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

What LVGL version are you using?

9.2

What do you want to achieve?

avoid/minimize flickering

What have you tried so far?

using one and/or two Buffers
reduced the LV_DEF_REFR_PERIOD to 10 and 20.
tried to use PSRAM, it minimized the flickering a little but its slowing down the other UI elements.
tried various sizes of draw_buf.

Code to reproduce

Add a code snippet which can run in the simulator. It should contain only the relevant code that compiles without errors when separated from your main code base.

The code block(s) should be formatted like:

#include <lvgl.h>
#include "gfx4desp32_gen4_ESP32_70CT_CLB.h"

#define TFT_HOR_RES 800
#define TFT_VER_RES 480
#define DRAW_BUF_SIZE (TFT_HOR_RES * TFT_VER_RES / 10 * (LV_COLOR_DEPTH / 8))
#define PI 3.14159265

uint32_t draw_buf[DRAW_BUF_SIZE / 4];
uint32_t draw_buf2[DRAW_BUF_SIZE / 4];

lv_display_t* display1;
lv_obj_t* chart;
lv_timer_t* chart_timer = NULL;
static lv_style_t style_chart_dark;
static lv_style_t style_chart_light;

gfx4desp32_gen4_ESP32_70CT_CLB gfx = gfx4desp32_gen4_ESP32_70CT_CLB();

void my_disp_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* color_p) {
  uint32_t numPixels = lv_area_get_size(area);
  //lv_draw_sw_rgb565_swap(color_p, (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1)); /// color swap, very important!!!!
  gfx.SetGRAM(area->x1, area->y1, area->x2, area->y2);
  gfx.pushColors((uint16_t*)color_p, numPixels);
  lv_display_flush_ready(disp);
}

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

static void add_data1(lv_timer_t* t) {
  static float phase = 0.0;
  static float freq = 0.2;
  lv_obj_t* chart = (lv_obj_t*)lv_timer_get_user_data(t);
  lv_chart_series_t* ser = lv_chart_get_series_next(chart, NULL);
  float raw = sin(2 * PI * phase);
  int32_t value = (int32_t)(raw * 35 + 50);    // Ergebnis in Bereich 15–85
  lv_chart_set_next_value(chart, ser, value);  // Skaliert auf sichtbare Werte
  phase += freq;
  if (phase >= 1.0) phase -= 1.0;
  lv_chart_refresh(chart);
  
}

void createDisplay() {
  display1 = lv_display_create(TFT_HOR_RES, TFT_VER_RES);
  lv_display_set_buffers(display1, draw_buf, draw_buf2, DRAW_BUF_SIZE, LV_DISPLAY_RENDER_MODE_PARTIAL);
  lv_display_set_default(display1);
  lv_display_set_flush_cb(display1, my_disp_flush_cb);
  lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0xff101010), LV_PART_MAIN | LV_STATE_DEFAULT);
}

void createChart() {
  chart = lv_chart_create(lv_screen_active());
  lv_chart_set_update_mode(chart, LV_CHART_UPDATE_MODE_SHIFT);
  lv_obj_set_pos(chart, 10, 8);
  lv_obj_set_size(chart, 780, 341);
  lv_obj_set_style_bg_color(chart, lv_color_hex(0xff000000), LV_PART_MAIN | LV_STATE_DEFAULT);
  lv_obj_set_style_line_width(chart, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
  lv_obj_set_style_size(chart, 0, 0, LV_PART_INDICATOR);
  lv_obj_set_style_border_color(chart, lv_color_hex(0xff631212), LV_PART_MAIN | LV_STATE_DEFAULT);
  lv_obj_set_style_radius(chart, 120, LV_PART_MAIN | LV_STATE_DEFAULT);
  lv_obj_set_style_border_width(chart, 5, LV_PART_MAIN | LV_STATE_DEFAULT);
  lv_obj_set_style_shadow_opa(chart, 255, LV_PART_INDICATOR | LV_STATE_DEFAULT);
  lv_obj_set_style_clip_corner(chart, true, LV_PART_MAIN | LV_STATE_DEFAULT);

  uint32_t i;
  lv_chart_set_point_count(chart, 60);
  lv_chart_series_t* ser = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y);
  for (i = 0; i < 80; i++) {
    lv_chart_set_next_value(chart, ser, (int32_t)lv_rand(10, 90));
  }
}

static void event_cb(lv_event_t* e) {
  Serial.println("clicked");
  LV_LOG_USER("Button %s clicked", lv_label_get_text(label));
}

void touch_read(lv_indev_t* indev, lv_indev_data_t* data) {
  gfx.touch_Update();
  bool touched = gfx.touch_GetPen();
  if (touched == TOUCH_PRESSED) {
    data->state = LV_INDEV_STATE_PRESSED;
    data->point.x = gfx.touch_GetX();
    data->point.y = gfx.touch_GetY();

  } else {
    data->state = LV_INDEV_STATE_RELEASED;
  }
}

void createinputDevice() {
  lv_indev_t* indev = lv_indev_create();           /* Create input device connected to Default Display. */
  lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); /* Touch pad is a pointer-like device. */
  lv_indev_set_read_cb(indev, touch_read);
}

void createRoller() {
  lv_obj_t* roller = lv_roller_create(lv_screen_active());
  lv_obj_set_pos(roller, 327, 357);
  lv_obj_set_size(roller, 130, 110);
  lv_roller_set_options(roller, "Option 1\nOption 2\nOption 3", LV_ROLLER_MODE_NORMAL);
  lv_obj_set_style_bg_color(roller, lv_color_hex(0xffdd3636), LV_PART_MAIN | LV_STATE_DEFAULT);
  lv_obj_set_style_text_color(roller, lv_color_hex(0xffffffff), LV_PART_MAIN | LV_STATE_DEFAULT);
  lv_obj_set_style_border_opa(roller, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
  lv_obj_set_style_radius(roller, 20, LV_PART_MAIN | LV_STATE_DEFAULT);
  lv_obj_set_style_bg_color(roller, lv_color_hex(0xff28252a), LV_PART_SELECTED | LV_STATE_DEFAULT);
  lv_obj_add_event_cb(roller, roller_event, LV_EVENT_ALL, NULL);
}

static void roller_event(lv_event_t* e) {
  lv_event_code_t code = lv_event_get_code(e);
  lv_obj_t* obj = lv_event_get_target_obj(e);
  char buf[32];
  lv_roller_get_selected_str(obj, buf, sizeof(buf));

  if (code == LV_EVENT_VALUE_CHANGED) {

    if (chart_timer != NULL) {
      lv_timer_del(chart_timer);
      chart_timer = NULL;
    }

    if (strcmp(buf, "Option 1") == 0) {
      chart_timer = lv_timer_create(add_data1, 200, chart);
      Serial.println("Option 1");
    } else if (strcmp(buf, "Option 2") == 0) {
      chart_timer = lv_timer_create(add_data1, 200, chart);
      Serial.println("Option 2");
    } else if (strcmp(buf, "Option 3") == 0) {
      chart_timer = lv_timer_create(add_data1, 200, chart);
      Serial.println("Option 3");
    }
  }
}

void setup() {
  Serial.begin(115200);
  lv_init();
  gfx.begin();
  gfx.touch_Set(TOUCH_ENABLE);
  

  createDisplay();
  lv_tick_set_cb(my_tick);
  createChart();
  createinputDevice();
  createRoller();

  lv_obj_t* toggle = lv_switch_create(lv_screen_active());
  lv_obj_align(toggle, LV_ALIGN_BOTTOM_LEFT, 0, 0);
  lv_obj_set_size(toggle, 200, 100);
  lv_obj_add_state(toggle, LV_STATE_CHECKED);
}

void loop() {
  lv_timer_handler();
}

Screenshot and/or video

Video Link

What you’re seeing is called screen tearing. Unless you get a better suggestion here, maybe try to simply increase the refresh rate in lv_conf as a start.

On my project I have also noticed that performance got smoother once I called lv_timer_handler() only ever 5ms or so. But maybe that has to do with specifics of my project.

I am very familiar with the ESP32 drivers and the size display you are using with the connection to the display being a 16 lane RGB connection.

Your code is going to have to be a hell of a lot more involved than what you have. You are going to need to use FreeRTOS (comes with the ESP-IDF SDK) to create a task on the second core of the ESP32 that will handle copying data to the full sized frame buffers.

This is what is happening. You are using an RGB display which means the display doesn’t have any internal memory (GRAM). Because of that the ESP32 is going to be responsible for keeping a constant flow of pixel data to the display. Even when the display has nothing being updated on it the stream of pixels keeps on being sent.

The internal mechanics of the RGB driver on the ESP32 creates a single frame buffer that holds the pixel data for everything you see on the display. You are writing to a smaller buffer in LVGL and then when that smaller buffer gets passed to the RGB driver the data in that buffer gets copied to the full buffer. what you are seeing is happening because only part of what is needing to be updated is being written…

what needs to be done is some “hacking” in order to be able to collect the full buffer from within the RGB driver and also to know when a buffer has actually finished being written. You need to stop the RGB driver from copying from one buffer to the next as that is something you are going to have to have to handle in your code. The reason why is you only want to write the full buffer to the display once all updates have finished rendering. That will eliminate the problems you are seeing.

I have written a driver to do this exact thing but it is part of another program and I would have to separate it from that program and modify it so it can be used as an add-on component/package.

I had started to do that and I have not finished it yet. I guess I could do some more work on it. It is going to take me a week to a week and a 1/2 to complete.

do yo have any example how to use it with lvgl?

i am excited to see it when it’s finished

when i use a bigger buffer, it doesnt compile because its too big for the RAM. I tried using SPIRAM with LV_DISPLAY_RENDER_MODE_FULL, but the reaction to touches got very slow ans i still have the flickering.

I just realized that you are using the Arduino IDE. What I am working on is not going to help you. It’s not compatible.

it did’nt change anything