Disabling animations when scrolling using touch

Description

Hi, I’m wondering if it’s possible to disable the animation when scrolling using touch. I have successfully removed it when scolling using an encoder by changing this line to LV_ANIM_OFF and the scroll feel is so much better.

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

MCU: nRF5340 together with Zephyr 3.2.99

What LVGL version are you using?

8.2.0

What do you want to achieve?

Smoother scrolling

What have you tried so far?

Searched around and tried modifying various places in the codebase without success.

Code to reproduce

This is the code: ZSWatch/application_manager.c at 877a7c27671d4ab66de2c1371d1add938a6d63fa · jakkra/ZSWatch · GitHub
Unfortunately I have not put this into a standalone sample. I don’t think it’s needed, as this is more of a question.

But this is the core code

    lv_obj_t *entry;
    static lv_style_t style;
    lv_style_init(&style);
    lv_style_set_flex_flow(&style, LV_FLEX_FLOW_ROW_WRAP);
    lv_style_set_flex_main_place(&style, LV_FLEX_ALIGN_SPACE_EVENLY);
    lv_style_set_layout(&style, LV_LAYOUT_FLEX);

    lv_obj_set_scrollbar_mode(lv_scr_act(), LV_SCROLLBAR_MODE_OFF);

    grid = lv_obj_create(root_obj);
    lv_obj_add_style(grid, &style, 0);
    lv_obj_set_scrollbar_mode(root_obj, LV_SCROLLBAR_MODE_OFF);
    lv_obj_set_style_border_side(grid, LV_BORDER_SIDE_NONE, 0);

    lv_obj_set_size(grid, LV_PCT(100), LV_PCT(100));
    lv_obj_center(grid);
    lv_obj_set_flex_flow(grid, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_scroll_dir(grid, LV_DIR_VER);
    lv_obj_set_scroll_snap_y(grid, LV_SCROLL_SNAP_CENTER);
    lv_obj_set_scrollbar_mode(grid, LV_SCROLLBAR_MODE_OFF);
    lv_obj_add_event_cb(grid, scroll_event_cb, LV_EVENT_SCROLL, NULL);

Then I just add entries to this grid object.

Screenshot and/or video

Long video: This is how scrolling looks and at the end you can also see scrolling with an encoder.

Short video: Here I have added the patch mentioned before about LV_ANIM_OFF and it solves the laggy behaviour when scrolling using an encoder.

I’m after the same behaviour, but when I’m scrolling using touch screen.

Thank you alot!

Take it as it’s unknown or not possible. If so, are there any ideas how I could optimize scrolling to make it look smoother?

Hi @jakkra ,

I have no idea of your level of knowledge so if I am telling you all the things your already know I apologise. :slight_smile:
I wouldn’t recommend changing the LVGL library source code in attempt to improve the performance. There are many working scenarios and projects based on LVGL and there is no reason at this stage to suspect the library as being the issue.

The hardware platform you are using has fairly limited processing power. For the system to work with a reasonable performance you will need to optimise things as much as possible.

Personally I haven’t worked with your hardware or the Zephyr OS and there are a huge amount of unknows making it very hard to speculate the cause of the performance issues, but here are some general things I would check based on what I am seeing in the videos…

Is the CPU clock rate at maximum?
Are you using DMA to transfer data to your display?
I assume you are using SPI for the display if so is the SPI hardware clock rate as high as possible?
Are there other tasks running in the Zephyr OS using lots of CPU time?
Does the OS have priorities if so can the priorities be adjusted to give better GUI performance?
Have you tried any compile time optimisation?
LVGL allows you to experiment with the display buffer sizes and methodology to try and find the most performant option, I would try some different things here maybe…

I hope that is helpful.

Kind Regards,

Pete

Hi @pete-pjb , thanks a lot for your reply.

Yes I’m aware of the limitations for MCUs like this and LVGL, I’m trying to understand what is due to the MCU and what is due to unoptimized code.

Is the CPU clock rate at maximum?

  • Yes I bump the CPU clock from 64->128MHz when I call lv_task_handler

This is how I handle the lvgl “run loop”

static void lvgl_render(struct k_work *item)
{
    // Running at max CPU freq consumes more power, but rendering we
    // want to do as fast as possible. Also to use 32MHz SPI, CPU has
    // to be running at 128MHz. Meaning this improves both rendering times
    // and the SPI transmit time.
    zsw_cpu_set_freq(ZSW_CPU_FREQ_FAST, true);
    const int64_t next_update_in_ms = lv_task_handler();
    zsw_cpu_set_freq(ZSW_CPU_FREQ_DEFAULT, false);
    k_work_schedule(&lvgl_work, K_MSEC(next_update_in_ms));
}

Are you using DMA to transfer data to your display?

  • Yes

I assume you are using SPI for the display if so is the SPI hardware clock rate as high as possible?

  • Yes 32MHz

Are there other tasks running in the Zephyr OS using lots of CPU time?

  • Not really

Does the OS have priorities if so can the priorities be adjusted to give better GUI performance?

  • I will play around with this a bit.

Have you tried any compile time optimisation?

  • No, will do this, running with debug optimization right now

LVGL allows you to experiment with the display buffer sizes and methodology to try and find the most performant option, I would try some different things here maybe…

  • I have played around a bit with this a while ago, but not after implementing touch controls, will give it a new try, I can afford a bit more RAM for drawing buffers.

A thing I have noticed in the Zephyr port for lvgl is here: lvgl/lvgl_display_16bit.c at zephyr · zephyrproject-rtos/lvgl · GitHub
A call from lv_task_handler will eventually end up here, but the Zephyr display_write APIs are blocking until SPI data is sent, hence it cannot be drawing even if using double buffering in the other buffer while the display is flushing, if I understand correctly?

Hi @jakkra ,

As I mentioned in my previous post I have no experience of Zephyr, I looked at it along time ago but went with FreeRTOS for my own projects as I personally found it much more intuitive. Looking at the code I can see your statement with regard to the display buffering seems correct. I am not sure but it doesn’t look like LVGL has been implemented on Zephyr in a multi-threaded way either. With out seeing all the source code though it is quite hard to speculate, but if the display_write() function is blocking then this is definitely bad… In an ideal world the function should really just program the DMA and kick it off which takes a few CPU cycles then an interrupt can notify using a Zephyr OS primitive that the DMA is complete. Also you should check this post/solution carefully if you are using double buffering with Version 8 as it is possible this scenario could be occurring for you also(It certainly cause massive performance issues for my projects!). I have also today written up another long post explaining a good way to structure LVGL with FreeRTOS which I am sure could also be applied to Zephyr, maybe it might be worth a look also.

I hope that helps.

Kind Regards,

Pete

Hi @jakkra ,

Something I missed in your post:

I have checked this and I am not sure this is a good way to set the repetition rate of the Zephyr task as in my own tests the first call to lv_task_handler() always returns 0 which is clearly undesirable.

So I would suggest doing something along these lines:

#define TASK_UPDATE_RATE   5
static void lvgl_render(struct k_work *item)
{
    // Running at max CPU freq consumes more power, but rendering we
    // want to do as fast as possible. Also to use 32MHz SPI, CPU has
    // to be running at 128MHz. Meaning this improves both rendering times
    // and the SPI transmit time.
    zsw_cpu_set_freq(ZSW_CPU_FREQ_FAST, true);
    const int64_t next_update_in_ms = TASK_UPDATE_RATE;
    zsw_cpu_set_freq(ZSW_CPU_FREQ_DEFAULT, false);
    k_work_schedule(&lvgl_work, K_MSEC(next_update_in_ms));
}

This is another parameter you can alter to see how it affects performance, you could also try a value of 10ms for example…

I hope that makes sense.

Kind Regards,

Pete

Hi @pete-pjb Thank you, I’ll take a read of what you linked. Very helpful, good that my thought about the Zephyr port may be a possible optimization, will dig into it more.

About const int64_t next_update_in_ms = lv_task_handler(); actually did like you suggested before, but got suggest to do it this other way instead, but I’ll take a look again on the return value and revert back if needed.

Thank you, I now have a lot to look into.
Actually I’m pretty happy with the overall UI response, it’s not too bad, just the scrolling using touch that is a bit slugish. But eventuelly I’ll do more complex things in my project and those optimaztions are more important.

Hi @jakkra ,

No problem, I’m glad to help… If you want to discuss any part of your implementation feel free to ask.

Let us know how you get on.

Kind Regards,

Pete

1 Like

32 mHz is awful slow for SPI. Is that a limitation of the display or the MCU? If it is a limitation imposed by the display you might consider change the display to something that is able to go up to 80mHz.

Couple of things that wasn’t asked.

What is the display resolution?
What is the display color depth?
What is the size of the frame buffer?
Are you using double buffering?

You will only get a performance gain from using DMA memory IF you have 2 buffers and both are allocated in DMA memory. The other thing is when you are making the call to flush_ready. When using DMA memory this call needs to be made when the actual buffer gets emptied and NOT in the flush function. There is typically an API in the MCU SDK that is specific to moving data from DMA memory to IO that allows you to register a callback when it has finished with the transfer. In that callback is where you want to call flush_ready.

When using DMA double buffering you will want to use a smaller buffer size. something along the lines of 1/10th to 1/8th the total display size in bytes is the magic area I believe. @pete-pjb might be able to chime in on what a good starting point is for the buffer size.

If the buffer is too large it will cause a wicked slow down. You will have a big slow down if using only a single buffer, this is because LVGL is going to have to wait until the buffer gets emptied before it can write anything into it, that is where double buffering steps in so that while one is being emptied the other is getting filled.

Hi @kdschlosser & @jakkra ,

Yes in my experience 1/10th is good!

Kind Regards,

Pete

Hi thank you for more details @kdschlosser @pete-pjb

Yes 32MHz is the max SPI of my MCU, display can do more.
240x240 display
Using 16 color depth
Framebuffer is 1/4
Done both double and single buffer, but not much (if any) difference, I think this is due to the Zephyr SDK LVGL port. Like I linked previously it block in the display flush. This I’ll need to fix to utilize double buffering correctly to make use of DMA and double bufffering correctly.

I will make sure double buffering is done correctly and report back the results.

Thank you!

1 Like

yeah make the screen buffer size smaller and get that DMA double buffer working the correct way and that is going to give you a pretty good boost in performance.

1 Like