Distorted display

I am trying to build a digital clock - this is my very first LVGL project (I have decades of development experience, though, with C and many other languages.)

My approach to LVGL seems to be misguided on this project, as my results seem to involve a corrupted display or similar no-no that I assume is due to LVGL unfamiliarity.

This example code demonstrates the issue, which manifests with these displays in order, and increasingly corrupt after that)…

The display is actually a Waveshare ESP32-S3-Touch-AMOLED-1.75 board, and the environment is Arduino 2.3.3 with LVGL 8.4.0. I have not found any tutorials that explain how to get started, only examples from which (I suppose) I should be able to get the gist of what’s happening. I have always found it hard to learn by example because examples rarely explain the reasons behind the code, and the choices that had to be made to get there.

So I’ve made this effort, but clearly there are some things I don’t understand. Glaringly, of course, is the question of why the display gets (or looks) corrupted. But also, I haven’t grasped the use of lv_timer_handler() and lv_obj_update_layout(entity). And probably other things are misused or abused below.

I am not looking for someone to correct this; rather, I want to understand how I have gone wrong and what the right approach would be. And if someone wants to point out ways my approach could improve, I’m eagerly open to that.

Can you help?

#include "rc.h"

lv_disp_draw_buf_t draw_buf;
lv_color_t buf[LCD_WIDTH * LCD_HEIGHT / 10];
LV_FONT_DECLARE(DigiClock_120); 

lv_obj_t *label;  // Global label object
SensorPCF85063 rtc;

Arduino_DataBus *bus = new Arduino_ESP32QSPI(
  LCD_CS /* CS */, LCD_SCLK /* SCK */, LCD_SDIO0 /* SDIO0 */, LCD_SDIO1 /* SDIO1 */,
  LCD_SDIO2 /* SDIO2 */, LCD_SDIO3 /* SDIO3 */);

Arduino_CO5300 *gfx = new Arduino_CO5300(
  bus, LCD_RESET /* RST */, 0 /* rotation */, LCD_WIDTH /* width */, LCD_HEIGHT /* height */, 6, 0, 0, 0);

void setup() {

  Serial.begin(115200); /* prepare for possible serial debug */
  while (! Serial) {} // Wait for serial connection

  gfx->begin();
  gfx->setBrightness(128);

  lv_init();

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

  lv_disp_draw_buf_init(&draw_buf, buf, NULL, LCD_WIDTH * LCD_HEIGHT / 10);

  /*Initialize the display*/
  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  /*Change the following line to your display resolution*/
  disp_drv.hor_res = LCD_WIDTH;
  disp_drv.ver_res = LCD_HEIGHT;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.rounder_cb = example_lvgl_rounder_cb;
  disp_drv.draw_buf = &draw_buf;
  disp_drv.sw_rotate = 1 ;
  disp_drv.rotated = LV_DISP_ROT_NONE ;
  lv_disp_drv_register(&disp_drv);

  lv_obj_t * scr = lv_scr_act();
  lv_obj_set_style_bg_color(scr, lv_color_hex(0x000000), LV_PART_MAIN);    // The page is black
  lv_obj_set_style_text_color(scr, lv_color_hex(0xffffff), LV_PART_MAIN);  // The ink is white
  lv_disp_set_rotation(NULL, LV_DISP_ROT_270) ; // We set "no rotation" and then "rotate 270" because straight-in 270 did not work for me

  /*Initialize the (dummy) input device driver*/
  static lv_indev_drv_t indev_drv;
  lv_indev_drv_init(&indev_drv);
  indev_drv.type = LV_INDEV_TYPE_POINTER;
  lv_indev_drv_register(&indev_drv);

  const esp_timer_create_args_t lvgl_tick_timer_args = {
    .callback = &example_increase_lvgl_tick,
    .name = "lvgl_tick"
  };

  const esp_timer_create_args_t reboot_timer_args = {
    .callback = &example_increase_reboot,
    .name = "reboot"
  };

  #define EXAMPLE_LVGL_TICK_PERIOD_MS 2
  esp_timer_handle_t lvgl_tick_timer = NULL;
  esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer);
  esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000);

  label = lv_label_create(lv_scr_act());
  lv_obj_set_style_text_font(label, &DigiClock_120, LV_PART_MAIN);  
  lv_obj_set_style_bg_color(label, lv_color_hex(0x000000), LV_PART_MAIN);    // The page is black
  lv_obj_set_style_text_color(label, lv_color_hex(0xffffff), LV_PART_MAIN);  // The ink is white
  lv_label_set_text(label,"Init...");
  lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
  lv_obj_update_layout(label) ;
  Serial.println("LCD Width = " + String(LCD_WIDTH) + ", Height = " + String(LCD_HEIGHT)) ;

  lv_obj_set_style_text_font(label, &DigiClock_120, LV_PART_MAIN);
  lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
  lv_obj_update_layout(label) ;

  if (!rtc.begin(Wire, IIC_SDA, IIC_SCL)) {
    Serial.println("Failed to find PCF85063 SDA/SCL - check your wiring!");
    Serial.print("IIC_SDA: ") ;
    Serial.print(IIC_SDA) ;
    Serial.print(", IIC_SCL: ") ;
    Serial.println(IIC_SCL) ;
    while (1) {
      delay(1000);
    }
  }
  Serial.print("The value of LV_COLOR_16_SWAP is ") ;
  Serial.println(LV_COLOR_16_SWAP) ;

  rtc.setDateTime(2026, 3, 3, 15, 20, 45) ;
  int i = 3 ;
  while (i) {
    lv_label_set_text_fmt(label, "Setup\nEnded\n%d", i) ;
    lv_timer_handler() ;
    delay(1000) ;
    i-- ;
  }
}



void updateClock() {
  static int lastMinuteOrSecond = -1 ; // Determines if we need to refresh the display
  int thisMinuteOrSecond ;             // Determines if we need to refresh the display
  RTC_DateTime datetime = rtc.getDateTime();

  lv_timer_handler();
  delay(5); 

#ifdef SHOW_SECONDS
  thisMinuteOrSecond = datetime.getSecond() ;
#else
  thisMinuteOrSecond = datetime.getMinute() ;
#endif
  if (thisMinuteOrSecond != lastMinuteOrSecond) { // Displayed time has changed
    lastMinuteOrSecond = thisMinuteOrSecond ;

#ifdef SHOW_SECONDS
    lv_label_set_text_fmt(label, " %02d/%02d\n%02d:%02d:%02d"
                                  , datetime.getMonth(), datetime.getDay()
                                  , datetime.getHour(), datetime.getMinute(), datetime.getSecond()
                                      );
#else
    lv_label_set_text_fmt(label, " %02d/%02d\n %02d:%02d\n76F 24C"
                                  , datetime.getMonth(), datetime.getDay()
                                  , datetime.getHour(), datetime.getMinute()
                                        );
#endif

    // Update label with current time
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
    lv_obj_update_layout(label) ;
    lv_timer_handler();
  }
}



void loop() {
  static int firstTime = 1 ;
  if (firstTime) {
    lv_label_set_text(label, "loop...") ;
    lv_obj_update_layout(label) ;
    lv_timer_handler() ;
    delay(2000) ;
  }
  updateClock() ;
  delay(20);
  if (firstTime) {
    lv_timer_handler() ;
    delay(2000) ;
    lv_label_set_text(label, "again") ;
    lv_obj_update_layout(label) ;
    lv_timer_handler() ;
    delay(2000) ;
    firstTime = 0 ;
  }
}




void example_lvgl_rounder_cb(struct _lv_disp_drv_t *disp_drv, lv_area_t *area) {
    area->x1 &= 0xfffffffe ; // Make it even - was:  if(area->x1 % 2 !=0)area->x1-- ;
    area->y1 &= 0xfffffffe ; // Make it even - was:  if(area->y1 % 2 !=0)area->y1-- ;
    area->x2 |= 0x00000001 ; // Make it odd  - was:  if(area->x2 %2 ==0)area->x2++ ;
    area->y2 |= 0x00000001 ; // Make it odd  - was:  if(area->y2 %2 ==0)area->y2++ ;
}


/* Display flushing */
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);

#if (LV_COLOR_16_SWAP != 0)
  gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
#else
  gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
#endif

  lv_disp_flush_ready(disp);
}

void example_increase_lvgl_tick(void *arg) {
  /* Tell LVGL how many milliseconds has elapsed */
  lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}

void example_increase_reboot(void *arg) {
  static uint8_t count = 0;
  count++;
  if (count == 30) {
    esp_restart();
  }
}


the rc.h file contains:

#include <lvgl.h>
#include "lv_conf.h"
#include "Arduino_GFX_Library.h"
#include "pin_config.h"
#include <Wire.h>
#include <SPI.h>
#include <Arduino.h>
#include "SensorPCF85063.hpp"

void example_lvgl_rounder_cb(struct _lv_disp_drv_t *disp_drv, lv_area_t *area) ;
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) ;
void example_increase_lvgl_tick(void *arg) ;
void example_increase_reboot(void *arg) ;

@delovelady, thanks for sharing. Is there a specific reason to use version 8.4.0? Considering this is the first project using LVGL, I suggest you use the main version and try running some examples. For this board, I found a repository hosted by Waveshare that contains examples for Arduino.

Please let me know if you have any trouble.

Thanks, @halyssonJr

It was from those examples (03…simpleTime, in fact) that the basis for this code was derived. (Recall what I said about examples not doing it for me. This is a prime example of why.)

Also, if the instructions are followed to the letter from that site, the version of LVGL that will be used, is the version I have used.

I still hope that, rather than throw other examples at me, someone might point out what is wrong with this code, which is what I thought I had asked.

@delovelady , I reviewed your code, and I noticed some inconsistencies that could be causing the distortions. Save your project as .txt, but you can rename it or just copy and paste it to your Arduino project. Please, check if it’s works.

demo.txt (5.9 KB)

Thanks for this. I need a few hours; I’m traveling now. I appreciate your efforts!@

So sorry I’ve been quiet for so long. You know that old curse: “May you live in interesting times.” I must say it has been interesting around here of late.

Anyway, back to the topic. The sample you provided resulted in no display at all … that is until I reinstated calls to lv_timer_handler(). Then, the distortion returned.

I did additional testing with this and have isolated the problem to the call to lv_disp_set_rotation(). If that is removed, the display stays solid (not distorted), although of course the orientation is not as desired.

Thoughts?

@delovelady,I simulated part of your project using WOKWI and, after reviewing your code, it seems to be working. I’m sharing a file containing: diagram.json, wokwi.toml, and the source code. Again, it’s not necessary to run the lv_timer_handler function on each label update.

LVGL_Demo.zip (23.3 KB)

Thanks, @halyssonJr - All I know is what I have tried. This may be one problem with simulation versus the real thing. In my case, for whatever reason, the display distorts when I try to rotate it via that method. And Waveshare support says that is the official method to use.

I am trying to move to LVGL v9 but that seems to be a major hurdle for someone coming from 0 experience. The distortion “feels” like a bug to me, and I expect it’s probably been resolved (the bad thing about being backlevel). But, oh my, they didn’t seem to make the transition easy, did they?

This portion of your code is incorrect…

disp_drv.sw_rotate should be set to 0. You are trying to manage the rotation the wrong way.

this line is where you want to handle the rotation…

Arduino_CO5300 *gfx = new Arduino_CO5300(
  bus, LCD_RESET /* RST */, 0 /* rotation */, LCD_WIDTH /* width */, LCD_HEIGHT /* height */, 6, 0, 0, 0);

you can see right here…

0 /* rotation */

you need to look into the driver package you are using and see what the macros are for rotating the display and use that macro when construction the driver.

There should also be a function that you can call once the driver has been constructed that will allow you to change the rotation real time if you wanted to.

Thanks, @kdschlosser but it’s not incorrect. I’ve been through this at length as you can see here. Also, during the midst of that “conversation,” I received confirmation from Waveshare that the approach I’m using is the correct approach, and the one you suggested will cause undesired results.

primary LVGL is memory render, no job for put data to display. Valid driver is required. And as @kdschlosser write, use sw rotate is last resort max uneffective method…
Secondary LVGL is event based and NEVER you can use in loop delays. this block all animation all input devies as touch or encoder. Summ delay in one loop must be less as refresh setup in normal device 16ms. But for slow rendering some transition require more as one render loop for one frame , then max delay go down to around 5ms. Never as you 2000.

And your distortion is maybe too based on long render rotate and miss refresh times. But best way is rotate by hw registers in LCD.

Thanks, @Marian_M . Apparently you missed my response about HW rotation being unavailable. See above. That said, thank you for your sample and especially for your insight with a little (very welcome) explanation! I will give it a look snd reply if I have questions.

Hi, @Marian_M

I hope it’s not an imposition to ask you to look at this fairly basic and minimal code that results in display distortion. I also hope it truly is mionimal enough that you can point at something and say, “Ah, there’s your problem.”

#include <lvgl.h>
#include "lv_conf.h"
#include "Arduino_GFX_Library.h"
#include "pin_config.h"
#include <Wire.h>
#include <SPI.h>
#include <Arduino.h>

#undef USE_DIGI_CLOCK
#undef USE_BIG_MONT

lv_disp_draw_buf_t draw_buf;


#if defined(USE_DIGI_CLOCK)
LV_FONT_DECLARE(DigiClock_120); 
#elif defined(USE_BIG_MONT)
LV_FONT_DECLARE(m120) ;
#else
#endif

lv_color_t buf[LCD_WIDTH * LCD_HEIGHT / 10];

const lv_font_t * mainFont ;
lv_obj_t *label;  // Global label object
uint32_t switchSecondsMS ;

Arduino_DataBus *bus = new Arduino_ESP32QSPI(
                                             LCD_CS    // CS
                                           , LCD_SCLK  // SCK
                                           , LCD_SDIO0 // SDIO0
                                           , LCD_SDIO1 // SDIO1
                                           , LCD_SDIO2 // SDIO2
                                           , LCD_SDIO3 // SDIO3
                                                );

Arduino_CO5300 *gfx = new Arduino_CO5300(
                                         bus
                                       , LCD_RESET  // RST
                                       , 0          // rotation
                                       , LCD_WIDTH  // width
                                       , LCD_HEIGHT // height
                                       , 6, 0, 0, 0
                                            );



void example_lvgl_rounder_cb(struct _lv_disp_drv_t *disp_drv, lv_area_t *area) {
    area->x1 &= 0xfffffffe ; // Make it even - was:  if(area->x1 % 2 !=0)area->x1-- ;
    area->y1 &= 0xfffffffe ; // Make it even - was:  if(area->y1 % 2 !=0)area->y1-- ;
    area->x2 |= 0x00000001 ; // Make it odd  - was:  if(area->x2 %2 ==0)area->x2++ ;
    area->y2 |= 0x00000001 ; // Make it odd  - was:  if(area->y2 %2 ==0)area->y2++ ;
}


// Display flushing
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);

//#if (LV_COLOR_16_SWAP != 0)
//  gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
//#else
  gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
//#endif

  lv_disp_flush_ready(disp);
}

#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
void example_increase_lvgl_tick(void *arg) {
  // Tell LVGL how many milliseconds has elapsed
  lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}

void example_increase_reboot(void *arg) {
  static uint8_t count = 0;
  count++;
  if (count == 30) {
    esp_restart();
  }
}



void setup() {
  Serial.begin(115200); // prepare for possible serial debug
  while (! Serial) {} // Wait for serial connection


#if defined(USE_DIGI_CLOCK)
  mainFont = &DigiClock_120 ;
#elif defined(USE_BIG_MONT)
  mainFont = &m120 ;
#else
  mainFont = &lv_font_montserrat_48 ;
#endif

  gfx->begin();
  gfx->setBrightness(120);
  lv_init();
  lv_disp_draw_buf_init(&draw_buf, buf, NULL, LCD_WIDTH * LCD_HEIGHT / 10);
  static lv_disp_drv_t disp_drv;
  esp_timer_handle_t lvgl_tick_timer = NULL;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = LCD_WIDTH;
  disp_drv.ver_res = LCD_HEIGHT;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.rounder_cb = example_lvgl_rounder_cb;
  disp_drv.draw_buf = &draw_buf;
  disp_drv.sw_rotate = 1 ;
  disp_drv.rotated = LV_DISP_ROT_NONE ; // Setting this to 270 initially, does not work for me.
  lv_disp_drv_register(&disp_drv);

  const esp_timer_create_args_t lvgl_tick_timer_args = {
            .callback = &example_increase_lvgl_tick,
            .name = "lvgl_tick"
          };

  const esp_timer_create_args_t reboot_timer_args = {
            .callback = &example_increase_reboot,
            .name = "reboot"
          };
  #define EXAMPLE_LVGL_TICK_PERIOD_MS 2
  esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer);
  esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000);

  label = lv_label_create(lv_scr_act());
  lv_obj_set_style_bg_color(lv_scr_act(), lv_color_hex(0x000000), LV_PART_MAIN);    // The page is black
  lv_obj_set_style_text_color(lv_scr_act(), lv_color_hex(0xffffff), LV_PART_MAIN);  // The ink is white
  lv_disp_set_rotation(NULL, LV_DISP_ROT_270) ; // We say "no rotation" and then "rotate 270" because straight-in 270 did not work for me
  lv_timer_handler() ;

  lv_obj_set_style_text_font(label, mainFont, LV_PART_MAIN);  
  lv_obj_set_style_bg_color(label, lv_color_hex(0x000000), LV_PART_MAIN);    // The page is black
  lv_obj_set_style_text_color(label, lv_color_hex(0xffffff), LV_PART_MAIN);  // The ink is white
#if defined(USE_DIGI_CLOCK)
  lv_label_set_text(label,"Digiclk");
#elif defined(USE_BIG_MONT)
  lv_label_set_text(label,"BIG\nMONT");
#else
  lv_label_set_text(label,"There is\nmore room on\ndisplay for\nthis font");
#endif
  lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
  lv_obj_update_layout(label) ;
  lv_timer_handler() ;
}

void loop() {
  lv_timer_handler() ;
  delay(1000) ;
}

It distorts regardless of those #define/#undef but this is about as generic as I can get. If you’re curious, this is what I see with this exact code (with the two #undef early on).

I never use display with rounder, but my tip is : if sw rotate use swap x y , then too rounder require swap … etc