文档结构调整

This commit is contained in:
2026-08-28 22:02:24 +08:00
parent 565f23ea18
commit 78eff31de7
9 changed files with 0 additions and 80 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,168 @@
# FTP 功能测试指导
> 适用对象STM32F4-Base 上基于 `lftpd` 的 FTP Server 功能验证。
> 配套源码:`Drivers/BSP/NET/lftpd/`、`App/task/net_task.c`、`Src/freertos.c`StartFtpTask
> 关联文档:`CH395F_Test_Guide.md`(网络测试总说明·阶段 8、`NET_Socket_FTP_Support_Plan.md`PASV 改造方案)、`CH395F_Trap_Records.md`。
---
## 1. 概述
- FTP Server`lftpd`)在**默认固件构建中即常驻运行**`Src/freertos.c``StartFtpTask`(默认 `__weak` 实现)直接调用 `lftpd_start("/", 21, &ftp)`,与测试套件选择无关。
- 监听端口 **21**,根目录 `/`(即 NAND 上的 FatFS 卷),**匿名登录**`USER anonymous` / `PASS` 任意)。
- 仅支持 **1 个并发客户端**(控制通道 Socket0 + PASV 数据通道动态 Socket2backlog=1
- 测试分两类:
- **PC 端手测(推荐,覆盖真实 FTP 行为)**:用 `test/` 下 Python 脚本或 GUI 客户端。
- **固件自动化TEST_SUITE_CH395F 阶段 8**TC-NET-801/802/803 文件传输。
---
## 2. 测试环境
### 2.1 硬件与网络
- CH395F 网口经以太网连接到 PC 或同一台交换机。
- PC 网卡与固件 IP **同网段**。固件默认静态 IP **192.168.1.100**(掩码 255.255.255.0PC 可设 192.168.1.x如 192.168.1.77)。
- 启动时 `netTask` 完成 `net_init()` + PHY 稳定(约 >2s`ftpTask` 另有 `osDelay(7000)` 等待后才 `listen`;若 `open_socket` 失败会 `vTaskDelay(3000)` 重试(最多 3 次)。**客户端应在上电约 7s 后、串口出现 `[FTP] waiting for connection...` 再连接。**
### 2.2 软件
- Python 3.x脚本位于 `test/`)。
- 可选 GUI 客户端FileZilla / MobaXterm / lftp。
---
## 3. 编译与运行(固件侧)
- **默认构建即可提供 FTP Server**,无需切换测试套件。
- 任何改动后必须执行 `@build`(即 `MDK-ARM/build.bat`),确认 **0 Error(s) 0 Warning(s)** 再烧录(遵循 AGENTS.md 铁律)。
- 若需跑**自动化 FTP 文件传输用例**:在 `test/test_config.h` 选择 `TEST_SUITE_CH395F`,并在 `test/ch395f_test.h` 打开 `ENABLE_PHASE8_TESTS`(文件传输阶段),重新编译烧录。
- 烧录后打开串口115200bps观察
- `net_init` 完成、PHY link up
- `[FTP] waiting for connection...`(约 3 次重试足够 PHY 稳定);
- 客户端连接后进入 FTP 会话(控制通道 220 欢迎)。
---
## 4. PC 端脚本手测(主要手段)
所有脚本支持 `--host`(默认 `192.168.1.100`)、`--port`(默认 21`test/` 目录运行。
### 4.1 冒烟测试:`ftp_test.py`
- 用途:验证连接、登录、`PWD``PASV``LIST` 基本链路。
- 命令:`python ./test/ftp_test.py --host 192.168.1.100`
- 预期:
- 连接成功并读到 `220` 欢迎;
- `USER anonymous` / `PASS` 返回 `230`
- `PASV` 返回 `227` 并解析出数据端口(或回退 `EPSV`
- `LIST` 返回目录列表;
- `QUIT` 正常关闭。
- 判据:全程无 `[NO RESPONSE]` / `CONNECT FAILED`,且 LIST 返回 `226` 即 PASS。**空卷下 LIST 数据通道为 0 字节属正常**(新格式化卷无文件),不必要求有目录列表输出。
### 4.2 上传 / 下载 / 比对:`ftp_up_dl_test.py`
- 用途:验证 `STOR`/`RETR`/`DELE` 与数据一致性(`TYPE I` 二进制 + `PASV`)。
- 命令:`python ./test/ftp_up_dl_test.py --host 192.168.1.100`
- 流程:登录 → `TYPE I``STOR up_test.dat``LIST` 校验存在 → `RETR up_test.dat` → 逐字节比对 → `DELE` 清理 → `QUIT`
- 预期:`*** MATCH! Upload/Download success. ***`,且 LIST 中出现 `up_test.dat`
- 判据:上传/下载字节数相等且内容逐字节一致。
### 4.3 速度与大数据量:`ftp_speed_test.py`
- 用途:测量上传/下载吞吐,作回归基线,同时验证大文件不丢字节。
- 命令:`python ./test/ftp_speed_test.py --host 192.168.1.100 --size 512`(默认 512KB`--size` 可调)。
- 预期:上传/下载均返回 `226`,回读 `MATCH!`,打印 KB/s。
- 注意:脚本使用 8.3 文件名 `SPEEDTST.DAT`**FTP 仅支持 8.3 短文件名**,见 §8.3以回读比对MATCH为最终判据中间打印的 Sent 字段仅供参考。
- 判据size 一致 + MATCH + 速度稳定(受 SPI2 分频 8 限制,通常约 300~450 KB/s参见 `CH395F_Test_Guide.md` 阶段 5 吞吐基线)。
### 4.4 抓包辅助:`ftp_capture.py`
- 用途:连接/传输过程中抓包,分析 PASV 端口、命令/数据通道交互,用于疑难排查。
- 用法参见脚本内 `--help` / 注释。
---
## 5. GUI 客户端手测(可选)
- **FileZilla**:主机 `192.168.1.100`,端口 21用户 `anonymous`,密码任意;可拖拽上传/下载、浏览目录。
- 要点:使用二进制模式传输;文件名遵守 8.3**一次只连一个客户端**。
---
## 6. 自动化测试TEST_SUITE_CH395F 阶段 8
- 入口:`ENABLE_PHASE8_TESTS` → 文件传输阶段100KB 文件)。
- 用例:
- `TC-NET-801`100KB 文件上发MCU→PC
- `TC-NET-802`100KB 文件下发PC→MCU
- `TC-NET-803`round-trip可选
- 详见 `docs/CH395F_Test_Guide.md` 阶段 8。
---
## 7. 测试清单Checklist
- [ ] 默认构建烧录,串口见 `[FTP] waiting for connection`
- [ ] `ftp_test.py` 冒烟 PASS连接 / 登录 / PASV / LIST
- [ ] `ftp_up_dl_test.py` 上传下载比对 `MATCH`
- [ ] `ftp_speed_test.py` 大文件 `MATCH` + 记录速度基线。
- [ ] 可选GUI 客户端手动上传/下载成功。
- [ ] (可选)阶段 8 自动化 `TC-NET-801/802/803` PASS。
---
## 8. 常见失败与排查(基于已知陷阱)
### 8.1 连接被拒 / 长时间无欢迎
- 确认 PC 与固件同网段、IP 正确;上电后等待约 7sPHY 稳定 + `ftpTask` 延迟)再连。
- 若反复 `open_socket` 失败:见 **TRAP-09**PHY 未稳定;固件已做 3 次重试 + 7s 延迟,仍失败检查网线/交换机)。
### 8.2 PASV 失败 / 数据通道连不上
- 确认 PC 防火墙未阻断高端口PASV 动态端口)。
- **数据通道必须先 `connect` 再发命令**(如 `LIST`/`STOR`),顺序反了会失败(脚本已遵循)。
- **TRAP-11**`net_accept_locked``ESTABLISHED` 状态导致 PASV 失败——需校验 `ESTABLISHED`
### 8.3 STOR 报 FR_INVALID_NAME (err=6)
- **8.3 文件名限制**`upload_test.dat`11+3超过 8.3 → `FR_INVALID_NAME`。改用短名如 `UPLOAD.DAT` / `SPEEDTST.DAT`。这是 FatFS LFN/8.3 约束,非驱动 bug。
### 8.4 大文件传输中途 RST / 速度抖动
- **TRAP-13** / 设计约束CH395F `WRITE_SEND_BUF` 芯片侧吞吐限制,单帧 >1KB 才稳定TCP 背压会"堵一会儿",属正常流控,不丢数据。
- 接收侧 `recv_len` 首包可能较大、次包可能为 0**必须循环读直到连接关闭**(脚本已处理)。
- 若持续 RST检查 **SPI 分频 = 8**PCB 设计限制)、`net_poll()` 周期10ms是否正常、中断是否统一在 `net_poll()` 中经 `GET_GLOB_INT_STATUS` 处理。
### 8.5 路径问题
- **TRAP-12**FatFS 根路径 `/` 前缀由 `to_fatfs_path()` 统一处理;客户端用绝对路径(如 `/UPLOAD.DAT`)即可。
### 8.6 单客户端限制
- FTP 仅接受 1 个并发客户端;上一个会话未 `QUIT` 前,新连接会被拒。每次测完务必 `QUIT` / 断开。
### 8.7 LIST/STOR/RETR 全部 550卷未挂载 / 无 FAT 文件系统
- 根因FTP 服务的是 NAND 上的 FatFS 卷。固件在 `StartDefaultTask` 启动期执行 `f_mount(&fs, "", 1)`(会触发 `disk_initialize``nand_ftl_init`),使卷可用。
- **首启自动建卷**:若 `f_mount` 返回 `FR_NO_FILESYSTEM`(如坏块重建/恢复测试后卷被清空),固件会自动 `f_mkfs`FM_FAT32、无分区表参数与存储测试一致再挂载。该过程上电会多花数秒格式化串口可见 `[FS] no FAT volume, formatting NAND...``[FS] FATFS mounted`
- 若串口见 `[FS] f_mount failed (fr=...)` 且非首次(已建卷仍失败):检查 `f_mkfs` 是否报错(`[FS] f_mkfs failed`),或 NAND 是否存在物理/ECC 错误;必要时用 `TEST_SUITE_STORAGE` 跑一次确认磁盘健康。
---
## 9. 诊断手段(沿用前期测试经验)
- **串口日志 + `DBG_*`**:在 `lftpd.c` / `net_socket` 关键路径加 `DBG_ERROR/INFO` 打印(注意编码安全铁律——只用 Edit 改中文源文件,禁止用 `Get/Set-Content` 整文件重写)。
- **抓包**`ftp_capture.py` 或 Wireshark 过滤 `tcp.port==21`
- **编译验证**`@build` 必须 0/0 再烧录AGENTS.md 铁律)。
- **套件切换**`test/test_config.h``TEST_SUITE_CH395F` / `TEST_SUITE_GD5F` / `TEST_SUITE_STORAGE`(三选一,互斥)。
---
## 10. 参考文档
- `docs/CH395F_Test_Guide.md` —— 网络测试总说明(含阶段 8 文件传输、FTP 陷阱清单)
- `docs/NET_Socket_FTP_Support_Plan.md` —— FTP / PASV Socket 改造方案
- `docs/CH395F_Trap_Records.md` —— CH395F 已知陷阱
- `docs/BSD_Socket_API_使用指南.md` —— BSD Socket API 用法

View File

@@ -0,0 +1,741 @@
# 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 |

View File

@@ -0,0 +1,105 @@
# SD2506_RTC 测试指南
> 对应驱动:`Drivers/BSP/SD2506/sd2506.c`
> 测试代码:`test/sd2506_test_task.c` + `test/sd2506_test.h`
## 1. 概述
本测试套件对 `sd2506.c` 做**黑盒功能自测**,只调用公共 API不触碰驱动内部函数。
每个用例通过 `SD2506_TEST_CHECK` 记录 pass/fail最终由 `SD2506_TEST_REPORT` 汇总。
历史背景:本驱动曾出现“时间写不进”问题,根因是寄存器宏写成 `0x0FH`KEIL/ARMCC
不识别 `H` 整数后缀,被编译为 0导致 `SD2506_REG_CTR1` 实际等于 0秒寄存器
`sd2506_write_enable()` 把 WRTC 写错地址、写保护永不解除。修正为 `0x0FU` 后恢复。
详见 `docs/SD2506_Trap_Records.md`
## 2. 启用与编译
`test/test_config.h` 中一次只能启用一个 `TEST_SUITE_*`(互斥):
```c
//#define TEST_SUITE_CH395F
//#define TEST_SUITE_STORAGE
//#define TEST_SUITE_GD5F
#define TEST_SUITE_RTC /* 启用 RTC 测试套件 */
```
`test/sd2506_test.h` 中控制各阶段(默认全开):
```c
#define ENABLE_RTC_BASIC_TESTS /* Phase 1: 基本读写 / 走时 */
#define ENABLE_RTC_API_TESTS /* Phase 2: 全功能 API 覆盖 */
```
编译(要求 0 错误 0 警告):
```bat
MDK-ARM\build.bat
```
烧录后在串口USART1, 115200bps观察 `[RTC_TEST]` 日志。
## 3. 测试用例
| 编号 | 目标 | 覆盖函数 | 判定 |
|------|------|----------|------|
| TC-RTC-001 | 初始化并读取当前时间 | `sd2506_init` / `sd2506_get_time` | init 返回 0get 返回 0 |
| TC-RTC-002 | 写固定时间立即回读一致 | `sd2506_set_time` / `sd2506_get_time` | set==readback允许 +1s 进位) |
| TC-RTC-003 | 走时验证 | `sd2506_get_time` | 延时 3s 后秒数前进 2~5s |
| TC-RTC-010 | BCD↔DEC 互转 | `sd2506_dec_to_bcd` / `sd2506_bcd_to_dec` | 10 组样本双向一致 |
| TC-RTC-011 | 内部温度读取 | `sd2506_get_temperature` | 返回 0值 ∈ [-40, 85] |
| TC-RTC-012 | 电池电压mV | `sd2506_get_battery_voltage` | 返回 0值 ∈ (2000, 5000) |
| TC-RTC-013 | 芯片 8 字节 ID | `sd2506_get_id` | 返回 0读出 8 字节 |
| TC-RTC-014 | 用户 SRAM 回环 | `sd2506_write_sram` / `sd2506_read_sram` | 写入 8 字节后回读一致 |
| TC-RTC-015 | 报警设置 + 清除 | `sd2506_set_alarm` / `sd2506_clear_alarm` | 两者均返回 0 |
## 4. 覆盖矩阵
| 公共 API | 是否覆盖 | 用例 |
|----------|----------|------|
| `sd2506_init` | ✅ | TC-RTC-001 |
| `sd2506_get_time` | ✅ | TC-RTC-001/002/003 |
| `sd2506_set_time` | ✅ | TC-RTC-002 |
| `sd2506_get_temperature` | ✅ | TC-RTC-011 |
| `sd2506_get_battery_voltage` | ✅ | TC-RTC-012 |
| `sd2506_get_id` | ✅ | TC-RTC-013 |
| `sd2506_read_sram` | ✅ | TC-RTC-014 |
| `sd2506_write_sram` | ✅ | TC-RTC-014 |
| `sd2506_set_alarm` | ✅ | TC-RTC-015 |
| `sd2506_clear_alarm` | ✅ | TC-RTC-015 |
| `sd2506_read_ctr1` | △(诊断) | run() 内打印 CTR1 |
| `sd2506_bcd_to_dec` / `sd2506_dec_to_bcd` | ✅ | TC-RTC-010 |
> 未覆盖(需专项验证,不在本自测范围):倒计时寄存器、温度报警历史、
> 跨重启持久化(原 Phase 3 已废弃,依赖实际掉电/上电,建议手动或另写用例)。
## 5. 运行与判定
- `sd2506_test_run()` 由测试调度器调用,先 init 并记录原始时间,最后用
`sd2506_set_time` 把板子时间**恢复**为测试前的值(不污染真实时钟)。
- 输出形如:
```
[RTC_TEST] === SD2506 Phase 1: basic read/write (public API) ===
[RTC_TEST] [PASS] sd2506_init() ret=0 (expect 0)
...
[RTC_TEST] === Phase 1 basic: 6/6 PASSED (failed=0) ===
[RTC_TEST] === SD2506 Phase 2: full API coverage ===
...
[RTC_TEST] === Phase 2 API: 9/9 PASSED (failed=0) ===
```
- 通过标准:所有 Phase 的 `failed=0`,且总量 `X/Y PASSED` 中 Y 与 PASS 数相等。
## 6. 失败排查
- **TC-RTC-002 FAILset≠readback**:优先查 `sd2506.h` 中各寄存器宏是否被写成
`0xNNH`H 后缀陷阱)。例如 `SD2506_REG_CTR1` 必须是 `0x0FU` 而非 `0x0FH`。
- **init/get/set 返回 -2I2C 错误)**:查 I2C1 接线PB6-SCL / PB7-SDA
上拉电阻、芯片供电与器件地址 `0x32`。
- **温度/电压越界**:多为芯片未上电或 I2C 读到全 0xFF按 I2C 链路排查。
## 7. 已知限制
- TC-RTC-015 会让 `sd2506_set_alarm` 置 `CTR2.INTAE=1`(报警中断允许)。
测试固件未挂 INT 中断处理,且 INT 引脚在板上未连接,对运行无影响;
若要完全复位,可手动再调用一次 `set_alarm(..., mask=0)`。
- SRAM 回环会在 `addr=0` 写入测试数据8 字节),属用户 SRAM 区域,不影响时间/控制寄存器。