How to improve UI speed to remove flickering / tearing on my display. DMA and two buffers?

Description

I created an example project with a slider that has background, indicator and knob images. When the slider moves fast enough, there’s lots of flickering / tearing around the knob part.

Here’s the project repository: GitHub - zalexzperez/LVGL_slider: Slider implemented using LVGL 9.1 on an SPI display controlled by the TFT_eSPI library and EEZ framework library (EEZ Studio GUI editor).

Notice there are two releases:

  • v0.1 is the implementation with a single buffer and no DMA.
  • v0.2 uses two buffers and DMA.

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

  • ESP32-S3, Arduino framework with PlatformIO.
  • 172x320 ST7789 SPI display.

What LVGL version are you using?

v9.1

What do you want to achieve?

I want to achieve smooth, flicker-free, and tear-free motion for rapidly changing sliders, numbers, and moving images.
Please, check my code and point me on how to get the best possible performance out of this hardware.

What have you tried so far?

  • Set SPI frequency to the highest possible value (80MHz) for my hardware.
  • Implemented my own flushing function with two buffers and DMA. I adapted this function from someone else’s project, which utilizes a single buffer with DMA (and LVGL 8.3).
  • Played a bit with buffer sizes, the current value gives the best result; worse results are observed if buffer size is larger or smaller.

Screenshot and/or video

This is v1:
v01

And this is v2, clearly better but not good enough (more artifacts expected the faster it moves):
v02

Not sure why they are at slightly different speeds.

Thanks in advance for your help.

Hi,
I fall on this question by searching another info, but if I could help…
From what I see in your code, I think your problem come from your GUI Task code.
All the stuff before the while(1) must be placed in your setup function.
Otherwise, at each call of your task, you redo all the initialisation stuff.
Hope it’ll help.

That’s not how tasks work.
The code before the while(1) is only executed once, after the task is created. That’s why I’m using it for initialization purposes.

Sorry, you’re right, I’m stupid :flushed:
Rule n°1 : do not answer in forum when you’re brain already busy. I misread your code.
However, as I already have the same setup for my current project, I will try your code.
I have fast moving objects in my lvgl ui and I didn’t experience this kind of “tearing”

Primary SPI displays isnt most perfect for animated GUI. Too use threads in arduino your way is absurd and reduce mcu power . And lv disp create is unreadable how speed how mode for communication have SPI …?

Which memory are you using for the buffers? Internal RAM or PSRAM?
Are the buffers described full frame sized?
Are you flushing the entire frame, or just the dirty areas? How granular are your dirty areas?

How many color bits per pixel?

Have you confirmed that the 4-bit SPI is running at 80MHz? The ST7789 spec indicates it can’t operate that fast

It seems that the only way tearing is happening as shown is the buffer is changing as the SPI DMA is refreshing the display

1 Like

tft_espi doesn’t do a correct DMA transfer. That’s for starters

The second this is this

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

is not creating buffers in DMA memory. That is creating buffers in normal memory space and depending on how you have your settings set for the ESP-IDF the buffers could be getting created in SRAM or PSRAM depending on the side of the buffer and what your settings are.

My suggestion to you is to not use the Arduino IDE because the code is going to be clower do to all of the wrappers needed to make the code work with the ESP32. The Arduino IDE also doesn’t use the ESP-IDF. It uses a cut up modified version of the IDF and a lot of the advanced features are simply not available.

I suggest you use the build system that is built into the actual ESP-IDF and stay away from the Arduino IDE…

There is an SPI driver in the IDF specifically for LCD displays and there is also a driver for the display IC you are using that is available in the ESP component registry.

1 Like

The tearing is beacuse you didn’t setup TE pin, check your driver datasheet and if available, wire the te pin of your driver to one of the esp gpio, then on driver init(init command sending), enable the te pin(read datasheet), and update your code to before sending next frame, wait for te pin to raise, thats gonna fix the problem

The TE pin doesn’t need to be used. I have never used it and never had that issue.

What is going on is the buffer is being flushed and then it takes time for LVGL to render to the buffer again. so by time you get around to flushing the rest of the update the display IC has sent the last change to the display already so you end up with the issue seen. Now if you use DMA memory while one buffer is being sent the second one is able to be filled by LVGL which reduces the time between flushes so you don’t end up with this problem.

1 Like

I see.
I will need to learn how to work with ESP-IDF from VSCode, since that’s what I’m used to. And also, add Arduino as a component, so I can use all the libraries I have in my current real project. Sounds hard!

I think this code should serve as a starting point.

By following all suggestions, I should expect at least twice the FPS without the tearing issue, right?

Thanks everyone.

Not twice the speed because rendering times are going to differ based on what is updating. and depending on the display size and color depth which is going to dictate where you will be able to allocate the memory for the frame buffers. It can be a really good increase in speed or it could be a decent increase in speed.

1 Like

and yes that code is a good place to start off with. It has all of the mechanics that you would need to use.

I must support the suggestion to use espidf framework within VScode.

Its not that hard to rewrite Arduino code to espidf. And the more hardcore you want to go, the bigger the benefit of using espidf framework directly. Trust me, after working with Arduino for many years. What really happens within the Arduino wrappers is not well documented, and not to mention, always lacking several releases behind the latest releases from espressif.

2 Likes

@FrankBJensen

I :100: % agree with you on that. The Arduino ESP32 API doesn’t use the ESP-IDF. it uses bits and pieces and the API doesn’t align directly. Things like using DMA memory and the lcd drivers are almost impossible to use properly. Even seasoned libraries like TFT_eSPI don’t have DMA functioning properly.

Hello,

the code above works, however, I encountered a problem with setting the resolution: the image doesn’t span the entire height if I attempt to set the vertical resolution to that of my display (172).

Based on this code, I created a new animated slider, but the tearing effect is still there :frowning:

esp_lcd_st7789_tearing

Here’s the new repository in case anyone wants to give it a look.

Thank you

4 things I want you to do.

1: I want you to increase the size of the partial buffers. instead of dividing by 10 divide by 8.

2: I want you to do is to edit your lv_config.h file and change the default refr time from 33 to 20. If you use menuconfig to make adjustment to LVGL then locate the default refr time and set it in menuconfig.

3: change this code…

void app_main()
{

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    TaskHandle_t taskHandle = NULL;
    xTaskCreatePinnedToCore(lvgl_task, "LVGL task", 8192, NULL, 4, &taskHandle, 0); // stack, params, prio, handle, core

    while (true)
    {

        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
}

to read

void app_main()
{

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    TaskHandle_t taskHandle = NULL;
    xTaskCreatePinnedToCore(lvgl_task, "LVGL task", 8192, NULL, 4, &taskHandle, 0); // stack, params, prio, handle, core
}

no reason to have that while loop which takes up processor cycles.

4: use host 2 for the SPI. Host 3 is GPIO gpio matrix only and is limited to something along the lines of a 28mhz transfer speed. you want to use the IOMUX which is a hardware SPI so you will be able to get the maximum of 80mhz. You nmeed to make sure the pin assignments align properly. Just dlancing at your pin assignments for the SPI it looks to be using the hardware SPI pins.

If you are still having an issue after that I will make some code changes and send you the file so you can see if the same issue happens. I have a suspicion that the exceedingly large amount of code that is running to something as simple as changing the value of the slider could be causing a significant amount of slow down. I would be curious to see if the same thing happens when setting the value of a native slider directly without using any of the ui designers code.

I will check for init sequence differences between the driver that I have written and the one that is in the esp_lcd component. That could have an effect on it as well…

OK so after a quick check to see what the driver is doing for initialization… It’s not doing much. it’s only setting the rotation, color depth and that’s pretty much it.

You are going to have to add initialization commands to the startup like what you did with the color inversion.

Here is the initialization that I have in a driver that I wrote. I have not had any reports of there being an issue with it and no complaints as far as there being any anomalies showing up.

It’s written in Python code, it should not be that hard to convert it over to C code.

These are the results:

1. Increase size of partial buffers
Check original footage here (gif conversion may have made motion worse from framerate conversion):

  • To divided by 8: the tearing on the slider knob is reduced by a lot:
    BufferDividedBy8

  • To divided by 6: the tearing on the slider knob is clearly back but the motion is a little more smooth:
    BufferDividedBy6

  • To divided by 4/2/1: the tearing on the slider knob is hard to notice, almost gone and the motion is okay:
    BufferDividedBy4

I don’t understand why increasing the buffer size to /6 shows an apparently worse result than /8, but in any case, the screen transition from right to left and left to right still exhibits tearing. I suppose this is expected due to the amount of pixels that are changing?

2. Reduce the refresh period from 33 to 20
The motion seems a little more smooth, yes.

3. Remove empty loop in app_main()
No effect observed.
I assumed app_main() was running on core 1 by default (and since lvgl_task was created on core 0…), like in the Arduino framework, but no.

4. Using SPI2 instead of SPI3
Yes, I did wire the display to the hardware SPI pins on purpose back in the day when I built my device, but I got the SPI2/SPI3 naming wrong.

After consulting the technical reference manual, you’re indeed right, FSPI (“Fast SPI”) = GP-SPI2. But specifying SPI2_HOST or SPI3_HOST when calling spi_bus_initialize() and esp_lcd_new_panel_io_spi() doesn’t seem to make a difference, so I assume it only cares whether the “good” pins are used or not.


There has been improvement. As mentioned, there’s still clear tearing when the screen change animation takes place.
I wouldn’t mind continuing the investigation if it makes sense to do so, although I’m uncertain whether my expectations are too high from this MCU/display.

Thanks a lot for your help.

Just updated my Arduino repository with the latest LVGL version and resized buffers and I’m getting a similar result