docs(lcd): add Chinese GFX development guide and agent rules
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
# GFX 表情显示应用与开发指南
|
||||
|
||||
本文面向本项目的开发者,说明当前 `esp_emote_gfx` 的具体应用及后续开发方法。正文使用中文,代码、路径和日志字段保留原名以便检索。适用基线为 2026 年 10 月 9 日的工作区实现;资源和性能数字随代码变化而变化。
|
||||
|
||||
## 一、当前方案与边界
|
||||
|
||||
本项目没有触摸和复杂界面交互,显示需求以精致表情、时间和电量为主。当前方案是轻量图层渲染,不是完整界面框架迁移。
|
||||
|
||||
- 底层为 ESP32-S3、ST7789P3、单线 SPI,当前像素时钟为 40 MHz,逻辑画布为 320×240。
|
||||
- 当前使用 `esp_emote_gfx` 3.0.5,由 `platform` 私有依赖。没有接入 `esp_emote_expression` 状态管理组件,也没有把当前表情绘制建立在 LVGL 上。依赖目录中存在其他库不等于当前显示链路使用它们。
|
||||
- 待机基础表情采用 GFX;其他表情仍走原 PNG 或步骤序列,不是所有资产都已经迁移。
|
||||
- 屏幕目前未使用 TE 同步。软件预合成和连续传输能改善撕裂观感,但不能保证面板扫描与写入完全同步。
|
||||
- 用户已确认缓存与固定时间轴版本的观感满意;最后的固定时间轴版本尚未提供新的量化日志,不能写成“稳定达到 30 FPS”。
|
||||
|
||||
已安装组件提供图片、网格图片、运动、动画、标签、二维码和按钮等接口。但本项目仅实际使用图片、网格、显示刷新及相关对象控制;通用中文文本、按钮、触摸和复杂布局不属于当前已实现能力。
|
||||
|
||||
## 二、分层与调用入口
|
||||
|
||||
```text
|
||||
控制层决定何时展示表情
|
||||
→ 领域层请求语义表情并查目录
|
||||
→ 平台层接收通用播放请求
|
||||
→ LCD 渲染任务串行播放
|
||||
→ GFX 合成或 PNG 合成
|
||||
→ SPI 写入屏幕
|
||||
```
|
||||
|
||||
上层通过领域层请求,不直接调用 GFX 或面板接口。例如返回待机:
|
||||
|
||||
```c
|
||||
const domain_expression_request_t request = {
|
||||
.id = DOMAIN_EXPRESSION_IDLE,
|
||||
.trigger = DOMAIN_EXPRESSION_TRIGGER_UNSPECIFIED,
|
||||
};
|
||||
esp_err_t err = domain_runtime_request_expression(&request);
|
||||
/* 调用方按自身策略处理 err;不要绕过领域映射直接写 LCD。 */
|
||||
```
|
||||
|
||||
新增语义表情应在 `components/domain/include/domain.h` 声明,并在领域目录中映射;平台层只接收播放原语,不应出现 `DOMAIN_EXPRESSION_*` 语义依赖。GFX 类型不得泄漏到上层公共接口。
|
||||
|
||||
当前请求采用单个待处理槽,不是完整优先级仲裁器。请求结构虽有优先级字段,也不能据此假定已经实现“低优先级自动排队”。
|
||||
|
||||
## 三、关键文件与职责
|
||||
|
||||
| 文件 | 职责 |
|
||||
| --- | --- |
|
||||
| `components/platform/src/lcd_direct.c` | 面板初始化、请求播放、GFX 图层、眼型生成和缓存、时间轴、状态栏、传输及性能日志 |
|
||||
| `components/platform/include/platform.h` | 通用显示请求和电池覆盖层公共契约 |
|
||||
| `components/platform/src/runtime/platform_lcd_runtime.c` | 平台显示入口转接 |
|
||||
| `components/domain/src/runtime/domain_runtime_adapters.c` | 语义表情目录、请求映射、电量状态映射 |
|
||||
| `components/platform/internal/lcd_emote_assets.h` | 私有压缩资产描述与解码声明 |
|
||||
| `components/platform/src/lcd_emote_assets.c` | 游程压缩解码与输入校验 |
|
||||
| `components/platform/src/generated/emote_gfx/idle_base.c` | 静态鼻子和胡须压缩数据 |
|
||||
| `components/platform/src/generated/emote_gfx/idle_eyes_open.c` | 原始睁眼压缩数据 |
|
||||
| `components/platform/CMakeLists.txt` | 私有组件依赖及实际参与编译的资产列表 |
|
||||
| `main/Kconfig.projbuild` | GFX 待机开关与测量日志开关 |
|
||||
| `tools/split_idle_expression.py` | 从旧三帧中拆出静态底图及眼睛图层 |
|
||||
| `tools/compress_idle_expression.py` | 将底图和睁眼图层转换为压缩 C 资产,并做编码往返检查 |
|
||||
| `tools/test_lcd_status_hud.py` | 状态栏及自动回待机回归测试 |
|
||||
| `tools/test_lcd_emote_transport.py` | 离屏捕获、缓存索引、传输、闭眼弧线和统计开关回归测试 |
|
||||
| `tools/test_lcd_emote_timeline.py` | 固定时间轴、时钟粒度、迟到及取消回归测试 |
|
||||
|
||||
`/tmp` 下修改过程中生成的补丁脚本不是工程运行依赖,不应提交或作为正式资源流程使用。
|
||||
|
||||
## 四、当前待机表情如何运行
|
||||
|
||||
### 1. 原始资产只解码一次
|
||||
|
||||
静态底图和睁眼图层以游程压缩数据进入应用镜像,初始化时解压到 PSRAM。每个游程由四字节组成:数量、RGB565 高字节、低字节、透明度。解压后是 RGB565 色彩平面加独立透明度平面,不是交错的三字节像素。
|
||||
|
||||
压缩合计为 19,336 字节,解压合计为 97,926 字节。后续眨眼不重新解析 PNG,也不重复解压这些数据。压缩不会在每一帧增加解码成本。
|
||||
|
||||
### 2. 网格用于首次生成,不是正常播放时反复绘制
|
||||
|
||||
睁眼图层为 238×93,锚点为 `(41,65)`。网格为 16 列、2 行,共 51 个控制点。闭合程度使用 `closure` 表示,0 为睁眼,255 为闭眼。
|
||||
|
||||
原网格向中线对称收缩,并增加弧度。GFX 会根据网格边界重新计算局部原点,因此代码补偿最小纵坐标偏移,防止眼睛闭合时整体向上漂移。修改网格后不能随意删除这个补偿。
|
||||
|
||||
闭合程度达到 232 后,不再把眼圈压成极薄纹理,而是显示程序绘制的抗锯齿弧线。弧线颜色取自原睁眼资源,避免色差。
|
||||
|
||||
### 3. 离屏生成九个缓存眼型
|
||||
|
||||
`lcd_direct_emote_prepare_cache()` 在同一任务中持有 GFX 锁,分别生成:
|
||||
|
||||
```text
|
||||
0、16、24、64、80、128、160、192、232
|
||||
```
|
||||
|
||||
其中 232 及以上共用闭眼图像。GFX 的真实合成输出通过 `lcd_direct_emote_capture()` 捕获到 PSRAM,预热期间不发送到 LCD,也不计播放帧率。
|
||||
|
||||
缓存窗口是 `(39,63)`,尺寸为 242×97,包含眼睛四周两像素余量。缓存是已经合成的、不透明 RGB565 图像:**其中包含窗口内的静态脸部像素,不是纯透明眼睛层**。修改鼻子、胡须、背景、锚点或配色时,必须同步重新生成缓存,不能拿旧缓存任意叠加到新背景。
|
||||
|
||||
九张缓存总计:`9×242×97×2 = 422,532` 字节。正常播放使用普通图片对象 `s_emote_cached_eyes` 切换来源。重复闭眼阶段不使对象失效、不再次传输。缓存生成失败时释放已分配的缓存,保留实时网格路径。
|
||||
|
||||
### 4. 固定时间轴播放
|
||||
|
||||
当前序列为:
|
||||
|
||||
```text
|
||||
24 → 80 → 160 → 232 → 255 → 255 → 232 → 192 → 128 → 64 → 16 → 0
|
||||
```
|
||||
|
||||
共十二个步骤、九次不同的眼型更新。正常待机先保持睁眼约两秒,再眨眼。
|
||||
|
||||
每轮记录共同起点,第 i 步截止时间为 `起点+(i+1)×34ms`。迟到时减少下一步等待,不把误差继续加到后面的目标时间。不跳眼型,不使用忙等,不修改全局系统时钟粒度。
|
||||
|
||||
十二步目标总长为 408ms,对应约 29.41 步/秒;九次更新除以完整眨眼时长对应约 22.06 次/秒。闭眼保持也占时间,二者都不是面板扫描率。实际仍受系统 10ms 时钟粒度及后台调度影响。
|
||||
|
||||
### 5. 预合成与连续传输
|
||||
|
||||
GFX 使用一个 153,600 字节 PSRAM 合成缓冲。虽容量为整屏,当前仍是分区缓冲模式,不是 RGB 显示的硬件换帧模式。
|
||||
|
||||
普通更新区域先合成完成,再进入 `lcd_direct_emote_flush()`。它将像素复制到一个 10,240 字节内部 DMA 暂存区,每批最多 5,120 像素。每批等中断通知传输完成后才复用暂存区;整个区域发送完再通知 GFX。
|
||||
|
||||
当前缓存图像全窗口更新时,242×97 像素通常需五批传输。这不是一次原子换帧。SPI 完成中断只释放信号量,不计算眼型、不解码、不打印日志。
|
||||
|
||||
DMA 等待超时后禁止继续复用可能仍被硬件读取的缓冲,GFX 传输路径需重启恢复。不能为了“继续显示”而清除故障标志并立即覆盖缓冲。
|
||||
|
||||
## 五、时间、电量及返回待机
|
||||
|
||||
左上显示系统本地时间,尚未同步时显示 `--:--`。当前配置为中国标准时间;时区应通过现有时间服务配置,不在显示模块硬编码。
|
||||
|
||||
右上显示领域层提供的电量百分比、有效性及充电状态。未知时显示 `--%`,充电显示绿色电池及闪电缺口,低电量显示红色。显示层不得从 ADC 电压自行重算电量。
|
||||
|
||||
状态栏复用内置 5×7 点阵数字和程序绘制图标,无需额外字体包。GFX 状态栏为 320×40 RGB565 图层,只在内容变化时刷新。PNG 表情在每帧合成时更新状态;二维码和屏幕边界测试不叠加它。
|
||||
|
||||
当前不能据此声称已实现任意中文文字。后续中文提示需要选择字体、字符子集、布局及字形缓存策略;数字时间和电量不需要引入完整中文字库或大型界面框架。
|
||||
|
||||
开机空闲、显式待机请求、有限动画结束、资源缺失及非取消播放错误都应进入统一待机后端。`lcd_direct_queue_idle_if_available()` 在状态锁内检查新请求和停止状态,不得改回无条件覆盖待处理槽。
|
||||
|
||||
历史 `jichubiaoqing` 请求在 `duration_ms=0` 且 GFX 就绪时也转到新路径,包括服务器错误的有限播放。只有 GFX 禁用或初始化失败才使用旧三帧后备。其他表情无需在这次迁移中全部替换。
|
||||
|
||||
## 六、资源目录、制作和替换
|
||||
|
||||
```text
|
||||
components/platform/assets/
|
||||
lcd_source/ 旧 PNG 源图,仍保留
|
||||
lcd_layers/jichubiaoqing/ 可编辑拆分图层、布局及预览
|
||||
components/platform/src/generated/emote_gfx/
|
||||
idle_base.c 实际编译的底图压缩数据
|
||||
idle_eyes_open.c 实际编译的睁眼压缩数据
|
||||
```
|
||||
|
||||
半闭眼、闭眼源图及对应历史生成文件不代表当前参与编译;以构建列表为准,不把目录里的全部文件自动加入固件,也不擅自删除旧资源。
|
||||
|
||||
在仓库根目录,确认 Python 环境有 Pillow 后,可执行:
|
||||
|
||||
```bash
|
||||
python3 tools/split_idle_expression.py
|
||||
python3 tools/compress_idle_expression.py
|
||||
```
|
||||
|
||||
第一条会从旧三帧重新生成拆分图层和预览,**会覆盖该目录中手工改过的图层**。仅替换已经拆好的图层时,通常只运行第二条。生成前先查看差异,保留美术编辑成果。
|
||||
|
||||
第二条会重新生成底图和睁眼的 C 压缩资产,并校验编码往返。当前脚本明确要求睁眼尺寸为 238×93,眼睛锚点固定为 `(41,65)`;它不是能适配所有角色的通用生成器。生成后的 C 数组不应手改。
|
||||
|
||||
替换资源时需要核对:
|
||||
|
||||
1. 原画布、裁剪尺寸、透明层和锚点是否一致。
|
||||
2. 两只眼睛中心、闭合中线、弧度和闭眼宽度是否仍适用;这些目前在代码中有专用参数。
|
||||
3. 新动作是否超出缓存窗口,是否会盖住时间、电量或静态脸部。
|
||||
4. 动作所有阶段是否在缓存表中;新增阶段不能静默复用错误眼型。
|
||||
5. 编码格式、颜色高低字节、透明度平面及显示字节交换是否一致,避免重复交换导致颜色错误。
|
||||
6. PNG 资产打包与 GFX 生成资产是两条不同管线;相关步骤参考现有素材指南。不能把图层 PNG 放进目录就认为 GFX 已经接入。
|
||||
|
||||
## 七、资源开销与取舍
|
||||
|
||||
| 当前常驻对象 | 明确像素缓冲占用 |
|
||||
| --- | ---: |
|
||||
| 解压后的底图与睁眼图层 | 97,926 字节 PSRAM |
|
||||
| 程序闭眼透明纹理 | 9,996 字节 PSRAM |
|
||||
| 时间电量图层 | 25,600 字节 PSRAM |
|
||||
| GFX 合成缓冲 | 153,600 字节 PSRAM |
|
||||
| 九个眼型缓存 | 422,532 字节 PSRAM |
|
||||
| 上述合计 | 709,654 字节 PSRAM |
|
||||
| GFX DMA 暂存区 | 10,240 字节内部 DMA 内存 |
|
||||
|
||||
此表不含对象管理、任务栈、SPI 描述符、PNG 缓存和系统其他模块。不能把表中合计当作整个显示组件的完整运行内存开销。
|
||||
|
||||
压缩源图像像素数据为 19,336 字节,不含描述信息和组件代码。运行时眼型缓存不写入 Flash。最新本地构建应用镜像为 3,040,480 字节,OTA 槽为 4,718,592 字节;这是当次构建快照,不是后续版本承诺。
|
||||
|
||||
实机日志曾显示联网、唤醒同时运行时,内部内存最低只剩约十 KiB,而 PSRAM 尚有数 MiB。后续优先考虑 PSRAM 和局部复用,不能因为外部内存充足就不断扩大 DMA 或内部任务栈。
|
||||
|
||||
## 八、性能日志如何读
|
||||
|
||||
通过 `menuconfig` 搜索 `TQ_LCD_EMOTE_GFX_TEST` 和 `TQ_LCD_EMOTE_FPS_LOG`。前者控制 GFX 待机路径;后者控制性能统计,默认关闭,当前本地测量配置开启。关闭统计不应改变动画节奏。
|
||||
|
||||
每轮完整眨眼输出一次 `emote_fps` 和 `emote_cost`,不在逐帧传输中打印:
|
||||
|
||||
- `blink_us`:真实眨眼总耗时,不含此前睁眼停留和该条日志输出。
|
||||
- `step_fps`:步骤数除以眨眼秒数;保持步骤也计入。
|
||||
- `eye_updates`:眼型步骤中确实完成至少一个区域传输的次数;多个 DMA 块不会重复计数。
|
||||
- `update_fps`:实际更新次数除以同一眨眼时长,不是仅运动段的瞬时频率。
|
||||
- `draw_avg_us`、`draw_max_us`:步骤绘制时间;平均包含没有传输的保持步骤。
|
||||
- `late_steps`:绘制达到或超过 34ms 的步骤数,不等于屏幕丢帧数,也不统计所有调度迟到。
|
||||
- `lock`:等待 GFX 锁;`compose`:眼型修改及合成、刷新处理,扣除复制和传输;`copy`:复制到 DMA;`spi`:提交并等待传输完成。
|
||||
|
||||
各项均为墙钟时间,可能包含被其他任务抢占。各项最大值可能来自不同步骤,不能相加。不能仅凭合成时间大就断言算法慢,也不能仅凭 SPI 等待大就断言线路带宽不足。
|
||||
|
||||
缓存前低负载合成平均约 19ms,缓存后约 1.4~1.8ms;缓存后前期更新吞吐率约 18.7~19.2 次/秒,后台任务运行后多数约 17 次/秒。这些是**固定时间轴修改之前**的设备样本,不应拿来宣称当前版本最终实测帧率。
|
||||
|
||||
## 九、后续开发建议
|
||||
|
||||
### 接入更多表情
|
||||
|
||||
先逐个迁移,保留未迁移 PNG 路径。每个表情准备静态层、动态图层、锚点、时间线、缓存预算及结束行为。固定短动作适合预生成缓存;大量不同动作或连续交互需要考虑按需生成、共享缓存和生命周期,不应给每个表情无上限常驻一份整屏缓存。
|
||||
|
||||
建议把几何参数和时间线整理为平台私有描述结构,避免每个表情复制一套线程、锁和 SPI 逻辑。表情语义仍由领域目录管理,不要往平台层添加服务器、联网或业务状态判断。
|
||||
|
||||
### 后续状态控制
|
||||
|
||||
当前不是完整状态机。建议控制层决定待机、监听、响应、联网、更新等状态何时进入和退出;领域层管理语义表情及触发原因;平台层执行播放、取消、完成及安全恢复。
|
||||
|
||||
改造前先定义取消与完成的区别、临时表情结束后恢复什么、并发请求优先级及超时语义。不能把停止或被抢占也记为成功完成,不能用“统一返回待机”覆盖正在执行的新状态。若需要恢复上一业务状态,而不是一律待机,应由控制层明确决定。
|
||||
|
||||
### 缩小缓存传输区域
|
||||
|
||||
当前每个缓存眼型传输固定窗口。下一阶段可按眼型裁剪,但必须刷新旧、新区域并集并恢复背景。由于缓存包含静态像素,不能只缩小新图片后留下旧眼圈。先验证残影、边界和状态栏遮挡,再比较吞吐率。
|
||||
|
||||
### 生命周期与解耦
|
||||
|
||||
缓存和 GFX 对象当前随 LCD 实例常驻,停止渲染循环不等于完整释放 GFX 引擎、图层和缓存。后续若支持重复初始化、热换资源或停机释放,需要明确停止、等待传输、注销对象、停止引擎和释放内存的顺序,补充错误注入测试。不要宣称当前已经具备完整热替换和销毁机制。
|
||||
|
||||
源码中的锁与显示完成等待来自现有组件实现。后续新增生命周期接口仍需有明确超时,不能把内部阻塞接口包装成没有边界的公开停止接口。
|
||||
|
||||
## 十、验证与交付
|
||||
|
||||
在仓库根目录执行:
|
||||
|
||||
```bash
|
||||
python3 tools/test_lcd_status_hud.py
|
||||
python3 tools/test_lcd_emote_transport.py
|
||||
python3 tools/test_lcd_emote_timeline.py
|
||||
```
|
||||
|
||||
这些脚本在电脑上从实际源码提取 C 函数,使用 Clang 与内存、未定义行为检查器。需要 Python 和 Clang;不是固件代码,不占设备 Flash 或 RAM。测试使用模拟接口,不能证明屏幕无撕裂、实时帧率或设备像素完全一致。
|
||||
|
||||
按根目录 `AGENTS.md` 激活 ESP-IDF 5.5.4,再执行 `idf.py build`。修改日志条件编译时还要检查开启与关闭两种配置,不能只验证当前开启的配置。
|
||||
|
||||
实机至少验证:
|
||||
|
||||
1. 缓存成功日志、首次生成时间及回退路径。
|
||||
2. 固定时间轴版本的帧率与分项耗时,低负载和唤醒、网络同时运行各采样。
|
||||
3. 向中间闭眼,无上移、残影、明显颜色差异或缓存窗口截断。
|
||||
4. 各表情切换后正确恢复待机,新请求不被旧动画结束覆盖。
|
||||
5. 跨分钟、首次时间同步、电量变化、充电插拔和二维码页面。
|
||||
6. 语音采集、唤醒、网络无明显退化;内部、DMA 和 PSRAM 的最低余量及分配失败。
|
||||
7. 新增停止或资源释放功能时的取消、超时和反复启动测试。
|
||||
|
||||
交付时分别报告源码检查、主机测试、编译和实机证据。不能把“编译通过”写成“设备已验证”,也不能只用目标帧率或一次录像承诺持续性能。
|
||||
@@ -1,7 +1,8 @@
|
||||
# 待机恢复与时间 / 电量状态栏
|
||||
|
||||
当前实现(2026-10-09)以本文为准,旧 lcd-expressions.md 中的测试图案、
|
||||
优先级仲裁描述不代表当前运行行为。
|
||||
完整现行说明见 [GFX 表情显示应用与开发指南](GFX表情显示应用与开发指南.md)。
|
||||
本文保留待机、状态栏和历次优化记录;旧 lcd-expressions.md 中的测试图案、
|
||||
优先级仲裁描述不代表当前运行行为。正常播放现在是缓存图像,不是逐帧网格。
|
||||
|
||||
## 返回待机
|
||||
|
||||
@@ -46,19 +47,85 @@ AddressSanitizer / UndefinedBehaviorSanitizer 检查 808 组有效性、充电
|
||||
## 无 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 blink composed draw_us_avg=... late_frames=.../12,
|
||||
+ 仅反映绘制预算,不是面板实际扫描帧率。
|
||||
+- DMA 超时会锁住 GFX 传输路径,不复用可能仍被硬件读取的缓冲;需重启恢复。
|
||||
+- 此方案不能保证面板扫描无撕裂。实机比较眨眼上下断层、闭眼亮点及原有
|
||||
+ 表情切换/状态栏行为;无 TE 时不能把软件预合成称为硬件同步双缓冲。
|
||||
普通眼睛更新先完成整个 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 量化及任务调度抖动,日志不是实测 FPS。
|
||||
|
||||
## 可开关的实测帧率日志
|
||||
|
||||
menuconfig 的屏幕配置中 `Log measured GFX blink FPS` 对应
|
||||
`CONFIG_TQ_LCD_EMOTE_FPS_LOG`。默认关闭,本次本地测试配置开启。关闭后
|
||||
统计计数和旧 draw_us_avg 日志均不编译,不改变动画/等待节奏。
|
||||
|
||||
每轮完整眨眼打印一次 `emote_fps`,被抢占/取消的轮次不打印:
|
||||
|
||||
- blink_us:实际整轮眨眼墙钟耗时,包含计算、SPI、等待和任务调度,
|
||||
不包含此前约 2 秒的静止睁眼时间,也不包含该条日志输出时间。
|
||||
- step_fps:12 个时序步骤 / 实测眨眼秒数,闭眼保持也算步骤。
|
||||
- eye_updates:步骤的刷新调用确实完成至少一个区域传输才计一次;
|
||||
一帧多个区域/多个 DMA 块不会重复计数,保持帧和等待期间的 HUD 更新不计。
|
||||
- update_fps:eye_updates / 相同的 blink_us,是本轮眼睛画面更新吞吐率,
|
||||
不是面板扫描率,也不是仅统计运动段的瞬时 FPS。
|
||||
- draw_avg_us / draw_max_us:步骤绘制及锁等待耗时,平均包含无需传输的保持步骤。
|
||||
- late_steps:绘制耗时达到或超过 34ms 帧预算的步骤数。
|
||||
|
||||
示例: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 个步骤计算,包含不传输的闭眼保持步骤;各项最大值可能来自不同步骤,
|
||||
不能相加。累计计数和快照都在 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 时钟,也未增加缓冲。
|
||||
|
||||
Reference in New Issue
Block a user