
做嵌入式或者单片机开发的同学应该都有这种体会写 UI 界面时最烦的不是逻辑而是“改一行代码就要重新编译、烧录、上电看效果”。一套流程下来几分钟没了一天调几十次时间全耗在等待上。所以在 PC 上搭一个 LVGL 模拟器直接在 Visual Studio 2022 里写界面、跑交互、调布局就成了提升效率的关键一步。但说实话LVGL 模拟器的环境配置对新手并不友好。我最早接触这个坑的时候光是环境就折腾了整整一个周末VS 2022 装好了、源码拉下来了结果不是报“无法打开包括文件 lvgl.h”就是链接时冒出一堆 LNK2019甚至跑到一半黑屏没反应。网上教程虽然多但版本混杂8.x 和 9.x 的目录结构完全不同照着旧的来十有八九白干。这篇文章我就把从零开始用 Visual Studio 2022 搭建 LVGL 模拟器这条路完整捋一遍。以目前最稳定的 LVGL 8.3 为主线穿插 9.x 的差异重点讲我踩过的坑、问题背后的原因以及真正能落地的配置方法。适合刚接触 LVGL、想在电脑上快速验证界面效果的同学。1. 为什么要在 VS2022 上折腾 LVGL 模拟器1.1 嵌入式 UI 开发最大的痛点是“改一次烧一次”很多初学者做嵌入式界面流程基本是这样写代码 - 交叉编译 - 下载到开发板 - 上电看效果。如果发现按钮位置不对、控件间距不合适再改再烧。这个循环看起来没毛病但实际效率非常低。一个稍微复杂点的界面光调整控件位置和大小就可能要烧录几十次。而且有些问题在硬件上还不好排查比如屏幕初始化失败、触摸坐标偏移、刷新频率不够到底是代码问题还是硬件问题很难一眼看出来。这时候模拟器的价值就体现出来了它把 LVGL 完整跑在 PC 上界面渲染、触摸事件、动画效果全部可以实时预览不用碰硬件。1.2 模拟器到底能帮你省下哪些事用模拟器做开发最直接的好处是“改完立即跑”。VS 2022 里直接运行窗口弹出来鼠标一拖、一滑LVGL 的界面就动起来了。我最常用的场景是这么几个验证控件的布局效果比如 Flex 布局、Grid 布局在不同分辨率下的表现测试页面的切换逻辑和动画时序调试自定义控件的外观和事件处理。等界面逻辑在模拟器上任人跑得差不多再移植到开发板上基本只需要改显示驱动和输入驱动两层业务代码零改动。1.3 这篇避坑指南适合谁如果你满足以下任一情况这篇文章大概率能帮到你在 Windows 上用 VS2022 做开发想跑 LVGL 但不知道从哪下手。已经照着网上的教程配过模拟器但被各种编译错误卡住。之前用 Keil 开发板移植过 LVGL想搭 PC 环境做 UI 原型验证。想了解 LVGL 8.3 和 9.x 在模拟器搭建上的区别。2. 搭建方案与版本选型先看清路再动工2.1 三种常见搭建方式对比我见过很多人一上来就找现成工程直接打开结果不是版本不匹配就是路径不对。这里先梳理主流的三种做法方便你根据自身情况选。方案上手难度依赖适用场景官方 lv_port_pc_vscode CMake较低CMake、SDL2想快速跑通愿意学一点 CMake 语法手动新建 VS 空项目中等SDL2想彻底搞懂配置逻辑不想依赖 CMakeEclipse 工程转 VS 工程较高额外插件老玩家迁移旧工程新手不推荐我个人的建议是如果你只是想快速看到效果直接走 CMake 路线最省事VS2022 本身就内置了对 CMake 的支持打开官方仓库的 CMakeLists.txt 就能自动生成工程。但如果你想长期维护一个自定义项目或者对 CMake 不熟悉那就老老实实手动建空项目。这篇博文主讲的也是第二种方式因为它的每一步都对应着明确的原理你只要搞懂一次之后不管换什么版本都能自己配。2.2 LVGL 8.3 还是 LVGL 9.x这是最容易踩坑的地方没有之一。LVGL 8.3 还是当前绝大多数教程和项目中在用的版本目录结构稳定第三方教程齐全官方提供了 lv_drivers 独立仓库来适配 SDL、X11 等平台驱动。LVGL 9.x 则是新一代架构变化非常大官方把原来独立的 lv_drivers 整个合并进了 lvgl 主仓库的 src/drivers 目录SDL 驱动从“外部组件”变成了“内置组件”。同时 9.x 对 C 标准的要求更高一般需要 C11 及以上l v_conf.h 的配置项也有不少改动。所以我给新手的建议很直接以 8.3.x 为起点。不是说 9.x 不好而是 8.3 的资料最全、踩坑成本最低。等你在 8.3 上把整个流程跑通再切到 9.x 会轻松很多因为核心思路是相通的无非是路径和函数名变了。2.3 SDL2 在方案里的作用LVGL 本身只是个图形库不带任何窗口系统它不知道“窗口”是什么概念。在 PC 模拟器里需要一个底层库来创建窗口、处理鼠标键盘事件、把像素渲染到屏幕上SDL2 做的就是这件事。可以把 LVGL 和 SDL2 的关系理解成“前端框架和浏览器”LVGL 负责画控件、处理布局和动画SDL2 负责在 Windows 上提供一块画布给你画并把鼠标移动、点击这些事件传给 LVGL。我见过有人图省事想跳过 SDL2结果只能在纯命令行环境里空跑逻辑看不到任何界面意义不大。SDL2 是绕不开的一环好在它对 Windows 的支持非常成熟配置一次后面基本不用动。3. Visual Studio 2022 环境配置完整实操3.1 VS2022 需要勾选的组件如果机器上已经装了 VS2022先确认一下“使用 C 的桌面开发”这个工作负载有没有勾选。没有的话打开 Visual Studio Installer点击“修改”勾上这个工作负载。这里特别提醒一点组件里有个“适用于最新 v143 生成工具的 C CMake 工具”如果你打算以后用 CMake 方案跑 LVGL一定要勾选。另外 Windows SDK 也要装否则后面编译会报“无法打开 SDKDDKVer.h”。我见过有人在编译 LVGL 时报这个头文件缺失排查半天才发现是最开始装 VS 时把 Windows SDK 组件省了。安装完成后打开 VS2022新建项目时选择“空项目”或者“控制台应用”都可以。我个人习惯用“空项目”因为它不会自动生成一堆模板代码也不会引入额外的预编译头设置。3.2 源码下载与目录组织这一步看似简单但版本对应关系一定要搞对。LVGL 官方 GitHub 仓库的 master 分支现在已经是 9.x 了如果你直接拉最新代码再配 8.3 的教程大概率对不上。我的做法是在项目根目录下分层存放避免所有文件堆在一起my_lvgl_sim/ ├── lvgl/ # LVGL 源码checkout 到 release/v8.3 ├── lv_drivers/ # 8.3 配套驱动checkout 到 v8.3 ├── lv_conf.h # LVGL 配置文件由模板改来 ├── main.c # 模拟器入口 └── SDL2/ # SDL2 开发库下载方式就直接用 git 拉取git clone --branch release/v8.3 https://github.com/lvgl/lvgl.git git clone --branch v8.3 https://github.com/lvgl/lv_drivers.git如果你要用 9.x那只需要克隆 lvgl 一个仓库因为驱动已经内置了不需要单独的 lv_drivers。这里再次强调拉代码时别只看“最新”一定要指定版本分支否则后面会遇到各种莫名其妙的接口差异。3.3 创建空项目并加入 LVGL 源码VS 里新建一个空项目后你需要把 lvgl 和 lv_drivers 的源码文件加进工程。这里有个很多人都会犯错的地方在解决方案资源管理器里右键源文件 - 添加 - 现有项然后手动去文件夹里一个个选中所有 .c 文件。LVGL 源码文件非常多这样操作不仅费时还容易漏掉关键源文件导致链接时出现“找不到函数实现”的诡异错误。正确做法是在解决方案资源管理器里选中项目右键 - 添加 - 现有筛选器然后直接进入 lvgl/src 目录全选所有 .c 文件。对于 lv_drivers同样把里面的 display、indev、win32drv 等目录下的 .c 文件加进来。头文件不用一个一个加只要在项目属性里把头文件所在的目录配进“附加包含目录”编译器就能自动找到它们。这一步是整个模拟器配置的核心目录配错一个后面全崩。3.4 lv_conf.h 的创建与关键宏LVGL 把所有功能开关都放在 lv_conf.h 里。源码包根目录下有一个 lv_conf_template.h我们要把它复制一份重命名为 lv_conf.h放到项目根目录也就是我上面目录结构里那个位置。复制完先别急着用打开它看几个关键宏#define LV_COLOR_DEPTH 32 #define LV_MEM_CUSTOM 0 #define LV_TICK_CUSTOM 1这三个宏的含义分别是颜色深度、内存分配方式、系统时钟来源。模拟器环境下 LV_MEM_CUSTOM 建议保持 0让 LVGL 用自己的内存管理LV_TICK_CUSTOM 建议设成 1这样 LVGL 会从 SDL 获取时间来驱动动画否则动画可能不跑或者跑得非常快、不受控。另外如果你要跑官方 demo需要在文件里把对应的 demo 开关打开比如#define LV_USE_DEMO_WIDGETS 1这个宏控制着官方控件集合的 demo。打开它之后在 main 里调用 lv_demo_widgets() 就能看到一堆控件展示在窗口里。注意一个细节lv_conf.h 的名字和位置必须是 LVGL 能找得到的。如果你在编译时遇到“lv_conf.h 不存在”的报错大概率是两种情况一是 lv_conf.h 没放到附加包含目录覆盖到的路径里二是 LVGL 认为你使用了“简单包含”模式但实际没有配 LV_CONF_INCLUDE_SIMPLE 宏。我的建议是最简单的方式把 lv_conf.h 放在项目根目录并且保证项目根目录本身在附加包含目录里不要去折腾 LV_CONF_PATH。3.5 接入 SDL2 库SDL2 的获取方式有几种官网下载预编译包、用 vcpkg 安装、用 CMake 的 FetchContent 自动拉取。我用的最多的是官网下载预编译的开发库它直接提供 include 和 lib 目录对 VS2022 来说最直观。去 SDL 官网的下载页面找 “SDL2-devel-x.x.x-VC.zip”解压后放到项目目录下的 SDL2 文件夹里。然后在 VS 项目属性中做三件事配置项路径VC 目录 - 包含目录SDL2/includeVC 目录 - 库目录SDL2/lib/x64链接器 - 输入 - 附加依赖项SDL2.lib; SDL2main.lib;这里有个前提如果你的系统是 64 位现在基本都是并且 VS 里选择的解决方案平台是 x64那就用 lib/x64 目录。如果选择的是 Win32则用 lib/x86。我建议直接在工具栏把方案配置切换成 x64因为 LVGL 的模拟器尺寸通常都比较大32 位编译也没必要。最后把 SDL2.dll 复制到生成目录比如 x64/Debug下或者直接复制到项目的运行目录中。否则程序能编译、链接成功但一运行就提示找不到 SDL2.dll。3.6 编写主程序入口到这里整个工程的核心依赖都配置完了我们来写一个最简单的入口 main.c。如果你用的是 LVGL 8.3参考这段代码#include stdio.h #include lvgl/lvgl.h #include lv_drivers/display/monitor.h #include lv_drivers/indev/mouse.h #include lv_demos/lv_demo.h int main(void) { lv_init(); monitor_init(); mouse_init(); /* 打开官方控件 demo用于验证环境 */ lv_demo_widgets(); while (1) { lv_timer_handler(); SDL_Delay(5); } return 0; }这段代码的逻辑很直白lv_init() 初始化 LVGLmonitor_init() 创建一个 SDL 窗口作为显示器mouse_init() 把鼠标模拟成触摸输入接着 lv_demo_widgets() 加载 demo 界面最后进入一个死循环不断调用 lv_timer_handler()让 LVGL 处理界面刷新和事件。SDL_Delay(5) 是控制循环频率的也可以理解成模拟器的 5ms 心跳。如果你坚持用 9.x入口长这样#include stdio.h #include lvgl/lvgl.h #include lvgl/src/drivers/sdl/lv_sdl_window.h #include lvgl/src/drivers/sdl/lv_sdl_mouse.h #include lvgl/demos/lv_demos.h int main(void) { lv_init(); lv_sdl_window_create(800, 480); lv_sdl_mouse_create(); lv_demo_widgets(); while (1) { lv_timer_handler(); SDL_Delay(5); } return 0; }9.x 里窗口和鼠标驱动都是 LVGL 内置的不用再单独初始化 lv_drivers函数名也从 monitor_init / mouse_init 变成了 lv_sdl_window_create / lv_sdl_mouse_create。这也是 9.x 最大的便利之一。3.7 项目属性里的三个决定性设置源码配好了、SDL2 也链上了但如果你直接编译大概率还是会爆一堆错。这里有三个项目属性必须提前确认缺一个都会让你在错误列表里迷路。第一个是“C 语言标准”。在项目属性 - C/C - 语言 - C 语言标准里选择“C11”或者更高。LVGL 8.3 虽然要求不算苛刻但 C11 最保险LVGL 9.x 更是明确规定需要 C11。如果你发现代码报了一堆“unknown type name bool”或者“true undefined”这类错误不用怀疑十有八九是语言标准没设对。第二个是“预编译头”。在 C/C - 预编译头 - 预编译头里选择“不使用预编译头”。LVGL 源码文件很多而且它们是纯 C 项目VS 默认的空项目模板有时候会自动带预编译头设置导致每个文件都报 C1010 错误非常烦人。第三个是“运行库”。在 C/C - 代码生成 - 运行库里Debug 模式选“多线程调试 DLL(/MDd)”Release 模式选“多线程 DLL(/MD)”。这个必须和 SDL2 预编译库的运行时保持一致否则链接时会报 LNK2038 之类的运行时库不匹配错误。4. 编译链接阶段的高频报错与解决办法4.1 平台工具集报错v100 还是 v143这个坑非常无语但确实有人会遇到。你在网上找到一些老教程配套的工程文件用 VS2022 打开时可能弹出一个提示无法找到 Visual Studio 2010 的生成工具平台工具集 “v100”。这是老项目用旧版本工具集保存导致的不是你的环境坏了。解决办法很统一选中项目右键 - 属性 - 常规 - 平台工具集把值从 v100 改成 Visual Studio 2022 (v143)然后重新编译。如果没有该选项检查一下 VS2022 的 C 桌面开发组件是否安装完整。4.2 error C4996、C2065 这一类 C 标准/宏问题C4996 通常是 SDL2 新版本里的某些函数被标记为 deprecated例如 SDL_GetTicks 在某些新库中用 SDL_GetTicks64 替代。同时 LVGL 自带代码中可能用到 sprintf、strcpy 之类的旧函数VS 的 C 运行时库会因为它们不安全而报 C4996。最简单的处理方式是在项目属性 - C/C - 命令行 - 其他选项里加上/utf-8 -D_CRT_SECURE_NO_WARNINGS/utf-8 是为了让源码里的中文注释不乱码_CRT_SECURE_NO_WARNINGS 则是关掉那些安全函数告警。这两个参数几乎是 LVGL 模拟器必备早点加上省心。C2065 常见是小写“undefined identifier”这类问题大概率是 C 标准没设到 C11回头去 3.7 节把语言标准改掉就好。4.3 fatal error C1010预编译头的干扰“fatal error C1010: 在查找预编译头时遇到意外的文件结尾”这句话新手看到基本心态就崩了。它并不是说你代码写错了而是工程自动设置了“创建/使用预编译头”但你给每个 .c 文件又没写 #include pch.h编译器当然会认为文件结构不对。处理方式就一条C/C - 预编译头 - 预编译头 选择“不使用预编译头”。这个选项对 LVGL 源码的任何编译单元都适用因为 LVGL 是纯 C 库不依赖预编译头来提速。4.4 LNK2019、LNK1120SDL 入口和链接库问题链接时报 LNK2019 无法解析的外部符号是模拟器配置中最常见的错误没有之一。表现形式类似LNK2019 unresolved external symbol WinMain referenced in function int __cdecl invoke_main(void)这个错误的根源往往是“子系统”和“入口点”对不上。VS 的链接器会根据子系统选择入口如果子系统是 Windows就找 WinMain如果子系统是控制台就找 main。SDL2 自带的 SDL2main.lib 已经实现了一个中间层能把标准 main 包装成 Windows 应用入口。但你如果没链接 SDL2main.lib同时又把子系统设成了 Windows就会报 WinMain 找不到。解决办法是到项目属性 - 链接器 - 系统 - 子系统里选择“控制台 (/SUBSYSTEM:CONSOLE)”。或者反过来保留 Windows 子系统但确保链接了 SDL2main.lib。我一般直接用控制台子系统这样跑程序时还能看到 printf 打印的调试信息。如果报错指向某个 LVGL 内部函数比如LNK2019 unresolved external symbol lv_demo_widgets referenced in function main那就是源码文件没有完整加入工程。去 3.3 节检查一下 lvgl/demos 目录下的源文件是否加进来了或者 lv_conf.h 里的 LV_USE_DEMO_WIDGETS 没置 1。4.5 LNK2038、C1189运行库和宏冲突LNK2038 的完整信息一般是“RuntimeLibrary mismatch”意思是项目里有的源文件用的是 /MT 静态运行时有的用的是 /MD 动态运行时。SDL2 预编译库默认是按 /MD 和 /MDd 编译的所以你必须把整个工程的运行库统一成“多线程 DLL”模式否则它和 SDL2 的库对接不上。另外如果你的工程里同时拷贝了一份 Lapack 或者 OpenCV 之类的库运行库冲突的情况会更频发因为第三方库的编译方式五花八门。稳妥起见只保留 LVGL 和 SDL2 两个依赖保证项目清爽。C1189 大多是 LVGL 的运行机制检查报错比如“LV_COLOR_DEPTH must be 8, 16 or 32”这类。这说明你改 lv_conf.h 时改了不合法值回去重新检查。这种错误通常比较直白不会太难处理。5. 运行验证与常见“黑屏/没反应”排障5.1 先跑官方 demo 验证环境编译链接全部通过后先别急着写自己的界面直接跑 lv_demo_widgets()。如果能看到一个窗口里面出现按钮、滑块、仪表盘、图表等控件并且鼠标滑过有高亮反馈说明整个模拟器环境已经通了。这时你后面写任何 UI 代码都不需要再去怀疑环境本身。如果你的工程是 8.3 但看不到 demo大概率是 LV_USE_DEMO_WIDGETS 没打开或者 demos 源码没加进工程。可以先用一个最简单的界面验证一下lv_obj_t *scr lv_scr_act(); lv_obj_t *label lv_label_create(scr); lv_label_set_text(label, LVGL Simulator OK); lv_obj_center(label);在 main 里把 lv_demo_widgets() 换成这段编译运行如果窗口中央出现一行文字说明核心环境没问题剩下的事就是慢慢玩控件。5.2 鼠标模拟触摸与高 DPI 适配LVGL 本身是为触摸屏设计的模拟器里用鼠标来模拟触摸点。在 8.3 的 lv_drivers 中mouse_init() 之后LVGL 会把鼠标坐标直接映射到触摸点。这里有个非常容易踩的坑Windows 显示缩放率不是 100% 时鼠标坐标和 SDL 窗口像素坐标会不一致导致点击错位。比如你屏幕缩放是 150%在 LVGL 界面上点击一个按钮实际触发的位置可能偏移到按钮上方或下方非常难受。解决方法是在 SDL 初始化之前禁用高 DPI 缩放SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, 1);这行代码要放在 SDL_Init 之前调用确保窗口以物理像素为单位创建鼠标坐标和渲染坐标保持一致。如果你用的是 9.x 的内置 SDL 驱动也可以在主程序开头先调用这个函数然后再 lv_init()。5.3 刷新率、窗口大小与性能优化模拟器窗口默认分辨率在 lv_drivers 的 monitor.c 里定义8.3 版本默认是 800x480。你可以直接在源文件里改成 1024x600 或者 1920x1080也可以定义编译宏来覆盖。LVGL 布局是自适应的改了分辨率之后界面元素会重新排列这也是模拟器调试布局特别好用的地方。刷新的节奏由 lv_timer_handler() 的调用频率决定。我在上面的示例中用了 SDL_Delay(5)也就是大约每 5 毫秒调一次理论上能达到 200FPS 的循环速度。但实际 LVGL 的刷新周期还受 lv_conf.h 里 LV_DISP_DEF_REFR_PERIOD 控制默认一般是 30 毫秒左右也就是说即便循环调得快LVGL 内部也会自己合并刷新操作。这个参数你可以根据动画流畅度调数值越大动画越迟钝越小越耗 CPU。如果跑 demo 时发现动画卡顿优先检查是否 Debug 模式Debug 下代码优化少渲染慢很正常改成 Release 模式会好很多。另外窗口尺寸别开得太大8.3 的默认 800x480 在模拟器阶段完全够用刻意追求 4K 窗口没有任何实际意义。5.4 中文显示、字体与后续扩展方向demo 跑通之后你大概率会想写几个汉字看看效果。结果一编译运行发现中文全变成了小方块。这不是模拟器坏了而是 LVGL 自带字体只有 ASCII 字符没有中文字形。解决办法是使用 LVGL 官方字体转换工具把 TTF 字体文件转成一个 C 数组并限制提取你需要的字符范围。把生成的 .c 文件加入工程然后在代码里使用 lv_style_set_text_font 或者 lv_obj_set_style_text_font 切换字体。中文字体文件体积很大模拟器调试阶段建议只提取常用汉字几百个字符足够测试显示效果。整个模拟器环境稳定之后后续的扩展方向就有很多了把界面逻辑直接移植到 STM32 和 FreeRTOS 上只需要替换显示和输入驱动把 LVGL 运行在 Windows 窗口里再叠加 C 业务代码做 MVP 产品原型或者把 NeHe 风格的 OpenGL 渲染融合进来做更复杂的图形验证。这些都是在模拟器成熟之后顺理成章的事。最后再分享一个个人经验模拟器跑通后别直接埋头在 PC 上开发两周然后一次性搬到板子上。最好的节奏是在模拟器上把控件布局和交互逻辑调到一个阶段立刻移植到真实硬件上验证一下触摸偏移、颜色深度、刷新率这些模拟器无法覆盖的硬件差异。越早发现驱动层的问题后面返工的成本就越低。第一次配环境确实要花点时间但配置一次之后每次做界面都相当于按下了快进键。