How to use TE/VSYNC trigger from display controller

Description

I have a Teensy MicroMod connected to an ILI9488 (16bit color depth) over an 8 bit parallel bus.
I use DMA to transfer most of the data to the display using two half screen sized frame buffers.

When doing full screen scrolling or page transitions there is sometimes noticeable tearing affect.

The display has a tearing effect pin connected to a GPIO and I would like to try use it to reduce tearing but need help in understanding how to do this.

At the moment this is how I write data to the display:

DMAMEM lv_color_t buf_1[(SCREEN_W * SCREEN_H)/2] __attribute__ ((aligned(32)));
DMAMEM lv_color_t buf_2[(SCREEN_W * SCREEN_H)/2] __attribute__ ((aligned(32)));
static lv_disp_draw_buf_t disp_buf;
static lv_disp_drv_t disp_drv;

void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) {
  if(area->x2 < 0) return;
  if(area->y2 < 0) return;
  if(area->x1 > SCREEN_W - 1) return;
  if(area->y1 > SCREEN_H - 1) return;
  arm_dcache_flush((uint16_t*)buf_1, sizeof(buf_1)); 
  arm_dcache_flush((uint16_t*)buf_2, sizeof(buf_2)); 
  lcd.pushPixels16bitDMA((uint16_t*)(color_p),area->x1, area->y1, area->x2, area->y2);
}

I have a callback registed to the display driver when the DMA engine finishes the transfer, and it calls the lvgl flush function:

void flushCB() {
  lv_disp_flush_ready(&disp_drv);
}

I can attach an interrup on the TE pin to trigger some function, but would like to hear what is the best method to connect this all into LVGL.

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

Teensy MicroMod (IMXRT1062)

What LVGL version are you using?

v8.3

What do you want to achieve?

Tear free scrolling/transitions

@kisvegabor @embeddedt perhaps either of you might have some suggestions based on your experience ?

Hi,

You can use the TE signal in 2 ways:

  1. In v8.3 there is new callback in lv_disp_drv_t called render_start_cb. Here you can wait for the TE signal to delay rendering until the desired time.
  2. Decouple the display refresh timer and call it when you detect the TE signal. See here.

@kisvegabor thanks!
Is there a basic code example for #1? If I have to use a while loop there it would not be optimal as I have a few other tasks that are running in parallel (canbus, ADC, PWM and SD)

True, that’s why I suggest #2 :slight_smile:

@kisvegabor makes sense now :slight_smile:
So I disable the display refresh timer after lv_init() and then in my vsync IRQ I would just call _lv_disp_refr_timer(NULL); ?
I assume I will also need to call the refr_timer above just once after setup to trigger the first screen write?
Do I keep lv_disp_flush_ready(&disp_drv) in my DMA IRQ as seen in the code example?

So I decoupled the display refresh timer and called it when TE is triggered, but all I get is a blank screen :confused:
I did call it once manually after startup code and creating the first screen - but that drew the first screen and stopped right after that.

Also, I did notice that TE triggers roughly 10ms after the DMA callback, so I think there is reason to try get this to work

Can you verify that _lv_disp_refr_timer is really called? Adding printf, LED blink or something into it?

I have a printf in my DMA interrupt and the TE interrupt - not seeing them being triggered again after disabling the timer.
Or do you want me to add the printf into _lv_disp_refr_timer?
Also, do I need to force full screen updates to use the TE pin?

Yes, I meant that.

AFAIK, TE signal is working even if there are no changes on the screen. It’s just show when the display controller copies the data from its RAM to the display.

So if you just show a label on the screen, you should see the TE signal continuously coming with 60-100Hz (or so)

I added a user log to _lv_disp_refr_timer and it gets called once, after the first half of the screen is updated (as I have two half screen sized buffers) and then stops there. Seems like the whole device is waiting for something as it’s not running…

Probably for lv_disp_flush_ready. Are you sure it’s called?

Yea I am calling it in the DMA callback.
But, does it HAVE to be called before _lv_disp_refr_timer in order to push the pixel data to the screen?

It should be called when you sent the pixels to the display from the flush_cb.

Just as a test, pleas try using 1 draw buffer without DMA.

Tried a single frame buffer, no DMA - still no luck :disappointed:

Would it help if I created an Eclipse project as reference?

At this stage - anything will help :slight_smile: thank you!

I’ve tried it out on PC and it worked well. I did this:

  /*Delete the original display refresh timer*/
  lv_disp_t * disp = lv_disp_get_default();
  lv_timer_del(disp->refr_timer);
  disp->refr_timer = NULL;

  while(1) {
      /* Periodically call the lv_task handler.
       * It could be done in a timer interrupt or an OS task too.*/
      lv_timer_handler();
      usleep(5 * 1000);

      /*Call this anywhere you want to refresh the dirty areas*/
      _lv_disp_refr_timer(NULL);
  }

Does it work for you? No TE signal here, just the decoupling of the display refresh timer.

Yes, this works!
Question now is, why doesn’t it work when triggered from the vsync pulse…

What’s the difference?

The TE signal is in an interrupt?
If so, be sure lv_timer_handler and the interrupt can’t run at the same time.