2. 开发环境搭建:基于模拟器(SDL/PC模拟器)的LVGL环境搭建、工程结构解析

说实话,很多初学者一上来就买开发板、接屏幕、焊排针,折腾半天连个像素点都没点亮。我个人习惯是——先用模拟器把逻辑跑通,再往硬件上移植。这样调试快,心态也不容易崩。

这一章,我们就来搭一套基于SDL的PC模拟器环境。你不需要任何硬件,一台电脑就够了。

2.1 为什么选SDL模拟器?

LVGL官方支持多种模拟器后端:SDL、X11、FrameBuffer、Windows GDI等。我推荐SDL,原因有三:

  • 跨平台:Windows、macOS、Linux都能跑,一套代码到处编译
  • 生态成熟:SDL2是游戏开发常用的库,文档多、坑少
  • 调试方便:可以在PC上打断点、看变量、测性能,比在嵌入式板子上舒服太多
我的经验: 之前有个项目,客户要求用STM32+RGB屏幕。我直接在模拟器上把整个UI框架搭完,最后移植只花了半天。如果直接在板子上调,光驱动屏幕就得一周。

2.2 环境准备:你需要什么?

嗯,这里列个清单,你照着准备就行:

工具/库 版本要求 说明
CMake ≥ 3.10 构建系统,跨平台编译必备
SDL2 ≥ 2.0.10 模拟器后端,负责窗口和输入
GCC/Clang/MSVC 支持C99+ 编译器,看你系统选
Git 任意 拉取LVGL源码
注意: 如果你在Windows上,SDL2的dll文件需要放在可执行文件同目录下,否则运行时会报“找不到SDL2.dll”。我曾经因为这个卡了半小时,后来发现是路径问题。

2.3 搭建步骤:从零到跑起来

我们一步步来。别急,每一步我都会说明为什么这么做。

2.3.1 拉取LVGL源码

打开终端,执行:

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

为什么用v8.3.0?因为v9.x改动较大,很多第三方库还没跟上。我们做课程,稳定第一。

2.3.2 拉取SDL模拟器工程

LVGL官方提供了一个SDL模拟器示例,在 lvgl/examples/porting 目录下。但更方便的是直接用 lv_port_pc_eclipse 这个仓库:

git clone https://github.com/lvgl/lv_port_pc_eclipse.git
cd lv_port_pc_eclipse

这个工程已经配好了CMakeLists.txt,你只需要把lvgl源码链接进来。

2.3.3 配置CMake

打开 CMakeLists.txt,你会看到类似这样的结构:

cmake_minimum_required(VERSION 3.10)
project(lvgl_simulator)

set(LVGL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lvgl)
add_subdirectory(${LVGL_DIR} lvgl)

add_executable(main main.c)
target_link_libraries(main lvgl SDL2)

这里要注意:LVGL_DIR 必须指向你刚才克隆的lvgl目录。我习惯用相对路径,这样换电脑也能编译。

2.3.4 编译并运行

在工程根目录下:

mkdir build && cd build
cmake ..
make -j4
./main

如果一切顺利,你会看到一个灰色窗口,中间有个LVGL的logo。恭喜,环境搭好了!

常见问题: 如果编译报错“找不到SDL2/SDL.h”,说明SDL2没装或者CMake找不到它。Linux下用 sudo apt install libsdl2-dev,macOS用 brew install sdl2,Windows去官网下载开发包。

2.4 工程结构解析:每个文件是干什么的?

你想想看,一个LVGL模拟器工程,说白了就三层:应用层、LVGL核心层、平台适配层。我们来看具体文件:

LVGL模拟器工程结构 应用层(Application Layer) main.c 用户自定义UI代码(你的控件、页面、逻辑) LVGL核心层(Core Layer) lvgl/src/ 下的所有源文件 控件库、绘图引擎、事件系统、样式管理 字体、图片、动画、内存管理 平台适配层(Porting Layer) lv_port_disp.c / lv_port_indev.c / lv_port_fs.c SDL2窗口初始化、鼠标/键盘输入、帧缓冲刷新 提示:移植到真实硬件时,只需要替换平台适配层,应用层和核心层基本不动

我们重点看几个关键文件:

2.4.1 main.c —— 程序的入口

这个文件里做了三件事:

  1. 初始化LVGL库:lv_init()
  2. 初始化显示驱动和输入驱动:调用 lv_port_disp_init() 和 lv_port_indev_init()
  3. 进入主循环:不断调用 lv_timer_handler() 和 SDL_Delay()

核心代码大概长这样:

int main(void) {
    lv_init();
    lv_port_disp_init();   // 初始化显示
    lv_port_indev_init();  // 初始化输入(鼠标/键盘)

    // 创建你的第一个控件
    lv_obj_t *btn = lv_btn_create(lv_scr_act());
    lv_obj_set_size(btn, 100, 50);
    lv_obj_center(btn);

    while(1) {
        lv_timer_handler();
        SDL_Delay(5);  // 控制帧率
    }
    return 0;
}
我的习惯: 我会把用户UI代码单独放到 ui.c 和 ui.h 中,main.c只负责初始化和主循环。这样后期移植到硬件时,main.c几乎不用改。

2.4.2 lv_port_disp.c —— 显示适配

这个文件负责把LVGL的帧缓冲(framebuffer)显示到SDL窗口上。关键函数是 disp_flush():

static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
    // 把color_p指向的像素数据复制到SDL纹理
    SDL_UpdateTexture(sdl_texture, NULL, color_p, screen_width * sizeof(lv_color_t));
    SDL_RenderCopy(sdl_renderer, sdl_texture, NULL, NULL);
    SDL_RenderPresent(sdl_renderer);
    lv_disp_flush_ready(disp_drv);  // 通知LVGL刷新完成
}

这里有个坑:lv_color_t 的大小取决于你在 lv_conf.h 中配置的颜色深度。默认是32位(ARGB8888),如果你改成16位,SDL纹理格式也要对应调整。

2.4.3 lv_port_indev.c —— 输入适配

这个文件把SDL的鼠标/键盘事件转换成LVGL能识别的输入事件。核心逻辑:

static void mouse_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) {
    // 从SDL事件队列中读取鼠标位置和按键状态
    data->point.x = mouse_x;
    data->point.y = mouse_y;
    data->state = mouse_pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
}

嗯,这里要注意:SDL的事件处理是在主循环中通过 SDL_PollEvent() 完成的,而 mouse_read() 是被LVGL定时调用的。所以你需要一个全局变量来保存鼠标状态。

2.5 lv_conf.h 配置:别用默认值

LVGL的配置都在 lv_conf.h 里。你从 lvgl/lv_conf_template.h 复制一份到工程根目录,改名为 lv_conf.h。我建议你至少改这几个:

宏定义 推荐值 说明
LV_COLOR_DEPTH 32 颜色深度,模拟器用32位,色彩更丰富
LV_MEM_CUSTOM 0 使用LVGL内置内存分配器,省心
LV_USE_DEMO_WIDGETS 1 打开官方demo,方便测试环境是否正常
LV_HOR_RES_MAX 800 模拟器窗口宽度
LV_VER_RES_MAX 480 模拟器窗口高度
避坑: 如果你改了 LV_COLOR_DEPTH,一定要同步修改 lv_port_disp.c 中的SDL纹理格式。否则显示出来全是花屏。我曾经在16位色深下忘了改格式,调试了一下午才发现是纹理格式不匹配。

2.6 验证环境:跑一个官方demo

环境搭好了,我们来验证一下。在 main.c 中加上:

#include "lvgl/demos/lv_demos.h"

int main(void) {
    lv_init();
    lv_port_disp_init();
    lv_port_indev_init();

    lv_demo_widgets();  // 运行官方控件demo

    while(1) {
        lv_timer_handler();
        SDL_Delay(5);
    }
}

重新编译运行,你会看到一个漂亮的仪表盘界面,各种控件都有。这说明你的环境完全OK了。

小结一下: 到这一步,你已经有了一个可以在PC上调试LVGL的完整环境。后续所有自定义控件的开发、调试、测试,都可以在这个环境里完成。等逻辑跑通了,再考虑移植到具体硬件上。

说实话,环境搭建这一步虽然枯燥,但基础打好了,后面会非常顺畅。我见过太多人因为环境没配好,浪费了大量时间在“跑不起来”上。按照上面的步骤来,应该不会出问题。