How to build LVGL applications using STM32CubeIDE

Revision as of 10:44, 22 August 2022 by Registered User (→‎Configure CubeIDE with SDK)
Applicable for STM32MP13x lines, STM32MP15x lines

This article gives information about how to easily develop a LVGL application on STM32MP1 using STM32CubeIDE.

1. Architecture overview[edit source]

The following diagram summarizes all the configurations of the LVGL library. In this article we will detail each of the possibilities and we will see how to generate the SDK.

1.1. Component presentation[edit source]

1.1.1. LVGL[edit source]

LVGL (Light and Versatile Graphics Library) is an open-source embedded GUI library that is written in C (with C++ compatibility) and is released under the MIT license. It’s optimized for deployment on micro-controllers and bare metal (no OS) devices, but on this article, we will see how to develop LVGL applications for our STM32MP1 microprocessor that runs the OpenSTLinux linux distribution.

1.1.2. SDL2[edit source]


  • SDL or Simple Direct Media Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware . it allows to show windows, show images, play sounds, manage keyboards...
  • SDL is written in C, works natively with C++, and there are bindings available for several other languages, including C# and Python.
  • SDL 2.0 is distributed under the zlib license. This license allows you to use SDL freely in any software.


1.1.3. Wayland / Weston[edit source]

  • Wayland is a protocol that allows a composer to communicate with multiple windows. It works only with systems using KMS (Kernel-based mode-setting), a kernel feature dependent on the graphics card driver used, hence the need to use a composer.
  • A composer implementing the Wayland protocol (Mutter, Kwin, Enlightenment, Weston...) is necessary (otherwise Wayland alone does nothing, and it is always an X server that would be used).
  • The composer is a window manager who uses a buffer in memory to manage each window and apply visual effects to it.
  • Weston is the reference implementation of a graphical composer window manager for the Wayland display protocol, its existence is justified by the need to develop Wayland without depending on the ups and downs of the development of libraries and environments, by the need to be able to rigorously test Wayland without experiencing bugs independent of Wayland.



1.1.4. DRM / KMS[edit source]

For more information about drm /kms, you can see the following article: DRM/KMS

1.2. Different configurations[edit source]

1.2.1. Weston Wayland SDL2[edit source]

1.2.2. DRM-KMS / SDL2 configuration with GPU[edit source]

1.2.3. DRM-KMS / SDL2 configuration without GPU[edit source]

2. How to generate SDK for the configuration[edit source]

2.1. Purpose to generate SDK[edit source]

  • SDK or Software development kit is a programming package that enables a programmer to develop applications for a specific platform .
  • In our case, this SDK will allow us to use the SDL2 library, without the need to download it, and include it in our LVGL project.

2.2. Generate SDK for SDL2 with GPU configuration[edit source]

  • Before generating the SDK, we will start by creating a recipe append file for the SDL2 library in our distribution package.
 mkdir /local/STM32MP15-Ecosystem-v3.1.0/Distribution-Package/openstlinux-5.10-dunfell-mp1-21-11-17/layers/meta-st/meta-st-openstlinux/recipes-graphics/libsdl2
 cd /local/STM32MP15-Ecosystem-v3.1.0/Distribution-Package/openstlinux-5.10-dunfell-mp1-21-11-17/layers/meta-st/meta-st-openstlinux/recipes-graphics/libsdl2
 touch libsdl2_%.bbappend
  • To configure SDL2 for stm32mp1 platform and Vivante GPU,copy the following script into the recipe.
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"

SRC_URI += "file://0001-Correction-of-bad-dlopen-for-libEGL.patch"

PACKAGECONFIG_GL = ""
PACKAGECONFIG ??= " \
    ${PACKAGECONFIG_GL} \
    ${@bb.utils.filter('DISTRO_FEATURES', 'alsa pulseaudio', d)} \
    ${@bb.utils.contains('DISTRO_FEATURES', 'wayland', 'wayland gles2', '', d)} \
    ${@bb.utils.contains("TUNE_FEATURES", "neon","arm-neon","",d)} \
    kmsdrm \
"
PACKAGECONFIG[gles2]      = "--enable-video-opengles2,--disable-video-opengles2,virtual/libgles2"
 
EXTRA_OECONF += " \
    --disable-video-vivante \
    --disable-video-vulkan \
    --disable-video-opengl \
"
 

  • To solve some problems of reading the symbols for the lib libegl , we need to create a patch called by the recipe .
  • On the same directory (workspace) , we create a subdirectory with the name of files and in it we create our patch with the name of 0001-Correction-of-bad-dlopen-for-libEGL.patch
  • Copy the following script into our patch.
--- a/src/loadso/dlopen/SDL_sysloadso.c
+++ b/src/loadso/dlopen/SDL_sysloadso.c
@@ -46,8 +46,13 @@ SDL_LoadObject(const char *sofile)
         return NULL;
     }
#endif
+    // SDL will skip dlopen'ing libEGL.so after it spots EGL symbols on our
+    // wrapper, so force loading it here.
+    if (strncmp(sofile, "libEGL.so", 9) != 0)
+        handle = dlopen(sofile, RTLD_NOW|RTLD_LOCAL | RTLD_GLOBAL | RTLD_LAZY);
+    else
+        handle = dlopen(sofile, RTLD_NOW|RTLD_LOCAL);

-    handle = dlopen(sofile, RTLD_NOW|RTLD_LOCAL);
     loaderror = dlerror();
     if (handle == NULL) {
         SDL_SetError("Failed loading %s: %s", sofile, loaderror);
  • Add libsdl2 to our image for sdk in CORE_IMAGE_EXTRA_INSTALL variable:
  cd /local/STM32MP15-Ecosystem-v3.1.0/Distribution-Package/openstlinux-5.10-dunfell-mp1-21-11-17/layers/meta-st/meta-st-openstlinux/recipes-st/images

  • Modify the script of st-image-weston.bb as follows :


  • To create SDK :
 cd /local/STM32MP15-Ecosystem-v3.1.0/Distribution-Package/openstlinux-5.10-dunfell-mp1-21-11-17
 DISTRO=openstlinux-weston MACHINE=stm32mp1 source layers/meta-st/scripts/envsetup.sh
 bitbake -c populate_sdk st-image-weston
  • Bitbake generates the sdk in the following directory:
  cd /local/STM32MP15-Ecosystem-v3.1.0/Distribution-Package/openstlinux-5.10-dunfell-mp1-21-11-17/build-openstlinuxweston-stm32mp1/tmp-glibc/deploy/sdk
  • To give the right of execution :
 chmod +x st-image-weston-openstlinux-weston-stm32mp1-x86_64-toolchain-3.1.11-snapshot.sh
  • Run the SDK and copy it back to the developer package.
 ./st-image-weston-openstlinux-weston-stm32mp1-x86_64-toolchain-3.1.11-snapshot.sh -d /local/STM32MP15-Ecosystem-v3.1.0/Developer-Package/SDK_SDL2_WITH_GPU

2.3. Generate SDK for SDL2 without GPU configuration[edit source]

To generate the SDK for sdl2 without using GPU , you can follow the same steps as with GPU , just add a line in the recipe that disables the GPU :

PACKAGECONFIG_remove = "gles2"

3. How to build LVGL project with STM32CubeIDE[edit source]

3.1. Get LVGL project[edit source]

  • Create a new folder.
  • Open a terminal in this folder.
  • Download the LVGL project with the following command:
 git clone --recursive https://github.com/lvgl/lv_port_pc_eclipse.git

If you are using Windows, you can go to LVGL's GitHub and download the folder and unzip it.

  • Rename your project.
  • Launch STM32CubeIDE
  • Import your project :

Click on file ==> Import ==> Existing Projects into Workspace ==> select your LVGL project.

  • Change the toolchain .

By default, stm32CubeIDE uses a Tool Chain for a x86_64 architecture, in our case we want to compile for our microprocessor with an ARM architecture. For this we need to change it:

Right-Click on you project ==> Properties ==> C/C++ Build ==> Tool Chain Editor ==> select OpenSTLinux SDK ==> Apply and Close

Toolchain.png

3.2. Configure CubeIDE with SDK[edit source]

  • To be able to use the SDK that we have generated , stm32CubeIDE needs the absolute path to access it :

Right-click on you project ==> Properties ==> C/C++ Build ==>Environment ==> Double click on SDKPATH ==> Paste the the absolute path ==> click on "OK" ==> click on "Apply an close "

SDK.png
  • Add include path (for libdrm) :

Right-click on you project ==> Properties ==> C/C++ Build ==>Settings ==> MPU GCC Compiler ==> Include paths == > add the followings path :

"${SDKPATH}/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi/usr/include/libdrm/"
Path.png
  • Add drm library for linker :

Right-click on you project ==> Properties ==> C/C++ Build ==>Settings ==> MPU GCC Linker ==> Libraries== > add a new library that we call drm.

Drmm.png

3.3. Proposed modifications on LVGL project[edit source]

To adapt LVGL to our stmp32mp1 board some modification are needed.

  • Remplace the content of the main file of your project , by the following script :

#include <stdio.h>
#include <stdlib.h>

#include "lvgl/lvgl.h"
#include "lv_drivers/display/fbdev.h"
#include "lv_drivers/display/drm.h"
#include "lv_drivers/indev/evdev.h"
#include "lv_demo_conf.h"
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>

#define DISP_BUF_SIZE (128 * 1024)

#define COMPILE_WIDGET 1
//#define COMPILE_MUSIC 1
//#define COMPILE_KEYPAD 1

int main(void)
{
    /*LittlevGL init*/
    lv_init();

#if USE_FBDEV
    /*Linux frame buffer device init*/
    fbdev_init();
#endif
#if USE_DRM
    drm_init();
#endif

    /*A small buffer for LittlevGL to draw the screen's content*/
    static lv_color_t buf[DISP_BUF_SIZE];

    /*Initialize a descriptor for the buffer*/
    static lv_disp_draw_buf_t disp_buf;
    lv_disp_draw_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE);

    /*Initialize and register a display driver*/
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.draw_buf   = &disp_buf;
#if USE_FBDEV
    disp_drv.flush_cb   = fbdev_flush;
#endif
#if USE_DRM
    disp_drv.flush_cb   = drm_flush;
#endif

    const char *width, *height, *rotate;
    width = getenv("LVGL_DRM_WIDTH");
    height = getenv("LVGL_DRM_HEIGHT");
fprintf(stderr, " ----> %s %s\n", width, height);
    if (width && height) {
        disp_drv.hor_res    = atoi(width);
        disp_drv.ver_res    = atoi(height);
    } else {
        disp_drv.hor_res    = 480;
        disp_drv.ver_res    = 800;
    }
    lv_disp_drv_register(&disp_drv);


    evdev_init();
#if USE_EVDEV
  const char *device_evdev;
  device_evdev = getenv("LVGL_EVDEV");
  if (device_evdev) 
     evdev_init_with_device(device_evdev);
  else
     evdev_init();
  static lv_indev_drv_t indev_drv;
  lv_indev_drv_init(&indev_drv);
  indev_drv.type = LV_INDEV_TYPE_POINTER;
  indev_drv.read_cb = evdev_read;
  lv_indev_t *evdev_indev = lv_indev_drv_register(&indev_drv);
#endif



#ifdef COMPILE_WIDGET
    lv_demo_widgets();
#endif
#ifdef COMPILE_MUSIC
    lv_demo_music();
#endif
#ifdef COMPILE_KEYPAD
    lv_demo_keypad_encoder();
#endif

    /*Handle LitlevGL tasks (tickless mode)*/
    while(1) {
        lv_task_handler();
        usleep(5000);
    }

    return 0;
}



/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/
uint32_t custom_tick_get(void)
{
    static uint64_t start_ms = 0;
    if(start_ms == 0) {
        struct timeval tv_start;
        gettimeofday(&tv_start, NULL);
        start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
    }

    struct timeval tv_now;
    gettimeofday(&tv_now, NULL);
    uint64_t now_ms;
    now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;

    uint32_t time_ms = now_ms - start_ms;
    return time_ms;
}
  • Remplace the content of the Makefile of your project , by the following script :
#
# Makefile
#
CC ?= gcc
LVGL_DIR_NAME ?= lvgl
LVGL_DIR ?= ${shell pwd}
CFLAGS += -O3 -g0 -I$(LVGL_DIR)/ -Wall -Wshadow -Wundef -Wmissing-prototypes -Wno-discarded-qualifiers -Wall -Wextra -Wno-unused-function -Wno-error=strict-prototypes -Wpointer-arith -fno-strict-aliasing -Wno-error=cpp -Wuninitialized -Wmaybe-uninitialized -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits -Wsizeof-pointer-memaccess -Wno-format-nonliteral -Wno-cast-qual -Wunreachable-code -Wno-switch-default -Wreturn-type -Wmultichar -Wformat-security -Wno-ignored-qualifiers -Wno-error=pedantic -Wno-sign-compare -Wno-error=missing-prototypes -Wdouble-promotion -Wclobbered -Wdeprecated -Wempty-body -Wtype-limits -Wshift-negative-value -Wstack-usage=2048 -Wno-unused-value -Wno-unused-parameter -Wno-missing-field-initializers -Wuninitialized -Wmaybe-uninitialized -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits -Wsizeof-pointer-memaccess -Wno-format-nonliteral -Wpointer-arith -Wno-cast-qual -Wmissing-prototypes -Wunreachable-code -Wno-switch-default -Wreturn-type -Wmultichar -Wno-discarded-qualifiers -Wformat-security -Wno-ignored-qualifiers -Wno-sign-compare
LDFLAGS += -lm -ldrm
BIN = demo


#Collect the files to compile
MAINSRC = ./main.c

include $(LVGL_DIR)/lvgl/lvgl.mk
include $(LVGL_DIR)/lv_drivers/lv_drivers.mk
include $(LVGL_DIR)/lv_demos/lv_demo.mk

OBJEXT ?= .o

AOBJS = $(ASRCS:.S=$(OBJEXT))
COBJS = $(CSRCS:.c=$(OBJEXT))

MAINOBJ = $(MAINSRC:.c=$(OBJEXT))

SRCS = $(ASRCS) $(CSRCS) $(MAINSRC)
OBJS = $(AOBJS) $(COBJS)

## MAINOBJ -> OBJFILES

all: default

%.o: %.c
	@echo "$(CC) $(CFLAGS) -c $< -o $@"
	@$(CC)  $(CFLAGS) -c $< -o $@
    
default: $(AOBJS) $(COBJS) $(MAINOBJ)
	$(CC) -o $(BIN) $(MAINOBJ) $(AOBJS) $(COBJS) $(LDFLAGS)

clean: 
	rm -f $(BIN) $(AOBJS) $(COBJS) $(MAINOBJ)

  • Remplace the content of the lv_demo_conf.h file of your project , by the following script :
/**
 * @file lv_demo_conf.h
 * Configuration file for v8.1.0-dev
 *
 */
/*
 * COPY THIS FILE AS lv_demo_conf.h
 */

#if 1 /*Set it to "1" to enable the content*/

#ifndef LV_EX_CONF_H
#define LV_EX_CONF_H


/*******************
 * GENERAL SETTING
 *******************/
#define LV_EX_PRINTF       0       /*Enable printf-ing data in demoes and examples*/
#define LV_EX_KEYBOARD     0       /*Add PC keyboard support to some examples (`lv_drivers` repository is required)*/
#define LV_EX_MOUSEWHEEL   0       /*Add 'encoder' (mouse wheel) support to some examples (`lv_drivers` repository is required)*/

/*********************
 * DEMO USAGE
 *********************/

/*Show some widget*/
#define LV_USE_DEMO_WIDGETS        1
#if LV_USE_DEMO_WIDGETS
#define LV_DEMO_WIDGETS_SLIDESHOW  0
#endif

/*Printer demo, optimized for 800x480*/
#define LV_USE_DEMO_PRINTER     1

/*Demonstrate the usage of encoder and keyboard*/
#define LV_USE_DEMO_KEYPAD_AND_ENCODER     1

/*Benchmark your system*/
#define LV_USE_DEMO_BENCHMARK   1

/*Stress test for LVGL*/
#define LV_USE_DEMO_STRESS      1

/*Music player demo*/
#define LV_USE_DEMO_MUSIC      1
#if LV_USE_DEMO_MUSIC
# define LV_DEMO_MUSIC_LANDSCAPE    0
# define LV_DEMO_MUSIC_LARGE        0
#define LV_DEMO_MUSIC_AUTO_PLAY     0
#endif

#endif /*LV_EX_CONF_H*/

#endif /*End of "Content enable"*/

we create a new patch that we rename 0001-extend-evdev.patch and we copy the following script :

From a9b0e2008e35e23910238bd1bac562c36499f765 Mon Sep 17 00:00:00 2001
From: Christophe Priouzeau <christophe.priouzeau@foss.st.com>
Date: Mon, 23 Aug 2021 09:42:17 +0200
Subject: [PATCH] extend evdev

---
 indev/evdev.c | 15 +++++++++++++++
 indev/evdev.h |  2 ++
 2 files changed, 17 insertions(+)

diff --git a/lv_drivers/indev/evdev.c b/lv_drivers/indev/evdev.c
index e85b7f4..b6f4f13 100644
--- a/lv_drivers/indev/evdev.c
+++ b/lv_drivers/indev/evdev.c
@@ -75,6 +75,21 @@ void evdev_init(void)
     evdev_key_val = 0;
     evdev_button = LV_INDEV_STATE_REL;
 }
+void evdev_init_with_device(char *device)
+{
+    evdev_fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
+    if(evdev_fd == -1) {
+        perror("unable open evdev interface:");
+        return;
+    }
+
+    fcntl(evdev_fd, F_SETFL, O_ASYNC | O_NONBLOCK);
+    evdev_root_x = 0;
+    evdev_root_y = 0;
+    evdev_key_val = 0;
+    evdev_button = LV_INDEV_STATE_REL;
+}
+
 /**
  * reconfigure the device file for evdev
  * @param dev_name set the evdev device filename
diff --git a/lv_drivers/indev/evdev.h b/lv_drivers/indev/evdev.h
index dcb9114..bc82cb4 100644
--- a/lv_drivers/indev/evdev.h
+++ b/lv_drivers/indev/evdev.h
@@ -45,6 +45,8 @@ extern "C" {
  * Initialize the evdev
  */
 void evdev_init(void);
+void evdev_init_with_device(char *device);
+
 /**
  * reconfigure the device file for evdev
  * @param dev_name set the evdev device filename
-- 
2.25.1
  • Apply patch 0001-extend-evdev.patch in lvgl root directory
 patch -p1 < 0001-extend-evdev.patch
  • In lvgl root directory (in file lvgl_conf.h ), we apply the following changes:

Replace this part of the code :

 /*Use a custom tick source that tells the elapsed time in milliseconds.
  *It removes the need to manually update the tick with `lv_tick_inc()`)*/
#define LV_TICK_CUSTOM 0
#if LV_TICK_CUSTOM
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h"         /*Header for the system time function*/ #define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis())    /*Expression evaluating to current system time in ms*/
*Expression evaluating to current system time in ms*/
 #endif   /*LV_TICK_CUSTOM*/

with :

  /*Use a custom tick source that tells the elapsed time in milliseconds.
  *It removes the need to manually update the tick with `lv_tick_inc()`)*/
uint32_t custom_tick_get(void);

#define LV_TICK_CUSTOM     1
 #if LV_TICK_CUSTOM
#define LV_TICK_CUSTOM_INCLUDE  <stdint.h>         /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (custom_tick_get())     /*Expression evaluating to current system time in ms*/
 #endif   /*LV_TICK_CUSTOM*/


Change maximum buffer size to allocate for rotation by replacing the following line :

#define LV_DISP_ROT_MAX_BUF (32*1024)

with :

#define LV_DISP_ROT_MAX_BUF (10*1024)

Replace this part of the code :

 /*Use SDL renderer API*/
#define LV_USE_GPU_SDL 0
#if LV_USE_GPU_SDL
#define LV_GPU_SDL_INCLUDE_PATH <SDL2/SDL.h>
/*Texture cache size, 8MB by default*/
#define LV_GPU_SDL_LRU_SIZE (1024 * 1024 * 8)
/*Custom blend mode for mask drawing, disable if you need to link with older SDL2 lib*/
#define LV_GPU_SDL_CUSTOM_BLEND_MODE (SDL_VERSION_ATLEAST(2, 0, 6))
#endif

with :

 /*Use SDL renderer API*/
//#define LV_USE_GPU_SDL 0
//#if LV_USE_GPU_SDL
//    #define LV_GPU_SDL_INCLUDE_PATH <SDL2/SDL.h>
//    /*Texture cache size, 8MB by default*/
//    #define LV_GPU_SDL_LRU_SIZE (1024 * 1024 * 8)
/*Custom blend mode for mask drawing, disable if you need to link with older SDL2 lib*/
//    #define LV_GPU_SDL_CUSTOM_BLEND_MODE (SDL_VERSION_ATLEAST(2, 0, 6))
//#endif
  • In lvgl root directory (in lv_drv_conf.h ), we apply the following changes:

Change the resolution by modifying the following parameters :

#if USE_MONITOR
#  define MONITOR_HOR_RES     480
#  define MONITOR_VER_RES     320

with :

#if USE_MONITOR
#  define MONITOR_HOR_RES     800
#  define MONITOR_VER_RES     480

we choose to use DRM/KMS by changing :

#  define USE_DRM           0

with :

#  define USE_DRM           1

we choose to use EVDEV by replacing :

#  define USE_EVDEV           0

with :

#  define USE_EVDEV          1

Replacing the following part of code :

#if USE_EVDEV || USE_BSD_EVDEV
#  define EVDEV_NAME   "/dev/input/event0"        /*You can use the "evtest" Linux tool to get the list of devices and test them*/

with :

#if USE_EVDEV || USE_BSD_EVDEV
#  define EVDEV_NAME   "/dev/input/event1"        /*You can use the "evtest" Linux tool to get the list of devices and test them*/

3.4. Run the demo[edit source]

  • Build the project

Right-Click on you project ==> Build project

  • If everything works well, in your project (folder "Debug") you find the binary file.

You can copy it to the board by using the following command:

 scp <name_of_your_binary> root@<IP_address>:/

To get the IP address of your target, type the following commands:

 minicom  -D /dev/ttyACM0
 ifconfig

inet is the ip address of your target

  • Run your binary
 sync

In our case, we will rely on DRM/KMS, so we will stop wayland/ weston and we execute the binary.

 systemctl stop weston@root
 cd /
 ./<name_of_your_binary >

To kill the current project:

 CTRL + c

3.5. Display performances[edit source]

To measure performances, you will need two terminals, the first to launch the application and the second to display the performances in real time.

  • After copying the binary, we launch the first terminal on the board with the following commands :
  minicom  -D /dev/ttyACM0
   ./<name_of_your_binary>
  • we launch the second terminal with SSH .
  ssh root@<IP_address>

on the same terminal paste and run the following script :


 (while true; do \
 
gpu_last_gc=$(cat /sys/kernel/debug/gc/idle); \
 
m=$(mpstat 4 1 | grep "Average:     all" | awk -F" " '{print "cpu load " $3 "%"}'); \
 
echo "-------------------------"; \
 
export fps=`cat /sys/kernel/debug/dri/0/state | grep fps -m1 | grep -o '[0-9]\+'`; echo display  ${fps}fps; \
 
gpu_on=$(echo $gpu_last_gc | tr -d '\n' | tr -d ',' | awk -F" " '{printf("%f\n", $2)}'); \
 
gpu_start=$(echo $gpu_last_gc | tr -d '\n' | tr -d ',' | awk -F" " '{printf("%f\n", ($2+$5+$8+$11))}'); \
 
tr -d '\n' < /sys/kernel/debug/gc/idle | tr -d ',' | awk -v on=$gpu_on -v start=$gpu_start -F" " '{printf ("gpu load %.0f%%\n", ($2 - on) * 100/($2+$4+$6+$8 - start));}'; \
 
echo $m; \
 
done) &

to stop the script: Click on

                      > 'f' +'g'+ ENTER  
                      > CTRL+C

4. To go further[edit source]

"LVGL project examples"[1] "Learn LVGL"[2] "LVGL forum"[3]

5. References[edit source]

"LVGL official web site"[4] "SDL official web site"[5] "Wayland official web site"[6]