How to build LVGL applications using STM32CubeIDE

Revision as of 10:54, 4 October 2022 by Registered User (→‎Get LVGL project)
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.

Lvgl configurations.png

1.1. Component presentation[edit source]

1.1.1. LVGL[edit source]

https://lvgl.io/

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]

https://www.libsdl.org/index.php

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]

https://wayland.freedesktop.org/architecture.html?_sm_au_=iVV4MHTR7sLqs9RQcLpsvK618Vf61

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 configuration with GPU[edit source]


Lvgl conf1.png

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

Lvgl conf2.png


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

Lvgl conf3.png

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

2.1. Get SDK[edit source]

For OpenSTLinux release >= 4.1, SDK provided with the release can be used
For previous releases, you may need to generate a SDK with following modifications for libSDL2
Backporting modifications from 4.1 release on following files
Add configuration to support of STMP32MP1 Vivante GPU
in layers/meta-st/meta-st-openstlinux/recipes-graphics/libsdl2/libsdl2_%.bbappend
Add libSDL2 to SDK
layers/meta-st/meta-st-openstlinux/packagegroups/packagegroup-framework-core-base.bb

2.2. 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
 cd lv_port_pc_eclipse/
 git checkout -b WORKING origin/release/v8.1
 git submodule init
 git submodule update

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


  • Import your project :

Click on file ==> Import ==> Projects from folder or archive ==> Next==> Directory ==> upload your LVGL project.

Chemin.png


  • Rename your 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 your project ==> Properties ==> C/C++ Build ==> Tool Chain Editor ==> select OpenSTLinux SDK ==> Apply and Close

Toolchain.png


2.3. 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



2.3.1. For usage of SDL2 ( configuration 1 and 2)[edit source]

  • Remove SDL2 Main libraries for linker

Right-click on you project ==> Properties ==> C/C++ Build ==>Settings ==> MPU GCC Linker ==> Libraries== >Remove SDL2main

Drmm.png


2.3.2. For usage of direct DRM/KMS ( configuration 3)[edit source]

  • 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


  • Remove SDL2 and SDL2 Main libraries for linker

Right-click on you project ==> Properties ==> C/C++ Build ==>Settings ==> MPU GCC Linker ==> Libraries== >Remove SDL2main and SDL2

2.4. Proposed modifications on LVGL project (common for all configuration)[edit source]

To adapt LVGL to our stmp32mp1 board some modification are needed.
The following modification are common for all the configuration described on this pages (SDL2 configurations or direct DRM/KMS configuration)

2.4.1. Adaptation of example source code (aka main.c)[edit source]

  • Replace the content of the main file of your project , by the following script :
/**
 * @file main.c
 *
 */
/*********************
 *      INCLUDES
 *********************/
#define _DEFAULT_SOURCE /* needed for usleep() */
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>

// For SDL2 usage
#define SDL_MAIN_HANDLED /*To fix SDL's "undefined reference to WinMain" issue*/
#include <SDL2/SDL.h>
#include "lvgl/lvgl.h"
#include "lvgl/examples/lv_examples.h"
#include "lv_demos/lv_demo.h"
#include "lv_drivers/sdl/sdl.h"

// For USE DRM usage
#include "lv_drivers/display/drm.h"
#include "lv_drivers/indev/evdev.h"

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void hal_init(void);
static int tick_thread(void *data);


/*********************
 *      DEFINES
 *********************/
/* select which demo you would like to execute */
#define COMPILE_WIDGET 1
//#define COMPILE_MUSIC 1
//#define COMPILE_KEYPAD 1


/**********************
 *   GLOBAL FUNCTIONS
 **********************/
int main(int argc, char **argv)
{
    (void)argc; /*Unused*/
    (void)argv; /*Unused*/

    /*Initialize LVGL*/
    lv_init();

    /*Initialize the HAL (display, input devices, tick) for LVGL*/
    hal_init();

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

    while(1) {
        /* Periodically call the lv_task handler.
         * It could be done in a timer interrupt or an OS task too.*/
        lv_timer_handler();
        usleep(5 * 1000);
    }

    return 0;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/
/**
 * Initialize the Hardware Abstraction Layer (HAL) for the LVGL graphics
 * library
 */
static void hal_init(void)
{
#if USE_DRM
    drm_init();
#endif
#if USE_SDL
    sdl_init();
#endif

    /* Tick init.
     * You have to call 'lv_tick_inc()' in periodically to inform LittelvGL about
     * how much time were elapsed Create an SDL thread to do this*/
 #if USE_SDL
    SDL_CreateThread(tick_thread, "tick", NULL);
#endif

    /* Create a display buffer */
    static lv_disp_draw_buf_t disp_buf1;

#if USE_DRM
    static lv_color_t buf1_1[128 * 1024];
    lv_disp_draw_buf_init(&disp_buf1, buf1_1, NULL, 128 * 1024);
#endif
#if USE_SDL
    static lv_color_t buf1_1[SDL_HOR_RES * 100];
    lv_disp_draw_buf_init(&disp_buf1, buf1_1, NULL, SDL_HOR_RES * 100);
#endif

    /*Create a display*/
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv); /*Basic initialization*/
    disp_drv.draw_buf = &disp_buf1;
#if USE_DRM
    disp_drv.flush_cb   = drm_flush;
    lv_coord_t drm_width, drm_height;
    /* get size from DRM/KMS backend */
    uint32_t drm_dpi;
    drm_get_sizes(&drm_width, &drm_height, &drm_dpi);
    disp_drv.hor_res    = drm_width;
    disp_drv.ver_res    = drm_height;
#endif
#if USE_SDL
    disp_drv.flush_cb = sdl_display_flush;
    disp_drv.hor_res = SDL_HOR_RES;
    disp_drv.ver_res = SDL_VER_RES;
#endif
    fprintf(stderr, "SCREEN SIZE ----> %d x %d\n", disp_drv.hor_res, disp_drv.ver_res);
    lv_disp_t * disp = lv_disp_drv_register(&disp_drv);

    lv_theme_t * th = lv_theme_default_init(disp,
                                            lv_palette_main(LV_PALETTE_BLUE),
                                            lv_palette_main(LV_PALETTE_RED),
                                            LV_THEME_DEFAULT_DARK, LV_FONT_DEFAULT);
    lv_disp_set_theme(disp, th);

#if USE_EVDEV
    /* warning: EVDEV_NAME on lv_drv_conf.h MUST be adapted to the correct input devince on board */
    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_drv_register(&indev_drv);
#endif

#if USE_SDL
    lv_group_t * g = lv_group_create();
    lv_group_set_default(g);

    /* Add the mouse as input device
     * Use the 'mouse' driver which reads the PC's mouse*/
    static lv_indev_drv_t indev_drv_1;
    lv_indev_drv_init(&indev_drv_1); /*Basic initialization*/
    indev_drv_1.type = LV_INDEV_TYPE_POINTER;

    /* This function will be called periodically (by the library) to get the mouse position and state */
    indev_drv_1.read_cb = sdl_mouse_read;
    lv_indev_t *mouse_indev = lv_indev_drv_register(&indev_drv_1);

    static lv_indev_drv_t indev_drv_2;
    lv_indev_drv_init(&indev_drv_2); /*Basic initialization*/
    indev_drv_2.type = LV_INDEV_TYPE_KEYPAD;
    indev_drv_2.read_cb = sdl_keyboard_read;
    lv_indev_t *kb_indev = lv_indev_drv_register(&indev_drv_2);
    lv_indev_set_group(kb_indev, g);

    static lv_indev_drv_t indev_drv_3;
    lv_indev_drv_init(&indev_drv_3); /*Basic initialization*/
    indev_drv_3.type = LV_INDEV_TYPE_ENCODER;
    indev_drv_3.read_cb = sdl_mousewheel_read;
    lv_indev_t * enc_indev = lv_indev_drv_register(&indev_drv_3);
    lv_indev_set_group(enc_indev, g);

    /* Set a cursor for the mouse*/
    LV_IMG_DECLARE(mouse_cursor_icon); /*Declare the image file.*/
    lv_obj_t * cursor_obj = lv_img_create(lv_scr_act()); /*Create an image object for the cursor */
    lv_img_set_src(cursor_obj, &mouse_cursor_icon);           /*Set the image source*/
    lv_indev_set_cursor(mouse_indev, cursor_obj);             /*Connect the image  object to the driver*/
#endif
}

/**
 * A task to measure the elapsed time for LVGL
 * @param data unused
 * @return never return
 */
static int tick_thread(void *data) {
    (void)data;

    while(1) {
 #if USE_SDL
        SDL_Delay(5);
 #endif
       lv_tick_inc(5); /*Tell LittelvGL that 5 milliseconds were elapsed*/
    }
    return 0;
}

/**
 * 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)
{
    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;
}

2.4.2. Configuration of demo for LVGL (aka lv_demo_conf.h)[edit source]

  • The file lv_demo_conf.h must be aligned with the template present on lv_demos/lv_demo_conf_template.h
 cp lv_demos/lv_demo_conf_template.h lv_demo_conf.h
  • Adapt configuration

Enable demo configuration , demo Benchmark, demo Music (with Lanscape) and demo Keypad

Replace at begin of file

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

With

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

Configure following defines to select the demos you want to build

#define LV_USE_DEMO_WIDGETS        1
#define LV_USE_DEMO_KEYPAD_AND_ENCODER    1
#define LV_USE_DEMO_MUSIC      1
# define LV_DEMO_MUSIC_LANDSCAPE    1

2.4.3. Configuration of LVGL (aka lv_conf)[edit source]

  • The file lv_conf.h must be aligned with the template present on lvgl/lv_conf_template.h
 cp lvgl/lv_conf_template.h lv_conf.h
  • Adapt configuration

Enable lvgl configuration, force color depth to 32, enable font for demo

Replace at begin of file

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

With

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

Configure following defines

Force Color Depth

#define LV_COLOR_DEPTH     32

Modify Memory Size to 128

#define LV_MEM_CUSTOM      1
#define  LV_MEM_SIZE    (128U * 1024U)          /*[bytes]*/

Enable font (could be adapted on function of fonts requested by elements used on demo)

#define LV_FONT_MONTSERRAT_8     1
#define LV_FONT_MONTSERRAT_10    1
#define LV_FONT_MONTSERRAT_12    1
#define LV_FONT_MONTSERRAT_14    1
#define LV_FONT_MONTSERRAT_16    1
#define LV_FONT_MONTSERRAT_18    1
#define LV_FONT_MONTSERRAT_20    1
#define LV_FONT_MONTSERRAT_22    1
#define LV_FONT_MONTSERRAT_24    1
#define LV_FONT_MONTSERRAT_26    1
#define LV_FONT_MONTSERRAT_28    1
#define LV_FONT_MONTSERRAT_30    1
#define LV_FONT_MONTSERRAT_32    1
#define LV_FONT_MONTSERRAT_34    1
#define LV_FONT_MONTSERRAT_36    1
#define LV_FONT_MONTSERRAT_38    1
#define LV_FONT_MONTSERRAT_40    1
#define LV_FONT_MONTSERRAT_42    1
#define LV_FONT_MONTSERRAT_44    1
#define LV_FONT_MONTSERRAT_46    1
#define LV_FONT_MONTSERRAT_48    1
#define LV_FONT_MONTSERRAT_12_SUBPX      1
#define LV_FONT_MONTSERRAT_28_COMPRESSED 1  /*bpp = 3*/
#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 1  /*Hebrew, Arabic, Perisan letters and all their forms*/
#define LV_FONT_SIMSUN_16_CJK            1  /*1000 most common CJK radicals*/
#define LV_FONT_UNSCII_8        1
#define LV_FONT_UNSCII_16       1
#define LV_FONT_FMT_TXT_LARGE   1
#define LV_USE_FONT_COMPRESSED  1
#define LV_USE_FONT_SUBPX       1
#define LV_USE_BIDI         1

If you like to see performance monitor, you can activate:

#define LV_USE_MEM_MONITOR 1

2.5. Proposed modifications on LVGL project for configuration 1 and 2 (SDL2 with GPU)[edit source]

To adapt LVGL to our stmp32mp1 board some modification are needed.
The following modification are valid only for SDL2 configuration.

2.5.1. Configuration for LVGL driver (aka lv_drv_conf.h)[edit source]

  • The file for LVGL driver lv_drv_conf.h must be aligned with the template present on lv_drivers/lv_drv_conf_template.h
 cp lv_drivers/lv_drv_conf_template.h lv_drv_conf.h
  • Adapt configuration

Enable driver configuration, SDL configuration and display size configuration:

Replace at begin of file

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

With

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

Configure following defines

# define USE_SDL 1
#  define SDL_HOR_RES     800
#  define SDL_VER_RES     480
Info white.png Information
The size of screen must be adapted to the size and the orientation of screen present on board:
stm32mp157f-dk2: 800x400 (configuration 1 Wayland)
stm32mp157f-dk2: 400x800 (configuration 2 DRM KMS)
stm32mp157f-ev1: 1280x720
stm32mp135f-dk: 480x272

2.6. Proposed modifications on LVGL project for configuration 3 (direct DRM/KMS no GPU)[edit source]

To adapt LVGL to our stmp32mp1 board some modification are needed.
The following modification are valid only for direct DRM/KMS configuration.

2.6.1. Configuration for LVGL (aka lv_conf.h)[edit source]

  • In the file lv_conf.h, for configuration without SDL, need to modify LV_TICK_CUSTOM to provide a custom tick source
#define LV_TICK_CUSTOM     1
#define LV_TICK_CUSTOM_INCLUDE  "stdint.h"
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (custom_tick_get())
Info white.png Information
You need to implement the custom_tick_get function on your main code

An example of implementation is provided on main.c example present on this wiki page.


2.6.2. Configuration for LVGL driver (aka lv_drv_conf.h)[edit source]

  • The file for LVGL driver lv_drv_conf.h must be aligned with the template present on lv_drivers/lv_drv_conf_template.h
 cp lv_drivers/lv_drv_conf_template.h lv_drv_conf.h
  • Adapt configuration

Enable driver configuration, drm configuration and EVDEV configuration:

Replace at begin of file

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

With

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

Configure following defines

#  define USE_DRM           1
#  define USE_EVDEV       1    
Info white.png Information
For EVDEV configuration, EVDEV_NAME MUST be adapted to the input device available on board
#  define EVDEV_NAME   "/dev/input/event1" /* touch screen on stm32mp157f-dk2 */
#  define EVDEV_NAME   "/dev/input/event0" /* touch screen on stm32mp135f-dk */ 

2.6.3. Adaptation of Makefile (aka Makefile)[edit source]

On Makefile, the CFLAGS and LDFLAGS MUST adapated to the usage of librdm instead of SDL2 library:

CFLAGS ?= -O3 -I$(LVGL_DIR)/ $(WARNINGS) 
LDFLAGS ?= -lm -ldrm

3. Run the demo[edit source]

  • You need to build binary for your configuration
  • Build the project

Right-Click on you project ==> Build project

  • If everything works well, in your project (folder "Debug") you find the binary file.
  • Setup terminal on the board.
 minicom  -D /dev/ttyACM0
  • To get the IP address of your target, type the following commands:
 ifconfig 
...
eth1      Link encap:Ethernet  HWaddr 10:E7:7A:E3:47:C8  
         inet addr:10.48.1.145  Bcast:10.48.3.255  Mask:255.255.252.0
         inet6 addr: fe80::12e7:7aff:fee3:47c8/64 Scope:Link
inet is the ip address of your target

or

  ip addr
 ...
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
   link/ether 10:e7:7a:e3:47:c8 brd ff:ff:ff:ff:ff:ff
   inet 10.48.1.145/22 metric 10 brd 10.48.3.255 scope global dynamic eth1


  • You can copy binary to the board by using the following command:
 scp <name_of_your_binary> root@<IP_address>:/usr/local/

To force binary writting on the sdcard

 sync

3.1. Run your binary with SDL2 configuration (configuration 1 and 2)[edit source]

For using SDL, you need to precise to SDL2 library which backend you would like to use: here wayland or kmsdrm

  • In configuration 1: Weston Wayland SDL2 configuration with GPU
The environment variable for SDK2 videodriver must be set to wayland

The binary need to be executed with weston permision:

  su -l weston -c "SDL_VIDEODRIVER=wayland /usr/local/<name_of_your_binary >"

To kill the binary execution:

 CTRL + c
  • In configuration 2: DRM-KMS / SDL2 configuration with GPU

For this configuration, you MUST stop Weston service before to run your binary

 systemctl stop weston-launch
 export SDL_VIDEODRIVER=kmsdrm 

To run the binary :

 /usr/local/<name_of_your_binary >

To kill the current project:

 CTRL + c

3.2. Run your binary in direct DRM-KMS configuration (configuration 3)[edit source]

To run the binary you MUST stop Weston service before to run your binary

 systemctl stop weston-launch
 /usr/local/<name_of_your_binary >

To kill the current project:

 CTRL + c

3.3. Summary[edit source]

  • As a summary, to run the different configurations

In configuration 1: Weston Wayland SDL2 configuration with GPU

 su -l weston 
 export SDL_VIDEODRIVER=wayland
 <path to binary>/<name_of_your_binary >

In configuration 2: DRM-KMS / SDL2 configuration with GPU

 systemctl stop weston-launch
 export SDL_VIDEODRIVER=kmsdrm 
 <path to binary>/<name_of_your_binary>

In configuration 3 : direct DRM/KMS

 systemctl stop weston-launch
 <path to binary>/<name_of_your_binary >

4. Debugging the demo[edit source]

  • Debugging with STM32CubeIDE


Create a Debug Configuration

Debug configuration1.png


Select STM32 Cortex-A Remote Application for debug configuration

Debug configuration2.png


In main tab

Set Project Name and Path

Select and Configure MPU SSH connection

Configure Remote Absolute Path

Debug configuration3.png



Debug configuration4.png



In debug tag

Set gdb debugger from sdk path to

<...>/sysroots/x86_64-ostl_sdk-linux/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-gdb

Debug configuration5.png


Follow these wiki articles

How_to_debug_a_user_space_application_with_STM32CubeIDE/User_space_project#Preparing Debug Configuration
How_to_debug_a_user_space_application_with_STM32CubeIDE/User_space_project#Debug Configuration
How_to_debug_a_user_space_application_with_STM32CubeIDE/User_space_project#Debug

5. References[edit source]

"LVGL official web site"[1]
"LVGL Get Started"[2]
"LVGL demos"[3]
"LVGL demos github" [4]
"LVGL project examples"[5]
"Learn LVGL"[6]
"LVGL forum"[7]
"SDL official web site"[8]
"Wayland official web site"[9]