144 lines
9.7 KiB
Markdown
144 lines
9.7 KiB
Markdown
# 待机恢复与时间 / 电量状态栏
|
||
|
||
完整现行说明见 [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 时钟,也未增加缓冲。
|