kdschlosser / lvgl_micropython

LVGL module for MicroPython
MIT License
42 stars 11 forks source link

Glitchy double-buffered display updates (ESP32-S3 w/RGB ST7262 LCD) #33

Open bland328 opened 2 months ago

bland328 commented 2 months ago

I'm unable to determine why display updates often flash and glitch, even though I'm (hopefully appropriately) using double buffering.

IMG_6088 2024-04-21 23_32_11

With apologies for the handheld video, there's what I'm seeing when I run the test code below. Any suggestions are greatly appreciated, as lvgl_micropython is otherwise working very well for me at this point.

FWIW, if I animate only one of the two sliders, there's rarely (if ever) any issue.

import time, urandom, lcd_bus, rgb_display, lvgl as lv

rgb_bus = lcd_bus.RGBBus(  # ST7262 parallel 800×480 4.3" IPS
    hsync=39,
    vsync=41,
    de=40,
    disp=-1,
    pclk=42,
    data0=8, data1=3, data2=46, data3=9, data4=1,
    data5=5, data6=6, data7=7, data8=15, data9=16, data10=4,
    data11=45, data12=48, data13=47, data14=21, data15=14,
    freq=12500000,
    hsync_front_porch=8,
    hsync_pulse_width=4,
    hsync_back_porch=8,
    hsync_idle_low=False,
    vsync_front_porch=8,
    vsync_pulse_width=4,
    vsync_back_porch=8,
    vsync_idle_low=False,
    de_idle_high=False,
    pclk_idle_high=False,
    disp_active_low=False,
    refresh_on_demand=False,
)

_WIDTH, _HEIGHT = 800, 480
_BUF1 = rgb_bus.allocate_framebuffer(_WIDTH * _HEIGHT * 2, lcd_bus.MEMORY_SPIRAM)
_BUF2 = rgb_bus.allocate_framebuffer(_WIDTH * _HEIGHT * 2, lcd_bus.MEMORY_SPIRAM)

display = rgb_display.RGBDisplay(
    data_bus=rgb_bus,
    display_width=_WIDTH,
    display_height=_HEIGHT,
    frame_buffer1=_BUF1,
    frame_buffer2=_BUF2,
    backlight_pin=2,
    color_space=lv.COLOR_FORMAT.RGB565,
    rgb565_byte_swap=False,
)
display.set_power(True)
display.set_backlight(100)

scrn = lv.screen_active()
lslider = lv.slider(scrn)
lslider.set_style_size(225, 480, lv.PART.MAIN)
lslider.align(lv.ALIGN.TOP_LEFT, 0, 0)
rslider = lv.slider(scrn)
rslider.set_style_size(225, 480, lv.PART.MAIN)
rslider.align(lv.ALIGN.TOP_RIGHT, 0, 0)

while True:
    lslider.set_value(urandom.randint(0, 100), lv.ANIM.OFF)
    rslider.set_value(urandom.randint(0, 100), lv.ANIM.OFF)
    time.sleep_ms(1)
    lv.tick_inc(1)
    lv.task_handler()
kdschlosser commented 2 months ago

98 milliseconds for it to refresh a single widget is a really long time. It shouldn't be taking that long. There is something that is glitched in it somewhere. If we look at the time for the 2 widgets you have 186 milliseconds. 98 * 2 = 196. that would indicate that the actual code used for doing the refreshing is taking 8 milliseconds to run. 8 milliseconds to copy everything that has changed from one buffer to the second one and 89 to 90 milliseconds to render it? That doesn't work out. It should take a whole lot less time 89 to 90 milliseconds to render a single slider. The math is not all that complicated when doing that. The thing that would be eating up the most amount of time is writing the pixel data to the buffer. The same thing is done when copying the updated data from one buffer to the next but there we are only seeing 8 milliseconds.

Try changing the widget to something else like 2 large switches and flip the state on them. lets see what happens there.

kdschlosser commented 2 months ago

I wanted to let you know that I didn't forget about you at all. I just pushed a HUGE change to the repo that I am hoping will address this issue.

I added bounce buffer support which I am hoping is going to provide the display a continuous stream of data.

You are going to replace this code

_WIDTH, _HEIGHT = 800, 480
_BUF1 = rgb_bus.allocate_framebuffer(_WIDTH * _HEIGHT * 2, lcd_bus.MEMORY_SPIRAM)
_BUF2 = rgb_bus.allocate_framebuffer(_WIDTH * _HEIGHT * 2, lcd_bus.MEMORY_SPIRAM)

with this code

_WIDTH, _HEIGHT = 800, 480
_BUF1 = rgb_bus.allocate_framebuffer(_WIDTH * _HEIGHT * 2, lcd_bus.MEMORY_SPIRAM)
_BUF2 = rgb_bus.allocate_framebuffer(_WIDTH * _HEIGHT * 2, lcd_bus.MEMORY_SPIRAM)
_BUF3 = rgb_bus.allocate_framebuffer(_WIDTH * 10 * 2, lcd_bus.MEMORY_INTERNAL)

You do not do anything with _BUF3 but it has to stay resident. What this does is it triggers the driver to create 2 bus drivers that are the size of 10 lines of the display. These buffers are created in internal DMA memory. it's a total of 19200 bytes which isn't all that much. But hopefully it will give us the edge that is needed to get it to render properly. You can change the 10 to any even number to tweak how well it works. I would not go more than 48 as that is going to be 20% of the display. I don't think you will be able to fit that much into the internal ram either. I want to say that limit is something around 80K of available internal DMA memory. I think that is going to be about 24 lines.

kdschlosser commented 2 months ago

I wanted to give you an update here. There are a couple of the developers including myself that are planning to do a video chat this coming week to discuss changes that can be made to improve performance when using double buffering.

Just wanted to give you an update.

kdschlosser commented 1 month ago

@bland328

OK so I went over things with the guys over at LVGL. There have been some imrpovements made that will be getting merged into LVGL hopefully soon. We have come to the conclusion that there might be some kind of a glitch in the ESP-IDF. I opened up an issue about it to see if they can help me out with it to nail down if there is an issue in the IDF that is causing the problem.

In the mean time something crossed my mind as to what could be happening. The frame buffers could be flip flopped for some reason. I made a change to see if maybe swapping the buffers around corrects the problem. Give it a shot and lemme know what the results are.

bland328 commented 1 month ago

Thanks for the update! I've been away from this project for a bit, but back on it today, and will let you know what I find soon.

kdschlosser commented 1 month ago

the build system is currently broken. you will have to use the code from 2 commits back.

I am working on fixing the build system

kdschlosser commented 1 month ago

ok so the build system is fixed.

I am going to be pushing a commit later today that will hopefully address the glitching issue.

kdschlosser commented 1 month ago

whenever you want to give it a go again. I have updated the RGB driver and hopefully it will do the trick. I don't have any other tricks up my sleeve to try after this. I am going to have to really put some thought to it.

I did open an issue with the folks over at espressif about it as well. They said it is abnormal and they agree that there is some kind of a syncing issue but the question as to why it is happening remains unanswered.

The examples that espressif has for the RGB driver pretty much negates any benefits of using DMA memory. They all stall the program right after calling the function to send the data. That is what I am wanting to avoid doing. I want to be able to use the DMA memory and perform other tasks while it is transferring. I believe I found the way to do it it is just a matter of getting the right mechanics in place to make it work.

kdschlosser commented 2 weeks ago

OK I just made a change that might alleviate some of this issue. I have not tested it yet but if you are running on an ESP32-S3 I added the ability to overclock the SPI bus used for the SPIRAM and the Flash. If you are using octal flash and octal ram the effective clock speed goes up from 80Mhz to 240Mhz. so it's a really significant jump. quad SPI will see an increase from 80 to 120Mhz. Still a substantial change.

A lot of the issue stems from reading and writing to the SPIRAM at the same time. when that happens the clock speed gets cutso read will see 40Mhz and write will see 40Mhz. so it drops quite a bit.

Here is how to do it if you have the following SPI typs.

octal spi FLASH, octal spi PSRAM: --onboard-mem-speed=120 --flash-mode=DTR quad spi FLASH, octal spi PSRAM: --onboard-mem-speed=120 --flash-mode=STR *quad spi FLASH, quad spi PSRAM: --onboard-mem-speed=120

Crap! I forgot to add something to make it work. I will do that now.

That list above is what you need to add to the build command.