Files
STM32F4-Base/simulator/README.md
T
wandering1 a7f526569a perf(mem): R-9 落地——LVGL 池 40→16 KB,SRAM1 74.9%→56.1%
取值依据是实测水位,不是预估:菜单根框 5,368 B、开页-关页往返
5,448 B、最重页面峰值 6,568 B(占 16 KB 的 40%)、真机开机
used=3,192 B。注意 16,384 B 建池后 lv_mem_monitor 报的 total 是
15,020 B(TLSF 控制块与对齐元数据 ≈1,364 B),核算余量用后者。

两端 lv_conf.h 同步改值(lv-conf-sync 生效值比对 163 项 PASS),
Lib/lv_conf.h 的 UTF-8 BOM 已按原样补回(Edit 会静默剥掉)。

验证:
- @build 0 错 0 警;ZI 154,456 → 129,880,精确 −24,576 B
- SRAM1 98,144/74.9% → 73,568/56.1%(余 57,504 B);CCM 不变 87.9%
- 模拟器菜单回归 PASS(8 状态像素 + 池基线 5,368 B 未变);11 页
  黄金参考逐字节 10/11 一致,往返池水位 5,448 B 分毫未变
- 唯一差异 netcfg_save 保存态:用 40 KB 旧池做 A/B 复测同样失败,
  确认与本改动无关(属 506eccc 的 cfg_file 行为变化),未刷黄金
  参考掩盖,留给该改动属主处置
- @test PASS:fw build Oct 15 2026 07:31:34,无 [ERR]/Fault,
  开机 622 ms 到 net init: OK,menu build done: used=3192
- 本地 CI 全绿(L0 + L0.5,15 runner / 955 断言)
- 台账受管区与内存快照区已 --write 回填,mem-budget / mem-numbers
  由红转绿

连带结论:R-2 后半待迁体量 54,146 → 29,570 B,瓶颈由 SRAM1 换到
CCM(只剩 7,904 B);解锁链第 ① 步完成,下一步只差缩堆 56→32 KB
(B5 是反向扩容,缩回前须重跑 stack_report.py + 上板 min_ever)。

文档同步 20 处:本报告按修复即销项删除 R-9 行,并改写 §0.2/§0.3/
§0.4/§5.1/§5.2/§5.3/§6.4 与变更记录、去处表;RAM使用分析 §2.6.2
新增实测水位表、阶段 1.1 标已执行;台账 §3 解锁链、登记簿 N-27、
任务规划 README W3 行与链路图;UI 四篇、产品需求、内存内容分配
策略、运行与自检记录、项目部署环境要求、测试开发标准流程、
simulator/README、ui-verify skill 常量。顺带订正三处与脚本不符的
旧值:池水位常量 4784/4864 → 5368/5448、FreeRTOS 堆 48 → 56 KB。
2026-10-15 09:56:15 +08:00

286 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LVGL PC 模拟器(simulator/)
> 在 PC 上编译运行与 MCU **同一份** UI 源码(`Drivers/BSP/UI/ui_menu.c` + `ui_font_cn_12.c`),
> 实时预览 160×160 1bpp 单色菜单界面,**无需烧录 MCU**。
>
> 部署细节与完整 FAQ 见 `docs/UI设计/LVGL模拟器部署与使用说明.md`;本文聚焦**本目录的内容与架构**。
---
## 一、目录内容
```
simulator/
├── CMakeLists.txt # 构建脚本(CMake + MSVC,x64)
├── build_sim.bat / .ps1 # 一键构建入口(自动探测 VS2019/2022/2026 生成器,x64;.bat 绕过执行策略)
├── main.c # 入口:SDL 初始化 → LVGL → 菜单 → 主循环
├── sim/ # 平台层(PC 专用实现;MCU 端对应实现在各 BSP 目录)
│ ├── sim_port.c / .h # 显示/输入端口(SDL 窗口 + 键盘 + mono 主题),替代 MCU 端 ui_port.c
│ ├── net_socket_sim.c # 网络后端:Winsock 实现 net_* BSD API,替代 MCU 端 net_socket.c(CH395F)
│ ├── ui_net_sink_backend_sim.c # RemoDispBus 平台 backend:取帧/注键(配合共享 ui_net_sink.c)
│ └── cfg_file_store_sim.c # 配置文件存储后端:本地 sim_config.ini(旧档 sim_netcfg.bin 已废弃,不读不删)
├── lv_conf.h # 模拟器专用 LVGL 配置(与 MCU 端 Lib/lv_conf.h 相互独立)
├── i1_to_png.py # 截图转 PNG:把 --screenshot 的 I1(1bpp) 原始位图转成 PNG(见 §3.5)
├── tests/ # 无头回归脚本 + 本地黄金参考(见 §3.6,流程文档 docs/UI设计/UI_自动化测试流程.md)
│ ├── check_menu_rows.py # 菜单 8 状态像素回归 + 池基线门禁
│ ├── check_content_pages.py # 文本断言 + 内容页黄金参考回归 + 往返池门禁(--update 刷参考)
│ ├── check_remo_sink.py # RemoDispBus 远端推流协议回归(起窗口模拟器 + TCP 取帧 + 参考比对)
│ ├── regen_ref_png.py # 由 ref/*.bin 批量重生成目检 PNG(--scale 6 --invert)
│ └── ref/ # 黄金参考 shot_*.bin + 目检 *.png(**本地生成物,不入库**)
├── dbg_log.h # 调试日志 stub:DBG_* 映射到 printf(覆盖 MCU 版头文件)
├── SDL2/
│ └── SDL2-2.32.10/ # SDL2 预编译 SDK(随仓库提供,无需安装)
│ ├── include/ # SDL2 头文件
│ │ └── SDL2/ # ★ 头文件副本(LVGL SDL 驱动写死 #include "SDL2/SDL.h")
│ └── lib/
│ ├── x64/ # SDL2.lib / SDL2main.lib / SDL2.dll(构建使用)
│ └── x86/ # 32 位库(当前未使用)
└── build/ # CMake 生成物(可整体删除重建,勿提交)
└── Debug/lvgl_sim.exe # 可执行文件(SDL2.dll 由 POST_BUILD 自动复制到同目录)
```
### 文件职责
| 文件 | 说明 |
|------|------|
| `main.c` | 程序入口。`SDL_Init(TIMER\|EVENTS)` → `lv_init()` → `sim_port_init(160,160)` → `ui_menu_init()`(共享代码)→ 主循环(SDL 事件 + `lv_timer_handler` + `lv_refr_now`) |
| `sim/sim_port.c` | 创建 SDL 窗口显示设备(`lv_sdl_window_create`)、初始化 **mono 主题**(`lv_theme_mono_init`,1bpp 必需)、创建键盘输入设备并挂默认输入组。**不创建鼠标**(纯键盘导航,与 MCU 端 keypad 一致)。`sim_port_get_screen_mono()` 经 `SDL_RenderReadPixels` 读回屏幕像素转 1bpp,供推流取帧 |
| `sim/net_socket_sim.c` | 网络后端:Winsock 实现 `net_*` BSD Socket API(`socket`/`bind`/`listen`/`accept`/`recv`/`send`/`close`),替代 MCU 端 `Drivers/BSP/NET/net_socket.c`(CH395F) |
| `sim/ui_net_sink_backend_sim.c` | RemoDispBus 平台 backend:`ui_net_sink_get_lcdmem`→`sim_port_get_screen_mono`、`ui_net_sink_inject_key`→`lv_group_send_data`;配合**共享** `Drivers/BSP/UI/ui_net_sink.c`(协议服务端,双端同一份,TCP 7003) |
| `sim/cfg_file_store_sim.c` | 通用配置文件存储后端:本地文件 `sim_config.ini`(`fopen`/`rename` 原子写),替代 MCU 端 `App/cfg/cfg_file_store_mcu.c`(FatFS);旧档 `sim_netcfg.bin` **已废弃**——不读、不删、不迁移(2026-10-10) |
| `lv_conf.h` | 模拟器专用 LVGL 配置。与 MCU 端 `Lib/lv_conf.h` 的关键差异见 §3.3 |
| `dbg_log.h` | MCU 版 `dbg_log.h` 依赖 `sys_clock.h`(RTC 时间戳),PC 无法编译。此 stub 把 `DBG_INFO/ERROR/DEBUG` 映射到 `printf`;因 `simulator/` 在 include 路径最前,**优先生效**覆盖 MCU 版 |
| `build_sim.bat` / `build_sim.ps1` | 一键构建入口:vswhere 探测本机 VS 大版本(16=2019 / 17=2022 / 18=2026,取已装最高)→ 映射 CMake 生成器;`build/` 缓存生成器与本机不符(换机/升级 VS)时自动删除重建(带重试,抗文件锁)。**构建入口一律走它,文档/命令中勿写死生成器版本**;`.bat` 仅负责绕过 PowerShell 执行策略并转交 `.ps1` |
| `CMakeLists.txt` | 收集 LVGL 源码(`../Lib/lvgl`)+ 共享 UI 源码(`../Drivers/BSP/UI`)+ 通用配置文件引擎(`../App/cfg_file.c`、`../Drivers/BSP/NETCFG` 的 net_cfg;`net_cfg_legacy` 已于 2026-10-10 删除)+ `sim/` 平台层,链接预编译 SDL2,产出 `lvgl_sim.exe` |
| `i1_to_png.py` | 把 `--screenshot` 产出的 I1(1bpp) 原始位图转成 PNG,便于离线比对设计稿。详见 §3.5 |
| `tests/` | 无头回归脚本(菜单 8 状态像素回归、内容页文本断言 + 黄金参考回归);`ref/` 为**本地生成物、不入库**(首次 `--update` 生成)。详见 §3.6 |
> **共享代码不复制**:`ui_menu.c` / `ui_font_cn_12.c` / `ui_net_sink.c` 直接引用 `Drivers/BSP/UI/` 下的原件,
> MCU 与 PC 两端永远同步。**不要**在本目录内复制 UI 源码。
---
## 二、架构框架
### 2.1 运行架构
```
main.c
│
├─ SDL_Init(TIMER | EVENTS) # 仅初始化计时/事件,视频由 LVGL SDL 驱动内部接管
├─ lv_init() # LVGL v9.3.0 内核
├─ sim_port_init(160, 160)
│ ├─ lv_group_create + set_default # 默认输入组
│ ├─ lv_sdl_window_create(160,160) # SDL 窗口 = 显示设备(I1 单色)
│ ├─ lv_theme_mono_init(disp, true, # mono 主题(白字黑底),1bpp 下不初始化 = 黑屏
│ │ LV_FONT_DEFAULT)
│ └─ lv_sdl_keyboard_create # 键盘 → 默认输入组(方向键/Enter/Esc)
├─ ui_menu_init() # ★ 与 MCU 共用(Drivers/BSP/UI/ui_menu.c)
│ # 基元控件页面导航,节点表驱动
└─ 主循环
├─ SDL_PollEvent # SDL_QUIT → 退出
├─ lv_timer_handler() # 驱动 LVGL 定时器/重绘
├─ lv_refr_now(NULL) # 强制整屏刷新(PARTIAL 模式下保证首帧输出)
└─ Sleep(lv_timer_handler 返回值) # 自适应休眠
```
### 2.2 渲染链路(1bpp 单色)
```
LVGL 场景(基元控件页面/label,LV_COLOR_FORMAT_I1)
→ SW 渲染器(仅启用 I1,LV_DRAW_SW_SUPPORT_I1=1)
→ SDL 窗口 blit(LV_SDL_RENDER_MODE_PARTIAL,1bpp 强制要求)
→ 屏幕(160×160 物理像素,Windows 高 DPI 可能放大显示,属正常)
```
对照 MCU 端链路(`ui_port.c`):
```
LVGL 场景(同一份 ui_menu.c)
→ SW 渲染器(I1)
→ flush 回调 memcpy 到内存帧缓冲 g_ui_fbuf(3200 B)
→ RemoDispBus 协议(TCP 7003)按需推给远端客户端显示
```
两端**场景层完全相同**,只有"渲染结果去向"不同:模拟器进 SDL 窗口,MCU 进内存帧缓冲再走网络。
### 2.3 MCU 端 vs 模拟器 对照
| 项 | MCU(Keil MDK) | 模拟器(CMake + MSVC) |
|----|----------------|------------------------|
| LVGL 编译 | `.uvprojx` 工程添加 `Lib/lvgl` 源文件 | `Lib/lvgl/CMakeLists.txt`(最小 CMake,**仅模拟器用**,不影响 Keil 编译) |
| lv_conf.h | `Lib/lv_conf.h` | `simulator/lv_conf.h`(独立,互不影响) |
| 显示端口 | `ui_port.c`(离屏 framebuffer + flush 回调) | `sim_port.c`(SDL 窗口) |
| 输入 | 远端 keypad(`ui_port_queue_key`,网络回传键码) | SDL 键盘(`lv_sdl_keyboard_create`) |
| 主题 | 无主题(`LV_USE_THEME_* = 0`),控件用内置基础样式 | **mono 主题必须开启**(否则 1bpp 黑底黑字) |
| 日志 | `Drivers/BSP/dbg_log.h`(USART1 + RTC 时间戳) | `simulator/dbg_log.h`(printf stub) |
| 业务任务 | netTask / ftpTask / timeSyncTask 等 | 无(仅编译纯软件部分的配置文件引擎 net_cfg/cfg_file,可离线验证配置加载/保存) |
| 时钟 | `lv_tick_inc` 由 ui_task 手动喂(10ms 周期) | SDL 驱动内部 `lv_tick_set_cb(SDL_GetTicks)`,**不可再手动喂** |
### 2.4 关键设计决策
1. **SDL2**:LVGL 官方 PC 端口同款显示方案;使用仓库内预编译 SDK(`SDL2/SDL2-2.32.10/`),无需系统安装
2. **CMake + MSVC**:`build_sim.ps1` 按本机 VS 版本自动选生成器(16=2019 / 17=2022 / 18=2026;不同机器版本不同,**勿写死**),`-A x64`;MSVC 下加 `/utf-8` 消除中文注释的 C4819 警告
3. **共享 UI 源码**:`ui_menu.c` / `ui_font_cn_12.c` 直接编译 `Drivers/BSP/UI/` 原件,改一处两端同时生效
4. **纯键盘导航**:不创建鼠标/触摸设备,与 MCU 端 keypad 操作语义一致(↑↓←→ / Enter / Esc)
5. **UI 为主**:不含 CH395F 网络 / NAND 存储 / ADC 等硬件业务;通用配置文件引擎(`cfg_file`)与 `net_cfg` 核心为纯软件,配置加载/保存可在模拟器离线验证(`sim_config.ini`;旧档 `sim_netcfg.bin` 已废弃、不参与)
---
## 三、构建与运行
### 3.1 环境要求
| 依赖 | 版本 | 说明 |
|------|------|------|
| CMake | ≥ 3.15 | 本机验证 4.3.0-rc1 |
| MSVC | VS2019 / 2022 / 2026 任一 | `build_sim.ps1` 自动探测生成器(16=2019 / 17=2022 / 18=2026,取已装最高),Hostx64/x64 |
| SDL2 | 2.32.10 | 已内置于 `SDL2/`,无需安装 |
| 操作系统 | Windows | 已验证 win32 |
### 3.2 构建命令
```powershell
# 一键构建(仓库根目录执行;自动探测本机 VS2019/2022/2026 生成器,
# build/ 缓存生成器与本机不符时自动删除重建)
& "simulator\build_sim.bat"
# 运行
.\simulator\build\Debug\lvgl_sim.exe
# 或:cmake --build simulator\build --config Debug --target run
# 全量重建
& "simulator\build_sim.bat" -Clean
```
> 改 `CMakeLists.txt` 后再跑一次 `build_sim.bat` 即可(缓存不匹配会自动重建);遇到奇怪问题加 `-Clean` 全量重建。
> 手动 CMake 等效:`cd simulator; cmake -B build -G "Visual Studio <NN> <YYYY>" -A x64; cmake --build build --config Debug`,生成器号须与本机 VS 大版本一致(16=2019 / 17=2022 / 18=2026)。
### 3.3 `lv_conf.h` 与 MCU 端的关键差异
| 配置 | MCU 端(`Lib/lv_conf.h`) | 模拟器(本目录) | 原因 |
|------|--------------------------|------------------|------|
| `LV_USE_SDL` | 0 | **1** | SDL 显示/键盘驱动 |
| `LV_SDL_RENDER_MODE` | — | **`PARTIAL`** | 1bpp + SDL 非 PARTIAL 会 `#error` |
| `LV_SDL_INCLUDE_PATH` | — | `"SDL2/SDL.h"` | 对应 `include/SDL2/` 头文件副本 |
| `LV_USE_THEME_MONO` | 0 | **1** | 1bpp 无主题 = 黑底黑字不可见 |
| `LV_DRAW_SW_SUPPORT_I1` | 1 | 1 | 仅启用 I1 渲染,裁掉其余色深 |
| `LV_MEM_SIZE` | 16 KB | 16 KB | **必须一致**(`lv-conf-sync` 比对生效值;2026-10-15 由 40 KB 同步收缩,PC 侧不再"放宽") |
| `LV_FONT_DEFAULT` | `&ui_font_cn_12` | 同 | 共用同一字体 |
| 其余控件/驱动 | 裁剪 | 基本一致(另开 `LV_USE_TEXTAREA/KEYBOARD/BUTTONMATRIX`) | 模拟器略宽松,便于调试 |
### 3.4 键盘操作
| 按键 | 功能 |
|------|------|
| ↑ / ↓ | 菜单项上下移动 |
| → | 进入子页 |
| Enter | 确认 / 进入子页 |
| ← / Escape | 返回上级 |
### 3.5 截图比对设计稿(离线出图)
窗口模式只适合人工目检。要把渲染结果存成 PNG 与设计参考(`docs/UI设计/mockups/` 下的原始参考图)做目视比对,用离屏截图 + 转 PNG:
```powershell
cd simulator
# 1) 离屏渲染首帧,输出 I1(1bpp) 原始位图(已剥离 8 字节调色板,纯像素数据)
.\build\Debug\lvgl_sim.exe --screenshot shot.bin
# 2) 转 PNG(默认 160x160;--scale 放大便于查看;python 统一用仓库 .venv,见 AGENTS.md「Python 环境」)
..\.venv\Scripts\python.exe i1_to_png.py shot.bin # -> shot.png
..\.venv\Scripts\python.exe i1_to_png.py shot.bin -o 预览.png --scale 6
```
**I1 位图格式**(`i1_to_png.py` 据此解析):1 位/像素,每字节 MSB 在左(bit7 = 该行最左像素),`bit=1` 前景 / `bit=0` 背景,每行 `宽/8` 字节、行内无填充。
`i1_to_png.py` 常用参数:
| 参数 | 说明 |
|------|------|
| `-o, --output` | 输出 PNG 路径(默认与输入同名 `.png`) |
| `-W, -H` | 宽高(默认 160/160,非默认分辨率时必填) |
| `--scale N` | 最近邻放大 N 倍,便于查看小图 |
| `--invert` | 前/背景互换 |
| `--fg / --bg` | 自定义前/背景色 `R,G,B`(默认黑白) |
| `--no-auto-palette` | 关闭调色板自动识别(按纯像素解析) |
> 依赖 Pillow(已含于仓库 `.venv`,见 `requirements.txt`)。`shot.bin`/`shot*.png` 均已列入 `.gitignore`,可随时删除重生成。
### 3.6 无头回归测试(tests/)
UI 改动的自动化门禁脚本(完整流程见 `docs/UI设计/UI_自动化测试流程.md`)。除 `check_remo_sink.py`(需 Pillow,`.venv` 已含)外均为纯标准库、任意 python3 均可;为环境统一,一律用仓库 `.venv` 运行:
```powershell
# 仓库根目录执行(前置:本目录已构建出 build/Debug/lvgl_sim.exe)
.venv\Scripts\python.exe simulator/tests/check_menu_rows.py # 菜单 8 状态像素回归 + 池基线门禁
.venv\Scripts\python.exe simulator/tests/check_content_pages.py # 文本断言 + 内容页黄金参考回归 + 往返池门禁
.venv\Scripts\python.exe simulator/tests/check_remo_sink.py # RemoDispBus 远端推流协议回归(起窗口模拟器 + TCP 取帧 + 参考比对)
```
| 脚本 | 原理 | 失败输出 |
|------|------|----------|
| `check_menu_rows.py` | 键序注入(u/d/e/s)跑 8 个菜单状态,断言选中行白带、非选行黑底白字、框体边框、往返状态逐像素一致;S0 池水位须等于脚本内 `MENU_POOL_BASELINE` | 未过断言清单 + 保留临时截图 |
| `check_content_pages.py` | ① 文本断言(解析字体字形,在截图内精确匹配期望串,不依赖 `ref/`)② 11 内容页无头截图与 `tests/ref/shot_*.bin` **逐字节**比对;另校验开页-关页往返池水位 = `POOL_AFTER_ROUNDTRIP` | 文本未命中项 / 差异像素数 + 双方 ink runs 对照 |
| `check_remo_sink.py` | 启动**窗口**模拟器(非无头,`CREATE_NEW_PROCESS_GROUP` 防管道阻塞),经 TCP 7003 发 LCDMEM 取 1bpp 帧;断言首帧=LCDMEM(无 INIT 污染)/位图 3200 字节/非平凡;复用 `i1_to_png` 出目检 PNG;与无头参考(`lvgl_sim --screenshot`)逐位比对 <2% 判 MATCH。守护"INIT 污染 / 快照卡死"两个历史缺陷 | 各判据失败原因 + `sim_err.txt` 尾部 + 保留 PNG(默认不删,`--clean` 才删) |
- **黄金参考与目检 PNG**(`ref/` 不入库):首次/换机后先生成一次——
`.venv\Scripts\python.exe simulator/tests/check_content_pages.py --update` 出 `ref/*.bin`,
`.venv\Scripts\python.exe simulator/tests/regen_ref_png.py` 出目检 `ref/*.png`(默认 `--scale 6 --invert`)。
有意改视觉时先肉眼确认新效果正确再 `--update`(会先跑文本断言防误刷)。
- **池水位常量漂移**:菜单/页面结构变更导致门禁失败时,确认新值合理后再更新脚本内常量并登记流程文档变更记录;无法解释的漂移 = 内存回归 bug。
- 退出码均为 0=PASS / 1=断言失败 / 2=环境错误(exe 缺失等)。
### 3.7 配置文件自检(--cfgtest)
配置引擎"加载 → 轮询 → 重载 → 保存"的进程内闭环验证(无需窗口 / LVGL,退出码 0=PASS / 非 0=FAIL),在当前工作目录产生 `sim_config.ini`:
```powershell
.\build\Debug\lvgl_sim.exe --cfgtest
```
验证链:`net_cfg_load`(旧档迁移已于 2026-10-10 删除,只走 INI)→ 第 1 次 poll 建基线 → 伪造"外部编辑"(原子写,等价 FTP 上传/文本编辑器)→ 2 次 poll 沉降(对应真机 4s 沉降)→ 断言 RAM 副本已更新为新值 → 对已存在文件做外科式保存、回读校验注释/值无损。
空目录运行 = 验证出厂模板生成;超限文件(>4096 B)= 判损坏并重建出厂模板;超限文档保存 = 拒绝且不动原文件。窗口模式主循环以与 MCU netTask 相同的 2s 周期调用 `cfg_file_poll()`:外部文件变更沉降后自动重载 `[network]` 段(模拟器无网络栈,变更回调为空操作,仅日志可见)。
---
## 四、已知坑(构建/运行)
1. **`cannot open include file "SDL2/SDL.h"`**
LVGL SDL 驱动写死 `#include "SDL2/SDL.h"`,而原版 SDK 头文件平铺在 `include/` 下。
必须把头文件复制一份到 `include/SDL2/` 子目录(本仓库已做好):
```powershell
$inc = "simulator\SDL2\SDL2-2.32.10\include"
New-Item -ItemType Directory -Path "$inc\SDL2" -Force
Copy-Item "$inc\*.h" "$inc\SDL2\"
```
2. **`cannot open include file "lv_conf.h"`**
`LV_CONF_DIR` / `SDL2_INCLUDE_DIR` 两个 CMake 变量必须在 `add_subdirectory(lvgl)` **之前**定义(见 `CMakeLists.txt` 中的顺序注释),勿随意调整。
3. **运行后窗口全黑**,按序排查:
- `LV_USE_THEME_MONO = 1` 且 `sim_port_init` 中调用了 `lv_theme_mono_init`
- 主循环调用了 `lv_refr_now(NULL)`(PARTIAL 模式保证首帧)
- 没有重复设置 tick(SDL 驱动内部已 `lv_tick_set_cb`,**不要**再 `SDL_AddTimer` + `lv_tick_inc`)
4. **中文显示空白**:字体 `ui_font_cn_12` 是子集字体(当前 122 汉字/符号 + ASCII,共 217 字形),缺字渲染为空白。
补字流程见 `docs/UI设计/UI_中文字体生成与配置.md`(`tools/font_gen/` 两步脚本)。
5. **改了 UI 代码但模拟器没变化**:确认改的是 `Drivers/BSP/UI/ui_menu.c`(共享原件)而非本目录副本(本目录无副本),然后重新 `cmake --build`。
6. **编码**:UI 源码为 UTF-8(带 BOM),MSVC 已加 `/utf-8`。**不要**用 PowerShell `Set-Content` 等重写含中文的文件(编码铁律,见根目录 `AGENTS.md`)。
7. **键盘输入无响应(方向键/Enter 无效)**:`main.c` 主循环**不得**调用 `SDL_PollEvent`。
LVGL SDL 驱动内部有 5ms 定时器(`lv_sdl_window.c` 的 `sdl_event_handler`)统一轮询分发 SDL 事件;
主循环若先轮询会消费掉事件队列,键盘输入永远到不了 LVGL(现象:画面正常但按键全部无效)。
关窗退出由驱动 `LV_SDL_DIRECT_EXIT=1` 自动 `exit(0)`,主循环无需处理 SDL_QUIT。
---
## 五、边界说明
- 模拟器**跑 UI + 配置文件引擎**:不含 CH395F 网络、NAND 存储、ADC、RS-485 等硬件业务;`cfg_file` 引擎与 `net_cfg`(纯软件部分)在模拟器编译运行,可离线验证配置加载/保存
- 模拟器**含** `ui_net_sink`(Winsock 版 RemoDispBus 推流服务端,TCP 7003)——远端看图链路
可在模拟器上验证(`simulator/tests/check_remo_sink.py`);CH395F 真实网络栈仍只在 MCU 上验证
- `build/` 为生成物,可随时删除重建(已在 `.gitignore` 中忽略)
- `Lib/lvgl/CMakeLists.txt` 仅被本模拟器引用,**不影响** Keil 的 MCU 编译
> **模拟器跑过 ≠ 真机跑过**:本目录与真机的**全部已知差异**(网络 pending 机制缺失、代次号恒 0、
> 日志/配置的 mtime 粒度与堆后端差异、IWDG/NAND/RTC 等硬件不可建模)统一登记在
> [`../docs/测试说明/模拟器差距清单.md`](../docs/测试说明/模拟器差距清单.md)——
> 改动触及 `sim/` 任何后端时,**必须同步在那里登记一行**;改动前先看 `docs/测试说明/模拟器差距清单.md` §3 判断是否必须回真机。