4、第一个LVGL程序:创建工程模板、初始化LVGL、创建简单屏幕、添加Label控件、编译与下载验证

说实话,学嵌入式GUI最怕什么?不是学不会API,而是折腾了半天,屏幕上啥都没显示出来。

我记得我第一次用LVGL时,光是把一个“Hello World”跑出来,就花了一整个下午。后来发现,其实就是初始化顺序搞错了。所以这一章,我带你稳扎稳打,把第一个LVGL程序跑起来。

4.1 创建工程模板

我习惯把工程模板分成三层:硬件层、中间层、应用层。这样后面加功能时,不会乱成一锅粥。

工程模板结构(以STM32 + GCC为例)

my_lvgl_project/
├── lvgl/                    # LVGL源码(核心库)
│   ├── src/
│   ├── lv_conf.h
│   └── lvgl.h
├── lv_drivers/              # 显示与触摸驱动
│   ├── display/
│   └── indev/
├── main.c                   # 主程序入口
├── lv_port_disp.c           # 显示接口移植
├── lv_port_indev.c          # 输入设备移植
└── Makefile / .ioc          # 构建配置

你可能会问:为什么要把lvgl和lv_drivers分开?

嗯,这里有个坑。LVGL版本更新很快,驱动层一般变化不大。分开后,你升级LVGL库时,只需要替换lvgl文件夹,驱动代码基本不用动。我在一个项目里吃过这个亏——把库和驱动混在一起,升级时改得想哭。

4.2 初始化LVGL

初始化LVGL,说白了就是告诉芯片:我要用图形界面了,你准备好内存和时钟。

我个人习惯把初始化拆成三步,每一步都做一件事,清晰明了。

  1. 调用 lv_init() —— 初始化LVGL内部数据结构
  2. 配置显示驱动 —— 告诉LVGL屏幕多大、怎么刷
  3. 配置输入设备 —— 告诉LVGL触摸或按键怎么读

来看一段实际代码。这是我在STM32F429上跑过的初始化流程:

#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"

int main(void)
{
    // 硬件初始化(时钟、GPIO、SPI等)
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_SPI_Init();

    // 第一步:初始化LVGL核心
    lv_init();

    // 第二步:初始化显示驱动
    lv_port_disp_init();

    // 第三步:初始化触摸驱动
    lv_port_indev_init();

    // 后面就是创建UI了
    while(1)
    {
        lv_timer_handler();  // LVGL心跳处理
        HAL_Delay(5);
    }
}

小技巧:lv_timer_handler() 必须周期性调用,一般放在主循环或定时器中断里。间隔建议5~10ms,太快了浪费CPU,太慢了界面会卡顿。

4.3 创建简单屏幕(Screen)

LVGL里,屏幕(Screen)就是最底层的画布。所有控件都放在屏幕上。默认有一个 lv_scr_act() 活动屏幕,但我们一般自己创建一个新的。

为什么?因为默认屏幕的样式不好控制。我习惯自己建一个,背景色、边距都自己定。

// 创建一个新屏幕
lv_obj_t * scr = lv_obj_create(NULL);
lv_obj_set_style_bg_color(scr, lv_color_hex(0x2C3E50), 0);  // 深蓝背景

// 将这个屏幕设置为活动屏幕
lv_scr_load(scr);

你看,就三行代码。第一行创建屏幕,第二行设置背景色,第三行加载显示。

这里有个细节:lv_obj_create(NULL) 表示这个对象没有父对象,它就是一个独立的屏幕。如果传了父对象,那就变成子控件了。

4.4 添加一个Label控件

屏幕有了,总得显示点东西吧。Label是最简单的控件,就是显示文字。

// 在屏幕上创建一个标签
lv_obj_t * label = lv_label_create(scr);
lv_label_set_text(label, "Hello, LVGL!");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);  // 居中显示

嗯,就这么简单。但实际项目中,我一般会加点样式:

// 带样式的标签
lv_obj_t * label = lv_label_create(scr);
lv_label_set_text(label, "嵌入式GUI入门");
lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(label, &lv_font_montserrat_20, 0);
lv_obj_align(label, LV_ALIGN_CENTER, 0, -20);

注意:使用自定义字体(如 lv_font_montserrat_20)前,必须在 lv_conf.h 中启用对应的字体宏。否则编译会报错,或者显示乱码。我曾经因为这个排查了半天,结果就是忘了开宏。

4.5 编译与下载验证

代码写完了,接下来就是编译、下载、看效果。

我一般用命令行编译,但用IDE(如STM32CubeIDE、Keil)也一样。关键是要确认以下几点:

检查项 说明
LVGL内存配置 lv_conf.h 中的 LV_MEM_SIZE 至少 16KB,推荐 32KB+
显示缓冲区 lv_port_disp.c 中缓冲区大小至少为屏幕宽×高/10
触摸校准 如果触摸不准,检查 lv_port_indev.c 中的坐标转换
时钟频率 SPI或RGB接口时钟不能太高,否则显示会花屏

编译通过后,下载到开发板。如果一切正常,你应该能看到屏幕中央显示“嵌入式GUI入门”几个字。

如果没显示?别急。我教你一个排查方法:

  1. 先确认背光有没有亮(硬件问题最常见)
  2. 再确认 lv_timer_handler() 有没有跑(加个GPIO翻转看看)
  3. 最后确认显示缓冲区地址对不对(有些MCU的LCD需要特定的内存地址)

避坑指南:我曾经在移植时,显示驱动里的 flush_cb 回调函数写错了像素格式。LVGL默认是RGB565,但我的屏幕是RGB888,结果颜色全乱套。后来在 lv_conf.h 里改了 LV_COLOR_DEPTH 为 32 才解决。

4.6 本章知识体系

下面这张图,是我画的本章核心流程。你看一遍,基本就清楚整个链路了。

第一个LVGL程序核心流程 创建工程模板 初始化LVGL核心 创建屏幕(Screen) 添加Label控件 编译下载验证 关键提醒 lv_init() → lv_port_disp_init() → lv_port_indev_init() → 创建UI → lv_timer_handler() 顺序不能乱!显示驱动必须在创建UI之前初始化。

你看,整个流程其实就五个节点。从工程模板开始,到初始化、建屏、加控件、编译下载。每一步都有明确的输入和输出。

我个人建议,第一次跑通后,可以试着改改背景色、文字内容,甚至换个字体。这样能快速熟悉LVGL的API风格。

总结一下:第一个LVGL程序,核心就是 lv_init() → 创建屏幕 → 添加控件 → 循环处理。你把这个骨架记住了,后面加按钮、加图片、加动画,都是在这个基础上添砖加瓦。