2. LVGL开发环境搭建:用PlatformIO搭个模拟器,跑通Hello World

说实话,很多初学者学LVGL,第一步就卡在环境搭建上。不是Keil工程配不对,就是屏幕驱动死活点不亮。我个人建议,别急着上硬件,先用模拟器跑起来。你想想看,在电脑上就能看到效果,调试还方便,何乐而不为?

这一章,我就带你用PlatformIO搭一个LVGL模拟器环境。整个过程我踩过不少坑,咱们一步步来,保证你能看到那个经典的"Hello World"。

2.1 为什么选PlatformIO?

市面上能跑LVGL模拟器的方案不少:

方案 优点 缺点
VS Code + PlatformIO 跨平台、插件生态好、调试方便 初次配置稍繁琐
Eclipse + SDL 老牌IDE、稳定 界面老旧、配置复杂
CLion + CMake 智能提示强 收费、学习曲线陡
在线模拟器 零安装 功能受限、不能调试

我自己的项目里,从Eclipse转到PlatformIO后,就再也没换回去。原因很简单:PlatformIO把库管理、编译、烧录、调试全整合了。你只需要写代码,剩下的它帮你搞定。

2.2 安装必备工具

嗯,这里要注意,顺序别搞反了。先装VS Code,再装PlatformIO插件。

  1. 安装VS Code:去官网下载,一路Next就行。
  2. 安装PlatformIO插件:打开VS Code,点左侧扩展图标,搜索"PlatformIO IDE",安装那个带小蚂蚁图标的。
  3. 安装SDL2库:模拟器需要SDL2来渲染窗口。
    • Windows:下载SDL2开发库,解压到某个目录。
    • macOS:brew install sdl2
    • Linux:sudo apt install libsdl2-dev
我曾经踩过的坑:Windows用户记得把SDL2的dll文件放到系统PATH里,或者直接丢到项目根目录。不然编译通过了,一运行就报"找不到SDL2.dll",那叫一个郁闷。

2.3 创建LVGL模拟器工程

装好插件后,重启VS Code。你会看到PlatformIO的蚂蚁图标出现在左侧栏。点它,然后:

  1. 点击"Open" → "New Project"。
  2. Project Name填 lvgl_simulator。
  3. Board选 Desktop 平台下的 Native。
  4. Framework选 LVGL。
  5. Location选个你记得住的位置。

点"Finish",PlatformIO会自动下载LVGL库和依赖。第一次会慢一点,去喝杯咖啡吧。

工程结构长这样:

lvgl_simulator/
├── .pio/            # 编译中间文件(别碰)
├── include/         # 头文件放这里
├── lib/             # 第三方库
├── src/             # 你的源代码
│   └── main.cpp
├── platformio.ini   # 项目配置文件
└── test/            # 单元测试

2.4 配置platformio.ini

这个文件是PlatformIO的灵魂。双击打开,把内容改成这样:

[env:native]
platform = native
build_flags = 
    -I/usr/local/include   ; SDL2头文件路径,Windows用户改成你的SDL2路径
    -L/usr/local/lib       ; SDL2库文件路径
lib_deps = 
    lvgl/lvgl@^8.3.0      ; LVGL核心库
    lvgl/lv_drivers@^8.3.0 ; LVGL驱动库(含SDL模拟器驱动)
build_unflags = -std=c++11
build_flags = ${env:native.build_flags} -std=c++17

小提示:Windows用户如果SDL2装在 C:\SDL2,那build_flags要写成:

-IC:\SDL2\include -LC:\SDL2\lib

别忘了把 SDL2.dll 复制到工程根目录。

2.5 编写main.cpp——Hello World!

现在,打开 src/main.cpp,敲入这段代码。我习惯把初始化流程写清楚,方便以后复用。

#include <lvgl.h>
#include <lv_drivers/display/monitor.h>
#include <lv_drivers/indev/mouse.h>

#define HOR_RES 800
#define VER_RES 480

int main(void) {
    // 1. 初始化LVGL
    lv_init();

    // 2. 初始化显示器驱动(SDL模拟)
    monitor_init();
    lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.flush_cb = monitor_flush;
    disp_drv.hor_res = HOR_RES;
    disp_drv.ver_res = VER_RES;
    lv_disp_drv_register(&disp_drv);

    // 3. 初始化鼠标输入
    mouse_init();
    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = mouse_read;
    lv_indev_drv_register(&indev_drv);

    // 4. 创建Hello World标签
    lv_obj_t *label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "Hello World!");
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

    // 5. 主循环
    while(1) {
        lv_timer_handler();
        lv_delay_ms(5);
    }

    return 0;
}

代码不长,但每一行都有讲究。我解释一下:

  • lv_init():LVGL的"开机"函数,必须先调。
  • monitor_init/flush:SDL模拟器的显示驱动。说白了就是把LVGL要画的内容,通过SDL画到你的电脑屏幕上。
  • mouse_init/read:鼠标驱动。这样你就能用鼠标点击模拟器上的按钮了。
  • lv_label_create:创建一个标签对象,这是LVGL里最基础的控件。
  • lv_timer_handler:LVGL的心跳函数。它处理所有待办任务,比如重绘屏幕、处理输入。必须放在主循环里频繁调用。

2.6 编译与运行

点PlatformIO底部工具栏的"→"(Build and Run)按钮。第一次编译会下载SDL2依赖,稍等片刻。

如果一切顺利,你会看到一个800x480的窗口弹出来,正中间显示着"Hello World!"。

常见错误及解决:

  • fatal error: SDL2/SDL.h: No such file or directory → build_flags里的SDL2路径不对,检查一下。
  • undefined reference to 'SDL_Init' → 链接库没加对,检查lib_deps和build_flags。
  • 窗口闪一下就消失 → 主循环里没有延时,或者lv_timer_handler没调。

2.7 工程结构深度解析

跑通了,咱们回头看看这个工程到底是怎么组织的。我画了张图,帮你理清关系。

LVGL模拟器工程结构 用户代码 src/main.cpp LVGL核心库 lvgl/lvgl LVGL驱动库 lvgl/lv_drivers SDL2库 系统/用户安装 操作系统 Windows/macOS/Linux PC硬件 调用关系说明 1. 用户代码调用LVGL API 2. LVGL调用驱动刷新屏幕 3. 驱动通过SDL2绘制窗口

从这张图你能看到,整个调用链是单向的:用户代码 → LVGL核心 → LVGL驱动 → SDL2 → 操作系统 → 硬件。每一层只关心自己的事,互不干扰。这就是嵌入式GUI开发里常用的分层思想。

2.8 第一个Hello World,背后发生了什么?

当你看到那个"Hello World!"标签时,LVGL在背后做了这些事:

  1. 对象创建:lv_label_create 在内存里分配了一个标签对象,挂到活动屏幕(lv_scr_act())上。
  2. 文本设置:lv_label_set_text 把字符串"Hello World!"存到对象里,并标记该区域为"脏区域"(需要重绘)。
  3. 对齐计算:lv_obj_align 根据父对象(屏幕)的尺寸,计算标签应该放在哪个坐标。
  4. 定时器触发:lv_timer_handler 检查到有脏区域,调用显示驱动的 flush_cb 函数。
  5. SDL渲染:monitor_flush 把LVGL传过来的像素数据,通过SDL2画到窗口上。

这个过程每5ms循环一次。你想想看,如果你的屏幕刷新率是60Hz,那每秒就要重复60次这个流程。所以lv_timer_handler的调用频率很关键,调太快浪费CPU,调太慢界面卡顿。

我的经验:在模拟器上,lv_delay_ms(5) 是个不错的起点。如果界面卡,可以降到2ms;如果CPU占用太高,升到10ms。实际嵌入式设备上,这个值通常用硬件定时器中断来控制,而不是软件延时。

2.9 避坑指南

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

  • 路径问题:Windows用户注意,路径里的反斜杠要转义,或者直接用正斜杠。我见过有人因为路径写错,编译报错查了一下午。
  • SDL2版本:LVGL 8.x 推荐用SDL2 2.0.10以上版本。太老的版本有些API不支持。
  • 中文显示:模拟器默认字体不带中文字符。如果你想显示中文,需要额外加载字体文件。这个咱们后面章节会详细讲。
  • 窗口大小:HOR_RES 和 VER_RES 定义了模拟器的分辨率。我习惯设成800x480,这是很多嵌入式屏幕的常见分辨率。你也可以改成320x240,模拟小屏效果。