Files
lucky-cat/docs/lcd-idle-status.zh-CN.md
T

144 lines
9.7 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.
# 待机恢复与时间 / 电量状态栏
完整现行说明见 [GFX 表情显示应用与开发指南](GFX表情显示应用与开发指南.md)。
本文保留待机、状态栏和历次优化记录;以下历史数字主要针对待机,listening 的
独立资源/时间线见新指南。旧 lcd-expressions.md 中的测试图案、
优先级仲裁描述不代表当前运行行为。正常播放现在是缓存图像,不是逐帧网格。
## 返回待机
- 开机 / 默认空闲、显式 IDLE 请求、有限动画 return_to_idle、缺失素材回退和
非取消的播放错误都进入同一个待机后端。
- CONFIG_TQ_LCD_EMOTE_GFX_TEST 启用且初始化成功时优先播放初始化生成的眼型缓存;
待机缓存失败保留实时网格。网格与闭眼弧线通常只用于待机缓存生成。
禁用或初始化失败才使用历史三帧 PNG 安全后备。
- 历史 jichubiaoqing 请求(duration_ms=0)也适配到 GFX。
有限播放保留 repeat_count,每轮为 frame_ms 睁眼停留 + 12 个 34ms 眨眼步骤;
不承诺与旧 PNG 完全相同的持续时间。服务错误因此不再临时退回三帧。
- 自动恢复只在没有 pending 请求且未停止时排入 IDLE,不能覆盖新来的请求。
- listening 也已迁移为 GFX 局部缓存,复用引擎/传输/HUD,缓存失败回退旧 PNG;
保持睁眼约 2.4 秒、眨眼目标 300ms,直到业务新请求或停止,不自行回待机。
- 除待机/listening 外,其他表情仍为 PNG / step sequence,未改动其图像或动画顺序。
pending 仍是单槽替换,不是完整优先级状态机。
## 状态栏
- 左上 HH:MM,沿用 SNTP 和系统时区;未同步显示 --:--。
- 左上时间已改成 Montserrat 14 原始字号,提取数字、冒号、横线直接绘制;
无 GFX label 分配、无 LVGL GUI 初始化。旧 5×7 字形已从 HUD 实现移除。
- 右上删除百分比,使用用户提供的 40×20 五档电池图标及对应充电版本;
未知电量不显示,保留原底部低电量提示。档位规则、成本和生成入口见新指南第五节。
- SOC、有效性和充电状态来自现有 domain battery 服务,不从 ADC 重算。
- GFX 顶部 320x40 RGB565 图层,常驻 25,600 B PSRAM;只在分钟或电池状态
改变时使图层失效并刷新。正常待机/listening 眨眼切换缓存,不重新解码 PNG;
实时网格与程序闭眼弧线属于待机缓存生成或缓存失败后备路径。
- PNG 表情复用相同 Montserrat 子集 / 电池素材绘制器,在每帧合成时更新;二维码和 LCD
边界测试页面不显示状态栏。PNG 无限等待期间不会独立重绘状态栏。
- 新增字形子集和十个电池素材编译数组,不创建字形/图标解码缓存;普通堆、
像素 PSRAM 和 DMA 无新增。旧 PNG 低电量底部告警保留;旧充电 PNG 不再
自动叠加。低电量图标颜色来自用户素材,不再由代码统一染红。
## 验证
`python3 tools/test_lcd_status_hud.py` 从实际源码提取绘制函数,用 Clang
AddressSanitizer / UndefinedBehaviorSanitizer 检查 2,048 组有效性、充电、
低电量、完整 uint8 电量输入组合和全天 1,440 个时间,验证行块与整帧等价、
边界、原图 Alpha、透明背景恢复、240/320 像素画布和字节交换开关。
使用 ESP-IDF v5.5.4 执行 idf.py build。实机需验证:
1. 联网后 --:-- 变成本地时间,跨分钟更新;未同步不显示伪造时间。
2. 插拔充电线、电量变化时状态更新,眼睛位置 / 眨眼无变化或残影。
3. 各有限动画、Wi-Fi 恢复、PTT 结束、OTA 退出及服务错误后回 GFX 待机。
4. 动画结束同时到达新请求时不被自动 IDLE 覆盖。
编译 / 主机测试不等同于上述实机验证。
## 无 TE 的软件减撕裂试验
- GFX 使用单个 153,600 B PSRAM 合成缓冲(partition 模式,不是 RGB 全帧模式),
普通眼睛更新先完成整个 dirty 区域,再进入 flush;不会在分块传输之间计算网格。
- flush 使用单个 10,240 B 内部 DMA 暂存区;按最多 5,120 像素连续搬运,
每批等待 ISR 完成后才复用,整区域发完才通知 GFX。PNG 原有 8 行块不变。
- 相对上一版移除 2×2,560 B DMA 小缓冲,增加 5,120 B DMA 暂存净容量
(不含 SPI 驱动描述符);新增合成缓冲和 9,996 B 闭眼纹理共 163,596 B PSRAM。
- closure >=232 时隐藏压缩眼圈并显示同色的抗锯齿闭眼弧线,避免极薄纹理抖动。
- 34ms 帧预算包含绘制/传输时间,超时不会再固定额外等 34ms。
- 每轮日志已替换为下文可开关的 emote_fps,区分时序步骤与眼睛实际更新。
- DMA 超时会锁住 GFX 传输路径,不复用可能仍被硬件读取的缓冲;需重启恢复。
- 此方案不能保证面板扫描无撕裂。实机比较眨眼上下断层、闭眼亮点及原有
表情切换/状态栏行为;无 TE 时不能把软件预合成称为硬件同步双缓冲。
主机传输/闭眼回归:`python3 tools/test_lcd_emote_transport.py`,检查 320 种
区域宽度、DMA 分块/数据顺序、错误/超时处理及闭眼对称性。当前 RTOS tick
为 10ms,实际眨眼间隔仍有 tick 量化及任务调度抖动;日志测量步骤/图像更新
吞吐率,不是面板扫描率。
## 可开关的实测帧率日志
menuconfig 的屏幕配置中 `Log measured GFX blink FPS` 对应
`CONFIG_TQ_LCD_EMOTE_FPS_LOG`。默认关闭,本次本地测试配置开启。关闭后
统计计数和旧 draw_us_avg 日志均不编译,不改变动画/等待节奏。
每轮完整眨眼打印一次 `emote_fps`,被抢占/取消的轮次不打印:
- blink_us:实际整轮眨眼墙钟耗时,包含计算、SPI、等待和任务调度,
不包含此前约 2 秒的静止睁眼时间,也不包含该条日志输出时间。
- step_fps:本轮步骤数 / 实测眨眼秒数(待机 12 步、listening 10 步),闭眼保持也算步骤。
- eye_updates:步骤的刷新调用确实完成至少一个区域传输才计一次;
一帧多个区域/多个 DMA 块不会重复计数,保持帧和等待期间的 HUD 更新不计。
- update_fps:eye_updates / 相同的 blink_us,是本轮眼睛画面更新吞吐率,
不是面板扫描率,也不是仅统计运动段的瞬时 FPS。
- draw_avg_us / draw_max_us:步骤绘制及锁等待耗时,平均包含无需传输的保持步骤。
- late_steps:绘制耗时达到或超过相应步长预算的步骤数(待机 34ms、listening 30ms)。
- face:idle/listening,区分当前表情。
示例:blink_us=570000 steps=12,则 step_fps=21.05;如果 eye_updates=9,
update_fps=15.78。没有 TE,这些指标不测量屏幕实际扫描或保证无撕裂。
### 分项耗时 emote_cost
同一开关下,每轮 emote_fps 后追加一条 emote_cost,各项均输出 avg_us / max_us。
平均按本轮步骤数计算(待机 12、listening 10),包含不传输的闭眼保持步骤;各项最大值可能来自不同步骤,
不能相加。累计计数和快照都在 GFX 渲染锁内,不计静止/HUD 更新的耗时。
- lock:调用 gfx_emote_lock 到获取锁的墙钟耗时。
- compose:获取锁后眼型变形/切换、GFX 合成和刷新调度的墙钟耗时,
扣除 copy 与 SPI 时间;包含 GFX/回调记账开销,不等于纯 CPU 使用时间。
- copy:PSRAM 合成区域复制到 DMA 暂存区的墙钟耗时。
- spi:清理完成信号、提交 LCD 窗口/像素传输并等待 DMA 完成的墙钟耗时。
包含调度等待,不是仅由 SPI 时钟决定的线上传输时间。
全部计时都可能包含任务被抢占时间。若 compose 大,不能仅凭该值认定网格算法
慢;若 spi 大,也不能仅凭该值认定 SPI 带宽不足。关闭统计仍不改变播放逻辑。
本次不自动关闭本地唤醒或网络任务;对照测试要保持其他条件一致。
## 眼型 PSRAM 缓存
初始化时用原网格/闭眼弧线离屏合成 9 个眼型,捕获 x=39,y=63 的 242x97
RGB565 区域(含该区域内的静态脸部像素)。总缓存 422,532 B PSRAM;不新增
Flash 图片或 DMA 缓冲。日志 `Eye cache ready frames=9 PSRAM=422532B prepare_us=...`
报告实际预热时间,播放日志 cached=1 表示正在使用缓存。
之后每步切换普通 GFX image,不重新计算网格,闭眼保持不刷新。帧率/耗时
日志继续工作。窗口含原眼睛四周 2px 余量,固定位置的鼻子/胡须保持原效果;
以后修改眼睛动作/锚点、静态脸部或亮度配色时必须重新生成缓存或调整窗口。
新增动作阶段必须同时加入缓存表,不能把不支持的阶段静默映射到错误眼型。
预热阶段不传输到 LCD、不计实际更新次数。缓存分配/生成失败时释放已生成
缓存并保留实时网格路径。实机需比较 cached=1、合成耗时、FPS、首次启动
延时,以及其他表情/时间电量/唤醒同时工作时的表现。编译及主机窗口捕获
测试不证明缓存与网格在实机上像素一致,也不证明已经达到 30 FPS。
## 固定时间轴眨眼试验
每轮在睁眼停留结束后记录起点,第 i 个步骤的截止时间固定为
`blink_start_us + (i+1)*34000`,而不是当前时间再加剩余帧间隔。
10ms tick 带来的晚唤醒会压缩下一次等待,不持续累积。已迟到时不额外
等待、不跳过眼型;每一步仍检查停止/新请求,传输等待语义不变。
12 步目标总长 408ms,约 29.41 步/秒;9 次画面更新除以完整眨眼时长,
其目标吞吐率约 22.06 次/秒,包含闭眼保持,不是面板扫描率。
实际可能受 tick 和后台调度影响,需要实机日志验证,不能保证始终达标。
未提高全局 tick 频率、任务优先级或 SPI 时钟,也未增加缓冲。