3、LVGL环境搭建:源码获取、编译配置、移植到不同平台(STM32/ESP32/Linux)、模拟器使用

好,咱们进入第三章。环境搭建这事儿,说简单也简单,说复杂也复杂。我见过不少新手,代码写得挺溜,结果卡在环境配置上,一卡就是好几天。说白了,环境搭不好,后面的学习全是纸上谈兵。

这一章,我会带着你从源码获取开始,一步步把LVGL跑起来。咱们会聊到STM32、ESP32、Linux这三个主流平台,还会讲讲模拟器——我个人觉得,模拟器是学习LVGL最快的方式,没有之一。

本章核心目标:让你能在自己的开发板上点亮LVGL的第一个Demo,并且知道怎么在不同平台之间切换。

LVGL 环境搭建全景图 LVGL 源码 PC 模拟器 嵌入式平台 Linux FrameBuffer SDL2 Emscripten STM32 ESP32 Linux FB / DRM 💡 建议:新手从 PC 模拟器入手,熟悉 API 后再移植到目标硬件 模拟器调试效率高,硬件问题少,是学习 LVGL 的最佳起点

3.1 源码获取:从GitHub到本地

LVGL的源码托管在GitHub上,项目名叫lvgl。我个人习惯直接克隆主仓库,而不是下载压缩包——这样方便后续切换版本和拉取更新。

git clone https://github.com/lvgl/lvgl.git
cd lvgl
git checkout v8.3.0  # 建议使用稳定版本

嗯,这里要注意一点。LVGL的版本迭代挺快的,v8和v9的API有不小差异。我建议初学者先锁定v8.3.x,这个版本最成熟,资料也最多。等你把v8玩熟了,再去看v9的新特性,会轻松很多。

小技巧:除了主仓库,你还需要一个lv_drivers仓库,里面封装了显示和触摸的驱动接口。克隆命令:git clone https://github.com/lvgl/lv_drivers.git

源码目录结构大致是这样的:

目录/文件 说明
src/ 核心源码,包括控件、布局、绘图等
examples/ 官方示例,学习必备
lv_conf_template.h 配置文件模板,你需要复制并重命名为lv_conf.h
lvgl.h 主头文件,包含所有API声明

3.2 编译配置:lv_conf.h 是核心

LVGL的配置全在一个文件里——lv_conf.h。这个文件控制着所有功能的开关、内存大小、颜色深度等等。说白了,它就是LVGL的「总开关面板」。

我曾经在一个项目里,因为忘了改LV_COLOR_DEPTH,导致显示出来的颜色全是花的。排查了半天,最后发现是配置里写的是16位,但屏幕硬件是18位。嗯,这种坑踩过一次就记住了。

下面是一个典型的配置片段:

/* 颜色深度:16位 RGB565 */
#define LV_COLOR_DEPTH     16

/* 内存池大小:32KB */
#define LV_MEM_SIZE        (32U * 1024U)

/* 开启 Tick 接口 */
#define LV_TICK_CUSTOM     1

/* 开启日志输出,调试用 */
#define LV_USE_LOG         1
#define LV_LOG_LEVEL       LV_LOG_LEVEL_INFO

/* 开启常用控件 */
#define LV_USE_BTN         1
#define LV_USE_LABEL       1
#define LV_USE_SLIDER      1

警告:LV_MEM_SIZE不要设得太小。如果内存不够,LVGL会静默失败——界面不显示,但也不报错。我建议至少32KB起步,复杂界面需要128KB以上。

3.3 移植到不同平台

移植LVGL的核心工作就两件事:提供显示缓冲和提供时钟节拍。其他都是锦上添花。

3.3.1 STM32 平台

STM32上跑LVGL,我一般用CubeMX生成基础工程,然后手动集成LVGL源码。显示驱动需要实现一个flush_cb回调函数:

/* 显示刷新回调 */
static void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    /* 将 color_p 中的数据通过 SPI/FSMC 发送到 LCD */
    LCD_DrawBitmap(area->x1, area->y1, area->x2 - area->x1 + 1, area->y2 - area->y1 + 1, (uint16_t *)color_p);
    
    /* 通知 LVGL 刷新完成 */
    lv_disp_flush_ready(disp_drv);
}

时钟节拍一般用硬件定时器,每1ms调用一次lv_tick_inc(1)。嗯,这里有个细节——定时器优先级要设得合适,别被其他中断抢占了导致界面卡顿。

3.3.2 ESP32 平台

ESP32上移植LVGL更简单,因为官方提供了lvgl_esp32_drivers组件。你只需要在menuconfig里配置好屏幕型号和引脚,剩下的驱动代码自动生成。

我个人觉得ESP32是学习LVGL性价比最高的硬件平台——便宜、WiFi/蓝牙都有、社区活跃。唯一要注意的是,ESP32的FreeRTOS任务栈大小要留够,我给LVGL的任务栈一般设4096字节。

/* ESP32 上的 LVGL 任务 */
void lvgl_task(void *arg)
{
    lv_init();
    lvgl_driver_init();  // 官方驱动初始化
    
    /* 创建 UI */
    lv_obj_t * label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "Hello ESP32!");
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
    
    while(1) {
        lv_task_handler();
        vTaskDelay(pdMS_TO_TICKS(5));
    }
}

3.3.3 Linux 平台

Linux下跑LVGL,通常用FrameBuffer或DRM/KMS驱动。我建议用DRM,它比老旧的FrameBuffer更稳定,支持双缓冲和垂直同步。

Linux移植不需要自己写底层驱动,直接用lv_drivers里的fbdev.c或drm.c就行。编译时链接-ldrm -lgbm库。

/* Linux DRM 初始化示例 */
lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.flush_cb = drm_flush;  // lv_drivers 提供
disp_drv.hor_res = 1024;
disp_drv.ver_res = 600;
lv_disp_drv_register(&disp_drv);

3.4 模拟器使用:学习效率翻倍

好了,重点来了。模拟器是我最推荐的学习方式。你想想看,在PC上写代码、编译、运行,整个过程不到10秒。而在嵌入式平台上,每次烧录都要等几十秒甚至几分钟。

LVGL官方提供了基于SDL2的模拟器。你只需要安装SDL2库,然后编译lv_sim_eclipse_sdl项目就行。

# Ubuntu 上安装 SDL2
sudo apt-get install libsdl2-dev

# 克隆模拟器项目
git clone https://github.com/lvgl/lv_sim_eclipse_sdl.git
cd lv_sim_eclipse_sdl
make -j4
./build/bin/main

运行起来后,你会看到一个窗口,里面就是LVGL的Demo界面。鼠标可以模拟触摸,键盘可以模拟按键。调试起来非常方便。

我的习惯:先在模拟器上把UI布局和交互逻辑调通,确认没问题了,再移植到硬件上。这样硬件调试的时间能减少80%。

模拟器还有一个隐藏好处——你可以用IDE的断点调试功能。这在嵌入式平台上很难做到,但在PC上就是家常便饭。遇到界面卡死或者显示异常,直接打断点看变量,问题一目了然。

3.5 避坑指南

最后,分享几个我踩过的坑:

  • lv_conf.h 没生效——检查文件路径,必须放在lvgl目录的上一级,或者在编译器里指定LV_CONF_PATH宏。
  • 屏幕闪烁——多半是刷新频率太低,或者没有使用双缓冲。在lv_conf.h里把LV_DISP_DEF_REFR_PERIOD改成30ms试试。
  • 触摸没反应——先确认触摸驱动是否返回了正确的坐标值。我习惯在touchpad_read_cb里加个串口打印,看看数据对不对。
  • 编译报错说找不到头文件——检查编译器包含路径,确保lvgl和lv_drivers的根目录都在路径里。

嗯,环境搭建这部分就到这里。记住一句话:模拟器是学习利器,硬件移植是最终目标。先把模拟器玩熟,后面的事情就水到渠成了。