修复 I1 绘制缓冲缺 8 字节 palette 头导致的越界写与整帧错位,补存储维护文档
This commit is contained in:
@@ -34,6 +34,7 @@ MCU **无物理屏幕**:LVGL 做 160×160 **1bpp 单色离屏渲染**,帧缓
|
||||
|---|---|---|
|
||||
| 屏幕 | 160×160,1bpp(`LV_COLOR_FORMAT_I1`) | `ui_port.h` |
|
||||
| 帧缓冲 | 3200 B(`g_ui_fbuf`,flush 回调整帧 memcpy) | `ui_port.c` |
|
||||
| 绘制缓冲 | **3200 + palette 8 B**(+32 B 余量):I1 索引色缓冲布局为 `[palette 8B][pixel data]`,见 1.4 第 8 条 | `ui_port.c` |
|
||||
| LVGL 内存池 | 40 KB(`LV_MEM_SIZE`) | `Lib/lv_conf.h` |
|
||||
| 刷新周期 | 10 ms(`ui_port_step(10)` + `osDelay(10)`) | `ui_task.c` |
|
||||
| 推流端口 | 7003(TCP,与 DTU-RemoteLCD 客户端对齐) | `ui_net_sink.h` |
|
||||
@@ -49,6 +50,12 @@ MCU **无物理屏幕**:LVGL 做 160×160 **1bpp 单色离屏渲染**,帧缓
|
||||
5. 模拟器与 MCU **共用** UI 源码(`ui_menu*.c` / `ui_font_cn_12.c`),禁止在 `simulator/` 下复制副本;新增文件须同时注册进 Keil `.uvprojx` 与模拟器 CMakeLists(双端注册)
|
||||
6. 验证顺序:模拟器预览 → `@build`(0 错 0 警)→ `@test`(Required 加 `UI port init: OK;UI menu init: OK`)
|
||||
7. **`LV_DRAW_SW_COMPLEX` 必须为 1**(两端均显式声明):主界面顶部"开口浅 V"装饰含斜线,`lv_line` 的斜段仅在复杂渲染器下绘制(`draw_line_skew`),关闭则只画水平线、两腿消失。且 `LV_DRAW_SW_CIRCLE_CACHE_SIZE` 须非零(默认 4),否则零长数组编译报错(C2466)
|
||||
8. **I1 绘制缓冲必须含 palette 头(2026-09-21 实证,别再踩)**:LVGL 9.3 索引色格式的 draw buffer 布局是 `[palette 8B][pixel data stride×h]`——
|
||||
`lv_draw_buf_create()` 按 `stride×h + LV_COLOR_INDEXED_PALETTE_SIZE(cf)×4` 分配,`lv_draw.c:497 → lv_draw_buf_goto_xy()` 取绘制指针时先 `+= palette`,`lv_refr.c get_max_row()` 也从 `data_size` 里扣掉 palette。
|
||||
但 `lv_display_set_buffers()` / `lv_draw_buf_init()` 的容量校验**只看 `stride×h`(不含 palette)** → 只给 3200 B 会被静默接受,后果有两条:
|
||||
① 末 8 个像素字节写到缓冲之外(曾把紧邻 `.bss` 的 `s_frame_box[0]` 指针打脏 → 野指针 HardFault;金丝雀实测越界 n=8);
|
||||
② flush 回调收到的首地址**含 palette**,照原样整帧拷贝会让推流画面**整体错位 8 字节**。
|
||||
故 `ui_port.c` 按 `stride×h + palette` 供给缓冲,上报尺寸含 palette,flush 时 `color_p + UI_DRAW_BUF_PALETTE` 取像素。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# STM32F4-Base 存储维护与容量恢复
|
||||
|
||||
> 版本:v1.0 日期:2026-09-21
|
||||
> 适用工程:STM32F4-Base(STM32F407ZGTx + GD5F2GQ5UE SPI NAND + dhara FTL + FatFs)
|
||||
> 关联:`docs/测试说明/GD5F2GQ5UE_Test_Guide.md`(破坏性用例清单)、`docs/内存管理/内存内容分配策略.md`(DMA 可达性守卫)
|
||||
|
||||
## 1. 三个维护动作,别混用
|
||||
|
||||
| 目标 | 开关组合 | 做了什么 | 代价 |
|
||||
|---|---|---|---|
|
||||
| **A. 卷/BBT 恢复** | `TEST_SUITE_STORAGE` + `ENABLE_STORAGE_RECOVERY_TESTS` | 重建 BBT(重扫出厂坏块并持久化)+ `nand_ftl_format()` 清 dhara map | 卷内容全失;**不恢复容量** |
|
||||
| **B. 容量恢复** | A 再叠加 `STORAGE_RECOVERY_FULL_WIPE` | 擦净 dhara 可见区 **2044 块**(连 journal checkpoint 一起)+ 重建 BBT;随后**断电重启** | 卷内容全失;2044 次块擦除(实测约 4 秒) |
|
||||
| **C. 日常回归** | 只开 `TEST_SUITE_*`,**不开** `ENABLE_DESTRUCTIVE_TESTS` | 只跑只读/参数校验用例,不写不擦不改坏块表 | 无 |
|
||||
|
||||
三个开关都在 `test/test_config.h`(A/B 的根开关在 `test/storage_test.h`),**默认全关**;维护完请重新注释并编译产品构建。
|
||||
|
||||
## 2. 为什么"容量"需要单独恢复(根因)
|
||||
|
||||
- 健康满容量 = **96064 pages(187 MB)**(`No valid map found, creating fresh` 分支下的满值)。
|
||||
- dhara 的坏块计数 `bb_current` / `bb_last` **持久化写在 journal checkpoint 头里**
|
||||
(`Lib/dhara/journal.c` 的 `hdr_set_bb_current/hdr_set_bb_last`,随 checkpoint 一起落盘)。
|
||||
容量按 `good_blocks = num_blocks − max(bb_last, bb_current) − 1` 递减。
|
||||
- `dhara_journal_clear()`(即 `nand_ftl_format()` 走的路)只做 `bb_last = bb_current`,**不归零**——
|
||||
所以动作 A 清不掉被扣掉的容量,断电重扫也一样(计数从 checkpoint 读回)。
|
||||
- 只有让下次启动 `dhara_map_resume` **失败**、走 `dhara_map_clear` 的**全新初始化**分支
|
||||
(此时 `bb_last = bb_current = 0`)才能回满 → 这就是动作 B 的原理。
|
||||
- 记账的触发:一次 `program verify` 失败(日志 `[ERR][NAND_FTL] program verify: data mismatch, blk N`)
|
||||
就让 dhara 多记一个坏块;破坏性套件(写完擦、坏块注入、格式化)会累积这个数字。
|
||||
|
||||
## 3. 操作步骤(以容量恢复为例)
|
||||
|
||||
1. `test/test_config.h`:取消注释 `TEST_SUITE_STORAGE`;
|
||||
2. `test/storage_test.h`:取消注释 `ENABLE_STORAGE_RECOVERY_TESTS` 与 `STORAGE_RECOVERY_FULL_WIPE`;
|
||||
3. `@build`(要求 0 错误 0 警告);
|
||||
4. 采集顺序 `open` → `clear` → 烧录 → `wait-idle`(擦除 2044 块约 4 秒,静默判据给 20~30s);
|
||||
5. 确认日志:
|
||||
|
||||
```
|
||||
=== Storage Test: TC-STO-04 maintenance (recovery / full wipe) ===
|
||||
[PASS] TC-STO-04 bbt rebuild
|
||||
wipe progress: ... 2044/2044
|
||||
wiped 2044 blocks (N skipped as bad, 2044 visible)
|
||||
[PASS] TC-STO-04 full wipe (2044 blocks erased)
|
||||
[PASS] TC-STO-04 persisted BBT version=N
|
||||
=== Storage Recovery: 3/3 PASSED (failed=0) ===
|
||||
```
|
||||
|
||||
6. **断电重启**(`MDK-ARM/reset.bat` 无参即可)——本次启动才会重建干净 journal;
|
||||
7. 恢复产品构建(两个头文件重新注释)→ `@build` → 烧录 → `@test`,确认:
|
||||
|
||||
```
|
||||
[NAND_FTL] No valid map found, creating fresh (err=3)
|
||||
[NAND_FTL] NAND FTL ready (capacity: 96064 pages = 187 MB)
|
||||
[FS] no FAT volume, formatting NAND... ← 首次会重建 FAT
|
||||
[FS] FATFS mounted
|
||||
```
|
||||
|
||||
## 4. 注意事项
|
||||
|
||||
- **单写者**:套件运行期间存储由 testTask 独占(defaultTask 打印 `mount/cfg skipped (STORAGE suite owns storage)`),勿并发访问。
|
||||
- **卷内容全失**:A/B 都会丢掉 `/log` 历史与 `/config.ini`(配置回落工厂默认,FAT 由启动流程自动重建)。
|
||||
- **坏块跳过**:全片擦除会自动跳过 BBT 中已记录的坏块(`skipped as bad`),保留块池(BBT 存储区)不动。
|
||||
- **别用 A 期待容量回来**:A 只重建 BBT + 格式化,容量仍是被扣掉的值——这是 2026-09-21 实测确认过的。
|
||||
- **别为了"省一次擦除"而手工擦单块**:容量是否恢复取决于 checkpoint 是否全部失效,只擦单块无法保证(`resume` 会锚定到更早的 checkpoint)。
|
||||
- 恢复后建议跑一次只读套件确认(GD5F 只读 16/16 或存储只读 3/3)。
|
||||
|
||||
## 5. 变更记录
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-09-21 | 新建:A/B/C 三个维护动作;容量恢复原理(dhara checkpoint 坏块计数不清零)与全片擦除恢复路径(实测 95200 → 96064) |
|
||||
Reference in New Issue
Block a user