Files
STM32F4-Base/docs/GD5F2GQ5UE_Test_Guide.md
2026-08-27 18:22:44 +08:00

742 lines
31 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.
# GD5F2GQ5UE NAND Flash 测试规范
> 本规范参照 `docs/CH395F_Test_Guide.md` 结构编写,覆盖 `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c`
> 全部公开/私有函数,并向上延伸到 FTL`nand_ftl.c`)与 FatFS diskio。
> 详细芯片行为见 `docs/GD5F2GQ5UExxG.md`,已知陷阱见 `docs/GD5F2GQ5UE_Trap_Records.md`。
## 1. 概述
### 设计原则
- **同步 SPI 访问**GD5F 驱动全部为**同步阻塞** SPI 事务CS 拉低 → 命令/地址/数据 → CS 拉高),
由调用任务直接执行,**不经过消息队列**(与 CH395F 的 netTask 串行化模型不同)。
因此测试代码可在任意任务上下文调用,但要注意调用任务会被 SPI 传输阻塞数毫秒(擦除最久)。
- **先擦后写**NAND 只能将 1 写为 0**写操作前目标块必须已擦除**,否则数据不可预期(见边界条件 TC-GD5F-1003
- **ECC 默认开启**`gd5f2gq5ue_init()` 会置 `Feature(B0h).ECC_EN=1`,读/写均走内部 ECC。
- **坏块以 BBT 管理**出厂坏块标记在每块的第一页page 0spare byte 0列地址 0x800见手册 §12.4 / Table 12-6 / Note`init` 阶段扫描构建 RAM 中的 BBT。
### 芯片参数(关键参数)
| 参数 | 值 | 说明 |
|------|-----|------|
| 制造商/设备 ID | `0xC8` / `0x52` | `gd5f2gq5ue_read_id()` 校验依据 |
| 页数据大小 | 2048 B | `GD5F_PAGE_SIZE` |
| Spare 大小 | 64 B | `GD5F_SPARE_SIZE`ECC 开启时有效) |
| 单页总长 | 2112 B | `GD5F_TOTAL_PAGE_SIZE`ECC 开启) |
| 每块页数 | 64 | `GD5F_PAGES_PER_BLOCK` |
| 块大小 | 128 KB | `GD5F_BLOCK_SIZE = 64×2048` |
| 总块数 | 2048 | `GD5F_TOTAL_BLOCKS` |
| 总容量 | 256 MB | `GD5F_TOTAL_SIZE` |
| 最小擦除单位 | 1 块128 KB | 不可按页/字节擦除 |
| DMA 阈值 | 32 B | `GD5F_DMA_THRESHOLD`>32B 走 `HAL_SPI_*_DMA` |
### 寄存器与状态位
| 寄存器 | 地址 | 关键位 |
|--------|------|--------|
| Status | `0xC0` | OIP(b0) 忙标志、WEL(b1) 写使能、E_FAIL(b2)、P_FAIL(b3)、ECCS1/0(b5/b4) ECC 状态 |
| Feature | `0xB0` | ECC_EN(b4)、QE(b0) |
| Protect | `0xA0` | BP2/1/0 块保护init 后写为 `0x00` 解除保护 |
ECC 状态(`ECCS1:ECCS0``00`=无错;`01/10/11`=纠正 1/2/3 bit`10`ECCS1=1,ECCS0=0= 超出纠正能力(>4bit不可纠正
驱动 `gd5f_check_ecc()``(status>>4)&0x03 == 2` 时返回 `GD5F_ECC_ERROR`
### 测试环境
- **硬件**STM32F407ZGTx + GD5F2GQ5UESPI NAND
- **SPI 接口**SPI1CubeMX 初始化),引脚:
- CS=`PE0`SCK=`PB3`MISO=`PB4`MOSI=`PB5`WP=`PB8`HOLD=`PE1`
- **调试输出**USART1115200bps`[NAND]` 标签(`dbg_log.h`
- **擦除/编程耗时**参考驱动超时PAGE_READ 等待 100ms、PROGRAM_EXEC 等待 1000ms、BLOCK_ERASE 等待 5000ms
### 统一测试调度ch395fTestTask
所有测试套件CH395F / 存储 / GD5F统一收归 `ch395fTestTask` 线程(`test/ch395f_test_task.c`
`StartCh395fTestTask`),通过 `test/test_config.h` 中的 `TEST_SUITE_*` 宏在**编译期**选择要跑的套件。
选中 `TEST_SUITE_GD5F` 时,`test_config.h` 会自动 `#define ENABLE_GD5F_TESTS` 及全部
`ENABLE_GD5F_PHASE*_TESTS`,随后由 `gd5f_test_run()``test/gd5f_test_task.c`)按阶段串行执行。
> `defaultTask` **不再承担任何测试**(仅空闲);所有测试都只在 `ch395fTestTask` 中运行。
`test/test_config.h` 配置示例(三选一,互斥,多选触发 `#error`
```c
// #define TEST_SUITE_CH395F
// #define TEST_SUITE_STORAGE
#define TEST_SUITE_GD5F /* 选中后自动开启 gd5f 全部阶段宏 */
```
执行顺序(由 `gd5f_test_run()` 按阶段串行,与函数依赖一致):
`init/read_id/reset``BBT``页读写``块擦除``ECC``SPI 原语``DMA 边界``FTL``FatFS``边界条件`
### 启用测试
1.`test/test_config.h` 取消注释所需的 `TEST_SUITE_*` 宏(如 `TEST_SUITE_GD5F`
2. 编译(`@build`)确认 0 错误 0 警告;
3. 烧录后串口观察 `[NAND-TEST]` 日志与各 TC 的 `PASS/FAIL`,结尾打印
`=== GD5F2GQ5UE Tests: X/Y PASSED ===`
4. 不选任何 `TEST_SUITE_*` 时为正常产品构建(`ch395fTestTask` 空闲,不跑测试)。
---
## 2. 阶段 1初始化与 ID 识别
### TC-GD5F-101: gd5f2gq5ue_init 全流程
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-101 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | 初始化成功复位→ID 校验→BBT 扫描→ECC 使能→解除块保护) |
| **前置条件** | SPI1 与 GPIO 已由 CubeMX 初始化 |
| **测试步骤** | 1. 调用 `gd5f2gq5ue_init()`<br>2. 观察日志Resetting / Reading NAND ID / Scanning bad blocks / Enabling ECC / Unlocking block protection / NAND init OK |
| **通过标准** | 返回 `GD5F_OK`;日志无 `ID mismatch` / `Reset failed`BBT 扫描打印 bad count |
| **覆盖原则** | 初始化主路径 |
### TC-GD5F-102: gd5f2gq5ue_read_id 返回正确 ID
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-102 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | 读取 MID=0xC8、DID=0x52 |
| **前置条件** | `gd5f2gq5ue_init()` 已成功(或至少 SPI 已初始化) |
| **测试步骤** | 调用 `gd5f2gq5ue_read_id(&mid, &did)` 并比较 |
| **通过标准** | `mid==0xC8 && did==0x52` |
| **覆盖原则** | `gd5f2gq5ue_read_id` |
### TC-GD5F-103: gd5f2gq5ue_reset 成功
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-103 |
| **优先级** | P1 |
| **类型** | 功能测试 |
| **标题** | 软复位后状态寄存器 OIP 清零 |
| **前置条件** | SPI 已初始化 |
| **测试步骤** | 调用 `gd5f2gq5ue_reset()`;随后 `gd5f_read_status(&s)` 检查 `OIP==0` |
| **通过标准** | `reset` 返回 `GD5F_OK``s & GD5F_STATUS_OIP == 0` |
| **覆盖原则** | `gd5f2gq5ue_reset` / `gd5f_read_status` |
---
## 3. 阶段 2坏块管理BBT
> 注:`gd5f2gq5ue_mark_block_bad()` / `gd5f2gq5ue_bbt_clear()` **仅修改 RAM 中的 BBT 位图**
> 不会写回 Flash 物理标记(与头注释"尝试物理标记"不符,见陷阱节)。重启后由 `init` 重新扫描出厂标记。
### TC-GD5F-201: 出厂坏块扫描
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-201 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | init 后 BBT 扫描可完成并打印坏块数 |
| **前置条件** | `gd5f2gq5ue_init()` 已成功 |
| **测试步骤** | 观察 init 日志 `BBT scan: N bad blocks found`;调用 `gd5f2gq5ue_print_bbt()` |
| **通过标准** | 扫描完成N 为合理值,通常 0~数十);`print_bbt` 能列出坏块偏移 |
| **覆盖原则** | BBT 扫描(`gd5f_private_bbt_scan` |
### TC-GD5F-202: gd5f2gq5ue_is_block_bad 查询
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-202 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | 好块返回 0坏块返回 1 |
| **前置条件** | `gd5f2gq5ue_init()` 已成功 |
| **测试步骤** | 1. 对已知好块(如 block 0除非其为坏块`is_block_bad` 应返回 0<br>2. 对 `print_bbt` 列出的坏块 `is_block_bad` 应返回 1 |
| **通过标准** | 好块返回 0坏块返回 1越界 block 返回 1 |
| **覆盖原则** | `gd5f2gq5ue_is_block_bad` |
### TC-GD5F-203: mark_block_bad 与 is_block_bad 一致性
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-203 |
| **优先级** | P1 |
| **类型** | 功能测试 |
| **标题** | 标记后查询一致RAM BBT |
| **前置条件** | `gd5f2gq5ue_init()` 已成功 |
| **测试步骤** | 取一个好块 B`gd5f2gq5ue_mark_block_bad(B)``is_block_bad(B)==1`;再 `gd5f2gq5ue_bbt_clear()``is_block_bad(B)==0` |
| **通过标准** | mark 后查得 1clear 后查得 0RAM 行为符合预期) |
| **覆盖原则** | `gd5f2gq5ue_mark_block_bad` / `gd5f2gq5ue_bbt_clear` |
### TC-GD5F-204: 初始化 BBT 稳定读取(无需 rescan
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-204 |
| **优先级** | P2 |
| **类型** | 一致性测试 |
| **标题** | 初始化 BBT 可直接读取且稳定(无需 rescan |
| **前置条件** | `gd5f2gq5ue_init()` 已成功 |
| **测试步骤** | 记录 `print_bbt` 坏块数;再次 `print_bbt` 比较坏块数(验证 init BBT 可直接读取且稳定) |
| **通过标准** | 两次坏块数一致(出厂标记未变) |
| **覆盖原则** | `gd5f2gq5ue_is_block_bad` / `gd5f2gq5ue_bbt_clear` / `gd5f2gq5ue_mark_block_bad`init BBT 读取) |
---
## 4. 阶段 3页读写跨页
> 重要:写操作前目标区域必须已擦除(见阶段 4 / 边界条件 TC-GD5F-1003
### TC-GD5F-301: 整页写读
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-301 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | offset=0, size=2048 写入递增模式并读回比对 |
| **前置条件** | 目标块已擦除 |
| **测试步骤** | 1. 填 `buf[i]=i&0xFF`(或 `(offset+i)&0xFF`<br>2. `gd5f2gq5ue_write(0, buf, 2048)`<br>3. `gd5f2gq5ue_read(0, rbuf, 2048)` 逐字节比对 |
| **通过标准** | 读回与写入完全一致 |
| **覆盖原则** | `gd5f2gq5ue_write` / `gd5f2gq5ue_read`(单页路径) |
### TC-GD5F-302: 跨页写读
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-302 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | offset=1000, size=3000 跨越页边界 |
| **前置条件** | 涉及块已擦除 |
| **测试步骤** | 写入 3000 字节offset=1000 起),读回同窗口比对 |
| **通过标准** | 跨页数据连续正确(`gd5f2gq5ue_read/write` 自动分页) |
| **覆盖原则** | 跨页拆分逻辑 |
### TC-GD5F-303: 非对齐 offset 写读
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-303 |
| **优先级** | P1 |
| **类型** | 边界测试 |
| **标题** | offset=1, size=2047 |
| **前置条件** | 目标块已擦除 |
| **测试步骤** | 同 TC-GD5F-301但 offset=1 |
| **通过标准** | 读回与写入一致(列地址 = offset%2048 正确) |
| **覆盖原则** | 列地址计算 |
### TC-GD5F-304: 多页顺序写读
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-304 |
| **优先级** | P1 |
| **类型** | 压力测试 |
| **标题** | 连续 10 页offset=0, size=20480 |
| **前置条件** | 前 10 页所在块已擦除 |
| **测试步骤** | 写入 20480 字节递增模式,读回全量比对 |
| **通过标准** | 全部一致 |
| **覆盖原则** | 多页循环 |
### TC-GD5F-305: 随机单字节访问
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-305 |
| **优先级** | P2 |
| **类型** | 功能测试 |
| **标题** | 任意 offsetsize=1 写入/读回 |
| **前置条件** | 目标块已擦除 |
| **测试步骤** | 在若干随机 offset 写单字节并读回 |
| **通过标准** | 单字节值正确,且不影响同页其余字节(部分页编程约束内) |
| **覆盖原则** | 单字节 `program_load` 列偏移 |
---
## 5. 阶段 4块擦除
### TC-GD5F-401: 单块擦除后读全 0xFF
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-401 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | 擦除 block 0先确认非坏块读出应全 0xFF |
| **前置条件** | block 0 非坏块 |
| **测试步骤** | `gd5f2gq5ue_erase(0, 128*1024)`;读整块比对全 0xFF |
| **通过标准** | 返回 `GD5F_OK` 且整块读回全 0xFF |
| **覆盖原则** | `gd5f2gq5ue_erase` / `gd5f_block_erase` |
### TC-GD5F-402: 多块擦除
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-402 |
| **优先级** | P1 |
| **类型** | 功能测试 |
| **标题** | size=3 块384KB连续擦除 |
| **前置条件** | 对应块非坏块 |
| **测试步骤** | `gd5f2gq5ue_erase(0, 3*128*1024)`;读回比对全 0xFF |
| **通过标准** | 返回 `GD5F_OK`,范围全 0xFF |
| **覆盖原则** | 多块循环擦除 |
### TC-GD5F-403: 非块对齐应返回错误
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-403 |
| **优先级** | P0 |
| **类型** | 负向测试 |
| **标题** | offset 或 size 非 128KB 整数倍 |
| **前置条件** | `gd5f2gq5ue_init()` 已成功 |
| **测试步骤** | `gd5f2gq5ue_erase(100, 128*1024)`offset 不对齐);`gd5f2gq5ue_erase(0, 100)`size 不对齐) |
| **通过标准** | 两者均返回 `GD5F_ERROR` |
| **覆盖原则** | 对齐校验 |
### TC-GD5F-404: 擦除-写-读 完整循环
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-404 |
| **优先级** | P0 |
| **类型** | 集成测试 |
| **标题** | erase → write → read 闭环比对 |
| **前置条件** | 目标块非坏块 |
| **测试步骤** | 1. `erase` 目标块<br>2. `write` 已知模式(如 0xAA 填充/递增)<br>3. `read` 比对 |
| **通过标准** | 读回 == 写入(验证"先擦后写"链路) |
| **覆盖原则** | 完整 NAND 写流程 |
---
## 6. 阶段 5ECC 验证
### TC-GD5F-501: ECC 开启下写读正确
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-501 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | ECC 使能时数据正确ECC status 无错 |
| **前置条件** | `gd5f2gq5ue_init()`(已使能 ECC目标块已擦除 |
| **测试步骤** | 写随机/递增模式;`gd5f_page_read(page)` + `gd5f_read_from_cache` 读回;`gd5f_check_ecc()` 检查 |
| **通过标准** | 数据一致且 `gd5f_check_ecc()` 返回 `GD5F_OK`ECCS=00 无错) |
| **覆盖原则** | ECC 数据路径 |
### TC-GD5F-502: gd5f_check_ecc 干净读
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-502 |
| **优先级** | P1 |
| **类型** | 功能测试 |
| **标题** | 干净页 `gd5f_check_ecc()` 返回 OK |
| **前置条件** | 已 `gd5f_page_read` 加载页 |
| **测试步骤** | 读后调 `gd5f_check_ecc()` |
| **通过标准** | 返回 `GD5F_OK`(非 `GD5F_ECC_ERROR` |
| **覆盖原则** | `gd5f_check_ecc` |
### TC-GD5F-503: ECC 纠错能力(可选)
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-503 |
| **优先级** | P3 |
| **类型** | 可选/深入测试 |
| **标题** | 观察 ECCS 位在纠正场景下的变化 |
| **前置条件** | 已知好块ECC 开启 |
| **测试步骤** | 写入后读回,检查 `ECCS`00 无错 / 01~11 已纠正 bit人为制造位翻转较困难建议仅读状态位验证逻辑 |
| **通过标准** | `gd5f_check_ecc()` 在不可纠正时返回 `GD5F_ECC_ERROR`,否则 `GD5F_OK` |
| **覆盖原则** | ECC 状态位语义 |
---
## 7. 阶段 6SPI 原语FTL 共享)
> 下列函数为 `gd5f2gq5ue.c` 的私有/半公开原语FTL 直接复用;通过组合调用验证。
### TC-GD5F-601: page_read + read_from_cache 组合
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-601 |
| **优先级** | P1 |
| **类型** | 功能测试 |
| **标题** | 单页加载到 cache 并读出 |
| **前置条件** | 目标页有已编程数据 |
| **测试步骤** | `gd5f_page_read(page)``gd5f_read_from_cache(0, buf, 2048)` |
| **通过标准** | 数据正确,返回 `GD5F_OK` |
| **覆盖原则** | `gd5f_page_read` / `gd5f_read_from_cache` |
### TC-GD5F-602: program_load + program_exec 组合
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-602 |
| **优先级** | P1 |
| **类型** | 功能测试 |
| **标题** | 单页编程load→exec |
| **前置条件** | 目标块已擦除 |
| **测试步骤** | `gd5f_write_enable()``gd5f_program_load(0, buf, 2048)``gd5f_program_exec(page)`;读回比对 |
| **通过标准** | 编程后数据一致,无 `P_FAIL` |
| **覆盖原则** | `gd5f_program_load` / `gd5f_program_exec` |
### TC-GD5F-603: gd5f_block_erase 单块
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-603 |
| **优先级** | P1 |
| **类型** | 功能测试 |
| **标题** | 原语级块擦除(地址=块首页地址) |
| **前置条件** | SPI 已初始化 |
| **测试步骤** | `gd5f_block_erase(block)`(注意传**块编号**,内部转块首页地址,见陷阱 01 |
| **通过标准** | 返回 `GD5F_OK`,无 `E_FAIL` |
| **覆盖原则** | `gd5f_block_erase`Trap 01 修复点) |
### TC-GD5F-604: write_enable / read_statusWEL 位)
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-604 |
| **优先级** | P2 |
| **类型** | 功能测试 |
| **标题** | 写使能后 WEL 位置位 |
| **前置条件** | SPI 已初始化 |
| **测试步骤** | `gd5f_write_enable()``gd5f_read_status(&s)` 检查 `s & GD5F_STATUS_WEL` |
| **通过标准** | WEL 位为 1 |
| **覆盖原则** | `gd5f_write_enable` / `gd5f_read_status` |
### TC-GD5F-605: gd5f_wait_busy 正常返回
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-605 |
| **优先级** | P2 |
| **类型** | 功能测试 |
| **标题** | 操作完成后 wait_busy 在超时内返回 0 |
| **前置条件** | 执行一次读/写操作后 |
| **测试步骤** | 调 `gd5f_wait_busy(1000)`,检查返回值 |
| **通过标准** | 返回 `GD5F_OK`OIP 已清零) |
| **覆盖原则** | `gd5f_wait_busy` |
---
## 8. 阶段 7DMA 边界(阈值 32 字节)
> 驱动 `GD5F_DMA_THRESHOLD=32``size>32` 走 `HAL_SPI_*_DMA`,否则轮询。两路径结果必须一致。
### TC-GD5F-701: 阈值下界(轮询路径)
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-701 |
| **优先级** | P1 |
| **类型** | 边界测试 |
| **标题** | size=32 走非 DMA 路径,读写正确 |
| **前置条件** | 目标块已擦除 |
| **测试步骤** | 写/读 32 字节并比对 |
| **通过标准** | 数据一致 |
| **覆盖原则** | `GD5F_DMA_THRESHOLD` 下界 |
### TC-GD5F-702: 阈值上界DMA 路径)
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-702 |
| **优先级** | P1 |
| **类型** | 边界测试 |
| **标题** | size=33 走 DMA 路径,读写正确 |
| **前置条件** | 目标块已擦除 |
| **测试步骤** | 写/读 33 字节并比对 |
| **通过标准** | 数据一致 |
| **覆盖原则** | `GD5F_DMA_THRESHOLD` 上界 |
### TC-GD5F-703: 大块 DMA 一致性
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-703 |
| **优先级** | P1 |
| **类型** | 功能测试 |
| **标题** | size=2048 整页DMA 路径)读写正确 |
| **前置条件** | 目标块已擦除 |
| **测试步骤** | 同 TC-GD5F-301确认 DMA 路径全页正确 |
| **通过标准** | 数据一致 |
| **覆盖原则** | DMA 大数据路径 |
## 9. 阶段 8FTL 层nand_ftl
### TC-GD5F-801: nand_ftl_init 成功
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-801 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | FTL 初始化(在 driver init 之上建立映射) |
| **前置条件** | `gd5f2gq5ue_init()` 已成功 |
| **测试步骤** | 调用 `nand_ftl_init()` |
| **通过标准** | 返回 0日志无异常 |
| **覆盖原则** | `nand_ftl_init` |
### TC-GD5F-802: nand_ftl_format 成功
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-802 |
| **优先级** | P1 |
| **类型** | 功能测试 |
| **标题** | 格式化后可正常使用 |
| **前置条件** | `nand_ftl_init()` 已成功 |
| **测试步骤** | 调用 `nand_ftl_format()` |
| **通过标准** | 返回 0 |
| **覆盖原则** | `nand_ftl_format` |
---
## 10. 阶段 9FatFS diskio可选待 FTL 完成)
### TC-GD5F-901: 挂载 + 文件写读
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-901 |
| **优先级** | P2 |
| **类型** | 集成测试 |
| **标题** | FatFS 挂载 `/`,写文件后读回比对 |
| **前置条件** | FTL 已 init/formatFatFS diskio 已对接 |
| **测试步骤** | `f_mount``f_open` 写若干 KB → `f_close``f_open` 读回比对 |
| **通过标准** | 文件内容一致 |
| **覆盖原则** | FatFS diskio 链路 |
---
## 11. 边界条件
### TC-GD5F-1001: offset 越界
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-1001 |
| **优先级** | P1 |
| **类型** | 负向测试 |
| **标题** | offset >= TOTAL_SIZE 应返回错误 |
| **前置条件** | `gd5f2gq5ue_init()` 已成功 |
| **测试步骤** | `gd5f2gq5ue_read(GD5F_TOTAL_SIZE, buf, 1)` / `write` 同 |
| **通过标准** | 返回错误码(非 GD5F_OK |
| **覆盖原则** | 边界保护 |
### TC-GD5F-1002: size=0
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-1002 |
| **优先级** | P2 |
| **类型** | 负向测试 |
| **标题** | 零长度读写不崩溃 |
| **前置条件** | `gd5f2gq5ue_init()` 已成功 |
| **测试步骤** | `gd5f2gq5ue_read(0, buf, 0)` / `write(0, buf, 0)` |
| **通过标准** | 返回 GD5F_OK 或合理错误,无 HardFault |
| **覆盖原则** | 零长度处理 |
### TC-GD5F-1003: 未擦除区域写入
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-1003 |
| **优先级** | P0 |
| **类型** | 注意事项/负向 |
| **标题** | 写前未擦除 → 数据不可预期 |
| **前置条件** | 目标块含旧数据(未擦除) |
| **测试步骤** | 直接 `write` 新数据并 `read` 比对 |
| **通过标准** | **不通过比对**(验证 NAND "先擦后写" 特性;测试本身用于确认驱动不会误报成功) |
| **覆盖原则** | NAND 写约束 |
### TC-GD5F-1004: 写保护解除验证
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-1004 |
| **优先级** | P1 |
| **类型** | 功能测试 |
| **标题** | init 后 Protect 寄存器为 0x00可写 |
| **前置条件** | `gd5f2gq5ue_init()` 已成功 |
| **测试步骤** | `gd5f_private_set_feature` 读 Protect(0xA0);或间接由"可正常 program"推断 |
| **通过标准** | 块保护已解除,编程不返回 `GD5F_PROGRAM_FAIL` |
| **覆盖原则** | 块保护解除 |
---
---
## 12. 存储套件测试TEST_SUITE_STORAGE / storage_test_run
> 本套件独立于 GD5F 驱动套件,由 `test_config.h` 的 `TEST_SUITE_STORAGE` 选择,
> 入口 `storage_test_run()``test/storage_test_task.c`),统一在 `ch395fTestTask` 中调度。
> 当前 `storage_test_run()` 已覆盖 mkfs/mount/文件写读/性能(与阶段 9 FatFs 同类路径),
> 并新增下列三项**集成测试**TC-STO-01~03覆盖 FTL journal 掉电恢复、大文件/随机访问、坏块注入下的存储可用性。
> 三项均已于 STORAGE 套件实测通过(`=== Storage Tests: 11/11 PASSED (failed=0) ===`)。
>
> **已知陷阱(已修复)**FTL 页缓存用 `s_cached_lpn = 0` 表示"缓存未加载",但 LPN 0 是合法页FAT 引导扇区所在页)。
> resume/重新初始化后 `s_cached_lpn` 被复位为 0导致首次 `f_mount` 读 LBA0 时误判缓存命中、返回 `memset` 的全 0 缓存,
> 引导扇区签名变成 `0000`FatFs 报 `FR_NO_FILESYSTEM`。正常启动因 `f_mkfs` 兜底分支掩盖了此问题(第二次挂载才真正读盘)。
> 修复:将"缓存空"哨兵改为非法值 `(dhara_sector_t)-1`0xFFFFFFFF远超实际容量涉及 `disk_initialize` / `nand_ftl_deinit` / `nand_ftl_format`。
### TC-STO-01: FTL 掉电恢复journal 持久化)
| 字段 | 值 |
|------|-----|
| **ID** | TC-STO-01 |
| **优先级** | P1 |
| **类型** | 集成/可靠性测试 |
| **标题** | 写入文件并同步后,可经 `dhara_map_resume` 恢复映射,文件不丢 |
| **前置条件** | `gd5f2gq5ue_init()` + `nand_ftl_init()` 已成功FatFS 可挂载 |
| **测试步骤** | 1. `f_mount` → 写已知内容文件 `pl.dat`4KB 位置相关模式)→ `f_sync`/`f_close` 落盘<br>2. 模拟掉电:`f_mount("",0)` 卸载 → `nand_ftl_deinit()` 复位 FTL → `nand_ftl_init()` 重新走 `dhara_map_resume`<br>3. `f_mount("",1)` 重新挂载 → 打开 `pl.dat` 读回比对 |
| **通过标准** | 掉电模拟前后文件内容逐字节一致resume 后映射有效(无异常) |
| **覆盖原则** | `disk_initialize` / `dhara_map_resume` / FatFS 掉电安全 |
| **依赖** | `nand_ftl_deinit()`(清 `s_initialized`/`s_cached_lpn`,使下次 init 真正重新 resume |
| **实测** | PASSSTORAGE 套件resume 后 `pl.dat` 内容逐字节一致;修复 `s_cached_lpn` 哨兵 bug 后通过) |
### TC-STO-02: 大文件 / 多扇区 / 随机 seek
| 字段 | 值 |
|------|-----|
| **ID** | TC-STO-02 |
| **优先级** | P1 |
| **类型** | 功能/压力测试 |
| **标题** | 256KB 文件顺序写读 + 随机偏移 seek 读一致性 |
| **前置条件** | FTL 已初始化、FatFS 已挂载 |
| **测试步骤** | 1. 写 256KB 文件 `big.dat`,每字节按位置相关模式填充(便于任意偏移校验)<br>2. 顺序读回全量比对<br>3. `f_lseek` 到若干随机偏移(如 0/33KB/128KB/200KB/末段)读小块比对 |
| **通过标准** | 顺序与全部随机偏移读回内容均一致(验证 FTL 大范围映射 + GC + FatFS 随机访问) |
| **覆盖原则** | FTL 大范围 LBA 映射 / 垃圾回收 / FatFs `f_lseek` + `f_read` |
| **实测** | PASSSTORAGE 套件256KB 顺序 + 随机 seek 读回一致) |
### TC-STO-03: 坏块注入下存储写入
| 字段 | 值 |
|------|-----|
| **ID** | TC-STO-03 |
| **优先级** | P1 |
| **类型** | 负向/健壮性测试 |
| **标题** | 运行时注入坏块后FatFS 仍可正常写读且坏块未被占用 |
| **前置条件** | `gd5f2gq5ue_init()` 成功FTL 已复位 |
| **测试步骤** | 1. `nand_ftl_format()` 清空旧 map → 选若干出厂好块 `gd5f2gq5ue_mark_block_bad()` 注入(更新并持久化 BBT<br>2. `nand_ftl_deinit()` + `nand_ftl_init()` 使 dhara 经 `is_block_bad` 看到新坏块<br>3. `f_mkfs` 重建文件系统 → 正常文件写读 `bbt.dat` 并校验<br>4. 校验注入坏块仍 `gd5f2gq5ue_is_block_bad()==1` |
| **通过标准** | 文件内容完整;注入坏块未被 dhara 分配使用(仍报告坏),存储可用 |
| **注意事项** | 本例会**永久**将若干好块标记为坏并持久化(测试设备专用);如需恢复,运行 TC-STO-04启用 `ENABLE_STORAGE_RECOVERY_TESTS` 编译 STORAGE 套件一次)即可重建 BBT + 重新格式化 |
| **实测** | PASSSTORAGE 套件:注入 3 坏块后文件完整且坏块仍报告坏) |
### TC-STO-04: 重建 BBT + 重新格式化(恢复)
| 字段 | 值 |
|------|-----|
| **ID** | TC-STO-04 |
| **优先级** | P1 |
| **类型** | 恢复/清理测试 |
| **标题** | 重建 BBT重新扫描出厂坏块并持久化并清空 dhara map恢复设备干净态 |
| **前置条件** | `gd5f2gq5ue_init()` 成功;需启用 `ENABLE_STORAGE_RECOVERY_TESTS` 编译 |
| **测试步骤** | 1. `gd5f2gq5ue_bbt_rebuild()`(关 ECC → 重新扫描出厂坏块 → 开 ECC → `gd5f_bbt_save` 持久化,覆盖注入坏块)<br>2. `nand_ftl_format()` 清空 dhara map<br>3. `gd5f_bbt_dump_flash` 校验持久化 BBT 版本 ≥1 |
| **通过标准** | 重建返回 OK 且持久化 BBT 版本 ≥1设备坏块回到出厂集合注入坏块被清除 |
| **注意事项** | 运行一次即恢复;恢复后建议断电重启让 init 重新加载干净 BBT。本 TC 默认关闭,避免常规测试每次重置设备 |
| **依赖** | `gd5f2gq5ue_bbt_rebuild`(详见 §4.8 持久化设计) |
---
## 附录 B通过/失败汇总
| TC-ID | 标题 | 优先级 | 实测 |
|-------|------|--------|------|
| TC-GD5F-101 | init 全流程 | P0 | PASS |
| TC-GD5F-102 | read_id 正确 ID | P0 | PASS |
| TC-GD5F-103 | reset 成功 | P1 | PASS |
| TC-GD5F-201 | 出厂坏块扫描 | P0 | PASS |
| TC-GD5F-202 | is_block_bad 查询 | P0 | PASS |
| TC-GD5F-203 | mark/clear 一致性 | P1 | PASS |
| TC-GD5F-204 | init BBT 稳定(无需 rescan | P2 | PASS |
| TC-GD5F-205 | 可用块数 < 总块数(保留池生效) | P1 | PASS |
| TC-GD5F-206 | RAM 标记→闪存持久化bit+version | P1 | PASS |
| TC-GD5F-207 | 清 RAM→reload 恢复坏块 | P1 | PASS |
| TC-GD5F-301 | 整页写读 | P0 | PASS |
| TC-GD5F-302 | 跨页写读 | P0 | PASS |
| TC-GD5F-303 | 非对齐 offset | P1 | PASS |
| TC-GD5F-304 | 多页顺序写读 | P1 | PASS |
| TC-GD5F-305 | 随机单字节访问 | P2 | PASS |
| TC-GD5F-401 | 单块擦除读 0xFF | P0 | PASS |
| TC-GD5F-402 | 多块擦除 | P1 | PASS |
| TC-GD5F-403 | 非块对齐返回错误 | P0 | PASS |
| TC-GD5F-404 | 擦-写-读 闭环 | P0 | PASS |
| TC-GD5F-501 | ECC 开启写读 | P0 | PASS |
| TC-GD5F-502 | check_ecc 干净读 | P1 | PASS |
| TC-GD5F-503 | ECC 纠错(可选) | P3 | 未实现(可选) |
| TC-GD5F-601 | page_read+cache 组合 | P1 | PASS |
| TC-GD5F-602 | program_load+exec 组合 | P1 | PASS |
| TC-GD5F-603 | block_erase 原语 | P1 | PASS |
| TC-GD5F-604 | write_enable WEL 位 | P2 | PASS |
| TC-GD5F-605 | wait_busy 正常返回 | P2 | PASS |
| TC-GD5F-701 | DMA 阈值下界(32) | P1 | PASS |
| TC-GD5F-702 | DMA 阈值上界(33) | P1 | PASS |
| TC-GD5F-703 | 大块 DMA 一致性 | P1 | PASS |
| TC-GD5F-801 | nand_ftl_init | P0 | PASS |
| TC-GD5F-802 | nand_ftl_format | P1 | PASS |
| TC-GD5F-901 | FatFS 挂载写读 | P2 | PASS |
| TC-GD5F-1001 | offset 越界 | P1 | PASS |
| TC-GD5F-1002 | size=0 | P2 | PASS |
| TC-GD5F-1003 | 未擦除写入 | P0 | PASS |
| TC-GD5F-1004 | 写保护解除 | P1 | PASS |
| TC-STO-01 | FTL 掉电恢复journal | P1 | PASS |
| TC-STO-02 | 大文件/随机 seek | P1 | PASS |
| TC-STO-03 | 坏块注入下存储写入 | P1 | PASS |
| TC-STO-04 | BBT 重建 + 重新格式化(恢复) | P1 | 受 `ENABLE_STORAGE_RECOVERY_TESTS` 控制(默认关) |
> 当前实测:`TEST_SUITE_GD5F` 构建一次运行全部阶段,结尾输出 `=== GD5F2GQ5UE Tests: 65/65 PASSED (failed=0) ===`65 为各 TC 内部断言总数,含 TC-GD5F-205~207 BBT 持久化用例TC 覆盖见上表)。`TEST_SUITE_STORAGE` 套件结尾输出 `=== Storage Tests: 11/11 PASSED (failed=0) ===`TC-STO-01/02/03 各含若干内部断言,且 TC-STO-01 依赖的 `s_cached_lpn` 哨兵 bug 已修复)。两套件均 0 错误 0 警告通过 MDK 编译。
---
## 附录 C驱动函数覆盖
| 函数 | 覆盖 TC |
|------|---------|
| `gd5f2gq5ue_init` | TC-GD5F-101 |
| `gd5f2gq5ue_read_id` | TC-GD5F-102 |
| `gd5f2gq5ue_reset` | TC-GD5F-103 |
| `gd5f2gq5ue_read` | TC-GD5F-301/302/303/304/305 |
| `gd5f2gq5ue_write` | TC-GD5F-301/302/303/304/305 |
| `gd5f2gq5ue_erase` | TC-GD5F-401/402/403/404 |
| `gd5f2gq5ue_is_block_bad` | TC-GD5F-202 |
| `gd5f2gq5ue_mark_block_bad` | TC-GD5F-203 |
| `gd5f2gq5ue_bbt_clear` | TC-GD5F-203 |
| `gd5f2gq5ue_is_block_bad` / 初始化 BBT | TC-GD5F-204 |
| `gd5f2gq5ue_print_bbt` | TC-GD5F-201 |
| `gd5f_wait_busy` | TC-GD5F-605 |
| `gd5f_write_enable` | TC-GD5F-604 |
| `gd5f_read_status` | TC-GD5F-103/604 |
| `gd5f_page_read` | TC-GD5F-601 |
| `gd5f_read_from_cache` | TC-GD5F-601 |
| `gd5f_program_load` | TC-GD5F-602 |
| `gd5f_program_exec` | TC-GD5F-602 |
| `gd5f_block_erase` | TC-GD5F-603 |
| `gd5f_check_ecc` | TC-GD5F-501/502 |
| `nand_ftl_init` | TC-GD5F-801 |
| `nand_ftl_format` | TC-GD5F-802 |
| `nand_ftl_deinit` | TC-STO-01 / TC-STO-03 |
| `gd5f2gq5ue_bbt_rebuild` | TC-STO-04 |
| `storage_test_run`(存储套件) | TC-STO-01/02/03/04 |
---
## 附录 D参数速查
| 项 | 值 |
|----|-----|
| 页数据/Spare/总长 | 2048 / 64 / 2112 B |
| 每块页数 / 块大小 | 64 / 128 KB |
| 总块数 / 容量 | 2048 / 256 MB |
| ID | MID=0xC8, DID=0x52 |
| 擦除最小单位 | 1 块128 KB须对齐 |
| DMA 阈值 | 32 B>32 走 DMA |
| 状态寄存器 | 0xC0OIP=0, WEL=1, E_FAIL=2, P_FAIL=3, ECCS=4/5 |
| Feature 寄存器 | 0xB0ECC_EN=4, QE=0 |
| Protect 寄存器 | 0xA0init 后写 0x00 解除保护 |
| SPI 引脚 | CS=PE0, SCK=PB3, MISO=PB4, MOSI=PB5, WP=PB8, HOLD=PE1 |