I am using LVGL 9.3 on an STM32H7R7 with a 1024×600 display, and I want to implement a large table:
600×6 table, with a total of 600 rows
15 visible rows at a time
Columns: Index, Battery Name, Voltage, Temperature, Internal Resistance, Timestamp
Data refreshes every 5 seconds
Currently testing with 60 rows for performance; 600 rows is the extreme case
What I have tried:
Full table drawing (lv_table) → only 6–8 FPS
Full row drawing with labels → 8–10 FPS
Canvas drawing for visible rows → 60 rows can reach ~30 FPS, but each data refresh requires full redraw
Even when drawing only a 15×6 table (visible portion), the FPS is still very low, below 15
I would like to achieve full-screen scrolling at more than 30 FPS with real-time data updates.
I would like to ask: Are there any official or community-recommended high-performance implementations? Are there any examples of virtual tables / row recycling? How to best combine draw_event or canvas for optimal refresh? Are there any recommended approaches or demos for smoothly scrolling large tables?
very very Thank you!
I’m using single-line labels with spaces to simulate the appearance of a table.
I also have three test demos:
Table scrolling over the whole screen is very slow (3–8 FPS)
Labels scrolling over the whole screen is very slow (3–8 FPS)
Canvas scrolling over the whole screen runs much better (~19 FPS)
The reason the table and label demos are slower than expected is that these demos fill the entire screen, whereas in our actual application the table only occupies about 2/3 of the screen.
Regarding your question “Care to show how you update the data in your table?”, in my label-based demo I update the data by dynamically changing the text of the corresponding label, for example using a timer for Row 7.
As for how it is possible that the canvas is much smoother, I don’t precisely know.
I can only point out that your table refresh function could be more optimized.
if(row == 6) { // Row 7 dynamic data
switch(col){
case 0: lv_snprintf(buf,len,"7"); break;
case 1: lv_snprintf(buf,len,"BT007"); break;
case 2: lv_snprintf(buf,len,"12.%03d",row7_data.vol); break;
case 3: lv_snprintf(buf,len,"20.%03d",row7_data.temp); break;
case 4: lv_snprintf(buf,len,"%d",row7_data.res); break;
case 5: lv_snprintf(buf,len,"2025-12-17 17:05"); break;
default: buf[0]=0; break;
}
return;
}
Column 0 and 1 probably will not or barely ever need to be updated, last row will only have to be updated once every minute.
But I doubt this will change the scrolling performance much as you only update the values once every 500ms.
My device also has trouble with scrolling the table using the built-in scrolling mechanism, it’s too slow.
I ended up creating some functions to scroll using buttons instead, that way the user cannot see the slow scrolling, is this perhaps an option for you?
I have a feeling you are creating 600 label widgets and you are updating the data for all 600 at a single go.
This is not only going to be really slow performance wise it is also going to use up a very large amount of memory.
My suggestion is to use the canvas widget to render the number of rows that will be able to be seen and add 2 more to it. You can register callbacks for touch events that would be for scrolling/dragging. Use those events to redraw the table.
You will need to store the data for the readings which is no biggie to do. you are also going to need to store the index for where the first row that is seen is being stored. When that first row is completely off the screen increment the index. Make sure to not increment it too far as to have the last item being pulled from memory that is not allocated for your data. You decrease the index when scrolling the opposite direction and the last line is completely off the screen.
It’s pretty easy to do. You can optionally use hard coded font height or you can also collect the font height from LVGL for the font that you are using. If the font you are using is static and doesn’t change it is best to hard code the height. add in the spacing value between each row and now you can use the delta from a drag event to move the position of the rows so it looks like they are scrolling.
Initially, I wanted to generate a 600×6 table of data directly, but in actual testing, this was completely unfeasible: not enough memory and the scrolling performance was very poor. I then reduced it to 50 rows, but the performance was still bad. Finally, I used a virtual table approach, where a maximum of 15 rows are displayed on the screen. I created 15 + 3 rows of data and used scrolling callbacks to dynamically update the relevant parameters, but the actual performance still didn’t work well.
During testing, I found that the larger the table occupies on the screen, the worse the performance becomes. If I only scroll a small portion of the screen, the performance is better. So I believe this is related to the rendering area—larger areas put more pressure on rendering. Additionally, I think the table component might not be suitable for scrolling (I was originally working on BSP drivers, and I have limited experience with LVGL, so I’m not sure if I’m right about this).
Currently, my approach is quite similar to your suggestion: canvas drawing, calculating the coordinate area, updating data, and scrolling through pages. However, I haven’t implemented it as meticulously as you suggested. Since I have PSRAM, I directly draw a canvas of 50 rows in height. This way, if there are 600 pieces of data, I only need to scroll through 6 pages. Additionally, there is a data comparison feature, so if there are any changes, I find the current coordinate position to update the data.
performance might not be in LVGL so much as it might be with the actual sending of the frame buffer data.
What kind of connection to the display are you using? are you using double buffering with DMA memory? is the MCU you are using single core? Does the MCU have a built in 2d graphics accelerator?. Are you using any kind of a RTOS? What kind of PSRAM does the MCU have? What kind of memory is the frame buffer being allocated in (PSRAM or internal)?. All of the things I am asking can have a HUGE impact in performance. I know you said you are new to LVGL and a lot of times people will gravitate to using pre built driver packages to get up and running. Unfortunately these driver packages are written in a manner to allow them to work as a complete system and they may not be optimized for a specific display or graphics library. They are often times not just driver packages but instead are a graphics framework and while they can be used to get a project going they are not going to be he most efficient way to do it and they will include a lot of boiler plate code to run. Code that causes additional work to be done when it really doesn’t need to be. I have found that if performance is high on the priorities as with your application it is often advantageous to write a display driver that is tailored to your use case.
The way LVGL is written is it uses a polling loop to handle reading input and in that same loop is also the rendering to the display. I have found there is a significant lag that can occur specifically with scrolling. The polling loop has a single entry point and that is when you call lv_task_handler. Reading input only occurs once every 33 milliseconds by default and that will cause what is perceived as a performance issue especially when scrolling. There is a way to get into the middle of that process in order to place priority on user input and updating the display as a result of the user input. This trick makes a noticeable impact especially when scrolling.
Does the MCU you are using have flash space that is not being used? Have you tried compiling adding the -Ofast or the -O3 compiler switches? This will compile for speed but it does result in a larger binary size. What do you have enabled in LVGL for internal checks? There are things that come enabled by default in LVGL that will impact code execution time. These things you want to have turned on when developing and can be turned off once you have the code working. Check your LV_USE_ASSERT_* macros in the config header file to see what is turned on. Logging in LVGL will have an impact even if you are not displaying logging output. so check that as well… If you have enough memory available and you are using images especially ones that are loaded using a decoder, check out image caching in LVGL. If you have available flash storage you might consider converting the images to raw binary format instead of using PNG or JPG. image decoding will cause huge impact to performance.