1、LVGL简介与环境搭建:LVGL是什么、特点与优势、在STM32/ESP32上的移植、第一个Hello World程序
1.1 LVGL到底是什么?
说实话,我第一次接触LVGL的时候,心里想的是:「这不就是个嵌入式版的UI库嘛」。但用了一段时间后,我发现它远不止这么简单。
LVGL的全称是Light and Versatile Graphics Library,翻译过来就是「轻量且通用的图形库」。它专门为嵌入式系统设计,能在资源受限的MCU上跑出漂亮的图形界面。我最早是在一个智能家居项目里用到它的,当时需要在STM32上做一个带触摸屏的控制面板,选来选去,最后还是LVGL最靠谱。
你想想看,一个只有几百KB Flash的单片机,要跑出类似手机App的界面效果,这本身就是件挺神奇的事。LVGL做到了,而且做得相当不错。
1.2 LVGL的特点与优势
我个人习惯把LVGL的优势总结成三点,这样好记:
- 轻量级:最小配置下,Flash占用不到64KB,RAM占用不到8KB。我在ESP32上跑过,绰绰有余。
- 功能丰富:按钮、滑块、图表、键盘、动画……你能想到的UI控件,它基本都有。
- 跨平台:支持STM32、ESP32、NXP、Raspberry Pi Pico等主流平台。
嗯,这里要注意一点:LVGL不是操作系统,它只是一个图形库。你可以把它跑在裸机上,也可以跑在FreeRTOS、RT-Thread等RTOS上。我在项目中遇到过有人把LVGL和RTOS搞混,其实它们是不同层面的东西。
核心优势一览:
| 特性 | 说明 |
|---|---|
| 开源免费 | MIT协议,商用无压力 |
| 硬件加速 | 支持DMA2D、GPU等硬件加速 |
| 触摸支持 | 内置触摸驱动框架 |
| 中文字体 | 原生支持中文显示 |
| 样式系统 | CSS风格的样式设置 |
1.3 在STM32上的移植
移植LVGL到STM32,说白了就是三步:下载源码、配置参数、对接显示驱动。
我曾经在STM32F407上移植过一次,踩了不少坑。这里我把关键步骤列出来:
- 下载LVGL源码:从GitHub上拉取最新版本,我建议用v8.x系列,稳定。
- 复制文件:把
lvgl文件夹复制到你的工程目录下。 - 配置lv_conf.h:这是最关键的配置文件。打开
lv_conf_template.h,改名为lv_conf.h,然后设置:
/* 颜色深度,我一般用16位 */
#define LV_COLOR_DEPTH 16
/* 显示缓冲区大小,建议至少10行 */
#define LV_HOR_RES_MAX 240
#define LV_VER_RES_MAX 320
/* 开启触摸支持 */
#define LV_USE_INDEV 1
我的小技巧:显示缓冲区的大小很关键。太小了刷新慢,太大了浪费RAM。我一般设置为屏幕高度的1/10,比如320像素的屏幕,缓冲区设32行。
- 实现显示回调函数:你需要写一个函数,把LVGL的绘图数据刷到屏幕上。
/* 显示刷新回调 */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
/* 这里调用你的LCD驱动函数 */
LCD_Fill(area->x1, area->y1, area->x2, area->y2, color_p);
lv_disp_flush_ready(disp); /* 通知LVGL刷新完成 */
}
避坑指南:我曾经忘记调用lv_disp_flush_ready(),结果屏幕死活不显示。这个函数必须放在刷新函数的末尾,否则LVGL会一直等待。
1.4 在ESP32上的移植
ESP32的移植其实更简单,因为LVGL官方提供了ESP32的示例工程。我个人习惯用ESP-IDF框架,当然Arduino也行。
关键步骤:
- 创建工程:使用
idf.py create-project创建新工程。 - 添加LVGL组件:把LVGL作为组件添加到
components目录下。 - 配置菜单:运行
idf.py menuconfig,在Component config → LVGL configuration里设置参数。
/* ESP32上典型的LVGL配置 */
#define LV_COLOR_DEPTH 16
#define LV_HOR_RES_MAX 320
#define LV_VER_RES_MAX 240
#define LV_USE_TICK_CUSTOM 1 /* 使用自定义时钟滴答 */
嗯,这里要注意:ESP32的RAM比STM32大得多,所以缓冲区可以设大一些。我一般设到屏幕高度的1/4,效果不错。
1.5 第一个Hello World程序
好了,环境搭好了,我们来写第一个程序。说白了就是创建一个按钮,点一下显示「Hello World」。
#include "lvgl.h"
void my_gui_init(void)
{
/* 创建一个按钮 */
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_pos(btn, 100, 100);
lv_obj_set_size(btn, 120, 50);
/* 在按钮上添加标签 */
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "点击我");
lv_obj_center(label);
/* 添加点击事件 */
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
}
/* 按钮点击回调 */
static void btn_event_cb(lv_event_t *e)
{
/* 创建一个消息框 */
lv_obj_t *mbox = lv_msgbox_create(lv_scr_act());
lv_msgbox_add_title(mbox, "提示");
lv_msgbox_add_text(mbox, "Hello World!");
lv_msgbox_add_close_btn(mbox, true);
lv_obj_center(mbox);
}
这段代码的逻辑很简单:
- 先创建一个按钮,放在屏幕中央附近
- 按钮上显示「点击我」
- 点击后弹出一个消息框,显示「Hello World!」
我的经验:第一次跑这个程序时,如果屏幕没反应,先检查两个地方:一是lv_tick_inc()有没有在定时器中断里调用,二是lv_task_handler()有没有在主循环里周期性调用。这两个是LVGL的心跳,缺一不可。
1.6 小结
这一章我们走完了LVGL的入门流程。从概念到移植,再到第一个程序,其实没有想象中那么复杂。我记得自己第一次点亮屏幕时,那种成就感还是挺爽的。
下一章我们会深入LVGL的对象系统,看看这些控件到底是怎么组织起来的。你想想看,一个按钮、一个标签,它们在内存里到底是什么样子?嗯,到时候你就知道了。