存储测试通过
This commit is contained in:
@@ -142,7 +142,7 @@ HAL_Init → SystemClock_Config → MX_GPIO_Init → MX_USART1_UART_Init
|
||||
2. READ_ID (9Fh)
|
||||
└─ 校验 MID=0xC8, DID=0x52
|
||||
3. BBT 扫描
|
||||
└─ 读每块最后一页 (page 63) spare byte 0
|
||||
└─ 读每块第一页 (page 0) spare byte 0(列地址 0x800,手册 §12.4 初始坏块标记位)
|
||||
└─ 非 0xFF 即出厂坏块,写入 s_bbt[]
|
||||
4. SET_FEATURE (B0h=10h) — 使能内部 8bit ECC
|
||||
5. SET_FEATURE (A0h=00h) — 解除全部块保护
|
||||
@@ -203,18 +203,111 @@ DMA 消除了轮询模式下 SPI 状态寄存器查检的逐字节 CPU 开销,
|
||||
|
||||
### 4.6 BBT (Bad Block Table)
|
||||
|
||||
- 初始化时扫描全部 2048 块最后一页的 spare byte 0
|
||||
- 初始化时扫描全部 2048 块第一页(page 0)的 spare byte 0(列地址 0x800)
|
||||
- `s_bbt[256]` 位图数组,1 bit 标识 1 个块 (0=好, 1=坏)
|
||||
- `gd5f2gq5ue_is_block_bad(block)` — 查询坏块状态
|
||||
- `gd5f2gq5ue_mark_block_bad(block)` — 标记坏块 (FTL 层在擦除/编程失败时调用)
|
||||
- `gd5f2gq5ue_bbt_clear()` — 清空 BBT(慎用,仅在重建时使用)
|
||||
- `gd5f2gq5ue_bbt_rescan()` — 重新扫描并重建 BBT(需 ECC 禁用时调用,见 GD5F2GQ5UE_Trap_Records.md)
|
||||
- `gd5f_bbt_scan()` — 在 `gd5f2gq5ue_init()` 阶段、ECC 使能前扫描构建 BBT;运行时不再提供公开重扫接口(见 GD5F2GQ5UE_Trap_Records.md)
|
||||
- `gd5f2gq5ue_print_bbt()` — 打印 BBT 摘要(调试用)
|
||||
|
||||
> **注意**:BBT 只能在 ECC 禁用时扫描(`gd5f_bbt_scan()`),ECC 使能后 spare area 会被 ECC 引擎干扰导致坏块检测不可靠。运行时坏块只来源于真实擦除/编程失败,不要主动重新扫描。
|
||||
> **注意**:`gd5f_private_bbt_scan()` 选择在 ECC 使能前扫描。按手册 §12.6,ECC 使能后整段 spare 仍可读取(仅 0x840~0x87F 的 ECC 校验区禁止编程),因此读取 0x800(spare byte 0)即便 ECC 开启也安全;但扫描放在 ECC 禁用阶段是更稳妥的默认做法。运行时坏块只来源于真实擦除/编程失败,不要主动重新扫描。
|
||||
|
||||
### 4.7 页内寻址模型(行地址 / 列地址)
|
||||
|
||||
NAND 访问是**二维寻址**,两个地址含义不同:
|
||||
|
||||
| 维度 | 含义 | 由谁发出 | 决定什么 |
|
||||
|------|------|----------|----------|
|
||||
| 行地址(页地址 / page address) | 第几页 | `gd5f_page_read(page)` | 把哪一页从存储阵列搬进芯片内部 cache 寄存器 |
|
||||
| 列地址(column address) | 该页内第几个字节 | `gd5f_read_from_cache(column, ...)`(发送 `0Bh` + 2 字节列地址) | 从 cache 里偏移 `column` 处开始吐数据 |
|
||||
|
||||
**一页的内部布局**(手册 §12.6 Table 12-8):一页 = 2KB 主数据 + 64B 备用区(spare) = 2112 字节,页内连续编址为 column 0 ~ 2111:
|
||||
|
||||
```
|
||||
page (2112B) = main(2048B) + spare(64B)
|
||||
column: 0 2047 2048 2111
|
||||
↑
|
||||
spare[0] / 坏块标记位 (0x800)
|
||||
```
|
||||
|
||||
- `column 0 ~ 2047` → 主数据区(main data)
|
||||
- `column 2048 ~ 2111` → 备用区(spare),其中 **2048(0x800)就是 spare 的第 0 字节**
|
||||
|
||||
**为什么坏块标记的列地址是 2048**:main 区占据了前 2048 字节,spare 区从 2048 起算,故 spare byte 0 落在列地址 2048(0x800)。驱动把 `GD5F_PAGE_SIZE`(=2048)作为 `column` 传给 `gd5f_read_from_cache()`(`gd5f2gq5ue.c:307-308` 将其拼成 2 字节列地址),芯片即从 spare[0] 开始回数据——这正是出厂坏块标记位(手册 §12.4 / Table 12-6:First spare area location = Byte 2048)。
|
||||
|
||||
> 一句话:**page 选页,column 选页内字节偏移;main 占前 2048 字节,spare 从 0x800 起算。**
|
||||
|
||||
### 4.8 BBT 持久化存储(2026-08-27 新增)
|
||||
|
||||
**需求背景**:出厂坏块标记写在每块首页 `spare[0]`(page 0),但 NAND 的**块擦除会同时清空主区与 spare 区**,因此运行时坏块(擦除/编程失败产生)无法回写到原厂标记位——否则该块一旦被擦除,坏块标记即丢失。必须把运行期坏块信息存到独立的、不参与擦写的**保留区域**,并在每次上电时加载回来。
|
||||
|
||||
**保留块池(BBT Pool)**:
|
||||
|
||||
- 从 NAND 顶部向下挑选 `GD5F_BBT_POOL_COUNT`(=4)个**出厂好块**作为 BBT 保留池,不交给 dhara 管理。
|
||||
- `gd5f_private_bbt_locate_pool()` 在 init 阶段从 `GD5F_TOTAL_BLOCKS-1` 向下收集前 4 个 `s_bbt[b]==0`(出厂好)的块号,存入 `s_bbt_pool_blocks[]`,并把 `s_usable_blocks` 设为其中**最低的块号**。
|
||||
- dhara 看到的可用块数 = `gd5f_get_usable_blocks()` = `s_usable_blocks`,即保留池以下的块;整个保留池(顶部 4 块)对文件系统不可见。文件系统可用块数由 2048 降为约 2044(若顶部块含出厂坏块则略少)。
|
||||
|
||||
**闪存上的 BBT 记录格式**(每个保留块 page 0):
|
||||
|
||||
```
|
||||
偏移 长度 字段
|
||||
0 4 magic = "GBBT"
|
||||
4 4 version (uint32 LE, 单调递增)
|
||||
8 4 len (uint32 LE, = GD5F_BBT_SIZE = 256)
|
||||
12 4 crc32 (uint32, 对下方 bitmap 计算, 多项式 0xEDB88320)
|
||||
16 256 bitmap (GD5F_BBT_SIZE 字节, 1 bit / 块, 与 RAM s_bbt 同布局)
|
||||
────────────────────────────────────
|
||||
合计 272 字节 (GD5F_BBT_HDR_SIZE + GD5F_BBT_SIZE)
|
||||
```
|
||||
|
||||
**写入(掉电安全,轮转)— `gd5f_bbt_save()`**:
|
||||
|
||||
- 版本号自增:`ver = s_bbt_version + 1`
|
||||
- 轮转写入下一保留槽 `s_bbt_write_slot`(环形指针,遇损坏槽 `s_bbt_slot_dead` 跳过)
|
||||
- 先 `BLOCK_ERASE` 目标保留块,再 `PROGRAM_LOAD(0)` 写入整页(header+bitmap),最后 `PROGRAM_EXEC`
|
||||
- 若某保留块自身擦除/编程失败,置 `s_bbt_slot_dead` 对应位并跳到下一槽
|
||||
- 每次 `gd5f2gq5ue_mark_block_bad()` 标记新坏块后都会调用 `gd5f_bbt_save()`;写入极罕见(仅坏块事件触发),磨损可忽略
|
||||
|
||||
**加载 — `gd5f_bbt_load()` → `gd5f_private_bbt_find_best()`**:
|
||||
|
||||
- 遍历全部保留块,校验 `magic` / `len` / `crc32`,丢弃损坏副本
|
||||
- 选出**版本号最高**的有效副本,将其 bitmap `OR` 进 RAM `s_bbt`(RAM 此时已含出厂坏块,`gd5f_bbt_scan` 先于本步执行)
|
||||
- 记录 `s_bbt_version` 与下一轮转槽 `s_bbt_write_slot`
|
||||
- 掉电保护:每次写入都是"擦除 + 整页编程"的完整新副本;若写入中途掉电,旧的高版本有效副本仍在,上电时按最高版本选取,不会读到半成品
|
||||
|
||||
**上电初始化顺序(与 §4.3 对应补充)**:
|
||||
|
||||
```
|
||||
1. RESET → READ_ID → 校验 MID/DID
|
||||
2. gd5f_bbt_scan() // 出厂坏块扫描 (ECC 关闭), 填充 s_bbt
|
||||
3. SET_FEATURE(0xB0,0x10) // 使能内部 8bit ECC
|
||||
4. SET_FEATURE(保护,0x00) // 解除块保护
|
||||
5. gd5f_private_bbt_locate_pool() // 选保留池, 设定 s_usable_blocks
|
||||
6. gd5f_bbt_load() // 叠加持久化的运行期坏块
|
||||
```
|
||||
|
||||
**调试 / 测试 API**:
|
||||
|
||||
| 函数 | 说明 |
|
||||
|------|------|
|
||||
| `gd5f_bbt_dump_flash(p_bbt, p_version)` | 读取闪存中当前生效的 BBT 位图与版本(验证持久化用) |
|
||||
| `gd5f_bbt_wipe_pool()` | 擦除保留块池(撤销持久化,回到仅出厂扫描态;出厂重置/测试用) |
|
||||
| `gd5f_bbt_reload()` | 从保留池重新加载持久化 BBT 到 RAM |
|
||||
|
||||
**关键常量**:
|
||||
|
||||
| 宏 / 变量 | 位置 | 值 | 说明 |
|
||||
|-----------|------|----|------|
|
||||
| `GD5F_BBT_POOL_COUNT` | `gd5f2gq5ue.h` | 4 | 保留块数量 |
|
||||
| `GD5F_BBT_SIZE` | `gd5f2gq5ue.c` | 256 (`=TOTAL_BLOCKS/8`) | 位图字节数(覆盖 2048 块,每块 1 bit) |
|
||||
| `GD5F_BBT_HDR_SIZE` | `gd5f2gq5ue.c` | 16 | 头部长度(magic+ver+len+crc) |
|
||||
| `s_bbt_magic` | `gd5f2gq5ue.c` | `"GBBT"` | BBT 存储魔数 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 5. dhara FTL (Flash Translation Layer)
|
||||
|
||||
### 5.1 概述
|
||||
@@ -371,7 +464,7 @@ f_mkfs("", &opts, work, size)
|
||||
| `gd5f2gq5ue_is_block_bad(block)` | 查询坏块 |
|
||||
| `gd5f2gq5ue_mark_block_bad(block)` | 标记坏块 |
|
||||
| `gd5f2gq5ue_bbt_clear()` | 清空 BBT |
|
||||
| `gd5f2gq5ue_bbt_rescan()` | 重新扫描 BBT |
|
||||
| `gd5f_bbt_scan()` | init 时扫描构建 BBT(内部静态) |
|
||||
| `gd5f2gq5ue_print_bbt()` | 打印 BBT |
|
||||
|
||||
**SPI 原语(FTL 和测试共享):**
|
||||
@@ -460,6 +553,6 @@ if (nand_ftl_format() == 0) {
|
||||
1. **擦除对齐** — `gd5f2gq5ue_erase()` 的 offset 和 size 必须严格按 GD5F_BLOCK_SIZE (128KB) 对齐和整数倍。
|
||||
2. **写前擦除** — NAND 不能原地覆写,FTL 内部自动管理擦除,但直接调用 `gd5f2gq5ue_write()` 前必须确保目标块已擦除。
|
||||
3. **FTL 首个扇区** — `dhara_map_init` 的第 5 个参数 (journal 页数) 影响 GC 效率,当前为 4,增大可减少写入放大但占用更多内存。
|
||||
4. **坏块传播** — FTL 在擦除/编程失败后自动调用 `dhara_nand_mark_bad` → `gd5f2gq5ue_mark_block_bad`,BBT 在 RAM 中更新,下次复位后重新扫描出厂坏块并叠加运行时坏块。
|
||||
4. **坏块传播** — FTL 在擦除/编程失败后自动调用 `dhara_nand_mark_bad` → `gd5f2gq5ue_mark_block_bad`,BBT 在 RAM 中更新并立即通过 `gd5f_bbt_save()` 持久化到保留块池;下次复位后 `gd5f_bbt_scan()` 重建出厂坏块,`gd5f_bbt_load()` 再叠加保留池中保存的运行期坏块,因此运行期坏块在掉电后依然有效(详见 §4.8)。
|
||||
5. **功耗** — 擦除操作最大耗时约 5ms (驱动超时设为 5s),页编程约 600ms (超时 1s),读写操作快。在低功耗场景需注意合理安排操作时序。
|
||||
6. **缓存一致性** — 单页缓存 (s_cache_buf) 仅对 FatFS 层可见,多任务读写同一文件需在应用层同步。
|
||||
|
||||
Reference in New Issue
Block a user