本文将讲解如何在灵眸EASY-EAI-Toolkit-SDK(简称SDK)的基础上移植轻量级GUI框架——LVGL,具体包括屏幕显示和触摸输入两部分移植。其中,屏幕显示部分需要使用到SDK的Display接口,触摸输入部分需要使用到SDK的TouchScreen接口。
首先简单介绍以下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
首先下载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连接。
然后将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
添加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才能消除。
拷贝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库,并没有可执行文件。
接下添加可执行程序入口文件。
在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;
}
添加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-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,允许正常编译其中的代码。
对照注释和灵眸的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);
}
这里删除了原有的很多无用的注释代码。
修改完以上代码后,需要修改入口代码,添加对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;
}
完成上述修改后,直接编译代码,会遇到编译错误。这是因为 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")
#######################################################################
#######################################################################
完成上述全部修改后,重新编译、运行,就可以在屏幕上看到画面了:
嗯,经典的LVGL widgets示例界面。
此时的界面,无法进行输入;虽然是触摸屏,但是按上去没有反应,界面不会响应。
接下来移植LVGL的触摸输入部分,实现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,允许正常编译其中的代码。
接下来修改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的触摸接口进行了修改。
类似的,修改完以上代码后,需要在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;
}
完成上述修改后,直接编译代码,会遇到编译错误。这是因为,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)
#######################################################################
#######################################################################
完成上述全部修改后,重新编译、运行,屏幕画面就可以响应触摸事件了:
这个界面是点击了顶部的Shop标签页之后显示的,中间的复选框页可以正常点击勾选了。
完成以上的修改,LVGL的界面显示和触摸输入功能已经可以使用了。但是,如果想要继续进行基准测试,就需要更精确的计时。
在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;
}
接下来,修改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
接下来修改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;
}
完成上述修改后,重新编译、运行lv_main,就可以进行基准测试了。测试完成后,可以看到基准测试汇总数据:
测试过程视频,可以看本帖底部的视频。
更多回帖