LVGL -- Light and Versatile Graphics Library

This morning I ran a few experiments with the LVGL graphics library. In the past, we’ve used GTK+ and Qt for graphics libraries on embedded Linux projects. However, for simple devices with a frame-buffer LCD (no graphics controller), Qt is becoming increasingly difficult to work with as their software renderer is low priority and the licensing is confusing and seems to be in flux. Thus, the LVGL project looked interesting. It’s genesis and primary focus seems to be with MCUs, so it is designed to be very efficient and optimized for software rendering.

So this morning I tried a few experiments. I cloned the main LVGL repo and even though it compiled, it was not obvious how to do anything with it. After a little more digging, I found Linux framebuffer port. I cloned the repo, set up the submodules, and ran it:

[cbrake@ceres lvgl]$ git clone

[cbrake@ceres lv_port_linux_frame_buffer]$ git submodule update --init
Submodule 'lv_drivers' ( registered for path 'lv_drivers'
Submodule 'lvgl' ( registered for path 'lvgl'
Cloning into '/scratch/lvgl/lv_port_linux_frame_buffer/lv_drivers'...
Cloning into '/scratch/lvgl/lv_port_linux_frame_buffer/lvgl'...
Submodule path 'lv_drivers': checked out '49c4b178494625efefb07891d1c8b9c13914edff'
Submodule path 'lvgl': checked out '0b5a1d4b23975b920ff841ea9cd038802f51711b'

[cbrake@ceres lv_port_linux_frame_buffer]$ time make -j12


[100%] Linking C executable lvgl_fb                                                                                                                                                                                       
[100%] Built target lvgl_fb                                                                                                                                                                                               
real    0m2.237s                                                                                                                                                                                                          
user    0m17.559s                                                                                                                                                                                                         
sys     0m4.865s                        

Wow, it builds in 2.2s!

I then tried to run it:

[cbrake@ceres lv_port_linux_frame_buffer]$ ./lvgl_fb 
ioctl(FBIOBLANK): Invalid argument
unable to open evdev interface:: Permission denied
[cbrake@ceres lv_port_linux_frame_buffer]$ export DISPLAY=:0
[cbrake@ceres lv_port_linux_frame_buffer]$ ./lvgl_fb 
ioctl(FBIOBLANK): Invalid argument
unable to open evdev interface:: Permission denied
[cbrake@ceres lv_port_linux_frame_buffer]$ sudo ./lvgl_fb 
[sudo] password for cbrake: 
ioctl(FBIOBLANK): Invalid argument

This probably makes sense – you can’t use the framebuffer on a modern desktop when X/Wayland/etc is already running on the graphics hardware.

I then tried the pc port.

This is set up the same way – there is a wrapper repo with LVGL set up as a submodule. Again it builds very fast, and this time it runs!

[cbrake@ceres lv_port_pc_eclipse]$ ./bin/main 
[Warn]  (0.000, +0)      lv_init: Memory integrity checks are enabled via LV_USE_ASSERT_MEM_INTEGRITY which makes LVGL much slower      (in lv_obj.c line #160)
[Warn]  (0.000, +0)      lv_init: Object sanity checks are enabled via LV_USE_ASSERT_OBJ which makes LVGL much slower   (in lv_obj.c line #164)
[Warn]  (0.000, +0)      lv_init: Style sanity checks are enabled that uses more RAM    (in lv_obj.c line #168)





The application is set up for touch input, so it’s not very well optimized to work with mouse, but touch drag, etc seems to work very well. On-screen keyboard also looks very well integrated and responds nicely to clicks in text fields.

The binary is 1.2MB and only links to libSDL2 and libc.

[cbrake@ceres lv_port_pc_eclipse]$ readelf -d bin/main                                                                                                                                                                    
Dynamic section at offset 0x114dd0 contains 27 entries:                                                                                                                                                                   
  Tag        Type                         Name/Value                                                                                                                                                                      
 0x0000000000000001 (NEEDED)             Shared library: []
 0x0000000000000001 (NEEDED)             Shared library: []
 0x000000000000000c (INIT)               0x4000
 0x000000000000000d (FINI)               0xb58b8
 0x0000000000000019 (INIT_ARRAY)         0x114810

The fb version is only 909KB and only links to libc (may not be exactly the same demo).

This is all quite amazing – a fairly full featured graphics library with minimal dependencies and produces tiny binaries.

I really like the build setup – a super project that pulls in dependencies as Git sub-modules makes a lot of sense. We’ve been doing this for a long time in Yoe. The RP2040 build system is another example.

Software developed for constrained targets is often good and wins in the end. Some examples of this:

  • Linux excels in server environments where efficiency (both CPU cycles and administration) and stability matters, thus it has also done very well in embedded applications as well.
  • Go was designed for server/cloud where again the goal was efficiency, simplicity, and reliability. A few % on 1000’s of servers matters to the bottom line. Easy deployment also matters. Thus it is also really good for edge/embedded devices.

LVGL is starting at the opposite end – deeply embedded MCU devices, but it may also scale well into embedded Linux, desktop, and other spaces for people who need an efficient graphics library that gets the job done. When built with musl libc, you could have a completely static binary with no dependencies.

Software developed for desktop systems seems to trend toward bloat because it does not need to worry about the constraints of data center power usage/administration or embedded systems. Unfortunately, many graphical libraries fall into this. Constraints are good …

1 Like

Neat! And it’s MIT licensed! :slight_smile:

At my previous job we ended up using Qt 4 for a bunch of things simply due to the licensing of Qt 5 and beyond. Qt 4 had enough functionality in the core LGPLv2 licensed parts that it was useful, if a bit dated in terms of UI and features. Qt 5 license costs were too high for the business case and we were prevented from using many LGPLv3 and GPLv3 software in-product.

Wish I would have known about LVGL at that time, could have been super useful!

Thanks for detailed review. It indeed seems ground up approach which always is good to shed some technical debt. Few things to look forward to.

  1. Write a recipe for Yoe
  2. Create a sample UI for both Linux ( perhaps with (yoe-glibc-systemd-eglfs) and without fb (yoe-glibc-systemd-x11) )
  3. Write a system using zephyr ( meta-zephyr ) based MPU SDK perhaps.

I also ways appreciate small.

Looks like there is an OE recipe:

I’m not sure how useful this is because it seems you’d want to embed the LVGL source in your project as a submodule.

The demo code worked well on both Linux desktop, as described by @cbrake above, as well as on Windows desktop.

Windows desktop

Linux desktop (Ubuntu 20.04) with frame buffer:

Embedded Linux - cross compiling on Ubuntu for aarch64

  • git clone
  • cd lv_port_linux_frame_buffer
  • git submodule update --init --recursive
  • mkdir build && cd build
  • download the “aarch64-linux-musl” option from and place in the build/ directory
  • export CC="aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc"
  • cmake -DCMAKE_EXE_LINKER_FLAGS="-static -Os" ..
  • make

The following error results:

[ 41%] Built target lvgl 
[ 46%] Built target lv_drivers
[ 60%] Built target lvgl_demos
[ 99%] Built target lvgl_examples
[100%] Linking C executable lvgl_fb
/home/******/lv_port_linux_frame_buffer/build/aarch64-linux-musl-cross/bin/../lib/gcc/aarch64-linux-musl/11.2.1/../../../../aarch64-linux-musl/bin/ld: cannot find -lwayland-client
/home/******/lv_port_linux_frame_buffer/build/aarch64-linux-musl-cross/bin/../lib/gcc/aarch64-linux-musl/11.2.1/../../../../aarch64-linux-musl/bin/ld: cannot find -lwayland-cursor
/home/******/lv_port_linux_frame_buffer/build/aarch64-linux-musl-cross/bin/../lib/gcc/aarch64-linux-musl/11.2.1/../../../../aarch64-linux-musl/bin/ld: cannot find -lxkbcommon
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/lvgl_fb.dir/build.make:104: lvgl_fb] Error 1
make[1]: *** [CMakeFiles/Makefile2:124: CMakeFiles/lvgl_fb.dir/all] Error 2
make: *** [Makefile:130: all] Error 2

If anyone has an idea, please let me know. Thanks!

@collinbrake if you want to cross compile using ubuntu you will need all dependencies cross-compiled as well. Its needing wayland and xkbcommon but these inturn might need more so it can be a bit more packages and they should be compiled for musl since thats what you are using. See this article also on how cross-compiling would work on a native linux distro

You can also take advantage of ubuntu/debian multiarch support, so add arm64 to your base distro

sudo dpkg --add-architecture arm64
sudo touch /etc/apt/sources.list.d/arm64-cross-compile-sources.list

The add the apt sources to /etc/apt/sources.list.d/arm64-cross-compile-sources.list

deb [arch=arm64] focal main restricted
deb [arch=arm64] focal-updates main restricted
deb [arch=arm64] focal universe
deb [arch=arm64] focal-updates universe
deb [arch=arm64] focal multiverse
deb [arch=arm64] focal-updates multiverse
deb [arch=arm64] focal-backports main restricted universe multiverse

add following to /etc/apt/sources.list

deb [arch=amd64] focal main restricted universe multiverse

run sudo apt update

now you can install arm64 packages

sudo apt-get install gcc-aarch64-linux-gnu
sudo apt install wayland:arm64
sudo apt install xkbcommon:arm64

Now you can use the cross compiler to build your packages use.
CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ in env

I would think it will be better to use Yocto SDK, perhaps with all dependencies built into SDK

maybe @cbrake can generate one for you using yoe

bitbake -cpopulate_sdk yoe-sdk-image

will generate it and then you can simply install it on your ubuntu box via the self installer that it will generate.

also see

@khem Thank you very much - I am working on it, and should go the yocto route from the beginning, as you said.

Start of a lvgl recipe here:

It has some packaging problems, but does build a binary that I can scp over to the target i.MX8 device. The result is not perfect but runs:

Probably need to configure the FB size or something …

Recipe for the LVGL PC demo:

Again, builds fine, but has some packaging issues.

Copied libsdl over to the target and installed it:

root@imx8qxp-var-som:~# opkg install libsdl2-2.0-0_2.0.22-r0.0_cortexa35-mx8.ipk 
Installing libsdl2-2.0-0 (2.0.22) on root
Configuring libsdl2-2.0-0.

Then scp’d the LVGL binary and ran it:

root@imx8qxp-var-som:~# ./main 
[Warn]  (0.000, +0)      lv_init: Memory integrity checks are enabled via LV_USE_ASSERT_MEM_INTEGRITY which makes LVGL much slower      (in lv_obj.c line #160)
[Warn]  (0.000, +0)      lv_init: Object sanity checks are enabled via LV_USE_ASSERT_OBJ which makes LVGL much slower   (in lv_obj.c line #164)
[Warn]  (0.000, +0)      lv_init: Style sanity checks are enabled that uses more RAM    (in lv_obj.c line #168)

But, nothing on the screen – probably need X/Wayland, etc.

Asked for help on the LVGL forum, and the author suggested changing the color format – it works!

[cbrake@ceres git]$ git diff
diff --git a/lv_conf.h b/lv_conf.h
index 6bfe50f..e83dc55 100644
--- a/lv_conf.h
+++ b/lv_conf.h
@@ -24,7 +24,7 @@
 /*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
-#define LV_COLOR_DEPTH 32
+#define LV_COLOR_DEPTH 16
 /*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/
 #define LV_COLOR_16_SWAP 0

Touchscreen is not working yet, so that is the next project …

1 Like

for touchscreen perhaps we need to look into

Some porting docs on input drivers

with the latest yoe/master, we are using Clang 15 and now getting a compile error in the fb demo, so opened an issue here:

Clang is trying to get rid of pre-c99 constructs which is nice to clean up but its everywhere :frowning: There is fix I proposed

yeah, forces everyone to clean up their code – not a bad thing. Works, thanks!

These diagnostics have been demoted into warnings in clang 15.0.1 release which we will have shortly for Yoe too.

nevertheless, I agree that 20 years is enough of a time to migrate

1 Like