Files
STM32F4-Base/docs/子系统设计/运行与自检记录设计说明.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

278 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.
# 运行/自检记录与待机显示设计说明
> **版本**:v1.1(已实施)
> **日期**:2026-09-14
> **状态**:S1–S9 全部完成——双端构建 0/0、`--logtest` PASS、ui-verify 五门禁全绿;真机 @test 待上电回归(§8.2)
> **关联文档**:`项目架构总览.md` · `STM32F4-Base存储架构说明.md` · `系统墙钟时间维护说明.md` · `UI设计/内容页架构.md`(如存在)· `docs/UI设计/mockups/`(待机显示页面.jpg / 运行记录页.jpg / 自检记录页.jpg / 密码页面实际机器的示例图片.jpg)
## 1. 背景与需求
| # | 需求 | 说明 |
|---|------|------|
| R1 | 密码错误提示 | 用户密钥页输错密码时提示"密码输入错误",并允许重新输入(现状:仅清空重输、无提示) |
| R2 | 待机显示页 | 长时间无操作时默认显示的界面(参考 `mockups/待机显示页面.jpg`:6 路电流值 + 底部时钟);**只有按确认键才进入菜单** |
| R3 | 运行记录页 | 记录装置上电时间、重启时间(参考 `mockups/运行记录页.jpg`:标题 + 逐条"时间戳 + 事件" + 页脚分页) |
| R4 | 自检记录页 | 记录开机自检结果,有问题才记录(参考 `mockups/自检记录页.jpg`) |
| R5 | FTP 可读存储 | 记录以**纯文本文件**落盘,FTP(根目录 = FatFS 卷根)可直接下载读取 |
| R6 | 先调研后设计 | 网络调研存储方式选型 → 写入本设计文档 → 按文档实施 |
## 2. 现状分析
### 2.1 UI 框架(`Drivers/BSP/UI/`,MCU/模拟器共用)
- **内容页 = 描述符驱动**:`content_page_def_t`(标题/几何/行数据源/4 钩子),框架 `ui_content_page.c` 统一建屏、切屏、焦点、按键分发与绘制;页面模块零 LVGL 对象。
- **分页已内建**:`selectable=1` + `rows_per_page`(↑↓移选、窗口跟随)+ 页脚"第 X 页,共 Y 页"自动计算;动态行经 `refresh(row, name, value)` 逐行取数(栈缓冲 **32B/列**),行数经 `count_cb()`。
- **菜单树已含目标叶子**:`ui_menu_tree.txt` "记录查询"下已有 **自检记录 = node 39、运行记录 = node 40**(当前为空白终点框,无内容页映射);无需改树。
- **密码页** `ui_page_password.c`:3 位数字框 + ↑↓←→/ENTER;输错仅清空重输(R1 缺口)。
- **按键流**:物理/远端键 → LVGL keypad → 默认输入组 → 焦点对象事件回调(菜单容器 `menu_key_cb` / 内容页容器 `cp_key_cb`)——空闲检测可在此统一挂钩。
- **字体**:子集位图字体 `ui_font_cn_12`,字符集 `ui_cn_chars.txt`(当前 122 汉字/符号 + ASCII)。
### 2.2 存储与时间
| 项 | 现状 |
|---|------|
| 卷 | GD5F2GQ5UE SPI NAND **256MB**(2048 块 × 64 页 × 2KB),dhara FTL(磨损均衡/坏块管理),FatFS FAT32,`FF_USE_LFN=0` → **文件名必须 8.3** |
| FTP | lftpd 根目录 = 卷根 `/`;现有文件 `/config.ini`(8.3);旧档 `/cfg/netcfg.bin` 已废弃——现固件不读不写也不删(仅可能残留在旧设备上) |
| 并发 | `FF_FS_REENTRANT=1` + 系统锁(`ffsystem.c` osMutex):单次 `f_*` 调用受保护,**多调用序列(open→seek→write→close)需单写者纪律** |
| 墙钟 | `sys_clock_get_str(buf,20)` → `"YYYY-MM-DD HH:MM:SS"`;`sys_clock_get()` → Unix 秒(失败 = `UINT32_MAX`);SD2506 RTC + timeSyncTask TCP 校时 |
| 挂载 | defaultTask:`f_mount(forced=1)`,空卷自动 `f_mkfs` FAT32,成功后置 `SYS_EVT_STORAGE_READY` |
### 2.3 现有自检检查点(v1 埋点位置)
| 检查点 | 位置 | 失败表现 |
|---|------|---------|
| RTC 读取/墙钟建立 | `app_main_init()`:`sd2506_init()` + `sys_clock_init()`(FreeRTOS 启动前) | 时间戳无效,后续日志无意义时间 |
| NAND 初始化 | `app_main_init()`:`gd5f2gq5ue_init()` 返回非 `GD5F_OK` | 存储不可用 |
| ADC 初始化 | `app_main_init()`:`tpafe5160_init()` 超时 | 采样不可用 |
| FatFS 挂载/建卷 | defaultTask:`f_mount`/`f_mkfs` 失败(置位 STORAGE_READY 前) | FTP/配置/记录全部不可写 |
| 网络链路 | net_task `net_stack_bring_up()`:PHY 等待 ≤3s 后仍 DISCONNECT | 组网可用但无链路 |
### 2.4 RAM/LVGL 池预算(依据 `docs/内存管理/RAM使用分析与优化方案.md`)
- FreeRTOS 堆真实余量 ≈ **11~12KB**;LVGL 池 16KB 静态,菜单常驻占用 ≈5KB。
- 本设计新增 RAM:**全部 < 1KB**(§7),不新增任务、不动任务栈。
## 3. 网络调研摘要(R6)
| 主题 | 结论 | 来源 |
|---|------|------|
| 日志行格式与时间戳 | syslog(RFC 5424):一行一条记录;TIMESTAMP 采用 **ISO 8601/RFC 3339**(`YYYY-MM-DDTHH:MM:SS[.frac][Z]`,机器可读、字典序可排序);MSG 用 UTF-8。本设计取空格变体 `YYYY-MM-DD HH:MM:SS`(与既有 `sys_clock_get_str` 输出一致),另附 epoch 秒字段供程序化解析 | RFC 5424 §6.2.3/§6.4(datatracker.ietf.org/doc/html/rfc5424) |
| 写入模型 | Linux printk:消息先进 **RAM 环形缓冲**,由**专用线程**异步刷持久存储;热路径禁止同步 I/O。本设计对应"RAM 待发队列 + defaultTask 单写者落盘" | kernel.org/doc/html/latest/core-api/printk-basics.html |
| FatFs 追加语义 | `FA_OPEN_APPEND`(= FA_WRITE\|FA_SEEKEND):打开即定位 EOF,文件不存在则创建;配合 `f_lseek(fp, FILE_SIZE)` 可取文件大小。本设计每条记录一次 `f_open→f_write(64B)→f_close` | 工程内 `Lib/FatFs/ff.h:399`、`ff.c:3871`(与官方文档一致) |
| NAND 写入代价 | dhara FTL 已做磨损均衡;FAT32 下每次小追加约消耗 1 个簇(4KB)。本场景**写频率极低**(每上电 1~N 条、故障才写),簇开销与磨损可接受;以文件容量上限约束增长 | `docs/架构设计/STM32F4-Base存储架构说明.md` §6.3 + GD5F 驱动参数 |
**选型结论**:纯文本、**定长 64B 记录、一行一条**(ISO 时间戳 + epoch + UTF-8 描述);追加写 + 容量上限截断;RAM 队列 + 单写者任务落盘。
不选变长行(需扫描计数/解析,掉电半行难界定);不选二进制格式(违背 R5 FTP 直读)。
## 4. 总体设计
### 4.1 模块与文件清单
| 文件 | 端 | 说明 |
|---|---|------|
| `App/log/sys_log.h` | 共享 | 公共 API + 记录格式常量 + 行拆分内联函数(无 OS 依赖) |
| `App/log/sys_log_mcu.c` | MCU | FatFS 后端:待发队列(启动前静态数组 + FreeRTOS 消息队列)、defaultTask 落盘、容量截断、上电/重启分类 |
| `sim/sim_syslog.c` | 模拟器 | 宿主文件后端(CWD 下 `sim_run.log` / `sim_selftest.log`),直接追加,`time()` 时间戳 |
| `Drivers/BSP/UI/ui_standby.h/.c` | 共享 | 空闲检测 + 待机屏(屏幕覆盖层:保存/还原前屏与焦点) |
| `Drivers/BSP/UI/ui_page_runlog.h/.c` | 共享 | 运行记录内容页描述符(动态行,数据源 = sys_log API) |
| `Drivers/BSP/UI/ui_page_selftest.h/.c` | 共享 | 自检记录内容页描述符(同上) |
| `App/app/app_main.c` | MCU | 埋点:RTC/NAND/ADC 失败 → `syslog_post(SYSLOG_SELFTEST, …)` |
| `Src/freertos.c`(defaultTask USER CODE) | MCU | 挂载失败埋点 + **上电/重启分类记录** + 队列排空落盘循环 |
| `App/task/net_task.c` | MCU | 埋点:链路超时 → `syslog_post(SYSLOG_SELFTEST, "通讯异常")` |
| `Drivers/BSP/UI/ui_page_password.c` | 共享 | R1:错误提示 + 重输 |
| `Drivers/BSP/UI/ui_content_page.h/.c`、`ui_menu.c` | 共享 | 新增 2 个内容页 ID/描述符表项/node 39·40 映射;键事件挂钩空闲通知 |
| `ui_cn_chars.txt` + `tools/font_gen/` | — | 补 10 字重生成字体(§6) |
| `MDK-ARM/*.uvprojx`、`simulator/CMakeLists.txt` | — | 新文件双端注册(new-source-file skill) |
| `simulator/main.c` | 模拟器 | `--logtest` / `--standby` / `--idle <s>` 无头自检与测试钩子 |
### 4.2 日志记录格式(定长 64B,一行一条)
```
偏移 长度 内容
[0..18] 19 "YYYY-MM-DD HH:MM:SS"(ISO 8601 空格变体;墙钟无效时 = "1970-01-01 08:00:00")
[19] 1 ' '
[20..29] 10 epoch 秒 ASCII("%010u",机器解析/上电重启分类用;墙钟无效 = "0000000000")
[30] 1 ' '
[31..58] 28 描述 UTF-8(≤28B = 9 个汉字),不足补 '\0'
[59..63] 5 '\0' 填充
```
示例行:`2026-09-14 16:48:34 1789465714 装置上电记录`(后随 NUL 填充至 64B)。
**定长收益**:条数 = `filesize / 64`(O(1),免扫描);第 k 条偏移 = `k*64`(随机读,UI 翻页零解析成本);掉电半行被 `floor(size/64)` 自然排除。
**epoch 10 位约束**:2026 年 Unix 秒已达 10 位(2001-09-23 起),字段按 `%010u` 定宽;uint32 上限 4294967295(2106 年)内不溢出。
**描述 ≤28B 约束**:受框架动态行栈缓冲 `CP_ROW_BUF_LEN=32`(含 NUL)限制,不改框架。
### 4.3 文件与容量
| 文件 | 内容 | 上限 | 满时策略 |
|---|------|------|---------|
| `/log/run.log` | 上电/重启记录 | **1024 条(64KB)** | 删除重建(整卷清空,串口告警);DTU 上电频率低,容量充裕 |
| `/log/selftest.log` | 自检故障记录 | **512 条(32KB)** | 同上 |
- 目录 `/log/` 首次落盘前 `f_mkdir`(已存在则忽略);文件名均 8.3 合规。
- FTP 视角:卷根下多一个 `LOG` 目录,两个 `.log` 文本文件可直接下载(R5)。
- 记录**只增不改不删**(除满时重建与未来"清除记录"功能),保证 FTP 端所见即历史。
### 4.4 写路径(单写者,调研结论落地)
```
任意上下文 syslog_post(id, desc)
├─ FreeRTOS 未运行(app_main_init 期)→ 静态待发数组(8 槽,满则丢弃+计数)
└─ 调度器已运行 → osMessageQueueSend(8 槽,满则丢弃+计数;日志不得阻塞业务)
defaultTask(唯一写者,挂载成功后):
1) f_mkdir("/log")(幂等)
2) 排空启动前数组 + 队列 → 逐条落盘(f_open FA_OPEN_APPEND → f_write(64B) → f_close)
3) 写"上电/重启"分类记录(§5.3,基于 run.log 末条 epoch)
4) 主循环:osMessageQueueGet(阻塞, 10s 超时) → 落盘;超时空转继续 LED 闪烁
```
- 读路径与写路径并发安全:UI 页在 uiTask 直接 `f_open(FA_READ)` 按偏移读记录(只读既有内容,写者只在 EOF 追加);FatFS 系统锁保护单次调用。
- **存储未就绪/挂载失败**:待发条目滞留 RAM(下次上电丢失),串口打印告警——存储故障时本就无法持久化,属预期行为。
### 4.5 读路径(UI / 模拟器)
```c
uint32_t syslog_count(syslog_id_t id); /* = filesize/64 */
int syslog_get_record(syslog_id_t id, uint32_t idx, char *ts, size_t ts_len,
char *desc, size_t desc_len); /* 越界返回 -1 */
```
- MCU:`f_open(FA_READ) → f_lseek(idx*64) → f_read(64B) → f_close`,内联拆分函数切 ts/desc。
- 模拟器:宿主文件同语义(UI 页双端零差异)。
- UI 页每帧仅读当前窗口 ≤8 条记录(每次重绘 8×64B SPI 读,用户翻页触发,频率极低)。
## 5. 功能设计
### 5.1 R1 密码错误提示(`ui_page_password.c`)
- ENTER 比对失败 → `s_err=1`:3 框清零、光标回首框,**数字框下方居中显示"密码输入错误"**(y≈104)。
- 错误态下按 ↑↓←→:**先清除提示**再正常执行该键(即"让用户重新输入");再按 ENTER = 重新比对。
- 比对成功路径不变(回调授权/关页)。布局常量与绘制沿用现有 culling 模式,零 LVGL 对象。
### 5.2 R2 待机显示页(`ui_standby.c/h`)
**进入条件**:UI 初始化完成后,**60s(默认,可配)内无任何按键** → 切待机屏。
- 空闲检测 = `lv_timer`(1s 周期,MCU/模拟器均经 `lv_timer_handler` 驱动,无 OS 依赖);
- 活动通知挂钩:`menu_key_cb`、`cp_key_cb` 每收到一个键调 `ui_standby_notify_activity()`(重置计时)。
**待机屏内容**(参考 mockup,全 ASCII 无需新字体字):
```
Ib = 0.00A ← 6 行电流值(v1 占位 0.00A,与 AC/DC 页现状一致;
Ic = 0.00A 待交流量标定管线落地后接 TPAFE5160 实时值)
Io = 0.00A
Ia2 = 0.00A
Ib2 = 0.00A
Ic2 = 0.00A
────────────────────── ← 分隔线(y≈132)
YYYY-MM-DD HH:MM:SS ← 底部实时时钟(每秒重绘,lv_timer 驱动 invalidate)
```
**键语义(用户定案)**:待机屏上**只有 ENTER(确认键)进入菜单**;其余按键忽略(同时重置空闲计时)。
- 实现为**屏幕覆盖层**:进入时保存当前活动屏 + 组内焦点对象,加载待机屏并聚焦其容器;ENTER 时还原前屏与焦点。
- 内容页打开期间超时 → 同样切待机(页面对象保留在底层,ENTER 返回后原样恢复,编辑态不丢)。
### 5.3 R3 运行记录(上电/重启分类)
defaultTask 挂载成功、排空待发队列**之后**写一条:
```
读 run.log 末条记录的 epoch 字段 E:
墙钟有效 且 (now - E) < 600s → "装置重启记录"(近期刚运行过 = 软复位/看门狗)
其余(无文件/首条/间隔≥10min/墙钟无效)→ "装置上电记录"
```
- 阈值 `SYSLOG_REBOOT_WINDOW_S = 600`:断电 <10min 会被误判为重启——无掉电检测硬件时该歧义不可消除,取工程折中并在此声明。
- 串口标记(供 @test):`runlog boot entry: 装置上电记录 / 装置重启记录`(`sys_log_mcu.c` `DBG_INFO`,与落盘描述串同源)。
### 5.4 R4 自检记录(v1 项表)
| 检查点 | 触发时机 | 描述串(落盘 + UI 显示同源) |
|---|------|------|
| RTC/墙钟建立失败 | app_main_init | `时钟芯片异常` |
| NAND 初始化失败 | app_main_init | `NAND异常` |
| ADC 初始化超时 | app_main_init | `ADC异常` |
| FatFS 挂载/建卷失败 | defaultTask(仅串口,存储不可写) | (无法落盘,串口 `[SELFTEST] fs: FAIL`) |
| 网络链路 3s 未建立 | net_task bring_up 后 | `通讯异常` |
- **只记故障**;全部通过不写记录(与 mockup"有问题进行记录"一致)。
- v1 不做"已复归/动作/标志字"(mockup 中为保护继电器专有语义)——列入开放问题 O2。
- UI 页空态:`count_cb` 返回 0 时以单条伪行 `无记录` 呈现(页脚"共 1 页",避免"共 0 页")。
## 6. UI 布局与字体
### 6.1 新增字符集(11 字)→ 重生成 `ui_font_cn_12.c`
| 用途 | 新字 |
|---|------|
| 密码错误提示"**密码输入错误**" | 输、入、错、误 |
| 运行记录"装置**上电**记录 / 装置**重**启记录" | 上、电、重 |
| 自检故障名"时**钟芯片异常**"等(异/常为公共后缀) | 芯、片、异、常 |
流程按 `font-regen` skill:改 `ui_cn_chars.txt` → extract_cn → make_font → diff。注意:**extract_cn 只扫 UI 源码**,故障描述串在非 UI 源码(sys_log_mcu.c/app_main.c/net_task.c)中,须手动补入字符集;其余文案(运行记录/自检记录/无记录/第 X 页共 Y 页)均已在字符集内;待机页全 ASCII。
### 6.2 页面几何(160×160,沿用框架常量体系)
| 页 | 参数 |
|---|------|
| 运行记录 / 自检记录 | `selectable=1, rows_per_page=8, top_y=24, pitch=14, band_h=13`(同 AC/DC 约定;**偏离 mockup**:实机照片无选中带,但框架 selectable 页选中行恒黑字、band_h=0 时黑字黑底不可见,故保留标准白带),`left_x=7`;**每条记录 = 2 行**:偶数行 = 时间戳(`YYYY-MM-DD HH:MM:SS`,19 ASCII ≈114px 可容)、奇数行 = 描述(≤9 汉字);↑↓按行移动(8 行/页 = 4 条/页),页脚自动分页 |
| 待机屏 | 6 数据行 y=28 起 pitch=18(label x≈24 / `=` x≈72 / 值 x≈96);分隔线 y≈132;时钟 y≈140 居中 |
| 密码页错误提示 | "密码输入错误" 居中,y≈104(数字框行 y=78..92 之下) |
## 7. 内存预算
| 项 | 大小 | 位置 |
|---|------|------|
| sys_log MCU:启动前待发数组 8×12B + 队列 8×12B + 状态 | ≈300B(静态)+ ≈150B(FreeRTOS 堆,消息队列) | .bss / heap_4 |
| ui_standby:待机屏 + 容器 LVGL 对象(常驻) | ≈300B | LVGL 池(16KB,当前占用 ≈5KB) |
| 记录页模块 | 0(描述符驱动,零 LVGL 对象;仅栈缓冲) | — |
| 字体 +10 字形 | ≈250B Flash | .rodata |
**结论:不新增任务、不动任务栈、FreeRTOS 堆增量 <200B,LVGL 池余量充足。**
## 8. 测试计划
### 8.1 模拟器(无头自动化)
| 用例 | 手段 |
|---|------|
| 日志引擎闭环 `--logtest` | post N 条 → count/get 回读断言 + 宿主文件字节级校验(64B 定长、ISO 时间戳、NUL 填充),0=PASS(仿 `--cfgtest`) |
| 待机屏渲染 `--standby` | 无头注键后直接调测试钩子强制进入待机 → 截图比对(布局/时钟行);窗口模式 `--idle <s>` 可人工验证真实超时路径 |
| 记录页像素回归 | `--keys` 导航至 node 39/40 → 截图 + 文本断言(时间戳行/描述行/页脚分页),纳入 ui-verify 门禁 |
| 密码错误提示 | `--keys` 输错密码序列 → 截图断言"密码输入错误"可见;再按方向键 → 提示消失 |
### 8.2 真机(@test 串口标记)
Required:`App init OK`、`Network Init`、`net init: OK`、**`runlog boot entry:`**(装置上电记录 / 装置重启记录)、**`UI standby: ready (idle=60s)`**;
Forbidden:`FAIL`/`ERROR`/`HardFault`。
人工目检:待机屏(静置 60s)、记录页翻页、FTP 下载 `/log/*.log` 文本可读性。
## 9. 风险与开放问题
| # | 项 | 策略 |
|---|----|------|
| O1 | 断电 <10min 的"上电/重启"歧义 | §5.3 阈值折中,文档声明;如需精确可后续加掉电标志(PVD) |
| O2 | 故障"已复归"/持续监测(mockup 语义) | v2:周期自检 + 活动故障状态机 + 恢复记录 |
| O3 | "清除记录"菜单项(node 27 #P,现为空框) | v2:UI 动作 = 删除/重建 log 文件(与满时策略同路径) |
| O4 | 待机页电流值接实时数据 | 待交流量标定管线(AC 页占位数据替换)落地后接入 `tpafe5160_snapshot()`(原子快照;旧 `tpafe5160_get_buf()` 已于 2026-10-10 删除,见 `AGENTS.md`) |
| R-1 | FAT32 小追加簇开销 | §3 调研结论:写频率极低可接受;容量上限约束增长 |
| R-2 | UI 读与落盘并发 | 只读既有内容 + EOF 追加 + FatFS 系统锁,无互斥需求(§4.4) |
## 10. 实施步骤(按序执行,每步双端构建 0/0)
> ✅ **S1–S9 已于 2026-09-14 全部完成**:Keil `@build` 0 错 0 警 + 模拟器 0 警告;S8 = `--logtest` PASS(期间修复 ts 19 字符 / epoch 10 位两处格式 bug)+ ui-verify 五门禁全绿(池基线 5440/5520B、pwc 参考更新)+ 记录页/待机屏/密码错误提示截图目检通过;S9 = 文档同步。
| # | 步骤 | 产物 |
|---|------|------|
| S1 | 字体补字 + 重生成 | `ui_cn_chars.txt`、`ui_font_cn_12.c` |
| S2 | 日志引擎:`sys_log.h`(共享 API/格式)+ `sys_log_mcu.c` + `sim/sim_syslog.c` | 双端可 post/count/get |
| S3 | 自检埋点 + defaultTask 落盘循环 + 上电/重启分类 | app_main/freertos/net_task 接线 |
| S4 | 密码错误提示 | ui_page_password.c |
| S5 | 待机屏 `ui_standby`(空闲检测 + 覆盖层)+ 键挂钩 | ui_menu/ui_content_page 各一行通知 |
| S6 | 记录页 ×2 + 内容页框架接线(枚举/描述符表/node 映射) | ui_page_runlog/selftest、ui_content_page.h/.c、ui_menu.c |
| S7 | 双端注册新文件(uvprojx + CMakeLists)+ 构建 0/0 | new-source-file skill |
| S8 | 模拟器无头回归(--logtest/--standby/截图断言)+ 真机 @test | 测试通过记录 |
| S9 | 文档同步(项目架构总览、AGENTS.md、本文档状态) | docs |