How to eliminate tearing?

Description

After writing an 8 bit parallel display driver for the Teensy MicroMod that supports DMA transfers, I have been able to plug it into LVGL and so far its working with no glitches (almost) on an ILI9488/9486 display.

I have noticed that on a simple gauge test with a moving needle, I am seeing quite noticeable tearing on the needle object.
Regardless of using one 1/4 screen sized buffer or two 1/8 screen sized buffers (I need to save ram for other apps a well), I don’t see much of a difference between the two.
Being that it’s such a small area to redraw, I wouldn’t expect it to pose such an issue.

Trying to change timing of the display update rate in lv_conf and matching that to the refresh rate of the screen or the baud rate on the parallel bus is quite a challenge and sometimes seems to make things worse.

I’ve seen on the forum a few threads about tearing, but no one has actually implemented it using an LCD TE output line and an interrupt (besides the STM32F769 example).

I have implemented the driver in such a way that LVGL will call the flush function to write the data, and when DMA is done transferring, it will trigger a callback and inform LVGL that it’s ready to flush more data.
Obviously, this is happening a little too fast and I get the tearing effect.

I was thinking of using the TE output on the LCD to trigger an interrupt that will tell LVGL that it’s now ready for the next flush, but I’m not sure how to implement it.
I was thinking of adding a while loop in the flush function with a boolean that is set by the TE interrupt, but that would cause the app to get stuck in a loop rather than freeing the processor and LVGL to run other tasks.

So how can I implement this?

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

Teensy MicroMod

What LVGL version are you using?

V8.1.0 (dev)

What do you want to achieve?

Minimal tearing on the display

What have you tried so far?

Nothing yet, just playing around with the idea.

Code to reproduce

#include "ILI948x_t4_mm.h"
#include "lvgl.h"
ILI948x_t4_mm lcd = ILI948x_t4_mm(17, 16, 5); //(dc, cs, rst)
const int screenWidth = 480;
const int screenHeight = 320;

#define LVGL_TICK_PERIOD 5
/*A static or global variable to store the buffers*/
static lv_disp_draw_buf_t disp_buf;
static lv_disp_drv_t disp_drv;          /*A variable to hold the drivers. Must be static or global.*/

/*Static or global buffer(s). The second buffer is optional*/
static lv_color_t buf_1[screenWidth * screenHeight /8];
static lv_color_t buf_2[screenWidth * screenHeight /8];

IntervalTimer tick;
static void lv_tick_handler(void)
{
  lv_tick_inc(LVGL_TICK_PERIOD);
}



static lv_obj_t * meter;
static void set_value(void * indic, int32_t v)
{
    lv_meter_set_indicator_value(meter, indic, v);
}

/**
 * A simple meter
 */
void lv_example_meter_1(void)
{
    meter = lv_meter_create(lv_scr_act());
    lv_obj_center(meter);
    lv_obj_set_size(meter, 200, 200);

    /*Add a scale first*/
    lv_meter_scale_t * scale = lv_meter_add_scale(meter);
    lv_meter_set_scale_ticks(meter, scale, 41, 2, 10, lv_palette_main(LV_PALETTE_GREY));
    lv_meter_set_scale_major_ticks(meter, scale, 8, 4, 15, lv_color_black(), 10);

    lv_meter_indicator_t * indic;

    /*Add a blue arc to the start*/
    indic = lv_meter_add_arc(meter, scale, 3, lv_palette_main(LV_PALETTE_BLUE), 0);
    lv_meter_set_indicator_start_value(meter, indic, 0);
    lv_meter_set_indicator_end_value(meter, indic, 20);

    /*Make the tick lines blue at the start of the scale*/
    indic = lv_meter_add_scale_lines(meter, scale, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_BLUE), false, 0);
    lv_meter_set_indicator_start_value(meter, indic, 0);
    lv_meter_set_indicator_end_value(meter, indic, 20);

    /*Add a red arc to the end*/
    indic = lv_meter_add_arc(meter, scale, 3, lv_palette_main(LV_PALETTE_RED), 0);
    lv_meter_set_indicator_start_value(meter, indic, 80);
    lv_meter_set_indicator_end_value(meter, indic, 100);

    /*Make the tick lines red at the end of the scale*/
    indic = lv_meter_add_scale_lines(meter, scale, lv_palette_main(LV_PALETTE_RED), lv_palette_main(LV_PALETTE_RED), false, 0);
    lv_meter_set_indicator_start_value(meter, indic, 80);
    lv_meter_set_indicator_end_value(meter, indic, 100);

    /*Add a needle line indicator*/
    indic = lv_meter_add_needle_line(meter, scale, 4, lv_palette_main(LV_PALETTE_GREY), -10);

    /*Create an animation to set the value*/
    lv_anim_t a;
    lv_anim_init(&a);
    lv_anim_set_exec_cb(&a, set_value);
    lv_anim_set_var(&a, indic);
    lv_anim_set_values(&a, 0, 100);
    lv_anim_set_time(&a, 2000);
    lv_anim_set_repeat_delay(&a, 100);
    lv_anim_set_playback_time(&a, 500);
    lv_anim_set_playback_delay(&a, 100);
    lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
    lv_anim_start(&a);
}

FASTRUN void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one
     *`put_px` is just an example, it needs to implemented by you.*/
      lcd.pushPixels16bitDMA((uint16_t*)(color_p),area->x1, area->y1, area->x2, area->y2);
    

}

FASTRUN void flushCB()
{
/* IMPORTANT!!!
  * Inform the graphics library that you are ready with the flushing*/
  lv_disp_flush_ready(&disp_drv);
  }

void setup() {
   
  Serial.begin(115200);
  delay(1000);
  Serial.print(CrashReport);
  
  pinMode(12, OUTPUT);
  pinMode(14, OUTPUT);
  digitalWrite(12, HIGH);
  digitalWrite(14, HIGH);
  
  lcd.begin(12);
  lcd.setRotation(3);
  lcd.onCompleteCB(&flushCB);
  
  
  lv_init();
  lv_disp_draw_buf_init(&disp_buf, buf_1, buf_2, screenWidth * screenHeight /8);
  
 
  lv_disp_drv_init(&disp_drv);            /*Basic initialization*/
  disp_drv.draw_buf = &disp_buf;          /*Set an initialized buffer*/
  disp_drv.flush_cb = my_flush_cb;        /*Set a flush callback to draw to the display*/
  disp_drv.hor_res = screenWidth;                 /*Set the horizontal resolution in pixels*/
  disp_drv.ver_res = screenHeight;                 /*Set the vertical resolution in pixels*/
  lv_disp_drv_register(&disp_drv); /*Register the driver and save the created display objects*/
  
  Serial.println("tick.begin");
  tick.begin(lv_tick_handler, LVGL_TICK_PERIOD * 1000);  // Start ticker
  lv_example_meter_1();
   
}

const int loopDelay1 = 5; // Make a request every 500ms
unsigned long timeNow1 = 0;
void loop() {
if (millis() > timeNow1 + loopDelay1)
    {
      timeNow1 = millis();
      lv_timer_handler(); /* let the GUI do its work */
    }

}

This may not be the cause but if you have the cache enabled the display buffer(s) must be located in a non-cacheable region otherwise tearing will occur. Not sure of your display size/colour depth but you could locate these in the OCRAM/DTCM.

Neither DMA or the LVGL buffers reside DMAMEM they’re in ITCM/DTCM, so it’s not a caching issue i’m afraid.
Display is 480*320 @16-bit per pixel, so roughly 307Kb of RAM is required

IMG_5121_480.mov.zip (3.8 MB)
I’ve attached a video zipped up.

Okay @reso, I’ve looked at the video and your visual sampling rate is way beyond mine since I’m not seeing any tearing to speak of. Maybe this is a terminology difference. I do see a slight stutter in update of the needle but no tearing (which is typically very obvious and unsightly). Did you upload the right video?

I admit its not easy to see in the video, but I took some screenshots of the original video to show you





I know some ghosting can occur and thats a display issue/limitation, but this to me looks like tearing. Please correct me if I am wrong.