23 KiB
STM32F4-Base 存储架构说明
1. 概述
本项目在 GD5F2GQ5UE SPI NAND Flash(256MB)上实现了四层存储软件栈:
┌─────────────────────────────────────────────────────────────┐
│ 应用程序 (main, freertos) │
├──────────────────────┬──────────────────┬──────────────────┤
│ │ │ │
│ FlashDB KVDB │ FlashDB TSDB │ FatFs │
│ (键值数据库) │ (时序数据库) │ (文件系统) │
│ │ │ │
├──────────┬───────────┴──────┬───────────┴──────────────────┤
│ │ │ │
│ FAL 抽象层 (分区访问) │ dhara FTL │
│ (Flash Abstraction Layer)│ (地址映射 · 磨损均衡 │
│ 直接转发到底层驱动) │ 坏块管理 · 垃圾回收) │
│ │ │ │
├──────────┴──────────────────┴──────────────────────────────┤
│ 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) │
│ │
│ ┌──────────────────────┬──────────────────┬───────────────┐ │
│ │ fdb_kvdb1 (64MB) │ fdb_tsdb1 (64MB) │ ftl_fatfs │ │
│ │ Block 0~511 │ Block 512~1023 │ Block 1024~2047│ │
│ │ FlashDB 键值数据库 │ FlashDB 时序数据库│ dhara + FatFS │ │
│ └──────────────────────┴──────────────────┴───────────────┘ │
│ 偏移: 0 64MB 128MB 256MB│
└─────────────────────────────────────────────────────────────┘
两条独立路径:
- FlashDB 路径:
FlashDB → FAL → gd5f2gq5ue 驱动,FAL 是薄封装层,将 FAL 读写请求直接转发给底层 NAND 驱动。FlashDB 在 FAL 之上自行管理磨损均衡和掉电安全。 - FatFS 路径:
FatFS → dhara FTL → gd5f2gq5ue 驱动,FTL 提供 LBA 到物理页的映射、磨损均衡、垃圾回收和坏块管理,FatFS 通过标准disk_*接口访问 FTL 提供的块设备。
两个路径共享最底层 NAND 驱动,但各自管理不同的物理分区(FlashDB 管理 Block 01023,FTL 管理 Block 10242047),互不干扰。
初始化顺序:
SPI1_Init → gd5f2gq5ue_init() → fdb_kvdb_init() [可选,FlashDB 路径]
→ fdb_tsdb_init() [可选,FlashDB 路径]
→ f_mount() [触发 FTL 初始化,FatFS 路径]
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/fal_cfg.h:18-30:
#define FDB_KVDB1_OFFSET 0
#define FDB_KVDB1_SIZE (64 * 1024 * 1024) // 64MB
#define FDB_TSDB1_OFFSET (FDB_KVDB1_OFFSET + FDB_KVDB1_SIZE)
#define FDB_TSDB1_SIZE (64 * 1024 * 1024) // 64MB
#define FTL_FATFS_OFFSET (FDB_TSDB1_OFFSET + FDB_TSDB1_SIZE) // 128MB
#define FTL_FATFS_SIZE (128 * 1024 * 1024) // 128MB
3.1 分区对照表
| 分区名 | 偏移 | 大小 | 物理块范围 | 逻辑用途 |
|---|---|---|---|---|
fdb_kvdb1 |
0 | 64 MB | Block 0~511 | FlashDB 键值数据库 (KVDB) |
fdb_tsdb1 |
64 MB | 64 MB | Block 512~1023 | FlashDB 时序数据库 (TSDB) |
ftl_fatfs |
128 MB | 128 MB | Block 1024~2047 | dhara FTL + FatFS |
3.2 配置联动
所有分区边界在 fal_cfg.h 定义后,自动传播到各子模块:
- FAL 分区表 —
FAL_PART_TABLE宏直接引用FDB_KVDB1_OFFSET/SIZE、FDB_TSDB1_OFFSET/SIZE - FTL —
nand_ftl.c通过#include "fal_cfg.h"引用FTL_FATFS_OFFSET / GD5F_BLOCK_SIZE计算FTL_START_BLOCK = 1024 - FatFS — 通过
disk_ioctl(GET_SECTOR_COUNT)获取 FTL 管理的扇区数
调整分区大小时只需修改
fal_cfg.h顶部 6 个宏,所有下游模块自动适配。
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 63) spare byte 0
└─ 非 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 块最后一页的 spare byte 0
s_bbt[256]位图数组,1 bit 标识 1 个块 (0=好, 1=坏)gd5f2gq5ue_is_block_bad(block)— 查询坏块状态gd5f2gq5ue_mark_block_bad(block)— 标记坏块 (FTL 层在擦除/编程失败时调用)
5. FAL 抽象层
FAL (Flash Abstraction Layer) 是 FlashDB 自带的 flash 抽象层,仅 FlashDB 路径使用。它是一个薄封装层,将 FAL 的 read/write/erase 请求直接转发给底层 NAND 驱动,不做地址映射或磨损均衡(这些由 FlashDB 自身在 FAL 之上完成)。
FAL 不参与 FatFS/FTL 路径。FTL 有自己的 HAL 回调直接对接底层 NAND 驱动,与 FAL 无关。
FlashDB ──→ FAL ──→ gd5f2gq5ue_read/write/erase (直接透传)
FatFs ──→ dhara FTL ──→ dhara_nand_* 回调 ──→ gd5f2gq5ue_* (独立路径)
5.1 设备注册 (fal_flash_gd5f2gq5ue.c)
const struct fal_flash_dev g_gd5f2gq5ue_flash = {
.name = "gd5f2gq5ue",
.addr = 0,
.len = GD5F_TOTAL_SIZE, // 256MB
.blk_size = GD5F_BLOCK_SIZE, // 128KB
.ops = { .init, .read, .write, .erase },
.write_gran = 8, // byte programmable
};
5.2 分区表 (fal_cfg.h)
#define FAL_PART_TABLE \
{ \
{FAL_PART_MAGIC_WORD, "fdb_kvdb1", "gd5f2gq5ue", FDB_KVDB1_OFFSET, FDB_KVDB1_SIZE, 0}, \
{FAL_PART_MAGIC_WORD, "fdb_tsdb1", "gd5f2gq5ue", FDB_TSDB1_OFFSET, FDB_TSDB1_SIZE, 0}, \
}
FlashDB 通过分区名 ("fdb_kvdb1", "fdb_tsdb1") 绑定到 FAL 分区。
6. FlashDB 数据库
6.1 配置 (fdb_cfg.h)
#define FDB_USING_KVDB // 启用键值数据库
#define FDB_USING_TSDB // 启用时序数据库
#define FDB_USING_FAL_MODE // 使用 FAL 存储模式
#define FDB_WRITE_GRAN 8 // 写粒度 8bit (字节可编程)
6.2 查询接口
FlashDB 通过 FAL 分区名查找对应的分区设备,无需 FlashDB 驱动额外的设备注册。
6.3 KVDB 键值数据库
适用于存储配置参数、运行状态等少量键值对。
特点:
- 掉电安全 (写操作带 CRC 校验)
- 支持 blob (二进制数据块)
- 自动磨损均衡
- 支持默认值 (首次启动自动写入)
6.4 TSDB 时序数据库
适用于存储采样数据、日志记录等时间序列数据。
特点:
- 固定长度记录
- 时间戳索引 (需提供
get_time回调) - 自动擦除 (新数据覆盖最旧数据)
- 支持按时间范围查询
7. dhara FTL (Flash Translation Layer)
7.1 概述
FTL 是 NAND Flash 上方最重要的组件,功能包括:
| 功能 | 说明 |
|---|---|
| 地址映射 | 将 FatFS 的逻辑扇区号 (LBA) 映射到 NAND 物理页 |
| 磨损均衡 | 均匀分布擦除次数,延长芯片寿命 |
| 垃圾回收 | 回收无效页面空间 (GC) |
| 坏块管理 | 擦除/编程失败时自动跳过并标记坏块 |
| ECC 处理 | 检测和上报硬件 ECC 不可纠正错误 |
7.2 数据结构
struct dhara_nand {
int log2_page_size; // 页大小对数 (2KB → 11)
int log2_ppb; // 每块页数对数 (64 → 6)
int num_blocks; // 管理的物理块数 (1024)
};
struct dhara_map {
// 内部状态:GC 队列、journal、映射表等
};
7.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 偏移转换为物理地址。
7.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)
7.5 页面缓存策略
FTL 之上还有一个 单页写回缓存 (write-back cache):
- 读命中: 直接返回 s_cache_buf 数据
- 读未命中: 刷出脏页 → 读新页到缓存
- 写: 写入缓存 → 标记脏
- 全页写入: 直接刷出 (跳过缓存)
- 同步 (CTRL_SYNC): 刷出脏页 + dhara_map_sync
8. FatFS 集成
8.1 配置 (ffconf.h)
#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 (固定时间戳)
8.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 |
8.3 容量计算
FTL 管理块数 = 1024 (Block 1024~2047)
每块页数 = 64
每页扇区数 (512B) = 4
总扇区数 = 1024 × 64 × 4 = 262144
总容量 = 262144 × 512 = 128MB (原始容量)
FTL 开销后 ≈ 93 MB (随 GC 和 journal 使用量波动)
9. 完整数据流
9.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 (持久化映射表)
9.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
10. API 参考
10.1 底层 NAND 驱动 (gd5f2gq5ue.h)
| 函数 | 说明 |
|---|---|
gd5f2gq5ue_init() |
初始化 NAND (含 ECC 使能、块保护解除) |
gd5f2gq5ue_read_id(mid, 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) |
标记坏块 |
10.2 FAL 接口 (via fal_flash_gd5f2gq5ue.c)
| 函数 | 说明 |
|---|---|
g_gd5f2gq5ue_flash |
FAL 设备实例 |
fal_flash_device_find("gd5f2gq5ue") |
查找 Flash 设备 |
fal_partition_find("fdb_kvdb1") |
查找分区 |
fal_partition_read/write/erase |
分区读写擦除 |
10.3 FlashDB (via FlashDB 库)
| 函数 | 说明 |
|---|---|
fdb_kvdb_init(&db, "kvdb1", "fdb_kvdb1", NULL, NULL) |
初始化 KVDB |
fdb_kvdb_set(&db, "key", "value") |
写键值 (字符串) |
fdb_kvdb_get(&db, "key", &data) |
读键值 |
fdb_kvdb_del(&db, "key") |
删除键值 |
fdb_kvdb_set_blob(&db, "key", &blob) |
写二进制 blob |
fdb_tsdb_init(&db, "tsdb1", "fdb_tsdb1", get_time, 1024, NULL) |
初始化 TSDB |
fdb_tsl_append(&db, data) |
追加时序记录 |
fdb_tsl_iter_by_time(&db, from, to, cb, cb_arg) |
时间范围查询 |
10.4 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 (清空映射表, 慎用) |
11. 使用示例
11.1 KVDB 初始化与使用
#include "flashdb.h"
#include "fdb_cfg.h"
/* KVDB 实例 */
static fdb_kvdb_t g_kvdb;
static int kvdb_init(void) {
fdb_err_t ret = fdb_kvdb_init(&g_kvdb, "kvdb1", "fdb_kvdb1", NULL, NULL);
return (ret == FDB_NO_ERR) ? 0 : -1;
}
static void kvdb_example(void) {
/* 写字符串 */
fdb_kvdb_set(&g_kvdb, "device_id", "STM32F407-001");
/* 读字符串 */
char buf[64];
fdb_kvdb_get(&g_kvdb, "device_id", buf, sizeof(buf));
/* 写二进制 blob */
struct fdb_blob blob;
uint32_t value = 42;
fdb_blob_make(&blob, &value, sizeof(value));
fdb_kvdb_set_blob(&g_kvdb, "counter", &blob);
}
11.2 TSDB 初始化与使用
static fdb_tsdb_t g_tsdb;
static time_t get_timestamp(void) {
return sys_clock_now(); // 从 RTC 获取 Unix 时间戳
}
static int tsdb_init(void) {
fdb_err_t ret = fdb_tsdb_init(&g_tsdb, "tsdb1", "fdb_tsdb1",
get_timestamp, 1024, NULL);
return (ret == FDB_NO_ERR) ? 0 : -1;
}
typedef struct {
float voltage;
float current;
float temperature;
} sensor_data_t;
static void tsdb_example(void) {
sensor_data_t data = {3.3f, 0.5f, 25.6f};
fdb_tsl_append(&g_tsdb, &data);
}
11.3 FatFS 文件操作
#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);
}
11.4 FTL 格式化
#include "nand_ftl.h"
/* 注意: 此操作将清空 FTL 分区全部数据, 慎用 */
if (nand_ftl_format() == 0) {
DBG_INFO("FTL formatted");
}
12. 注意事项
- 擦除对齐 —
gd5f2gq5ue_erase()的 offset 和 size 必须严格按 GD5F_BLOCK_SIZE (128KB) 对齐和整数倍。 - 写前擦除 — NAND 不能原地覆写,FTL 和 FlashDB 内部自动管理擦除,但直接调用
gd5f2gq5ue_write()前必须确保目标块已擦除。 - FTL 首个扇区 —
dhara_map_init的第 5 个参数 (journal 页数) 影响 GC 效率,当前为 4,增大可减少写入放大但占用更多内存。 - 坏块传播 — FTL 在擦除/编程失败后自动调用
dhara_nand_mark_bad→gd5f2gq5ue_mark_block_bad,BBT 在 RAM 中更新,下次复位后重新扫描出厂坏块并叠加运行时坏块。 - 功耗 — 擦除操作最大耗时约 5ms (驱动超时设为 5s),页编程约 600ms (超时 1s),读写操作快。在低功耗场景需注意合理安排操作时序。
- 分区隔离 — 三个分区物理隔离,FlashDB 操作不会影响 FTL 数据,反之亦然。修改分区布局时需同步更新
fal_cfg.h和确认 FTL 宏自动适配。 - 缓存一致性 — 单页缓存 (s_cache_buf) 仅对 FatFS 层可见,多任务读写同一文件需在应用层同步。