【EASY EAI Nano人工智能开发套件试用体验】移植轻量级GUI框架——LVGL - RISC-V MCU技术社区 - 电子技术论坛 - 广受欢迎的专业电子论坛
分享 收藏 返回

【EASY EAI Nano人工智能开发套件试用体验】移植轻量级GUI框架——LVGL

本文将讲解如何在灵眸EASY-EAI-Toolkit-SDK(简称SDK)的基础上移植轻量级GUI框架——LVGL,具体包括屏幕显示和触摸输入两部分移植。其中,屏幕显示部分需要使用到SDK的Display接口,触摸输入部分需要使用到SDK的TouchScreen接口。

一、LVGL简介

首先简单介绍以下LVGL是什么?LVGL是Light and Versatile Graphics Library的英文缩写,直接翻译就是——“轻量级多用途图形库”。

官方文档的描述是:

LVGL (Light and Versatile Graphics Library) is a free and open-source graphics library providing everything you need to create embedded GUI with easy-to-use graphical elements, beautiful visual effects and low memory footprint.

翻译一下就是:

LVGL(Light and Versatile Graphics Library)是一个免费的开源图形库,提供了创建嵌入式GUI所需的一切,包括易于使用的图形元素、优美的视觉效果和低内存占用。

LVGL官网: https://lvgl.io/

LVGL文档: https://docs.lvgl.io/master/index.html

LVGL代码仓:https://github.com/lvgl/lvgl

二、LVGL基础移植

2.1 下载EASY-EAI-Toolkit-Demo

首先下载EASY-EAI-Toolkit-Demo代码仓:

git clone https://github.com/EASY-EAI/EASY-EAI-Toolkit-C-Demo.git

进入到对应的例程目录执行编译操作,具体命令如下所示:

cd EASY-EAI-Toolkit-C-Demo/peripheral-display/
./build.sh

注意:由于依赖库部署在板卡上,因此交叉编译过程中必须保持adb连接。

2.2 添加LVGL代码

然后将LVGL代码下载到peripheral-display子目录下,可以使用命令:

git clone https://github.com/lvgl/lvgl.git

或者网页下载最新Release版本的压缩包:

https://github.com/lvgl/lvgl/archive/refs/tags/v8.3.7.tar.gz

下载完成后,拷贝到虚拟机中的~/EASY-EAI-Toolkit-C-Demo/子目录,再将其解压出来也可以。

这里v8.3.7为例,解压后目录为:~EASY-EAI-Toolkit-C-Demo/lvgl-8.3.7

2.3 添加编译脚本

添加build.sh文件,内容如下:

#!/bin/bash

CMAKE_OPTIONS=""
CMAKE_OPTIONS="$CMAKE_OPTIONS -DCMAKE_SYSTEM_PROCESSOR=arm"
CMAKE_OPTIONS="$CMAKE_OPTIONS -DCMAKE_C_COMPILER=arm-linux-gnueabihf-gcc"
CMAKE_OPTIONS="$CMAKE_OPTIONS -DCMAKE_CXX_COMPILER=arm-linux-gnueabihf-g++"

# 生成Makefile
echo "CMAKE_OPTIONS=$CMAKE_OPTIONS"
echo cmake -S . -B build/ $CMAKE_OPTIONS
cmake -S . -B build/ $CMAKE_OPTIONS

# 构建
cmake --build build/ -j

# 拷贝
cp ./build/env_support/posix/lv_main /mnt/userdata/Demo/

添加完该脚本文件,将其添加可执行权限:

chmod +x build.sh

之后,执行该脚本就可以开始编译 lvgl-8.3.7 了。

这时候编译会有警告输出,需要创建lv_conf.h才能消除。

2.4 添加lv_conf.h文件

拷贝lvgl-8.3.7顶层的lv_conf_template.h为lv_conf.h:

cp lv_conf_template.h lv_conf.h

接着需要修改部分内容:

diff --git a/lv_conf_template.h b/lv_conf.h
index 86ca207..2acd682 100644
--- a/lv_conf_template.h
+++ b/lv_conf.h
[url=home.php?mod=space&uid=1999721]@@[/url] -12,7 +12,7 @@
  */

 /* clang-format off */
-#if 0 /*Set it to "1" to enable content*/
+#if 1 /*Set it to "1" to enable content*/

 #ifndef LV_CONF_H
 #define LV_CONF_H
@@ -24,7 +24,7 @@
  *====================*/

 /*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
-#define LV_COLOR_DEPTH 16
+#define LV_COLOR_DEPTH 32

 /*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

首先,将顶部的#if 0改为#if 1,打开这部分编译;

接下来的,LV_COLOR_DEPTH 修改为 32;从注释可以看到对应的是 ARGB8888,没有灵眸Display简易接口的RGB888;

这样再次编译,就没有之前的警告了。

此时编译将会输出三个.a库,并没有可执行文件。

2.5 添加入口文件

接下添加可执行程序入口文件。

在env_support目录下创建posix目录,然后在其中创建 lv_posix_main.c 文件,内容如下:

#include "lvgl.h"
#include "lv_demo_benchmark.h"
#include "lv_demo_keypad_encoder.h"
#include "lv_demo_music.h"
#include "lv_demo_stress.h"
#include "lv_demo_widgets.h"

#include <unistd.h>

int main(int argc, char *argv[])
{
    lv_init();

    // uncomment one of these demos
    lv_demo_widgets();
    // lv_demo_benchmark();
    // lv_demo_keypad_encoder();
    // lv_demo_music();
    // lv_demo_stress();

    /* handle the tasks of LVGL */
    while (1)
    {
        lv_task_handler();
        usleep(10 * 1000);
        lv_tick_inc(10); // 主动增加LVGL内部 tick,仅能保证正常运行,帧率、CPU统计不准确
    }
    return 0;
}

2.6 修改CMakeLists.txt

添加env_support/posix/lv_posix_main.c文件后,还需要修改CMakeLists.txt文件,才能正常生成可执行程序文件,接下来介绍如何修改。

env_support/posix目录内,添加CMakeLists.txt文件,内容如下:

if(CMAKE_HOST_SYSTEM MATCHES Linux)
    message(STATUS "Build for POSIX...")
    message(STATUS "LVGL root is ${LVGL_ROOT_DIR}")
endif()

add_executable(lv_main
    lv_posix_main.c
)

target_include_directories(lv_main
    PRIVATE ${LVGL_ROOT_DIR}/demos/benchmark
    PRIVATE ${LVGL_ROOT_DIR}/demos/keypad_encoder
    PRIVATE ${LVGL_ROOT_DIR}/demos/music
    PRIVATE ${LVGL_ROOT_DIR}/demos/stress
    PRIVATE ${LVGL_ROOT_DIR}/demos/widgets
    PRIVATE ${LVGL_ROOT_DIR}/examples/porting
)

target_link_libraries(lv_main
    lvgl
    lvgl_demos
    lvgl_examples
)

lvgl-8.3.7目录下的CMakeLists.txt内,添加如下内容:

if(CMAKE_HOST_SYSTEM MATCHES Linux)
  add_subdirectory(${LVGL_ROOT_DIR}/env_support/posix)
endif()

完成以上修改后,使用build.sh脚本就能够编译出lv_main可执行文件了。但这时的lv_main,执行后,并不能在屏幕上看到相应的画面。

三、LVGL显示移植

接下来,将移植LVGL的显示驱动,实现将LVGL画面显示到屏幕上。

3.1 创建LVGL显示移植文件

lvgl-8.3.7 的 examples/porting 目录下提供了 lv_port_disp_template.h 和 lv_port_disp_template.c 文件,为显示驱动的移植提供了模板。我们移植LVGL显示驱动,可以这两个文件为模板。

首先将这两个文件拷贝一份,分别命名为 lv_port_disp.h 和 lv_port_disp.c :

cp ./examples/porting/lv_port_disp_template.h examples/porting/lv_port_disp.h
cp ./examples/porting/lv_port_disp_template.c examples/porting/lv_port_disp.c

然后,类似的,分别打开这两个文件,将顶部的#if 0修改为#if 1,允许正常编译其中的代码。

3.2 修改LVGL显示移植文件

对照注释和灵眸的Display接口文档进行修改,lv_port_disp.c 修改后的内容为:

#include "lv_port_disp.h"
#include <stdbool.h>

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include <disp.h>

#define DISP_WIDTH 720
#define DISP_HEIGHT 1280
#define DISP_PIXEL_SIZE 4
#define DISP_FRAME_SIZE (1280 * 720 * DISP_PIXEL_SIZE)

static void port_disp_init(void);
static void port_disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);

static disp_screen_t screen = {0};

void lv_port_disp_init(void)
{
    port_disp_init();

		// 创建两个缓冲区
    static lv_disp_draw_buf_t draw_buf_dsc_2;
    static lv_color_t buf_2_1[DISP_WIDTH * DISP_HEIGHT];                        /*A buffer for 10 rows*/
    static lv_color_t buf_2_2[DISP_WIDTH * DISP_HEIGHT];                        /*An other buffer for 10 rows*/
    lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, DISP_WIDTH * DISP_HEIGHT);   /*Initialize the display buffer*/

    static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);   /*Basic initialization*/

    /*Set the resolution of the display*/
    disp_drv.hor_res = DISP_WIDTH;
    disp_drv.ver_res = DISP_HEIGHT;

    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = port_disp_flush;

    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc_2;

    /*Required for Example 3)*/
    disp_drv.full_refresh = 1;

    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
}

static void port_disp_init(void)
{
    screen.screen_width = DISP_WIDTH;
    screen.screen_height = DISP_HEIGHT;

    memset(&screen.wins[0], 0, sizeof(screen.wins[0]));
    screen.wins[0].enable = 1;
    screen.wins[0].in_fmt = IMAGE_TYPE_ABGR8888;
    screen.wins[0].in_w = DISP_WIDTH;
    screen.wins[0].in_h = DISP_HEIGHT;
    screen.wins[0].HorStride = DISP_WIDTH;
    screen.wins[0].VirStride = DISP_HEIGHT;
    screen.wins[0].win_w = DISP_WIDTH;
    screen.wins[0].win_h = DISP_HEIGHT;

    int ret = disp_init_pro(&screen); // 调用灵眸SDK API
    if (ret)
    {
        printf("error func:%s, line:%d\\n", __func__, __LINE__);
    }
}

volatile bool port_disp_flush_enabled = true;

void disp_enable_update(void)
{
    port_disp_flush_enabled = true;
}

void disp_disable_update(void)
{
    port_disp_flush_enabled = false;
}

static void port_disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
    if (port_disp_flush_enabled)
    {
        // 调用灵眸SDK API
        disp_commit_pro(disp_drv->draw_buf->buf1, 0, DISP_FRAME_SIZE);

        // 交换两个缓冲区
        void* tmp = disp_drv->draw_buf->buf1;
        disp_drv->draw_buf->buf1 = disp_drv->draw_buf->buf2;
        disp_drv->draw_buf->buf2 = tmp;
    }

    /*IMPORTANT!!!
     *Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}

这里删除了原有的很多无用的注释代码。

3.3 在main中添加lv_port_disp_init调用

修改完以上代码后,需要修改入口代码,添加对lv_port_disp_init函数的调用:

#include "lvgl.h"
#include "lv_demo_benchmark.h"
#include "lv_demo_keypad_encoder.h"
#include "lv_demo_music.h"
#include "lv_demo_stress.h"
#include "lv_demo_widgets.h"

#include "lv_port_disp.h" // 添加
#include <unistd.h>

int main(int argc, char *argv[])
{
    lv_init();

    lv_port_disp_init(); // 添加

    // uncomment one of these demos
    lv_demo_widgets();
    // lv_demo_benchmark();
    // lv_demo_keypad_encoder();
    // lv_demo_music();
    // lv_demo_stress();

    /* handle the tasks of LVGL */
    while (1)
    {
        lv_task_handler();
        usleep(10 * 1000);
        lv_tick_inc(10); // 主动增加LVGL内部 tick,仅能保证正常运行,帧率、CPU统计不准确
    }
    return 0;
}

3.4 解决display相关的编译问题

完成上述修改后,直接编译代码,会遇到编译错误。这是因为 lv_port_disp.c 中使用到了EASY-EAI-Toolki-SDK中的接口;需要修改 lvgl-8.3.7/env_support/cmake/custom.cmake 文件,在其中添加:

#######################################################################
#######################################################################
## for EASY EAI Nano
set(eai_sdk_root ${LVGL_ROOT_DIR}/../easyeai-api)

set(eai_sdk_inc_dir
	/mnt/usr/include
	${eai_sdk_root}/peripheral_api/display
)

# 依赖 EASY EAI SDK 头文件
target_include_directories(lvgl_examples
  PRIVATE ${eai_sdk_inc_dir})

# lv_port_disp.c 依赖 libdisplay 和 libeasymedia
target_link_libraries(lvgl_examples PUBLIC "${eai_sdk_root}/peripheral_api/display/libdisplay.a")
target_link_libraries(lvgl_examples PUBLIC "/mnt/usr/lib/libeasymedia.so")
target_link_options(lvgl_examples PUBLIC "LINKER:-rpath=/mnt/lib/arm-linux-gnueabihf")
#######################################################################
#######################################################################

3.5 测试LVGL显示功能

完成上述全部修改后,重新编译、运行,就可以在屏幕上看到画面了:

Untitled

嗯,经典的LVGL widgets示例界面。

此时的界面,无法进行输入;虽然是触摸屏,但是按上去没有反应,界面不会响应。

四、LVGL输入移植

接下来移植LVGL的触摸输入部分,实现LVGL的画面响应触摸屏上触摸动作。

4.1 创建LVGL输入移植文件

lvgl-8.3.7 的 examples/porting 目录下提供了 lv_port_indev_template.h 和 lv_port_indev_template.c 文件,为输入驱动的移植提供了模板。我们移植LVGL显示驱动,可以这两个文件为模板。

首先将这两个文件拷贝一份,分别命名为 lv_port_indev.h 和 lv_port_indev.c :

cp ./examples/porting/lv_port_indev_template.h examples/porting/lv_port_indev.h
cp ./examples/porting/lv_port_indev_template.c examples/porting/lv_port_indev.c

接下来,分别打开这两个文件,将顶部的#if 0修改为#if 1,允许正常编译其中的代码。

4.2 修改LVGL输入移植文件

接下来修改lv_port_indev.c,将其中内容修改为:

#include "lv_port_indev.h"

#include "../../lvgl.h"

#include <stdio.h>
#include <unistd.h>
#include "touchscreen.h"

static void touchpad_init(void);
static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);

lv_indev_t * indev_touchpad;

void lv_port_indev_init(void)
{
    static lv_indev_drv_t indev_drv;

    /*Initialize your touchpad if you have*/
    touchpad_init();

    /*Register a touchpad input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touchpad_read;
    indev_touchpad = lv_indev_drv_register(&indev_drv);
}

static struct {
    uint32_t event;
    lv_coord_t x;
    lv_coord_t y;
} touch_state;

static int touch_event_handler(uint32_t event, int x , int y )
{
    touch_state.event = event;
    touch_state.x = x;
    touch_state.y = y;
    return 0;
}

/*Initialize your touchpad*/
static void touchpad_init(void)
{
    Init_TsEven(NULL, 0); //使用环境变量,非阻塞
    set_even_handle(touch_event_handler);
}

/*Will be called by the library to read the touchpad*/
static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    /*Save the pressed coordinates and the state*/
    if (touch_state.event & TS_PRESS) {
        data->state = LV_INDEV_STATE_PRESSED;
    }
    else {
        data->state = LV_INDEV_STATE_RELEASED;
    }

    /*Set the last pressed coordinates*/
    data->point.x = touch_state.x;
    data->point.y = touch_state.y;
}

这里删除了大量无关的代码和注释,只保留了触摸屏相关的部分,并按照EASI-EAI-Toolkit-SDK的触摸接口进行了修改。

4.3 在main中添加lv_port_indev_init调用

类似的,修改完以上代码后,需要在main函数中添加对lv_port_disp_init函数的调用:

#include "lvgl.h"
#include "lv_demo_benchmark.h"
#include "lv_demo_keypad_encoder.h"
#include "lv_demo_music.h"
#include "lv_demo_stress.h"
#include "lv_demo_widgets.h"

#include "lv_port_disp.h"
#include "lv_port_indev.h"  // 添加

#include <unistd.h>

int main(int argc, char *argv[])
{
    lv_init();

    lv_port_disp_init();
    lv_port_indev_init();  // 添加

    // uncomment one of these demos
    lv_demo_widgets();
    // lv_demo_benchmark();
    // lv_demo_keypad_encoder();
    // lv_demo_music();
    // lv_demo_stress();

    /* handle the tasks of LVGL */
    while (1)
    {
        lv_task_handler();
        usleep(10 * 1000);
        lv_tick_inc(10); // 主动增加LVGL内部 tick,仅能保证正常运行,帧率、CPU统计不准确
    }
    return 0;
}

4.4 解决touch相关的编译问题

完成上述修改后,直接编译代码,会遇到编译错误。这是因为,lv_port_indev.c中使用到了EASY-EAI-Toolki-SDK中的接口;需要修改 lvgl-8.3.7/env_support/cmake/custom.cmake 文件,在其中添加:

#######################################################################
#######################################################################
## for EASY EAI Nano
set(eai_sdk_root ${LVGL_ROOT_DIR}/../easyeai-api)

set(eai_sdk_inc_dir
	/mnt/usr/include
	${eai_sdk_root}/peripheral_api/display
  ${eai_sdk_root}/peripheral_api/touchscreen ## 这里添加一行
)

# 依赖 EASY EAI SDK 头文件
target_include_directories(lvgl_examples
  PRIVATE ${eai_sdk_inc_dir})

# lv_port_disp.c 依赖 libdisplay 和 libeasymedia
target_link_libraries(lvgl_examples PUBLIC "${eai_sdk_root}/peripheral_api/display/libdisplay.a")
target_link_libraries(lvgl_examples PUBLIC "/mnt/usr/lib/libeasymedia.so")
target_link_options(lvgl_examples PUBLIC "LINKER:-rpath=/mnt/lib/arm-linux-gnueabihf")

## 这里添加几行:
# lv_port_indev.c 依赖 libtouchscreen 和 libts
target_link_libraries(lvgl_examples PUBLIC "${eai_sdk_root}/peripheral_api/touchscreen/libtouchscreen.a")
target_link_libraries(lvgl_examples PUBLIC "/mnt/lib/arm-linux-gnueabihf/libts.so")
target_link_libraries(lvgl_examples PUBLIC pthread)
#######################################################################
#######################################################################

4.5 测试LVGL触摸输入功能

完成上述全部修改后,重新编译、运行,屏幕画面就可以响应触摸事件了:

Untitled

这个界面是点击了顶部的Shop标签页之后显示的,中间的复选框页可以正常点击勾选了。

五、LVGL基准测试和计时支持

完成以上的修改,LVGL的界面显示和触摸输入功能已经可以使用了。但是,如果想要继续进行基准测试,就需要更精确的计时。

5.1 添加自定义tick支持文件

在env_support/posix目录内,创建lv_posix_tick.h文件,内容如下:

#ifndef LV_POSIX_TICK_H
#define LV_POSIX_TICK_H

void lv_posix_tick_init();

int lv_posix_tick_get();

#endif // LV_POSIX_TICK_H

类似的,创建lv_posix_tick.c文件,内容如下:

#include "lv_posix_tick.h"

#include <time.h>
#include <stdint.h>

static struct timespec ts_start;
static uint64_t ms_start;

#define MS_PER_SEC (1000)

#define TS_TO_MS(ts) (ts.tv_sec * MS_PER_SEC + (int) (ts.tv_nsec * 1e-6))

void lv_posix_tick_init()
{
    clock_gettime(CLOCK_REALTIME, &ts_start);
    ms_start = TS_TO_MS(ts_start);
}

int lv_posix_tick_get()
{
    uint64_t ms_now;
    struct timespec ts_now;

    clock_gettime(CLOCK_REALTIME, &ts_now);
    ms_now = TS_TO_MS(ts_now);
    return ms_now - ms_start;
}

5.2 修改lv_conf.h以支持自定义计时

接下来,修改lv_conf.h文件,找到并修改如下代码段:

/*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 1
#if LV_TICK_CUSTOM
    #define LV_TICK_CUSTOM_INCLUDE "lv_posix_tick.h"
    #define LV_TICK_CUSTOM_SYS_TIME_EXPR lv_posix_tick_get()
    // #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*/
    /*If using lvgl as ESP32 component*/
    // #define LV_TICK_CUSTOM_INCLUDE "esp_timer.h"
    // #define LV_TICK_CUSTOM_SYS_TIME_EXPR ((esp_timer_get_time() / 1000LL))
#endif   /*LV_TICK_CUSTOM*/

/*Benchmark your system*/
#define LV_USE_DEMO_BENCHMARK 1

5.3 修改lv_posix_main.c文件

接下来修改lv_posix_main.c文件,用于运行基准测试程序:

#include "lvgl.h"
#include "lv_demo_benchmark.h"
#include "lv_demo_keypad_encoder.h"
#include "lv_demo_music.h"
#include "lv_demo_stress.h"
#include "lv_demo_widgets.h"

#include "lv_port_disp.h"
#include "lv_port_indev.h"
#include "lv_posix_tick.h"  // 添加

#include <unistd.h>

int main(int argc, char *argv[])
{
    lv_init();

    lv_port_disp_init();
    lv_port_indev_init();
    lv_posix_tick_init();  // 添加

    // uncomment one of these demos
    // lv_demo_widgets();  // 修改
    lv_demo_benchmark();   // 修改
    // lv_demo_keypad_encoder();
    // lv_demo_music();
    // lv_demo_stress();

    /* handle the tasks of LVGL */
    while (1)
    {
        lv_task_handler();
        usleep(10 * 1000);
        // 删除:
        // lv_tick_inc(10); // 主动增加LVGL内部 tick,仅能保证正常运行,帧率、CPU统计不准确
    }
    return 0;
}

5.4 运行LVGL基准测试

完成上述修改后,重新编译、运行lv_main,就可以进行基准测试了。测试完成后,可以看到基准测试汇总数据:

Untitled

测试过程视频,可以看本帖底部的视频。

六、参考链接

  1. LVGL官网: https://lvgl.io/
  2. LVGL文档: https://docs.lvgl.io/master/index.html
  3. LVGL代码仓:https://github.com/lvgl/lvgl
  4. 灵眸官方文档,显示控制: https://www.easy-eai.com/document_details/3/43
  5. 灵眸官方文档,触摸控制: https://www.easy-eai.com/document_details/3/288

VID_20230624_210340_nv

回帖(1)

任凭风吹

2023-6-25 15:04:10
文章太棒了,向大佬学习

更多回帖

×
发帖