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上移植过一次,踩了不少坑。这里我把关键步骤列出来:

  1. 下载LVGL源码:从GitHub上拉取最新版本,我建议用v8.x系列,稳定。
  2. 复制文件:把lvgl文件夹复制到你的工程目录下。
  3. 配置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行。

  1. 实现显示回调函数:你需要写一个函数,把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也行。

关键步骤:

  1. 创建工程:使用idf.py create-project创建新工程。
  2. 添加LVGL组件:把LVGL作为组件添加到components目录下。
  3. 配置菜单:运行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的对象系统,看看这些控件到底是怎么组织起来的。你想想看,一个按钮、一个标签,它们在内存里到底是什么样子?嗯,到时候你就知道了。