解决一个擦除的bug

This commit is contained in:
2026-07-21 20:18:28 +08:00
parent f50b9c9e66
commit 66a1e8fd6a
25 changed files with 2005 additions and 61 deletions

View File

@@ -0,0 +1,31 @@
# GD5F2GQ5UE 陷阱记录
## Trap 01 — 块擦除地址错误(有效)
**发现时间**2026-07-21
**现象**
`gd5f_block_erase()` 使用 `byte_addr = block × 128KB`(字节地址)作为 D8h 命令的入参,但 GD5F2GQ5UE 要求的是**页地址**`block × 64`)。
只有 block 0 的 byte_addr=0 与 page_addr=0 巧合一致,其余块全部指向了错误的物理块。导致:
- 验证流程从未擦除过大部分块,厂标坏块每次重启都恢复
- 基于错误擦除的实验结论全部无效
**根因**
- `gd5f_block_erase(block_addr)``block_addr × GD5F_BLOCK_SIZE` 计算地址
- 芯片 D8h 命令将 24-bit 地址作为页地址(行地址)解析:`RA<16:6>` 选块,`RA<5:0>` 选页内页
- 对于 block=1发送 `{0xD8, 0x00, 0x02, 0x00}` → 芯片视为 page=512 → 擦除 block=8
- 导致 block 1~7、9~15、17~23…等大量块从未被擦除
**修复**:改用 `page_addr = block_addr × GD5F_PAGES_PER_BLOCK`(块首页地址)
**验证结果**(修复后):
- 全部 2048 块正确擦除,厂标全部清除,重启后 BBT scan 为 0
- ECC 开启时 BBT rescan 正常工作
- ECC 校验码正确生成ECCS=0
- Block 0 无特殊行为
**经验教训**
- D8h 和 13h/10h 都使用 24-bit 行地址,格式一致
- 块擦除不使用 `block × block_size`(字节偏移),而用块首页地址
- 芯片手册的 memory mapping 必须严格遵守

View File

@@ -49,9 +49,18 @@
**初始化顺序:**
```
SPI1_Init → gd5f2gq5ue_init()fdb_kvdb_init() [可选FlashDB 路径]
→ fdb_tsdb_init() [可选FlashDB 路径]
→ f_mount() [触发 FTL 初始化FatFS 路径]
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() // 应用层初始化
├─ FlashDB: fdb_kvdb_init() // KVDB (可选)
├─ FlashDB: fdb_tsdb_init() // TSDB (可选)
└─ f_mount() // 触发 FTL 初始化和 FatFS 挂载
→ osKernelStart() // FreeRTOS 启动
├─ defaultTask → 存储测试 + 业务
├─ netTask → 网络协议栈轮询
├─ adcTask → ADC 采集
└─ rs485Task → RS485 通信
```
---
@@ -113,7 +122,7 @@ SPI1_Init → gd5f2gq5ue_init() → fdb_kvdb_init() [可选FlashDB 路径]
所有分区边界在 `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`
- **FTL** — `nand_ftl.c:29` 通过 `#define FTL_START_BLOCK (FTL_FATFS_OFFSET / GD5F_BLOCK_SIZE)` 自动计算起始块号(当前 = 1024`FTL_NUM_BLOCKS` 自动适配剩余块数
- **FatFS** — 通过 `disk_ioctl(GET_SECTOR_COUNT)` 获取 FTL 管理的扇区数
> 调整分区大小时只需修改 `fal_cfg.h` 顶部 6 个宏,所有下游模块自动适配。
@@ -220,6 +229,11 @@ DMA 消除了轮询模式下 SPI 状态寄存器查检的逐字节 CPU 开销,
- `s_bbt[256]` 位图数组1 bit 标识 1 个块 (0=好, 1=坏)
- `gd5f2gq5ue_is_block_bad(block)` — 查询坏块状态
- `gd5f2gq5ue_mark_block_bad(block)` — 标记坏块 (FTL 层在擦除/编程失败时调用)
- `gd5f2gq5ue_bbt_clear()` — 清空 BBT慎用仅在重建时使用
- `gd5f2gq5ue_bbt_rescan()` — 重新扫描并重建 BBT需 ECC 禁用时调用,见 GD5F2GQ5UE_Trap_Records.md
- `gd5f2gq5ue_print_bbt()` — 打印 BBT 摘要(调试用)
> **注意**BBT 只能在 ECC 禁用时扫描(`gd5f_bbt_scan()`ECC 使能后 spare area 会被 ECC 引擎干扰导致坏块检测不可靠。运行时坏块只来源于真实擦除/编程失败,不要主动重新扫描。
---
@@ -276,6 +290,15 @@ FlashDB 通过分区名 (`"fdb_kvdb1"`, `"fdb_tsdb1"`) 绑定到 FAL 分区。
FlashDB 通过 FAL 分区名查找对应的分区设备,无需 FlashDB 驱动额外的设备注册。
#### 已知 Bug 修复:状态读取偏移
**问题**`Lib/FlashDB/src/fdb_utils.c``_fdb_get_status()` 函数曾有一处错误的 off-by-one 修复(在第 130-139 行额外多减了一次 `status_num`),导致:
- 循环少检查最高状态位(只查 `byte[1,0]` 而非 `byte[2,1,0]`
- `EMPTY`(状态 1被错误地读成 `USING`(状态 2
- 所有空扇区触发 KV 遍历,在全 `0xFF` 区域发现 `len=0xFFFFFFFF`,产生 `"The KV @0xXXXX0014 length has an error"` 报错
**修复**2026-07-21 移除 `_fdb_get_status()` 中多余的 `status_num--`,恢复原始正确的循环边界。详见 `Lib/FlashDB/src/fdb_utils.c:126-144`
### 6.3 KVDB 键值数据库
适用于存储配置参数、运行状态等少量键值对。
@@ -285,6 +308,7 @@ FlashDB 通过 FAL 分区名查找对应的分区设备,无需 FlashDB 驱动
- 支持 blob (二进制数据块)
- 自动磨损均衡
- 支持默认值 (首次启动自动写入)
- 支持 select/poll I/O 多路复用(`net_select.c`
### 6.4 TSDB 时序数据库
@@ -445,14 +469,31 @@ f_mkfs("", &opts, work, size)
| 函数 | 说明 |
|------|------|
| `gd5f2gq5ue_init()` | 初始化 NAND ( ECC 使能块保护解除) |
| `gd5f2gq5ue_read_id(mid, did)` | 读芯片 ID |
| `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 |
| `gd5f2gq5ue_bbt_rescan()` | 重新扫描 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 状态位 |
### 10.2 FAL 接口 (via fal_flash_gd5f2gq5ue.c)
@@ -468,10 +509,12 @@ f_mkfs("", &opts, work, size)
| 函数 | 说明 |
|------|------|
| `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_kv_set(&db, "key", "value")` | 写键值 (字符串) |
| `fdb_kv_get(&db, "key")` | 读键值 (返回 char*) |
| `fdb_kv_del(&db, "key")` | 删除键值 |
| `fdb_kv_set_blob(&db, "key", &blob)` | 写二进制 blob |
| `fdb_kv_get_blob(&db, "key", &blob)` | 读二进制 blob |
| `fdb_kv_set_default(&db)` | 格式化所有 sector 并写入默认值 |
| `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)` | 时间范围查询 |
@@ -498,7 +541,7 @@ f_mkfs("", &opts, work, size)
#include "fdb_cfg.h"
/* KVDB 实例 */
static fdb_kvdb_t g_kvdb;
static struct fdb_kvdb g_kvdb;
static int kvdb_init(void) {
fdb_err_t ret = fdb_kvdb_init(&g_kvdb, "kvdb1", "fdb_kvdb1", NULL, NULL);
@@ -507,24 +550,31 @@ static int kvdb_init(void) {
static void kvdb_example(void) {
/* 写字符串 */
fdb_kvdb_set(&g_kvdb, "device_id", "STM32F407-001");
fdb_kv_set(&g_kvdb, "device_id", "STM32F407-001");
/* 读字符串 */
char buf[64];
fdb_kvdb_get(&g_kvdb, "device_id", buf, sizeof(buf));
const char *val = fdb_kv_get(&g_kvdb, "device_id");
/* 写二进制 blob */
struct fdb_blob blob;
uint32_t value = 42;
fdb_blob_make(&blob, &value, sizeof(value));
fdb_kvdb_set_blob(&g_kvdb, "counter", &blob);
fdb_kv_set_blob(&g_kvdb, "counter", &blob);
/* 读二进制 blob */
uint32_t read_val;
fdb_blob_make(&blob, &read_val, sizeof(read_val));
fdb_kv_get_blob(&g_kvdb, "counter", &blob);
/* 格式化(清除所有数据) */
fdb_kv_set_default(&g_kvdb);
}
```
### 11.2 TSDB 初始化与使用
```c
static fdb_tsdb_t g_tsdb;
static struct fdb_tsdb g_tsdb;
static time_t get_timestamp(void) {
return sys_clock_now(); // 从 RTC 获取 Unix 时间戳
@@ -544,7 +594,9 @@ typedef struct {
static void tsdb_example(void) {
sensor_data_t data = {3.3f, 0.5f, 25.6f};
fdb_tsl_append(&g_tsdb, &data);
struct fdb_blob blob;
fdb_blob_make(&blob, &data, sizeof(data));
fdb_tsl_append(&g_tsdb, &blob);
}
```