一、LVGL 是什么
LVGL(Light and Versatile Graphics Library)是一款开源的嵌入式图形库,专为资源受限的微控制器和嵌入式设备设计。它以极低的内存占用和丰富的控件体系著称,能够在没有操作系统或仅有 RTOS 的裸机环境中运行,帮助开发者在 MCU 上快速构建现代化的图形用户界面。
LVGL 的核心特点包括:
- 轻量高效:最小配置下仅需几十 KB 的 RAM 和 Flash,适合 STM32、ESP32、NXP 等主流 MCU。
- 控件丰富:内置按钮、标签、滑块、图表、列表、键盘、表格等数十种控件。
- 渲染灵活:支持单色、灰度、RGB565、ARGB8888 等多种颜色格式,可适配不同显示驱动。
- 跨平台:既能在真实硬件上运行,也能在 PC 模拟器中调试界面。
- MIT 开源协议:商业项目可自由使用,社区活跃、文档完善。
二、LVGL 的整体架构
理解 LVGL 的分层结构,有助于后续移植和调试。其架构大致可以分为以下几层:
其中,对象树 是 LVGL 的核心概念:所有控件都是 lv_obj_t 对象,通过父子关系组织成一棵树,父对象移动或删除时子对象会随之变化。样式系统则采用类似 CSS 的思路,通过 lv_style_t 统一管理外观属性。
三、开发环境准备
3.1 硬件与软件要求
| 项目 | 推荐配置 |
|---|---|
| 主控芯片 | STM32F4/F7、ESP32-S3、NXP i.MX RT 等 |
| 显示分辨率 | 240×240 至 800×480 |
| RAM | 建议不低于 64 KB |
| Flash | 建议不低于 256 KB |
| 开发工具 | STM32CubeIDE、ESP-IDF、PlatformIO 等 |
3.2 获取 LVGL 源码
LVGL 官方推荐通过 Git 获取源码,便于后续升级版本:
bash
git clone https://github.com/lvgl/lvgl.git
cd lvgl
git checkout release/v8.3
将 lvgl 目录整体拷贝到工程中,并在编译配置里加入其源文件路径即可。
四、移植 LVGL 到 MCU
4.1 配置 lv_conf.h
LVGL 的所有功能开关都集中在 lv_conf.h 中。将模板文件 lv_conf_template.h 复制为 lv_conf.h,并修改开头的宏定义以启用配置:
c
#if 1 /* 将 0 改为 1 以启用配置文件 */
#define LV_COLOR_DEPTH 16
#define LV_MEM_CUSTOM 0
#define LV_MEM_SIZE (48U * 1024U)
#define LV_TICK_CUSTOM 1
#define LV_USE_LOG 1
#endif
其中 LV_COLOR_DEPTH 需要与显示屏的颜色格式保持一致,LV_MEM_SIZE 则根据芯片 RAM 大小合理分配。
4.2 实现显示驱动
显示驱动负责把 LVGL 渲染好的像素缓冲区刷新到屏幕上。核心是注册一个 flush_cb 回调:
c
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[MY_DISP_HOR_RES * 40];
static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
LCD_FillArea(area->x1, area->y1, area->x2, area->y2, (uint16_t *)color_p);
lv_disp_flush_ready(disp_drv);
}
void lv_port_disp_init(void)
{
lv_disp_draw_buf_init(&draw_buf, buf, NULL, MY_DISP_HOR_RES * 40);
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = MY_DISP_HOR_RES;
disp_drv.ver_res = MY_DISP_VER_RES;
disp_drv.flush_cb = disp_flush;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
}
注意:flush_cb 执行完成后必须调用 lv_disp_flush_ready(),否则 LVGL 会一直等待刷新完成。
4.3 实现输入设备驱动
如果设备带有触摸屏,需要注册输入设备驱动,把触摸坐标上报给 LVGL:
c
static void touch_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
uint16_t x, y;
if (TP_GetPoint(&x, &y)) {
data->point.x = x;
data->point.y = y;
data->state = LV_INDEV_STATE_PR;
} else {
data->state = LV_INDEV_STATE_REL;
}
}
void lv_port_indev_init(void)
{
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = touch_read;
lv_indev_drv_register(&indev_drv);
}
4.4 提供系统心跳
LVGL 依赖一个毫秒级的时间基准来驱动动画、定时器和事件处理。可以在 SysTick 中断中调用:
c
void SysTick_Handler(void)
{
lv_tick_inc(1);
}
然后在主循环中周期性调用 lv_timer_handler():
c
int main(void)
{
HAL_Init();
SystemClock_Config();
LCD_Init();
TP_Init();
lv_init();
lv_port_disp_init();
lv_port_indev_init();
ui_create();
while (1) {
lv_timer_handler();
HAL_Delay(5);
}
}
五、第一个 LVGL 界面
完成移植后,就可以创建第一个界面了。下面的示例创建一个带标题和按钮的简单页面:
c
static void btn_event_cb(lv_event_t *e)
{
lv_obj_t *label = lv_event_get_user_data(e);
lv_label_set_text(label, "按钮已被点击");
}
void ui_create(void)
{
lv_obj_t *scr = lv_scr_act();
lv_obj_t *title = lv_label_create(scr);
lv_label_set_text(title, "Hello LVGL");
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 20);
lv_obj_t *btn = lv_btn_create(scr);
lv_obj_set_size(btn, 120, 50);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
lv_obj_t *btn_label = lv_label_create(btn);
lv_label_set_text(btn_label, "点击我");
lv_obj_center(btn_label);
lv_obj_t *status = lv_label_create(scr);
lv_label_set_text(status, "等待操作");
lv_obj_align(status, LV_ALIGN_BOTTOM_MID, 0, -20);
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, status);
}
这段代码展示了 LVGL 的典型开发流程:创建对象、设置属性、对齐布局、绑定事件回调。
六、样式与布局
LVGL 的样式系统支持复用和继承,推荐把通用样式定义为全局变量,避免重复设置:
c
static lv_style_t style_btn;
void style_init(void)
{
lv_style_init(&style_btn);
lv_style_set_bg_color(&style_btn, lv_palette_main(LV_PALETTE_BLUE));
lv_style_set_radius(&style_btn, 8);
lv_style_set_text_color(&style_btn, lv_color_white());
}
/* 应用到控件 */
lv_obj_add_style(btn, &style_btn, LV_STATE_DEFAULT);
布局方面,LVGL 提供 Flex 和 Grid 两种弹性布局,能够显著减少手动计算坐标的工作量:
c
lv_obj_set_layout(scr, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(scr, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(scr, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
七、常见问题与优化建议
- 界面卡顿 :优先检查
lv_timer_handler()的调用频率,以及flush_cb中是否使用了阻塞式刷新。 - 内存不足 :减小
LV_MEM_SIZE之外的缓冲区尺寸,或启用LV_MEM_CUSTOM使用外部 SDRAM。 - 花屏或错位 :确认
LV_COLOR_DEPTH与屏幕颜色格式一致,并检查坐标区域是否越界。 - 触摸不准:需要做触摸校准,并在驱动层完成坐标映射。
- 字体过大:只启用实际用到的字体和字号,避免 Flash 占用过高。
八、总结
LVGL 为嵌入式设备提供了一套完整且轻量的 GUI 解决方案。掌握其对象树、样式系统和驱动移植流程后,就可以在 MCU 上构建出接近移动端体验的界面。建议从官方示例入手,先在 PC 模拟器中验证界面效果,再逐步移植到目标硬件,这样能够显著降低调试成本。