数据面(App/algo,纯逻辑,双端编译) - wave_ring:8 路 × 320 点列优先环(1 kHz,预触发 160 + 后触发 160),adcTask 唯一写者;触发/冻结/封存/冷却四态,冻结持续到落盘回执才解冻,滞留 2 s 自弃 兜底——下一次真故障无波可录比丢一段更贵 - wave_store:段头编解码 + 拉模式打包游标 + 自带无表位运算 CRC-16/MODBUS(带 锚点自检)+ 段名与整段长度等式;格式不依赖平台类型宽度 - 段头只存原始码值与封存瞬间生效系数,工程量只当单位标签、不进任何算式 - 三套主机单测(环 113 / 编解码 170 / 触发状态机 110),全部无桩、生产源原样 纳入;新文件补 stddef 是 ARMCC 与主机差异,主机侧照不出这条分叉 - test/host/wave_read.py 是按 §6.2 的独立第二实现(dump/fft/shape/plot),92 条 自洽断言;三份合成段互读双校验全过、哈希钉死,格式面双向闭合 - 三套新单测不加专属钩子,由覆盖率总闸全量真跑(沿用既有裁定) 接线(任务层) - adc_cal:新增生效系数的 32 位定点原值入口(与既有浮点入口共用生效视图), 段头按最低有效位精确记账,不经浮点回算 - adcTask:每拍压 8 路语义原始码值并推进状态机,封存那一拍同拍交棒封存瞬间事实; 台架定周期自抓走双层编译门,产品镜像里零存在;串口新增一条 wave: 周期行 - defaultTask:卷空闲后一次心跳整段搬完(try 锁 + 256 字节分片 + 一次完整开档到 关档),成败都回执;失败先删半成品再计错,段号只在成功时推进;持锁超 50 ms 补一条告警并记历史峰值(喂狗余量可核) - Lead 复核补第四道并发陷阱:采集任务优先级高于落盘道,搬段中途它可能正好把滞留 超时的本段自弃、甚至封出下一段——数据区校验和是对「实际读到的字节」算的, 半新半旧的段照样自洽,故关档后复检环侧身份判废:删文件、不补账、段号不推进 - Lead 复核抓到触发挂号不是原子操作(旧实现分三个字节写,消费者可在窗口里读到 上一段的触发源与元件号):已把载荷压进一个字节,越界当场拒且不记任何计数; 段文件字节布局零改动,三份合成段哈希逐字节不变 - protectTask:保护启动上升沿同点请求录波,一拍一次、元件号取最低位;判据本体 零改动 人机(160×160 单色屏) - 菜单树节点 26「手动录波」绑页面 + 记录查询组末新增「录波回放」叶,重跑生成器 得 43 节点 / 34 叶 / 30 已绑,页面映射 28 → 30 - 回放页一屏一路、触发点前后各一周波共 40 点、每点 3 像素阶梯线(只用水平与 垂直两种线段——单色屏斜线会被阈值裁掉);零点虚线、触发光标、窗口峰值、 单位徽标与形状受限徽标;空态与坏段各一句显式文案,绝不用半截波形冒充数据 - ui_data 新增录波注入通道(六件操作表、三态返回);执行侧胶水带段头缓存、 手动请求后连缓存一起作废、逐点走全仓唯一的码值换算入口 - 模拟器接真实环与编解码合成两段(含坏段与空态旗标),6 张新黄金参考,既有 60 张逐字节零漂移未用更新模式;字库 218 → 224 字 / 319 字形 - 两份图形配置里图表组件仍为关;零堆分配;页面池水位不变 - 回放读路径的 uiTask 阻塞上界按实码写清:一次渲染 = 段表刷新 + 未缓存段头最坏 22 次小读;已验段只剩一次开窗读 + 40 点解码;无实测数不编,实测归台架、 仪器用已在跑的单轮工作耗时探针,不新加埋点 台账与文档 - 内存台账:录波环与落盘/回放静态逐条入册,三档对照 81,536 → 88,296 → 89,936 字节(62.2% → 68.6%),累计 +8,400;预估 5.9 KB 与实测 +6,760 的差额点名; 散列文件头注释同步;任务栈表核对后无需抬栈;体积趋势行与同码凭证由权威构建 重打(0 错误 0 警告,四项体积与上一轮相同——本轮改动只有注释与声明位置) - 设计说明:§5 补第四条并发陷阱;§6.3 定字段口径(两份独立实现的共同依据); §7 回填实测;§9 周期行写实;§10 台架判据改两段式且不比绝对相位;§12 补读路径 风险行;抬头状态改成「代码面已交付、未做完的只有板级那一轮」 - 卡片:软件闭环一栏列证据;台架三条按用户裁定保持未完成,不做虚假销项 - 口径同步:数据可信、UI 补齐、页面补齐规划、产品需求、采集短板评测、任务规划 索引与两份 README;陈旧现状句清扫 16 处(带日期戳的历史取数行一律不动) - 门禁说明:nested-comment 记仓库根探测必须兼容 linked worktree 的 gitdir 文件 形态(修前该钩子在任务树里恒报环境错、静默不可用);coding-standard 记 「函数头注释超过 24 行就把函数功能挤出回看窗口」这条机制与本次现场 - 覆盖率基线登记两个新覆盖模块(套件 41 → 44 套);环单测重打后该文件有效行 227 → 226、覆盖行 226 → 225,比率仍 99.6%,是分母变小不是覆盖率回退 为什么是单一提交:文档链接、双端注册、生成器幂等(菜单与字库)、内存数字与 体积事实源、编号门禁这五道整仓门禁要求代码、页面、菜单树、字库、文档与台账 同处一个提交才自洽,按数据面/接线/人机/台账拆成四笔必然红其中至少四道 编号门禁新增行 14 处命中已按语义改写:魔数常量按字符命名、段名样例改由格式串 生成、频谱表头改 f / 2x / 5x 并加一行图例;改写后自洽断言 92 条仍全过
13 KiB
LVGL PC 模拟器部署与使用说明
适用于:
simulator/目录(LVGL v9.3.0 SDL2 模拟器) 用途:在 PC 上预览/调试 160×160 1bpp 单色菜单 UI,无需烧录 MCU
一、方案概述
1.1 背景
STM32F4-Base 的 UI 是 160×160 1bpp 单色菜单(基元控件页面导航:lv_obj 页面 + 返回按钮切换),使用自定义中文字体 ui_font_cn_12。在 MCU 上开发 UI 需要反复烧录调试,效率低。本模拟器在 PC 上直接编译运行相同的 UI 代码(ui_menu.c),实现所见即所得。
1.2 架构
simulator/main.c ──→ lv_init → sim_port_init(SDL 窗口 + 键盘)→ ui_menu_init(共享 MCU 代码)
│
└── 主循环:SDL 事件 → lv_timer_handler → lv_refr_now
| 模块 | 文件 | 说明 |
|---|---|---|
| 入口 | simulator/main.c |
SDL 初始化、主循环、键盘事件 |
| 端口 | simulator/sim/sim_port.c |
SDL 显示 + 键盘输入 + mono 主题初始化(替代 MCU 端 ui_port.c) |
| 配置 | simulator/lv_conf.h |
模拟器专用 LVGL 配置(1bpp + SDL 启用) |
| 共享 UI | Drivers/BSP/UI/ui_menu.c |
与 MCU 端共用,不做任何修改 |
| 共享字体 | Drivers/BSP/UI/ui_font_cn_12.c |
与 MCU 端共用 |
| LVGL 源码 | Lib/lvgl/ |
项目自带 v9.3.0,新增最小 CMakeLists.txt |
| SDL2 SDK | simulator/SDL2/SDL2-2.32.10/ |
本地预编译,无需安装 |
1.3 关键设计决策
- SDL2:LVGL 官方
lv_port_pc_vscode同款显示方案 - CMake + MSVC:
build_sim.ps1按本机 VS 版本自动选生成器(16=2019 / 17=2022 / 18=2026,取已装最高;-A x64)。不同机器 VS 版本不同,勿把生成器写死 - 仅 UI 预览:模拟器只跑 UI,不包含网络(netTask/ftpTask)等 MCU 业务
- 共享代码不复制:UI 源码直接引用
Drivers/BSP/UI/,MCU 与 PC 同步维护
二、环境要求
| 依赖 | 版本 | 说明 |
|---|---|---|
| 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 | 已解压到 simulator/SDL2/,无需安装 |
| 操作系统 | Windows | 已验证 win32 |
说明:
simulator/SDL2/SDL2-2.32.10/是SDL2-devel-2.32.10-VC.zip解压后的目录,已包含include/、lib/x64/。
三、首次部署(一次性)
3.1 下载 SDL2(如未包含)
若 simulator/SDL2/ 为空,需手动下载解压:
- 下载
SDL2-devel-2.32.10-VC.zip(官方 GitHub Releases 或官网) - 解压到
simulator/SDL2/,确保目录结构为:simulator/SDL2/SDL2-2.32.10/ include/ ← SDL.h 及各头文件 lib/x64/ ← SDL2.lib, SDL2main.lib, SDL2.dll
⚠️ 重要:LVGL SDL 驱动内部写死
#include "SDL2/SDL.h"。原版 SDL2 包的头文件直接平铺在include/下(没有SDL2/子目录),因此需要先把头文件复制一份到include/SDL2/子目录:$inc = "simulator\SDL2\SDL2-2.32.10\include" New-Item -ItemType Directory -Path "$inc\SDL2" -Force Copy-Item "$inc\*.h" "$inc\SDL2\"如未执行此步骤,编译报
fatal error C1083: cannot open include file: "SDL2/SDL.h"。
3.2 配置 CMake 并构建
# 仓库根目录执行(自动探测本机 VS 版本:2019/2022/2026)
& "simulator\build_sim.bat"
手动等效:
cd simulator; cmake -B build -G "Visual Studio <NN> <YYYY>" -A x64; cmake --build build --config Debug,生成器号须与本机 VS 大版本一致(16=2019 / 17=2022 / 18=2026)。
生成的可执行文件:simulator/build/Debug/lvgl_sim.exe(SDL2.dll 由 POST_BUILD 自动复制到同目录)。
3.3 运行
cd simulator\build\Debug
.\lvgl_sim.exe
或使用 CMake 自定义目标:cmake --build build --config Debug --target run。
四、日常使用
4.1 构建 + 运行
# 仓库根目录执行(增量构建;生成器变化时自动删 build/ 重建)
& "simulator\build_sim.bat"
.\simulator\build\Debug\lvgl_sim.exe # 运行
改 CMakeLists.txt 后再跑一次
build_sim.bat即可(缓存不匹配会自动重建)。 遇到奇怪问题时加-Clean全量重建:& "simulator\build_sim.bat" -Clean
4.2 键盘操作
| 按键 | 功能 |
|---|---|
| ↑ / ↓ | 菜单项上下移动 |
| ← / → | 展开/折叠 / 导航 |
| Enter | 确认/进入子页 |
| Escape | 返回上级 |
说明:模拟器未创建鼠标,纯键盘导航(与 MCU 端 keypad 一致)。
4.3 修改 UI 代码
- 修改
Drivers/BSP/UI/ui_menu.c直接生效(重新编译模拟器即可预览) - 修改
Drivers/BSP/UI/ui_font_cn_12.c同样直接生效 - 不要在
simulator/下复制 UI 源码——保持共享,避免两份代码不同步
五、文件详解
5.1 simulator/CMakeLists.txt
| 配置项 | 作用 |
|---|---|
SDL2_DIR |
SDL2 SDK 根目录 |
LVGL_DIR |
指向 ../Lib/lvgl(项目自带源码) |
UI_DIR |
指向 ../Drivers/BSP/UI(共享 UI 代码) |
SDL2_INCLUDE_DIR |
必须在 add_subdirectory(lvgl) 之前定义,供 LVGL SDL 驱动找到 SDL2/SDL.h |
LV_CONF_DIR |
指向 simulator/ 自身,供 LVGL 找到模拟器版 lv_conf.h |
add_subdirectory("${LVGL_DIR}" lvgl_build) |
编译 LVGL 静态库 |
| UI 源文件 | 直接编译 ui_menu.c + ui_font_cn_12.c |
| POST_BUILD | 自动复制 SDL2.dll 到输出目录 |
5.2 Lib/lvgl/CMakeLists.txt(新增)
LVGL 官方源码原本没有 CMakeLists.txt(只有 src/),本工程新增了一个最小化版本:
file(GLOB_RECURSE ...)收集src/**/*.c编译成静态库lvgl- 通过
LV_CONF_DIR/SDL2_INCLUDE_DIR两个可选变量注入 include 路径 - 这些变量由上层
simulator/CMakeLists.txt在add_subdirectory前设置 - MSVC 下关闭警告(
/W0)
⚠️ 这个文件只被模拟器使用,不影响 Keil 的 MCU 编译(Keil 用自己的
.uvprojx工程)。
5.3 simulator/lv_conf.h(模拟器专用)
与 MCU 端 Lib/lv_conf.h 的差异:
| 配置 | MCU 端 | 模拟器 |
|---|---|---|
LV_USE_SDL |
0 | 1(SDL 驱动) |
LV_SDL_RENDER_MODE |
- | LV_DISPLAY_RENDER_MODE_PARTIAL(1bpp 必须) |
LV_SDL_INCLUDE_PATH |
- | "SDL2/SDL.h" |
LV_USE_THEME_MONO |
0 | 1(否则 1bpp 下文字黑底黑字不可见) |
LV_FONT_DEFAULT |
&ui_font_cn_12 |
同 |
LV_MEM_SIZE |
16KB | 16KB(必须与 MCU 端同值,lv-conf-sync 比对生效值;2026-10-15 由 40KB 同步收缩,PC 侧不再"放宽") |
5.4 simulator/sim/sim_port.c
MCU 端 ui_port.c 的 PC 替代实现:
lv_sdl_window_create(160,160)→ SDL 窗口lv_theme_mono_init(disp, true, LV_FONT_DEFAULT)→ 必须,否则黑屏lv_sdl_keyboard_create()→ 键盘输入(关联默认 group)- 无鼠标/触摸设备(纯键盘导航)
5.5 simulator/dbg_log.h
MCU 端 Drivers/BSP/dbg_log.h 会引用 sys_clock.h(RTC 时间戳),PC 上无法编译。simulator/dbg_log.h 是一个 stub:DBG_INFO/ERROR/DEBUG 直接映射到 printf,且 simulator/ 在 include 路径最前会优先生效,覆盖 MCU 版本。
若 UI 代码新增
DBG_*输出,会打印到模拟器控制台,方便调试。
六、注意事项与已知坑
6.1 构建相关
SDL2/SDL.h找不到:需先执行 3.1 的include/SDL2/头文件复制步骤。lv_conf.h找不到:LV_CONF_DIR必须在add_subdirectory(lvgl)之前定义(CMake 变量顺序问题)。当前 CMakeLists 已正确处理,勿随意调整顺序。- 改 CMakeLists.txt 后不生效:MSVC 生成器可能缓存旧配置,先删
build/重建。 - 中文字符串文件编码:源码是 UTF-8,CMake 已加
/utf-8编译选项消除 C4819 警告。不要用 PowerShellSet-Content等重写含中文的文件(参照 AGENTS.md 编码铁律)。
6.2 渲染相关
- 黑屏排查顺序:
LV_USE_THEME_MONO是否 = 1 且在sim_port_init中调了lv_theme_mono_init(1bpp 无主题 = 黑底黑字)- 主循环是否调用
lv_refr_now(NULL)强制首帧输出(PARTIAL 模式下保证) - 是否重复设置 tick:SDL 驱动内部
lv_tick_set_cb(SDL_GetTicks),不要再SDL_AddTimer+lv_tick_inc
- 中文字体缺字:
ui_font_cn_12是子集字体(当前 224 汉字/符号 + ASCII 0x20~0x7E,共 319 字形;2026-10-29 实测)。缺字才会显示为空白——新增汉字必须重生成字体(见下文 6.3)。早期只有 83 字、当时菜单需要 186 字,那是 2026-09-03 的历史快照,不是现状。 LV_SDL_RENDER_MODE必须为PARTIAL:1bpp + SDL 若非 PARTIAL,lv_sdl_window.c会#error编译失败。
6.3 字体生成(如需补全中文字符)
用 LVGL 官方 lv_font_conv(Node.js)重新生成:
# 生成包含全部菜单字符 + ASCII 的 12px 1bpp 字体
lv_font_conv --font simsun_regular.ttf --size 12 --bpp 1 \
-r 0x20-0x7E \
--symbols "<全部菜单用到的汉字,用引号包裹>" \
--format lvgl --lv-font-name ui_font_cn_12 \
-o Drivers/BSP/UI/ui_font_cn_12.c
菜单字符集合:
主界面 装置信息 实时数据 交流量 直流量 开入量 定值参数 定值 遥调 零压 电压 查看 遥测量 测量值 遥调系数 遥测系数 双字节 装置遥调 装置维护 时间设置 强制复归 手动录波 自动录波 通讯设置 通讯参数 网络配置 SNTP设置 记录查询 SOE记录 事故记录 操作记录 告警记录 变位告警 遥信记录 自检记录 定值记录 录波记录 厂家设置 元件设置 恢复默认 全部恢复 默认参数 默认定值 默认系数 元件参数 版本显示 测量遥调 直流遥调 开出遥调 装置记录 遥调记录 信号复归 版本信息(去重后 76 字)注意:字体符号集是有损的——
lv_font_conv对不在符号集内的字符不生成字形,运行时该字符渲染为空白。这是当前缺字的根因。
6.4 与 MCU 端的差异
| 项 | MCU(Keil) | 模拟器(CMake+MSVC) |
|---|---|---|
| LVGL 编译 | Keil 工程添加源文件 | 新增 Lib/lvgl/CMakeLists.txt |
| lv_conf.h | Lib/lv_conf.h |
simulator/lv_conf.h(独立,互不影响) |
| 显示端口 | ui_port.c(离屏 framebuffer) |
sim_port.c(SDL 窗口) |
| 输入 | 远端 keypad | SDL 键盘 |
| 业务任务 | netTask/ftpTask 等 | 无,纯 UI |
6.5 其他
run目标:CMake 定义的--target run会执行build/Debug/lvgl_sim.exe。- 窗口无法关闭:点击窗口关闭按钮会触发
SDL_QUIT正常退出;Escape 由 LVGL 键盘驱动处理(返回上级页)。 - 多显示器/高 DPI:SDL 窗口固定 160×160 物理像素,Windows 缩放可能放大显示,属正常现象。
七、常见问题(FAQ)
Q1: 编译报 cannot open include file "SDL2/SDL.h"?
A: 未执行 include/SDL2/ 头文件复制,见 3.1。
Q2: 编译报 cannot open include file "lv_conf.h"?
A: LV_CONF_DIR 变量未在 add_subdirectory(lvgl) 前定义,检查 simulator/CMakeLists.txt 变量顺序。
Q3: 运行后窗口全黑?
A: 按 6.2 黑屏排查顺序检查:mono 主题 → lv_refr_now → tick 不重复。
Q4: 菜单某些中文显示空白?
A: 子集字体未含该字(当前字符集 224 汉字/符号 + ASCII 0x20~0x7E,共 319 字形;2026-10-29 实测),见 6.3 重新生成字体。
Q5: 修改了 UI 代码但模拟器没变化?
A: 确认修改的是 Drivers/BSP/UI/ui_menu.c 而非 simulator/ 内的副本(本工程无副本);然后重新 cmake --build。
Q6: 能调试网络/存储等功能吗?
A: 不能。模拟器仅预览 UI,不含 CH395F/NAND/ADC 等硬件业务。
八、文件清单
| 路径 | 状态 | 说明 |
|---|---|---|
simulator/main.c |
新增 | 入口 |
simulator/sim/sim_port.c / .h |
新增 | SDL 端口 |
simulator/lv_conf.h |
新增 | 模拟器 LVGL 配置 |
simulator/dbg_log.h |
新增 | printf 日志 stub |
simulator/CMakeLists.txt |
新增 | 构建脚本 |
simulator/build_sim.bat / .ps1 |
新增 | 一键构建(VS2019/2022/2026 生成器自适应;.bat 绕过 PowerShell 执行策略) |
simulator/SDL2/SDL2-2.32.10/ |
新增 | SDL2 SDK(含 include/SDL2/ 子目录) |
Lib/lvgl/CMakeLists.txt |
新增 | LVGL 最小 CMake(仅模拟器用) |
Drivers/BSP/UI/ui_menu.c |
未改 | 共享 UI 代码 |
Drivers/BSP/UI/ui_font_cn_12.c |
未改 | 共享字体(缺字待补) |
simulator/build/ |
生成物 | 构建输出,可随时删除重建 |