Files
wandering1 4a4ad6bc5f chore(全仓): @build/@test 旧文档别名清退为技能调用名
现状口径:/keil-build 与 /serial-boot-test 是 DSH 会话的技能调用名,
不是 shell 命令;「名 + 命令行参数」的命令形态改为可执行体
run_boot_test.py 的真实调用。

- AGENTS.md:别名说明重写为「技能调用名不是 shell 命令」,两条目改
  「编译(/keil-build)」「串口回归(/serial-boot-test)」
- keil-build / serial-boot-test 两 skill 调用名与示例同步;task-worktree
  方案文档去掉 @build/@test 惯例从句;@task 保留(不在点名范围)
- 123 文件现状陈述全改;台架捕获、变更记录/时间线、rename 映射输入
  等 41 行历史证据保留旧名
- gen_memory_svg.py 6 处措辞随改,3 张 SVG 产物同批重跑
- 顺手补并行方案同行漏网计数 51→52 钩子

验证:提交期快钩子对 123 文件实跑全 Passed;check_doc_links --all
rc=0(3038 处、无法判定 0);check_mermaid --all rc=0;YAML 解析 52
钩子不变;skill-size rc=0;py_compile 全过;字节卫生零漂移。
2026-10-30 13:03:12 +08:00

38 KiB
Raw Permalink Blame History

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。

2026-10-03 错误通道硬化(见 问题记录/GD5F2GQ5UE_Trap_Records.md TRAP-GD5F-06): wait_busy 区分"超时"(GD5F_WAIT_EXPIRED,旧名 GD5F_BUSY_TIMEOUT)与"真故障"(GD5F_ERROR)、bbt_scan 失败放弃持久化、 init 全链路检查返回值 + 回读 B0h 校验 ECC 使能、read 的 raw 契约文档化、公开 API 统一 int32_t。 套件回归 === GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===。 ⚠ 编排器注意:TC-GD5F-704 会预期打出 [ERR][NAND] RX DMA buffer not DMA-reachable(回落轮询,用例 PASS)—— 用 run_all_suites.py 跑本套件时 --forbidden 须去掉 [ERR](脚本已按套件覆盖,见其 SUITES 注释)。

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 0)spare byte 0(列地址 0x800,见 docs/芯片手册/GD5F2GQ5UExxG.md §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 阈值 1024 B GD5F_DMA_THRESHOLD,>1024B 走 HAL_SPI_*_DMA;非 SRAM 缓冲由运行时守卫回落轮询(见阶段 7)

寄存器与状态位

寄存器 地址 关键位
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 + GD5F2GQ5UE(SPI NAND)
  • SPI 接口:SPI1(CubeMX 初始化),引脚:
    • CS=PE0,SCK=PB3,MISO=PB4,MOSI=PB5,WP=PB8,HOLD=PE1
  • 调试输出:USART1(115200bps),[NAND] 标签(dbg_log.h)
  • 擦除/编程耗时(参考驱动超时):PAGE_READ 等待 100ms、PROGRAM_EXEC 等待 1000ms、BLOCK_ERASE 等待 5000ms

统一测试调度(ch395fTestTask)

所有测试套件(CH395F / 存储 / GD5F)统一收归 ch395fTestTask 线程(test/board/ch395f_test_task.c 的 StartCh395fTestTask),通过 test/board/test_config.h 中的 TEST_SUITE_* 宏在编译期选择要跑的套件。 选中 TEST_SUITE_GD5F 时,test_config.h 会自动 #define ENABLE_GD5F_TESTS 及只读阶段宏 (PHASE1/2/10),随后由 gd5f_test_run()(test/board/gd5f_test_task.c)按阶段串行执行。

破坏性用例开关(2026-09-21 新增,默认关闭):test_config.h 的 ENABLE_DESTRUCTIVE_TESTS 控制所有会改变设备持久状态的用例——写/擦 NAND、nand_ftl_format、FatFS 建文件写文件、 向 BBT 持久化坏块标记(永久减少可用容量)。关闭时为只读模式,可随时在在用设备上跑; 打开后才编译并运行 PHASE3~9,以及 PHASE2 的 TC-GD5F-206/207、PHASE10 的 TC-GD5F-1003/1004。 依据:2026-09-21 实测一轮破坏性 GD5F 运行即让 dhara 多记一个坏块(容量 95248 → 95200; dhara 把该计数持久化在 journal checkpoint 头里,普通恢复模式清不掉——见下文容量恢复说明), 故破坏性用例只在产品首次上电/产线首启或明确需要时开启。

test/board/test_config.h 配置示例(三选一,互斥,多选触发 #error):

// #define TEST_SUITE_CH395F
// #define TEST_SUITE_STORAGE
#define TEST_SUITE_GD5F             /* 选中后自带 gd5f 只读阶段宏 */
// #define ENABLE_DESTRUCTIVE_TESTS  /* 需写/擦/格式化/落盘坏块时才打开(默认关闭) */

执行顺序(由 gd5f_test_run() 按阶段串行,与函数依赖一致):

  • 只读(默认):init/read_id/reset → BBT(读取与 RAM 内标记) → DMA 守卫读方向(TC-GD5F-704) → 边界条件(越界 / size=0)
  • 破坏性(开关打开时插入其间):页读写 → 块擦除 → ECC → SPI 原语 → DMA 边界(701~703、705) → FTL(含 format) → FatFS

开机横幅会明确打印本次模式:mode: READ-ONLY (...) 或 mode: DESTRUCTIVE (...)。

defaultTask 不再承担任何测试(仅空闲);所有测试都只在 ch395fTestTask 中运行。

启用测试

  1. 在 test/board/test_config.h 取消注释所需的 TEST_SUITE_* 宏(如 TEST_SUITE_GD5F), 需要写/擦类用例时再打开 ENABLE_DESTRUCTIVE_TESTS;
  2. 编译(/keil-build)确认 0 错误 0 警告;
  3. 烧录后串口观察 mode: ... 横幅、[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()
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
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 后查得 1,clear 后查得 0(RAM 行为符合预期)
覆盖原则 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)
2. gd5f2gq5ue_write(0, buf, 2048)
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
类型 功能测试
标题 任意 offset,size=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 目标块
2. write 已知模式(如 0xAA 填充/递增)
3. read 比对
通过标准 读回 == 写入(验证"先擦后写"链路)
覆盖原则 完整 NAND 写流程

6. 阶段 5:ECC 验证

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. 阶段 6:SPI 原语(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)(注意传块编号,内部转块首页地址,见TRAP-GD5F-01)
通过标准 返回 GD5F_OK,无 E_FAIL
覆盖原则 gd5f_block_erase(TRAP-GD5F-01 修复点)

TC-GD5F-604: write_enable / read_status(WEL 位)

字段 值
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. 阶段 7:DMA 边界与 DMA 可达性守卫(阈值 1024 字节)

驱动 GD5F_DMA_THRESHOLD=1024:size>1024 走 HAL_SPI_*_DMA,否则轮询。两路径结果必须一致。 前置约束:DMA 源/目的缓冲必须落在 SRAM(RW_IRAM1,0x20000000~0x2001FFFF)。CCM(0x10000000) 为 DMA1/DMA2 物理不可达区,误用会静默不落地;驱动已加运行时守卫 gd5f_dma_buf_ok(), 越界缓冲自动回落轮询并打印 [ERR][NAND] … not DMA-reachable … fallback poll。

TC-GD5F-701: 阈值下界(轮询路径)

字段 值
ID TC-GD5F-701
优先级 P1
类型 边界测试
标题 size=1024(= GD5F_DMA_THRESHOLD)走非 DMA 路径,读写正确
前置条件 目标块已擦除
测试步骤 写/读 1024 字节并比对
通过标准 数据一致
覆盖原则 GD5F_DMA_THRESHOLD 下界

TC-GD5F-702: 阈值上界(DMA 路径)

字段 值
ID TC-GD5F-702
优先级 P1
类型 边界测试
标题 size=1025(= GD5F_DMA_THRESHOLD+1)走 DMA 路径,读写正确
前置条件 目标块已擦除
测试步骤 写/读 1025 字节并比对
通过标准 数据一致
覆盖原则 GD5F_DMA_THRESHOLD 上界

TC-GD5F-703: 大块 DMA 一致性

字段 值
ID TC-GD5F-703
优先级 P1
类型 功能测试
标题 size=2048 整页(DMA 路径)读写正确
前置条件 目标块已擦除
测试步骤 同 TC-GD5F-301,确认 DMA 路径全页正确
通过标准 数据一致
覆盖原则 DMA 大数据路径

TC-GD5F-704: DMA 可达性守卫负路径(读方向,只读,两种模式都跑)

字段 值
ID TC-GD5F-704
优先级 P0
类型 边界/健壮性测试(只读,不受 ENABLE_DESTRUCTIVE_TESTS 门控)
标题 目的缓冲位于 CCM(不可达区)时守卫应拒绝 DMA 并回落轮询,数据仍正确
前置条件 FreeRTOS 堆驻留 CCM(scatter RW_CCM);探针缓冲由 pvPortMalloc(2048) 取得(堆不在 CCM 时用例跳过)
测试步骤 1. 先用 SRAM 静态缓冲参考读同页
2. 探针缓冲预填参考值的反码
3. gd5f2gq5ue_read() 整页读入 CCM 探针缓冲
4. 与参考缓冲逐字节比对
通过标准 数据与参考完全一致,且串口出现 [ERR][NAND] RX DMA buffer not DMA-reachable (p=0x1000xxxx size=2048), fallback poll
判定依据 本板实证 DMA 对 CCM 静默不落地:若误走 DMA,探针缓冲保持预填反码必失配——故"数据一致"即证明回落真实发生,而非仅"未报错"
覆盖原则 DMA 可达性守卫(读方向);堆缓冲禁入 DMA 的运行时强制(内存分配策略 R3)

TC-GD5F-705: DMA 可达性守卫负路径(写方向)

字段 值
ID TC-GD5F-705
优先级 P0
类型 边界/健壮性测试
标题 源缓冲位于 CCM(不可达区)时守卫应拒绝 DMA 并回落轮询,数据仍正确落盘
前置条件 同 TC-GD5F-704;目标块已擦除
测试步骤 1. 探针缓冲填入 i^0x5A 图案
2. gd5f2gq5ue_write() 整页写入
3. 读回 SRAM 静态缓冲逐字节比对
通过标准 写读一致,且串口出现 [ERR][NAND] TX DMA buffer not DMA-reachable … fallback poll
判定依据 同 TC-GD5F-704(写方向 DMA 读 CCM 同样不落地)
覆盖原则 DMA 可达性守卫(写方向);堆缓冲禁入 DMA 的运行时强制(内存分配策略 R3)

⚠ 卷破坏警告(仅 ENABLE_DESTRUCTIVE_TESTS 打开时):阶段 8 会调用 nand_ftl_format() 清空 dhara map,阶段 9 会写入文件——整轮运行后卷数据即失效;大写入还可能使 dhara 多记一个 坏块(2026-09-21 实测:一次整轮运行容量 95248 → 95200,日志见 [ERR][NAND_FTL] program verify: data mismatch, blk 305,该块被 dhara 隔离,测试仍 68/68 通过)。

容量记账与恢复(2026-09-21 读码 + 台架实证):

  • 健康满容量 = 96064 pages(187 MB)(No valid map found, creating fresh 分支下的满值)。
  • 容量随 dhara 计到的坏块数递减,且该计数持久化在 journal checkpoint 头里 (journal.c hdr_set_bb_current/bb_last);dhara_journal_clear() 只做 bb_last = bb_current(不归零)——所以 nand_ftl_format()(TC-STORAGE-04 普通恢复) 清不掉被扣掉的容量,断电重扫也一样。
  • 恢复满容量:启用 ENABLE_STORAGE_RECOVERY_TESTS 并叠加 STORAGE_RECOVERY_FULL_WIPE 跑一次 STORAGE 套件(擦净 dhara 可见区 2044 块 + 重建 BBT),随后断电重启—— dhara_map_resume 失败 → 走 dhara_map_clear 全新初始化(bb_last = bb_current = 0)→ 容量回满。 实测 2026-09-21:95200 → 96064 ✓(代价:卷内容全失,启动时 FatFs 自动重建)。

9. 阶段 8:FTL 层(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. 阶段 9:FatFS diskio(可选,待 FTL 完成)

TC-GD5F-901: 挂载 + 文件写读

字段 值
ID TC-GD5F-901
优先级 P2
类型 集成测试
标题 FatFS 挂载 /,写文件后读回比对
前置条件 FTL 已 init/format;FatFS 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/board/storage_test_task.c),统一在 ch395fTestTask 中调度。 当前 storage_test_run() 已覆盖 mkfs/mount/文件写读/性能(与阶段 9 FatFs 同类路径), 并新增下列三项集成测试(TC-STORAGE-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-STORAGE-01: FTL 掉电恢复(journal 持久化)

字段 值
ID TC-STORAGE-01
优先级 P1
类型 集成/可靠性测试
标题 写入文件并同步后,可经 dhara_map_resume 恢复映射,文件不丢
前置条件 gd5f2gq5ue_init() + nand_ftl_init() 已成功;FatFS 可挂载
测试步骤 1. f_mount → 写已知内容文件 pl.dat(4KB 位置相关模式)→ f_sync/f_close 落盘
2. 模拟掉电:f_mount("",0) 卸载 → nand_ftl_deinit() 复位 FTL → nand_ftl_init() 重新走 dhara_map_resume
3. f_mount("",1) 重新挂载 → 打开 pl.dat 读回比对
通过标准 掉电(模拟)前后文件内容逐字节一致;resume 后映射有效(无异常)
覆盖原则 disk_initialize / dhara_map_resume / FatFS 掉电安全
依赖 nand_ftl_deinit()(清 s_initialized/s_cached_lpn,使下次 init 真正重新 resume)
实测 PASS(STORAGE 套件:resume 后 pl.dat 内容逐字节一致;修复 s_cached_lpn 哨兵 bug 后通过)

TC-STORAGE-02: 大文件 / 多扇区 / 随机 seek

字段 值
ID TC-STORAGE-02
优先级 P1
类型 功能/压力测试
标题 256KB 文件顺序写读 + 随机偏移 seek 读一致性
前置条件 FTL 已初始化、FatFS 已挂载
测试步骤 1. 写 256KB 文件 big.dat,每字节按位置相关模式填充(便于任意偏移校验)
2. 顺序读回全量比对
3. f_lseek 到若干随机偏移(如 0/33KB/128KB/200KB/末段)读小块比对
通过标准 顺序与全部随机偏移读回内容均一致(验证 FTL 大范围映射 + GC + FatFS 随机访问)
覆盖原则 FTL 大范围 LBA 映射 / 垃圾回收 / FatFs f_lseek + f_read
实测 PASS(STORAGE 套件:256KB 顺序 + 随机 seek 读回一致)

TC-STORAGE-03: 坏块注入下存储写入

字段 值
ID TC-STORAGE-03
优先级 P1
类型 负向/健壮性测试
标题 运行时注入坏块后,FatFS 仍可正常写读且坏块未被占用
前置条件 gd5f2gq5ue_init() 成功;FTL 已复位
测试步骤 1. nand_ftl_format() 清空旧 map → 选若干出厂好块 gd5f2gq5ue_mark_block_bad() 注入(更新并持久化 BBT)
2. nand_ftl_deinit() + nand_ftl_init() 使 dhara 经 is_block_bad 看到新坏块
3. f_mkfs 重建文件系统 → 正常文件写读 bbt.dat 并校验
4. 校验注入坏块仍 gd5f2gq5ue_is_block_bad()==1
通过标准 文件内容完整;注入坏块未被 dhara 分配使用(仍报告坏),存储可用
注意事项 本例会永久将若干好块标记为坏并持久化(测试设备专用);如需恢复,运行 TC-STORAGE-04(启用 ENABLE_STORAGE_RECOVERY_TESTS 编译 STORAGE 套件一次)即可重建 BBT + 重新格式化
实测 PASS(STORAGE 套件:注入 3 坏块后文件完整且坏块仍报告坏)

TC-STORAGE-04: 重建 BBT + 重新格式化(恢复)

字段 值
ID TC-STORAGE-04
优先级 P1
类型 恢复/清理测试
标题 重建 BBT(重新扫描出厂坏块并持久化)并清空 dhara map,恢复设备干净态
前置条件 gd5f2gq5ue_init() 成功;需启用 ENABLE_STORAGE_RECOVERY_TESTS 编译
测试步骤 1. gd5f2gq5ue_bbt_rebuild()(关 ECC → 重新扫描出厂坏块 → 开 ECC → gd5f_bbt_save 持久化,覆盖注入坏块)
2. nand_ftl_format() 清空 dhara map
3. gd5f_bbt_dump_flash 校验持久化 BBT 版本 ≥1
通过标准 重建返回 OK 且持久化 BBT 版本 ≥1;设备坏块回到出厂集合,注入坏块被清除
注意事项 运行一次即恢复;恢复后建议断电重启让 init 重新加载干净 BBT。本 TC 默认关闭,避免常规测试每次重置设备
依赖 gd5f2gq5ue_bbt_rebuild(详见 §3 坏块管理与 BBT 持久化)

附录 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 阈值下界(1024) P1 PASS
TC-GD5F-702 DMA 阈值上界(1025) P1 PASS
TC-GD5F-703 大块 DMA 一致性 P1 PASS
TC-GD5F-704 DMA 守卫负路径(读,CCM) P0 PASS
TC-GD5F-705 DMA 守卫负路径(写,CCM) P0 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-STORAGE-01 FTL 掉电恢复(journal) P1 PASS
TC-STORAGE-02 大文件/随机 seek P1 PASS
TC-STORAGE-03 坏块注入下存储写入 P1 PASS
TC-STORAGE-04 BBT 重建 + 重新格式化(恢复) P1 受 ENABLE_STORAGE_RECOVERY_TESTS 控制(默认关)

当前实测(2026-09-21,ENABLE_DESTRUCTIVE_TESTS 关闭 / 打开两种配置): 只读模式 === GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===(PHASE1/2/7 那批只读阶段用例,含 704 那条只读守卫项;无任何写操作, 卷内容与容量均不变);破坏性模式 === GD5F2GQ5UE Tests: 68/68 PASSED (failed=0) ===(全部阶段)。 TEST_SUITE_STORAGE 只读模式实测 === Storage Tests: 3/3 PASSED (failed=0) ===(mount + TC-STORAGE-05, 卷内容与容量不变);破坏性模式为全量集成用例(2026-09-20 实测 20/20 PASSED,含 TC-STORAGE-01/02/03/05/06)。 各套件在两种模式下均 0 错误 0 警告通过 MDK 编译。

破坏性用例清单(仅在 ENABLE_DESTRUCTIVE_TESTS 打开时编译运行): GD5F:TC-GD5F-206/207、301305、401404、501502、601605、701703、705、801802、901、1003~1004 (TC-GD5F-704 为只读,两次运行都跑);存储:TC-STORAGE-01、02、03、06 与内建文件系统写读/ 性能压测(TC-STORAGE-05 为只读,始终运行);RTC:TC-RTC-002(写时间,跑完复原)、014(写 SRAM)、015(写报警)。


附录 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-STORAGE-01 / TC-STORAGE-03
gd5f2gq5ue_bbt_rebuild TC-STORAGE-04
storage_test_run(存储套件) TC-STORAGE-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 阈值 1024 B(>1024 走 DMA;非 SRAM 缓冲由守卫回落轮询)
状态寄存器 0xC0;OIP=0, WEL=1, E_FAIL=2, P_FAIL=3, ECCS=4/5
Feature 寄存器 0xB0;ECC_EN=4, QE=0
Protect 寄存器 0xA0;init 后写 0x00 解除保护
SPI 引脚 CS=PE0, SCK=PB3, MISO=PB4, MOSI=PB5, WP=PB8, HOLD=PE1