
做嵌入式GUI开发的朋友估计都有这种经历调一个按钮间距、改一个字体颜色、换一套主题配色都要编译烧录到板子上反复几轮十分钟就没了。LVGL作为目前使用率很高的开源嵌入式图形库解决这个问题的方式其实很成熟——PC模拟器。先在电脑上把界面跑通确认布局、配色、交互都没问题再移植到真实硬件省下来的时间非常可观。不过很多同学卡在了“配置模拟器环境”这一步尤其是VScode配合CMake和MinGW时环境变量稍微配错一点各种奇怪报错就来了。这篇分享一个我平时用得最顺的极简配置法VScode 官方SDL模拟器工程LVGL全程不修改系统环境变量把工具链路径全部限定在VScode配置层。按这个流程熟练之后从打开VScode到看到LVGL窗口确实能做到5分钟左右。文章会覆盖工具下载、工程准备、VScode配置、编译运行、问题排查几个部分新手可以直接照着抄作业老手也可以看看免环境变量的思路能不能用到别的地方。1. 为什么我建议用模拟器先跑通LVGL界面1.1 传统开发流程的痛点很多刚接触LVGL的人习惯性把LVGL源码直接放进STM32工程然后写显示驱动、触摸驱动、内存配置最后编译烧录到板子。这套流程本身没问题但效率确实低。改一个控件边距从改代码到看到效果要经历编译、烧录、重启、观察来回一次起码两分钟。如果用的是老开发板下载器速度慢一点可能更久。一上午调几个像素人是会崩溃的。还有一个隐性成本嵌入式交叉编译环境通常比较脆弱编译报错往往不是LVGL本身的问题而是链接脚本、堆栈配置、内存池设置等方面出了问题。遇到这种报错新手很容易陷入“改LVGL代码反而报更多错”的恶性循环。把界面验证放到PC模拟器上能把这个环节的变量降到最少。模拟器方案的本质是把LVGL当成一个纯软件库来跑显示和输入都交给SDL2处理。底层不再依赖单片机的LCD驱动也不依赖触摸芯片。这样LVGL内部的布局系统、动画系统、事件系统都可以在电脑上完整运行。鼠标点击会模拟成触摸事件窗口刷新就是屏幕刷新所见即所得。1.2 模拟器方案怎么选四类方案的取舍LVGL官方和社区提供了不少模拟方案我把常见的几种列个表格直接对比方案上手难度可自定义程度适合场景官方在线模拟器很低低只能看Demo快速预览LVGL效果Eclipse SDL模拟器工程较高高熟悉Eclipse的开发者VScode SDL模拟器工程中低高日常开发、快速验证直接移植到真实硬件高高驱动联调、性能验证在线模拟器最大的问题在于不能跑自己的代码只能看看官方Demo长什么样。Eclipse那套方案在Windows上配置起来比较繁琐Java环境、Eclipse插件、工具链路径都要处理对只写嵌入式C代码的人来说有点重。VScode加官方SDL模拟器工程是我目前最推荐的方式因为VScode本身轻量插件生态成熟CMake Tools又能把“配置/构建/运行”压缩成几次点击。真实硬件移植永远躲不掉但顺序应该是先在模拟器里把UI层调通再做驱动层适配。逻辑分开后两边的问题都不会互相干扰。1.3 免环境变量的核心思路通常配置MinGW和CMake的教程第一件事就是让你把D:\xxx\mingw64\bin和D:\xxx\cmake\bin塞进系统PATH。这一步“配置环境变量”劝退了很多人而且有时候PATH改完不生效、顺序不对、和其他软件冲突反而带来更多问题。所谓免环境变量不是不需要工具链而是不把工具链塞进系统全局。Windows系统不知道cmake、gcc、mingw32-make在哪没关系VScode启动后CMake Tools插件会从我们指定的绝对路径去调用这些可执行文件。也就是说系统只管启动VScode剩下的事情全在VScode的配置层解决。你可以理解成平时点外卖只写“某某小区”骑手还得自己查楼栋这就是靠PATH找命令。免环境变量的做法是把门牌号写到清清楚楚骑手不用查直接上门。这样对系统没有任何全局改动换电脑、重装系统只要把工具目录原样拷走配置一次就能继续用。2. 极简配置工具与工程准备2.1 三件套下载MinGW-w64、CMake、SDL2先说一句话所有工具都认准“免安装版”下载解压就能用千万别去装那种带安装向导的版本否则又绕回环境变量了。MinGW-w64是编译器的集合包含gcc、g、mingw32-make。推荐直接去winlibs.com下载这个站点提供的MinGW-w64是打包好的zip格式解压后里面直接有bin\gcc.exe和bin\mingw32-make.exe。下载时选择UCRT runtime的64位版本即可文件大约两三百MB解压到D:\lvgl_env\tools\mingw64这样的目录下。不要用MSYS2因为MSYS2装完还要额外配置path或者通过它的终端来启动VScode不符合“极简免环境变量”的定位。CMake到官网download页面找“Windows x86_64 ZIP”包不是那个.msi安装包是zip格式。解压出来会有个cmake-3.29.3-windows-x86_64文件夹里面bin\cmake.exe就是主程序。版本选择3.20以上就行。SDL2比较关键这是LVGL模拟器的显示后端负责创建窗口、处理鼠标和键盘事件。到SDL2的GitHub Releases页面下载SDL2-devel-2.28.5-mingw.zip主要是认准文件名里的mingw字样不要下成vc版本因为MinGW编译的工程没法直接链接MSVC编译的库。解压后目录名是SDL2-2.28.5里面能看到x86_64-w64-mingw32文件夹这个我们在配置时会用到。2.2 拉取LVGL模拟器工程并整理目录LVGL官方维护了一个专门用于PC模拟的仓库叫lv_sim_eclipse_sdl。虽然名字里带Eclipse但它本身是用CMake组织工程的VScode用起来完全没问题。用git拉取时注意这个仓库里包含LVGL核心源码子模块所以要用递归方式git clone --recursive https://github.com/lvgl/lv_sim_eclipse_sdl.git如果网络不稳定导致子模块拉取失败进到仓库目录后再执行一下git submodule update --init --recursive拉取完成后目录结构大致是lv_sim_eclipse_sdl/ ├── SDL/ # SDL2适配层 ├── lvgl/ # LVGL核心源码 ├── Template/ # 模板工程含main.c ├── CMakeLists.txt # 构建脚本 └── README.md如果实在不方便用git也可以直接在GitHub页面下载zip包但一定要记得手动把LVGL源码也下载好并放进lvgl目录并且版本要和模拟器工程匹配。具体版本看仓库README里的说明通常模拟器工程默认会跟踪某个LVGL release分支。硬套最新master代码不一定会报错但API变了之后编译问题会很多。2.3 目录规划与版本搭配建议我习惯把所有工具集中放在一个目录里整体可迁移。比如这样D:/lvgl_env/ ├── tools/ │ ├── cmake-3.29.3-windows-x86_64/ │ ├── mingw64/ │ └── SDL2-2.28.5/ └── projects/ └── lv_sim_eclipse_sdl/这里有个非常重要的经验整个路径不要出现中文和空格。MinGW对带空格路径的支持虽然在慢慢变好但CMake和SDL2在解析路径时遇到空格还是容易出幺蛾子尤其是Windows上经常默认把用户名导致C:\Users\张三这种路径。为了省事直接在D盘根目录建一个纯英文目录就好。我把这当作一条铁律来遵守。版本搭配上我的建议是LVGL模拟器工程里锁定的LVGL版本是什么你就用那个版本不要随便升级LVGL核心源码。LVGL 8和LVGL 9的API改动不小比如lv_btn_create变成lv_button_create这类重命名升级一次可能改几十个文件。模拟器工程的README都把配套版本写得很清楚照着来最稳妥。3. VScode侧的关键配置不碰系统环境变量3.1 只装三个扩展就行VScode扩展市场里输入“LVGL”可能会搜到一堆辅助插件但对模拟器配置来说真正必需的就三个C/C微软官方那个、CMake、CMake Tools。前两个负责代码高亮、跳转和CMake语法支持第三个是核心它提供了图形化的配置、构建、运行入口。安装完成后VScode底栏会出现一个状态栏区域显示“No Kit Selected”、“Build”等按钮。这些按钮由CMake Tools插件提供后文所有操作都靠它们完成不需要敲命令行。有人可能会问为什么不用VScode内置终端直接敲cmake ..和make因为免环境变量的关键就是不让系统PATH感知这些工具终端里的命令默认走PATH自然找不到cmake。而CMake Tools插件不走终端继承的PATH它可以读取绝对路径来启动cmake和gcc这才是整套方案能成立的基础。3.2 核心编写工具链文件并指定编译器绝对路径在lv_sim_eclipse_sdl工程根目录下新建一个文件toolchain-windows.cmake内容如下# toolchain-windows.cmake set(CMAKE_SYSTEM_NAME Windows) set(CMAKE_C_COMPILER D:/lvgl_env/tools/mingw64/bin/gcc.exe) set(CMAKE_CXX_COMPILER D:/lvgl_env/tools/mingw64/bin/g.exe) set(CMAKE_MAKE_PROGRAM D:/lvgl_env/tools/mingw64/bin/mingw32-make.exe) set(CMAKE_C_FLAGS_INIT -O2) set(CMAKE_CXX_FLAGS_INIT -O2)这个文件的作用是告诉CMake你要用哪个编译器用哪个make工具。注意CMAKE_MAKE_PROGRAM这一行非常关键如果缺了它CMake会去PATH里找mingw32-make找不到时就报“CMAKE_MAKE_PROGRAM is not set”的错误。如果你希望编译产物是可调试版本可以把-O2改成-O0 -g。不过正常情况下模拟器目的是快速看界面Release模式的编译速度反而快一些而且运行更流畅我建议直接用-O2调试UI不需要深究LVGL内部变量。3.3 配置settings.json与IntelliSense打开VScode后按CtrlShiftP输入“Preferences: Open Workspace Settings (JSON)”把以下内容写入工作区设置{ cmake.cmakePath: D:/lvgl_env/tools/cmake-3.29.3-windows-x86_64/bin/cmake.exe, cmake.toolchainFile: D:/lvgl_env/projects/lv_sim_eclipse_sdl/toolchain-windows.cmake, cmake.generator: MinGW Makefiles, cmake.buildDirectory: ${workspaceFolder}/build, cmake.configureEnvironment: { CC: D:/lvgl_env/tools/mingw64/bin/gcc.exe, CXX: D:/lvgl_env/tools/mingw64/bin/g.exe }, cmake.configureArgs: [ -DSDL2_DIRD:/lvgl_env/tools/SDL2-2.28.5 ] }逐行解释一下cmake.cmakePath直接告诉CMake Toolscmake的可执行文件在哪不通过PATH查找。cmake.toolchainFile指向前面创建的工具链文件这样配置阶段就会用绝对路径找到gcc和g。cmake.generator指定为“MinGW Makefiles”这样才能配合mingw32-make工作。cmake.buildDirectory把构建输出统一放到工程目录下的build文件夹方便清理和查找产物。cmake.configureArgs里的SDL2_DIR是给find_package(SDL2)用的CMake在执行时会在指定目录下查找SDL2的配置文件。如果你第一次操作时不确定SDL2目录名先解压后看下实际文件夹名字确认里面有没有x86_64-w64-mingw32这个子目录。SDL2_DIR应该指到SDL2解压后的根目录不是x86_64-w64-mingw32这一级也不是bin或lib那一级。为了让代码提示和跳转更舒服建议再在工程.vscode目录下建一个c_cpp_properties.json{ configurations: [ { name: Win64, includePath: [ ${workspaceFolder}/lvgl, ${workspaceFolder}/lvgl/src, ${workspaceFolder}/lvgl/demos, ${workspaceFolder}, D:/lvgl_env/tools/SDL2-2.28.5/x86_64-w64-mingw32/include ], defines: [ LV_CONF_INCLUDE_SIMPLE, SDL_MAIN_HANDLED ], compilerPath: D:/lvgl_env/tools/mingw64/bin/gcc.exe, cStandard: c11, cppStandard: c17, intelliSenseMode: windows-gcc-x64 } ], version: 4 }includePath里的lvgl/src和lvgl/demos是LVGL头文件所在位置加上之后点击LVGL函数就能跳转。LV_CONF_INCLUDE_SIMPLE这个宏的作用是让LVGL直接通过#include lv_conf.h方式定位配置头文件不加这个宏的话LVGL会尝试从lvgl/src/lv_conf_internal.h里找路径容易找不到。SDL_MAIN_HANDLED用来告诉SDL2不用接管main入口避免和模拟器模板工程的main函数冲突。4. 一键编译与运行模拟器4.1 CMake Tools的完整操作流程配置完成后用VScode打开lv_sim_eclipse_sdl文件夹。如果之前的settings.json已经生效底栏状态栏应该会直接显示CMake相关按钮。如果提示“No Kit Selected”按CtrlShiftP输入“CMake: Scan for Kits”CMake Tools会自动扫描编译器。因为我们在toolchainFile里已经指定了编译器路径通常不需要手动选Kit直接点“Configure”按钮就行。按F1输入“CMake: Configure”回车。第一次配置会运行CMake检查编译器能不能工作、SDL2能不能找到、LVGL源码目录是否完整。如果一切正常终端会输出“Configuring done”。接着按F1输入“CMake: Build”开始编译。首次构建因为要编译LVGL核心源码和SDL2适配层大约一到两分钟之后增量编译快很多改一个文件几秒就搞定。编译完的exe位于build目录下。如果运行点击没反应或提示找不到SDL2.dll需要手动把D:/lvgl_env/tools/SDL2-2.28.5/x86_64-w64-mingw32/bin/SDL2.dll复制到exe同目录下。这一步很容易漏我建议直接在CMakeLists.txt最后加一段自动复制逻辑省得每次手动add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SDL2_DIR}/x86_64-w64-mingw32/bin/SDL2.dll $TARGET_FILE_DIR:${PROJECT_NAME} )加好后重新Configure一次再Build以后每次编译都会自动把SDL2.dll放到输出目录。运行方式有两种直接到build目录双击exe或者按F1输入“CMake: Run without Debugging”。我习惯用后者因为它会自动找到目标文件并启动。4.2 改分辨率、加载Demo和自定义UI第一次启动模拟器默认会显示LVGL的widgets Demo里面是各种按钮、滑块、进度条等控件鼠标点击会触发点击效果。这个Demo本身就能验证接口是否正常。分辨率设置一般在main.c或者模板工程的显示驱动初始化里。不同LVGL版本位置不太一样LVGL 8的模拟器模板通常在main.c里设置disp_drv.hor_res和disp_drv.ver_resLVGL 9改成调用lv_display_set_resolution(disp, 宽, 高)。不知道具体位置时在工程里搜索hor_res或resolution关键字一眼就能找到。加载自己的UI代码在main.c中找到类似lv_demo_widgets()的调用替换成自己的函数。比如先写一个最简单界面试试手void my_ui_create(lv_obj_t *parent) { lv_obj_t *label lv_label_create(parent); lv_label_set_text(label, Hello LVGL); lv_obj_center(label); lv_obj_t *btn lv_btn_create(parent); lv_obj_set_pos(btn, 10, 10); lv_obj_set_size(btn, 100, 40); } int main(void) { lv_init(); /* 初始化SDL显示驱动 */ /* 创建默认display */ my_ui_create(NULL); while (1) { lv_timer_handler(); SDL_Delay(5); } }这段以LVGL 8 API为例LVGL 9里lv_btn_create要换成lv_button_create。核心思路是你在真实板子上写的UI代码完全可以拷到模拟器里跑除了一些硬件相关API需要替换LVGL公共API完全一致。改完代码后按F1输入“CMake: Build”再“CMake: Run without Debugging”几秒钟就能看到新界面。这个“改代码-看效果”的循环就是模拟器最大的价值一天下来能验证几十个界面方案。4.3 模拟器与真实硬件的衔接点模拟器验证通过后移到真实板子一般不会太痛苦但有几个坑要注意。LVGL的颜色深度要和屏幕驱动匹配。如果你的屏是RGB565但模拟器里lv_conf.h的LV_COLOR_DEPTH设成了24那界面整体色调可能偏色。这个宏在lv_conf.h里真实板子和模拟器保持一致才好互相迁移。触摸输入的处理也不一样。模拟器把鼠标位置映射成坐标通过SDL事件模拟触摸。真实板子上要把触摸芯片得到的坐标通过lv_indev_set_xy之类的东西喂给LVGL。UI代码不用改但输入设备初始化的部分要重写。性能差异也要心里有数。模拟器运行在PC大内存高主频环境下动画自然流畅。真实MCU上如果觉得卡优先检查LVGL配置里的内存大小、刷新频率以及动画是否开启太多。布局和逻辑在模拟器验证没问题但性能优化必须在真实硬件上做。5. 常见问题与排查记录5.1 编译阶段最常踩的坑我见过最多的报错是“CMake Error: CMAKE_MAKE_PROGRAM is not set”。这个几乎都是因为没在toolchain文件里设置CMAKE_MAKE_PROGRAM或者把make路径写错了。确认D:/lvgl_env/tools/mingw64/bin/mingw32-make.exe存在再看toolchain文件路径是不是写成了反斜杠。CMake里建议统一用正斜杠/反斜杠在某些解析环节会被当转义符导致路径出错。还有一个高频问题是“The C compiler is not able to compile a simple test program”。这个往往是编译器路径不对或者MinGW下载不完整。打开cmd直接运行D:/lvgl_env/tools/mingw64/bin/gcc.exe --version能打印版本号说明编译器正常。如果不能运行重新解压MinGW包别手动删里面的文件。如果报错提示找不到SDL2检查SDL2_DIR是否指向正确目录且该目录下确实存在SDL2Config.cmake或sdl2-config.cmake这种文件。有时候解压后多套了一层文件夹比如SDL2-2.28.5/SDL2-2.28.5那SDL2_DIR就要指向内层那个。实在不行直接用绝对路径-DSDL2_DIRD:/lvgl_env/tools/SDL2-2.28.5不要用~之类的缩写。5.2 运行阶段的典型问题编译成功但运行时提示缺SDL2.dll按前面4.1的方法处理把SDL2.dll复制到exe旁边或者加POST_BUILD自动复制一劳永逸。窗口能出来但整体黑屏什么都不显示大概率是SDL初始化没成功或LVGL的刷新不完整。检查main.c里SDL的初始化是否在lv_init()之后显示驱动创建是否成功。可以试试在循环里加一句打印判断是否进入lv_timer_handler()如果没进去说明循环逻辑出了问题。中文显示成方块而不是文字这是LVGL的经典问题。原因很简单LVGL默认字体里没有中文字形需要自己转换字体。字体转换工具用LVGL官方提供的把ttf字体转成c数组或bin文件然后在lv_conf.h里启用对应字体再在label上手动指定该字体。模拟器阶段先别纠结中文等界面框架搭好了再处理字体映射。改了lv_conf.h里的配置但感觉没生效注意lv_conf.h的路径。模拟器工程一般自带一份lv_conf.h放到工程根目录LVGL通过LV_CONF_INCLUDE_SIMPLE找到它。如果你同时改了好几份lv_conf.h编译器可能读的是另外一份。搜索整个工程目录确认唯一性。5.3 几个提高效率的实操细节用Release模式编译之后每次改代码重新构建基本都在5秒以内这个反馈速度是整个工作流的核心价值。在接近真实开发时我会在模拟器里同时打开多个LVGL Demo切不同主题看效果确定风格后再落地到具体页面上。工程目录放到固态硬盘上跑首次构建会快很多。机械硬盘编译LVGL源码时要读几千个文件差距还是很明显的。如果你经常在不同电脑之间切换开发环境只要把D:/lvgl_env整个目录拷走再装一遍VScode和三个扩展打开工程就能继续用。这就是“免环境变量版”最大的好处不污染系统也不依赖系统。我自己实际用下来这套流程在Win10和Win11上都能稳定跑CMake Tools偶尔提示Kit变化时重新Configure一次就好不影响后续开发。最后还有一个个人经验模拟器里鼠标操作和真实触摸在交互体验上略有区别比如触摸没有悬停态鼠标有。做桌面风格界面时无所谓但如果目标设备是触摸屏记得在模拟器里开启触控模拟模式SDL驱动里一般有配置项至少能提前发现一些误触问题。这个细节不算复杂但能省掉真机联调时不少返工。