Files
STM32F4-Base/docs/STM32F4-Base存储架构说明.md
2026-08-27 18:22:44 +08:00

559 lines
24 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.
# STM32F4-Base 存储架构说明
## 1. 概述
本 项目在 GD5F2GQ5UE SPI NAND Flash256MB上实现了三层存储软件栈
```
┌─────────────────────────────────────────────────────────────┐
│ 应用程序 (main, freertos) │
├─────────────────────────────────────────────────────────────┤
│ │
│ FatFs (文件系统) │
│ │
├─────────────────────────────────────────────────────────────┤
│ dhara FTL │
│ (地址映射 · 磨损均衡 · 坏块管理 · 垃圾回收) │
├─────────────────────────────────────────────────────────────┤
│ GD5F2GQ5UE NAND Flash 驱动 (底层 SPI) │
│ 硬件 SPI · 页读写 · 块擦除 · BBT · 内部 ECC 使能 │
├─────────────────────────────────────────────────────────────┤
│ SPI1 硬件外设 (PB3 SCK, PB4 MISO, PB5 MOSI) │
│ 42MHz · Mode 0 · MSB First · DMA2 (S0-RX, S3-TX) │
│ 小包轮询(≤32B) · 页数据 DMA (>32B) │
└─────────────────────────────────────────────────────────────┘
```
```
┌─────────────────────────────────────────────────────────────┐
│ 物理分区布局 (256MB) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ ftl_fatfs (256MB) │ │
│ │ Block 0~2047 │ │
│ │ dhara FTL + FatFS全部 2048 个块) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ 偏移: 0 256MB│
└─────────────────────────────────────────────────────────────┘
```
**数据路径:**
- **应用** → `FatFS``dhara FTL``gd5f2gq5ue 驱动`。FTL 提供 LBA 到物理页的映射、磨损均衡、坏块管理和垃圾回收FatFS 通过标准 `disk_*` 接口访问 FTL 提供的块设备。
**初始化顺序:**
```
HAL_Init → SystemClock_Config → MX_GPIO_Init → MX_USART1_UART_Init
→ MX_SPI2_Init → MX_SPI1_Init
→ gd5f2gq5ue_init() // NAND 底层驱动 (含 BBT 扫描 + ECC 使能)
→ app_main_init() // 应用层初始化
→ osKernelStart() // FreeRTOS 启动
├─ defaultTask → f_mount() → 存储测试 + 业务
├─ netTask → 网络协议栈轮询
├─ adcTask → ADC 采集
└─ rs485Task → RS485 通信
```
---
## 2. 硬件规格GD5F2GQ5UE
| 参数 | 值 |
|------|-----|
| 容量 | 256 MB (2 Gb) |
| 页大小 (主区) | 2048 bytes (2 KB) |
| 备校区 (Spare) | 64 bytes/页 |
| 每块页数 | 64 |
| 块大小 | **128 KB** (64 × 2 KB) |
| 总块数 | 2048 |
| 总页面数 | 131072 |
| 内部 ECC | 支持 (每页 8bit ECC) |
| 接口 | SPI (Mode 0, CPOL=0 CPHA=0) |
| 最高时钟 | 42 MHz |
| 读页延迟 | < 60 μs |
| 编程延迟 | < 600 μs |
| 块擦除延迟 | < 3 ms |
**SPI 引脚分配:**
| 信号 | GPIO | 说明 |
|------|------|------|
| CS# | PE0 | 片选 (低有效) |
| SCK | PB3 | SPI1 SCK |
| MISO | PB4 | SPI1 MISO (主入从出) |
| MOSI | PB5 | SPI1 MOSI (主出从入) |
| WP# | PB8 | 写保护 (本驱动恒拉高) |
| HOLD# | PE1 | 保持 (本驱动恒拉高) |
---
## 3. 分区布局
单个分区,配置在 `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c`
```c
#define FTL_FATFS_OFFSET 0 // 偏移 0占用全部 256MB
```
### 3.1 分区对照表
| 分区名 | 偏移 | 大小 | 物理块范围 | 逻辑用途 |
|--------|------|------|-----------|----------|
| `ftl_fatfs` | 0 | 256 MB | Block 0~2047 | dhara FTL + FatFS |
### 3.2 配置联动
- **FTL** — `nand_ftl.c``FTL_START_BLOCK = 0``FTL_NUM_BLOCKS = 2048`
- **FatFS** — 通过 `disk_ioctl(GET_SECTOR_COUNT)` 获取 FTL 管理的扇区数
---
## 4. 驱动层gd5f2gq5ue.c/h
### 4.1 SPI 命令集
| 命令 | 编码 | 用途 |
|------|------|------|
| `WRITE_ENABLE` | `0x06` | 写使能 (每个写/擦除前必须发送) |
| `WRITE_DISABLE` | `0x04` | 写禁止 |
| `GET_FEATURE` | `0x0F` | 读状态/feature 寄存器 |
| `SET_FEATURE` | `0x1F` | 写 feature 寄存器 (前需写使能) |
| `READ_ID` | `0x9F` | 读芯片 ID |
| `PAGE_READ` | `0x13` | 将页数据从存储阵列载入内部缓存 |
| `READ_FROM_CACHE` | `0x0B` | 从内部缓存读取数据 |
| `PROGRAM_LOAD` | `0x02` | 将数据写入内部缓存 |
| `PROGRAM_EXEC` | `0x10` | 将缓存数据编程到存储阵列 |
| `BLOCK_ERASE` | `0xD8` | 块擦除 (128KB) |
| `RESET` | `0xFF` | 芯片复位 |
### 4.2 Feature 寄存器
| 地址 | 名称 | 说明 |
|------|------|------|
| `0xA0` | Protect | 块保护 (写入 `0x00` 解除全部保护) |
| `0xB0` | Feature | ECC 使能 (bit4=1 启用) |
| `0xC0` | Status | 状态标志 (OIP/WEL/E_FAIL/P_FAIL/ECC) |
| `0xD0` | Driver | 驱动强度 |
### 4.3 初始化序列
```
1. RESET (FFh)
└─ 等待 5ms
2. READ_ID (9Fh)
└─ 校验 MID=0xC8, DID=0x52
3. BBT 扫描
└─ 读每块第一页 (page 0) spare byte 0列地址 0x800手册 §12.4 初始坏块标记位)
└─ 非 0xFF 即出厂坏块,写入 s_bbt[]
4. SET_FEATURE (B0h=10h) — 使能内部 8bit ECC
5. SET_FEATURE (A0h=00h) — 解除全部块保护
```
### 4.4 读写擦除操作
**页读取**(任何字节偏移均可,驱动自动定位到页):
```
PAGE_READ (13h + 3字节行地址) → 等待 OIP 清零 → READ_FROM_CACHE (0Bh + 2字节列地址 + dummy)
```
**页编程**NAND 只能将 bit 从 1→0 翻转,编程前必须擦除):
```
WRITE_ENABLE → PROGRAM_LOAD (02h + 列地址 + 数据) → PROGRAM_EXEC (10h + 行地址) → 等待完成 → 检查 P_FAIL
```
**块擦除**(最小擦除单位 128KB参数是字节地址而非块编号
```
WRITE_ENABLE → BLOCK_ERASE (D8h + 3字节字节地址) → 等待完成 → 检查 E_FAIL
```
### 4.5 DMA 传输策略2026-07-21 新增)
SPI1 使用 DMA2 实现页数据级别的不阻塞传输,配置如下:
| 通道 | DMA | 流 | 通道 | 方向 | 优先级 | 模式 |
|------|-----|----|------|------|--------|------|
| SPI1_RX | DMA2 | Stream 0 | CH3 | 外设→内存 | LOW | NORMAL |
| SPI1_TX | DMA2 | Stream 3 | CH3 | 内存→外设 | LOW | NORMAL |
**传输策略:**
- **小包轮询(≤ 32 bytes**:命令字、地址、状态寄存器等短数据使用 `HAL_SPI_Transmit/Receive` 轮询模式,避免 DMA 初始化开销
- **页数据 DMA> 32 bytes**`READ_FROM_CACHE``PROGRAM_LOAD` 的页数据段使用 `HAL_SPI_Receive_DMA` / `HAL_SPI_Transmit_DMA`
**HAL 内部路由说明:**
`HAL_SPI_Receive_DMA()` 在 2 线 Master 模式内部调用 `HAL_SPI_TransmitReceive_DMA()`,但由于 SPI 状态设置为 `HAL_SPI_STATE_BUSY_RX`HAL 的完成回调为 `HAL_SPI_RxCpltCallback`(而非 `TxRxCpltCallback`),需确保该回调在 `gd5f2gq5ue.c` 中实现。
**完整回调链条:**
| 操作 | 触发回调 | 实现位置 |
|------|---------|---------|
| `PROGRAM_LOAD` TX DMA | `HAL_SPI_TxCpltCallback` | `gd5f2gq5ue.c` |
| `READ_FROM_CACHE` RX DMA | `HAL_SPI_RxCpltCallback` | `gd5f2gq5ue.c` |
| 任意外设错误 | `HAL_SPI_ErrorCallback` | `ch395f.c`(含 SPI1 分支) |
**性能(顺序 128KB 读写FatFS + FTL**
| 模式 | 写 | 读 |
|------|----|----|
| 轮询(改造前) | 330 KB/s | 547 KB/s |
| DMA改造后 | **831 KB/s** | **1855 KB/s** |
| 42MHz SPI 理论极限 | ~5.25 MB/s受 NAND tPROG ≈ 500μs/页 限制) | ~5.25 MB/s |
DMA 消除了轮询模式下 SPI 状态寄存器查检的逐字节 CPU 开销,读写性能分别提升 **2.5×****3.4×**。进一步优化需考虑 Cache Read 模式(重叠 NAND 内部 tR 延迟)或批量编程(减少 tPROG 次数)。
### 4.6 BBT (Bad Block Table)
- 初始化时扫描全部 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慎用仅在重建时使用
- `gd5f_bbt_scan()` — 在 `gd5f2gq5ue_init()` 阶段、ECC 使能前扫描构建 BBT运行时不再提供公开重扫接口见 GD5F2GQ5UE_Trap_Records.md
- `gd5f2gq5ue_print_bbt()` — 打印 BBT 摘要(调试用)
> **注意**`gd5f_private_bbt_scan()` 选择在 ECC 使能前扫描。按手册 §12.6ECC 使能后整段 spare 仍可读取(仅 0x840~0x87F 的 ECC 校验区禁止编程),因此读取 0x800spare 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其中 **20480x800就是 spare 的第 0 字节**
**为什么坏块标记的列地址是 2048**main 区占据了前 2048 字节spare 区从 2048 起算,故 spare byte 0 落在列地址 20480x800。驱动把 `GD5F_PAGE_SIZE`=2048作为 `column` 传给 `gd5f_read_from_cache()``gd5f2gq5ue.c:307-308` 将其拼成 2 字节列地址),芯片即从 spare[0] 开始回数据——这正是出厂坏块标记位(手册 §12.4 / Table 12-6First 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 概述
FTL 是 NAND Flash 上方最重要的组件,功能包括:
| 功能 | 说明 |
|------|------|
| **地址映射** | 将 FatFS 的逻辑扇区号 (LBA) 映射到 NAND 物理页 |
| **磨损均衡** | 均匀分布擦除次数,延长芯片寿命 |
| **垃圾回收** | 回收无效页面空间 (GC) |
| **坏块管理** | 擦除/编程失败时自动跳过并标记坏块 |
| **ECC 处理** | 检测和上报硬件 ECC 不可纠正错误 |
### 5.2 数据结构
```c
struct dhara_nand {
int log2_page_size; // 页大小对数 (2KB → 11)
int log2_ppb; // 每块页数对数 (64 → 6)
int num_blocks; // 管理的物理块数 (1024)
};
struct dhara_map {
// 内部状态GC 队列、journal、映射表等
};
```
### 5.3 NAND HAL (nand_ftl.c)
dhara 通过 7 个回调函数访问物理 NAND
| 回调 | 功能 | 对应底层函数 |
|------|------|-------------|
| `dhara_nand_is_bad` | 查询坏块 | `gd5f2gq5ue_is_block_bad(block + 1024)` |
| `dhara_nand_mark_bad` | 标记坏块 | `gd5f2gq5ue_mark_block_bad(block + 1024)` |
| `dhara_nand_erase` | 擦除块 | `nand_block_erase(block + 1024)` |
| `dhara_nand_prog` | 写页 | `nand_program_load + nand_program_exec` |
| `dhara_nand_read` | 读页 | `nand_page_read_to_cache + nand_read_from_cache` |
| `dhara_nand_is_free` | 检查页空闲 | 读前 64 字节判断全为 0xFF |
| `dhara_nand_copy` | 页拷贝 (GC 用) | read + prog 组合 |
所有回调自动将 dhara 逻辑块/页加 `FTL_START_BLOCK` 偏移转换为物理地址。
### 5.4 初始化流程 (disk_initialize)
```
1. 设置 nand 参数 (log2_page_size=11, log2_ppb=6, num_blocks=1024)
2. dhara_map_init(&s_map, &s_nand, s_page_buf, 4)
├─ s_page_buf: dhara 内部使用的 2KB 工作缓冲区
└─ 4: journal 页面数量 (影响 GC 效率, 增大可减少写入放大)
3. dhara_map_resume(&s_map)
├─ 成功: 加载已有映射表
└─ 失败: dhara_map_clear 创建空映射表
4. 页面缓存初始化 (s_cache_buf, s_cached_lpn, s_cache_dirty)
```
### 5.5 页面缓存策略
FTL 之上还有一个 **单页写回缓存 (write-back cache)**
- **读命中**: 直接返回 s_cache_buf 数据
- **读未命中**: 刷出脏页 → 读新页到缓存
- **写**: 写入缓存 → 标记脏
- **全页写入**: 直接刷出 (跳过缓存)
- **同步 (CTRL_SYNC)**: 刷出脏页 + dhara_map_sync
---
## 6. FatFS 集成
### 6.1 配置 (ffconf.h)
```c
#define FF_FS_READONLY 0 // 读写模式
#define FF_USE_MKFS 1 // 启用格式化
#define FF_MIN_SS 512 // 最小扇区大小
#define FF_MAX_SS 512 // 最大扇区大小
#define FF_VOLUMES 1 // 单卷
#define FF_FS_TINY 0 // 非 tiny 模式
#define FF_FS_NORTC 1 // 无 RTC (固定时间戳)
```
### 6.2 disk I/O 接口
| 函数 | 功能 | 关键实现 |
|------|------|----------|
| `disk_initialize` | 初始化 FTL | 见 7.4 节 |
| `disk_status` | 查询状态 | 返回初始化状态 |
| `disk_read` | 读扇区 | 通过 FTL 映射读物理页 |
| `disk_write` | 写扇区 | 通过 FTL 映射写 (缓存优化) |
| `disk_ioctl` | 控制命令 | GET_SECTOR_COUNT/SIZE/BLOCK_SIZE + CTRL_SYNC |
### 6.3 容量计算
```
FTL 管理块数 = 2048 (Block 0~2047)
每块页数 = 64
每页扇区数 (512B) = 4
总扇区数 = 2048 × 64 × 4 = 524288
总容量 = 524288 × 512 = 256MB (原始容量)
FTL 开销后 ≈ 186 MB (随 GC 和 journal 使用量波动)
```
---
## 7. 完整数据流
### 7.1 写文件流程
```
f_write("hello.txt")
└─ FatFS: 计算 LBA, 写扇区
└─ disk_write(0, data, sector=100, count=2)
├─ 计算 LPN = sector / 4 = 25
├─ 缓存未命中 → ftl_flush_cache() → ftl_read_page(25)
├─ 拷贝数据到 s_cache_buf → 标记脏
└─ 全页面写入 → ftl_flush_cache()
└─ dhara_map_write(&s_map, 25, s_cache_buf)
├─ 查找页映射 (或分配新页)
├─ dhara_nand_prog(pg, data) → 物理写
└─ 更新映射表
f_close → disk_ioctl(CTRL_SYNC)
└─ ftl_flush_cache() → dhara_map_sync()
└─ 写 journal 到 NAND (持久化映射表)
```
### 7.2 f_mkfs 格式化流程
```
f_mkfs("", &opts, work, size)
├─ disk_initialize(0) → FTL 初始化
├─ disk_write: 写入引导扇区 (MBR/PBR)
├─ disk_write: 写入 FAT 表
├─ disk_write: 创建根目录
└─ disk_ioctl(CTRL_SYNC) → FTL sync
```
---
## 8. API 参考
### 8.1 底层 NAND 驱动 (gd5f2gq5ue.h)
| 函数 | 说明 |
|------|------|
| `gd5f2gq5ue_init()` | 初始化 NAND (BBT 扫描 + ECC 使能 + 块保护解除) |
| `gd5f2gq5ue_read_id(p_mid, p_did)` | 读芯片 ID |
| `gd5f2gq5ue_read(offset, buf, size)` | 读数据 (支持跨页) |
| `gd5f2gq5ue_write(offset, buf, size)` | 写数据 (支持跨页) |
| `gd5f2gq5ue_erase(offset, size)` | 块擦除 (需块对齐) |
| `gd5f2gq5ue_reset()` | 复位芯片 |
| `gd5f2gq5ue_is_block_bad(block)` | 查询坏块 |
| `gd5f2gq5ue_mark_block_bad(block)` | 标记坏块 |
| `gd5f2gq5ue_bbt_clear()` | 清空 BBT |
| `gd5f_bbt_scan()` | init 时扫描构建 BBT内部静态 |
| `gd5f2gq5ue_print_bbt()` | 打印 BBT |
**SPI 原语FTL 和测试共享):**
| 函数 | 说明 |
|------|------|
| `gd5f_wait_busy(timeout_ms)` | 轮询等待 OIP 清零 |
| `gd5f_write_enable()` | 发送 06h 写使能 |
| `gd5f_read_status(p_status)` | 读状态寄存器 (C0h) |
| `gd5f_page_read(page_addr)` | 页读取 (13h) |
| `gd5f_read_from_cache(column, buf, size)` | 读内部缓存 (0Bh) |
| `gd5f_program_load(column, buf, size)` | PROGRAM LOAD (02h) |
| `gd5f_program_exec(page_addr)` | PROGRAM EXEC (10h) |
| `gd5f_block_erase(block_addr)` | 块擦除 (D8h) |
| `gd5f_check_ecc()` | 检查 ECC 状态位 |
### 8.2 FTL/FatFS (via nand_ftl.c / ff.h)
| 函数 | 说明 |
|------|------|
| `f_mount(&fs, "", 1)` | 挂载文件系统 (首次调用自动初始化 FTL) |
| `f_mkfs("", &opts, work, size)` | 格式化 FAT32 |
| `f_open/f_close/f_read/f_write` | 标准文件操作 |
| `f_unlink("test.txt")` | 删除文件 |
| `disk_initialize(0)` | 显式初始化 FTL (通常由 f_mount 自动调用) |
| `nand_ftl_format()` | 直接格式化 FTL (清空映射表, 慎用) |
---
## 9. 使用示例
### 9.1 FatFS 文件操作
```c
#include "ff.h"
static FATFS fs;
static int fatfs_init(void) {
FRESULT res = f_mount(&fs, "", 1);
if (res == FR_NO_FILESYSTEM) {
/* 首次使用需格式化 */
MKFS_PARM opts = {FM_FAT32, 0, 0, 0, 0};
uint8_t work[512];
res = f_mkfs("", &opts, work, sizeof(work));
if (res != FR_OK) return -1;
res = f_mount(&fs, "", 1);
}
return (res == FR_OK) ? 0 : -1;
}
static void fatfs_write_read(void) {
FIL fil;
UINT bw, br;
const char *msg = "Hello Storage!";
char buf[32];
/* 写文件 */
f_open(&fil, "data.txt", FA_CREATE_ALWAYS | FA_WRITE);
f_write(&fil, msg, strlen(msg), &bw);
f_close(&fil);
/* 读文件 */
f_open(&fil, "data.txt", FA_READ);
f_read(&fil, buf, sizeof(buf), &br);
buf[br] = '\0';
f_close(&fil);
}
```
### 9.2 FTL 格式化
```c
#include "nand_ftl.h"
/* 注意: 此操作将清空 FTL 分区全部数据, 慎用 */
if (nand_ftl_format() == 0) {
DBG_INFO("FTL formatted");
}
```
---
## 10. 注意事项
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 中更新并立即通过 `gd5f_bbt_save()` 持久化到保留块池;下次复位后 `gd5f_bbt_scan()` 重建出厂坏块,`gd5f_bbt_load()` 再叠加保留池中保存的运行期坏块,因此运行期坏块在掉电后依然有效(详见 §4.8)。
5. **功耗** — 擦除操作最大耗时约 5ms (驱动超时设为 5s),页编程约 600ms (超时 1s),读写操作快。在低功耗场景需注意合理安排操作时序。
6. **缓存一致性** — 单页缓存 (s_cache_buf) 仅对 FatFS 层可见,多任务读写同一文件需在应用层同步。