简介
LVGL(Light and Versatile Graphics Library)是一个 轻量级、开源、高度可定制的图形界面库,专为嵌入式系统设计,适用于低资源的 MCU(如 STM32、ESP32、RP2040 等)和中高端平台(如 Linux framebuffer)。
核心特性
属性 | 内容 |
---|---|
📦 名称 | LVGL(Light and Versatile Graphics Library) |
官网 | https://lvgl.io |
🔧 授权协议 | MIT(可商用、闭源) |
🧱 架构特点 | 跨平台、平台无关、完全移植依赖驱动层 |
📲 输入支持 | 触摸屏、按键、编码器等 |
🖥️ 显示支持 | 单缓存、双缓存、DMA、GPU 加速 |
🎯 最小资源需求 | 4 kB Flash,16 kB RAM起可运行 |
🌍 代码仓库 | https://github.com/lvgl |
代码结构
本节代码目录结构以lvgl-v8.3.11为例进行说明,一级目录全部进行展示,二级目录针对重点文件进行保留
1 | . |
移植
本部分以单片机(MCU)类型系统的移植为例说明。本次移植方式不破坏源码目录,通过手动构建CMAKE的引用源码目录和自定义的移植文件而实现lvgl最小编译系统,具体说明如下:
建立基础框架
新建CMakeLists.txt 和 main.c文件1
2
3
4
5
6
7
8
9// CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(lvgl C CXX ASM)
add_compile_options(-Wall -O2)
set(SOURCES main.c)
add_executable(${PROJECT_NAME} ${SOURCES})1
2
3
4
5
6
7
8// main.c
int main(int argc, char* argv[])
{
return 0;
}添加LVGL功能实现的源码
以下是 CMakeLists.txt 需要新添加的内容,lvgl-v8.3是lvgl源码目录自行选择自己的目录即可,另外还需要添加其它依赖否则头文件将找不到
1
2
3
4
5
file(GLOB_RECURSE LVGL_SOURCES lvgl-v8.3/src/*.c)
include_directories(lvgl-v8.3)
add_compile_options(-DLV_LVGL_H_INCLUDE_SIMPLE=ON -DLV_CONF_INCLUDE_SIMPLE=ON)
set(SOURCES main.c ${LVGL_SOURCES})添加移植文件
配置文件lv_conf.h
将源码目录 lv_conf_template.h 文件拷贝出来重命名成 lv_conf.h,并将里面的条件判断语句置成1使能配置
显示适配文件lv_port_disp.c & lv_port_disp.h
将源码目录 examples/porting/lv_port_disp_template.c 和 examples/porting/lv_port_disp_template.h 文件拷贝出来并分别重命名成lv_port_disp.c和lv_port_disp.h
输入(键盘、触摸、按键)适配文件lv_port_indev.c & lv_port_indev.h
将源码目录 examples/porting/lv_port_indev_template.c 和 examples/porting/lv_port_indev_template.h 文件拷贝出来并分别重命名成lv_port_indev.c和lv_port_indev.h
编译适配文件
1
2// 将适配文件添加进编译选项
set(SOURCES main.c ${LVGL_SOURCES} lv_port_disp.c lv_port_indev.c)修改显示适配文件
添加编译
将源文件和头文件的if 0语句换成if 1进行编译,同时带有templete的头文件引用换成重命名后的定义屏幕宽高
MY_DISP_HOR_RES 和 MY_DISP_VER_RES 分别配置成屏幕实际的宽高尺寸,并去掉条件编译选项取消警告屏幕初始化API
disp_init()函数调用屏幕驱动初始化函数:包括初始化数组的写入和模式的配置等功能,其它地方初始化同样可以,保证一次即可
屏幕刷新API
disp_flush()函数调用屏幕驱动画点函数LCD_DrawPoint( x, y, color_p->full) ,xy是坐标值,color_p->full是16位的颜色值,后续具体是使用dma还是cpu模式就自行优化只是效率和效果问题配置缓存方式
默认提供了三种缓存配置方式:单buffer大小10行;双buffer大小各10行;双buffer大小各一帧。这三种方式的配置取决于内存大小、想要的目标效果等等方面。目前单纯显示配制成第一种方式即可,注释掉2和3方式(line83 - line94)
修改触摸适配文件
添加编译
将源文件和头文件的if 0语句换成if 1进行编译,并把带有templete的头文件引用换成重命名后的,同时将关于lvgl的引用更换成 #include “lvgl.h”,否则编译出错触摸初始化API
touchpad_init()函数调用触摸驱动函数:主要是i2c设备的注册同时可以读CHIPID验证是否注册成功
是否有触摸事件发生API
touchpad_is_pressed()函数调用驱动的触摸状态检测函数,按下返回1,未按下返回0
触摸坐标API
touchpad_get_xy()函数调用驱动的坐标获取函数;
其它
对于其它没有用到的外部事件可以删除也可以保留不影响功能
提供时间基准(心跳)
为LVGL提供准确的心跳。必须间隔精准地,调用时基函数:lv_tick_inc(),俗称心跳,让LVGL精准地知道时间的流逝;
对于不同的芯片和SDK有TIMER的不同实现方式,通用方法就是这是1ms中断,中断handler调用 lv_tick_inc 即可
任务刷新调度
间隔5ms左右,调用周期性任务函数: lv_timer_handler() ,它的作用是检查所有注册任务的时间戳,执行那些已经到期的任务,如:屏显更新、动画更新、触控、定时器事件等
同样不同的环境实现不同,建议是在主函数的while中进行lv_timer_handler的循环调用,并进行相应间隔的延时,但一定不要在中断中调用,避免由于执行时间较长影响系统
添加主函数(测试)
这里可以直接编译LVGL提供的默认例程进行测试,比如将demo/下的源文件添加到cmake编译,这里以widgets为例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24bool repeating_timer_callback(__unused struct repeating_timer *t) {
lv_tick_inc(1);
return true;
}
int main()
{
stdio_init_all();
printf("(%s %s) Welcome PICO \n", __DATE__, __TIME__);
struct repeating_timer timer;
lv_init(); // LVGL初始化
lv_port_disp_init(); // 注册LVGL显示任务
lv_port_indev_init(); // 注册LVGL触屏任务
add_repeating_timer_ms(1, repeating_timer_callback, NULL, &timer);
lv_demo_widgets(); // 测试demo
while(1) {
┆ lv_timer_handler();
┆ sleep_ms(5);
}
return 0;
}