LVGL 入门指南:从零搭建嵌入式 GUI 界面

一、LVGL 是什么

LVGL(Light and Versatile Graphics Library)是一款开源的嵌入式图形库,专为资源受限的微控制器和嵌入式设备设计。它以极低的内存占用和丰富的控件体系著称,能够在没有操作系统或仅有 RTOS 的裸机环境中运行,帮助开发者在 MCU 上快速构建现代化的图形用户界面。

LVGL 的核心特点包括:

  • 轻量高效:最小配置下仅需几十 KB 的 RAM 和 Flash,适合 STM32、ESP32、NXP 等主流 MCU。
  • 控件丰富:内置按钮、标签、滑块、图表、列表、键盘、表格等数十种控件。
  • 渲染灵活:支持单色、灰度、RGB565、ARGB8888 等多种颜色格式,可适配不同显示驱动。
  • 跨平台:既能在真实硬件上运行,也能在 PC 模拟器中调试界面。
  • MIT 开源协议:商业项目可自由使用,社区活跃、文档完善。

二、LVGL 的整体架构

理解 LVGL 的分层结构,有助于后续移植和调试。其架构大致可以分为以下几层:

flowchart TD A[应用层:业务界面与交互逻辑] --> B[控件层:Button / Label / Chart 等] B --> C[核心层:对象树、事件、样式、动画] C --> D[渲染层:绘制、图层、字体、图片解码] D --> E[驱动层:显示驱动、输入设备驱动] E --> F[硬件层:LCD、触摸屏、编码器]

其中,对象树 是 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 模拟器中验证界面效果,再逐步移植到目标硬件,这样能够显著降低调试成本。

相关推荐
女神下凡3 小时前
芯参谋(68):ADC模数转换器电路设计指南
arm开发·单片机·嵌入式硬件·硬件工程·设计规范
芯岭技术郦4 小时前
入门级 32 位 MCU,PY32F002B
单片机·嵌入式硬件
三佛科技-134163842124 小时前
景观雾化器方案开发,鱼缸/假山造雾器方案开发(主控FT61F021封装SOP16)
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
架构师伯子明4 小时前
socket通信
单片机·嵌入式硬件
创界工坊工作室5 小时前
46-基于AT89C51脉冲式电表数据采集器设计
stm32·单片机·嵌入式硬件·51单片机·proteus
恒锐丰科技林技术员6 小时前
EG27324 双通道驱动芯片:小封装下的高性能功率驱动方案
经验分享·嵌入式硬件·硬件工程
Brilliantwxx6 小时前
【STM32】 独立看门狗 IWDG 深度详解:从硬件结构到工程实战
数据库·stm32·单片机·嵌入式硬件·mongodb
AMASENSYS艾马斯6 小时前
变频器过载能力参数解读与工程应用
驱动开发·嵌入式硬件·其他·硬件工程·精益工程
cu1436 小时前
细谈IT6563的具体功能与其应用
c语言·c++·单片机·嵌入式硬件