Files
STM32F4-Base/docs/FlashDB使用说明.md

401 lines
12 KiB
Markdown
Raw Permalink 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.
# FlashDB 使用说明
## 1. 概述
本项目使用 [FlashDB](https://github.com/armink/FlashDB) 嵌入式数据库,提供两种数据库:
- **KVDB**(键值数据库):存储键值对,类似 Redis 简化版
- **TSDB**(时序数据库):存储带时间戳的日志记录,支持按时间范围查询
底层存储介质为 GD5F2GQ5UE SPI NAND Flash256MB通过 FALFlash Abstraction Layer抽象层访问。
## 2. 软件架构
```
┌─────────────────────────────────────────┐
│ 用户应用main.c
├──────────────┬──────────────────────────┤
│ fdb_kvdb │ fdb_tsdb │
│ (键值数据库) │ (时序数据库) │
├──────────────┴──────────────────────────┤
│ FlashDB 核心 │
│ Lib/FlashDB/src/ │
├─────────────────────────────────────────┤
│ FAL 抽象层 │
│ Lib/FlashDB/port/fal/src/ │
├─────────────────────────────────────────┤
│ FAL 设备适配层 │
│ fal_flash_gd5f2gq5ue.c │
├─────────────────────────────────────────┤
│ GD5F2GQ5UE SPI NAND 驱动 │
│ gd5f2gq5ue.c │
├─────────────────────────────────────────┤
│ SPI1 硬件外设 + HAL 驱动 │
└─────────────────────────────────────────┘
```
## 3. 文件清单
| 文件 | 说明 | 是否自编 |
|------|------|----------|
| `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h` | NAND 驱动头文件 | 是 |
| `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c` | NAND 驱动实现 | 是 |
| `Drivers/BSP/GD5F2GQ5UE/fal_flash_gd5f2gq5ue.c` | FAL 设备适配 | 是 |
| `Drivers/BSP/GD5F2GQ5UE/fal_cfg.h` | FAL 设备表 + 分区表 | 是 |
| `Drivers/BSP/GD5F2GQ5UE/fdb_cfg.h` | FlashDB 功能配置 | 是 |
| `Lib/FlashDB/src/` | FlashDB 核心源码 | 否 |
| `Lib/FlashDB/port/fal/src/` | FAL 硬件抽象框架源码 | 否 |
## 4. 硬件配置
### 4.1 Flash 芯片参数
| 参数 | 值 |
|------|-----|
| 型号 | GD5F2GQ5UEGigaDevice |
| 总容量 | 2Gbit = 256MB |
| 页大小 | 2048 字节 |
| Spare 区 | 64 字节 |
| 每块页数 | 64 页 |
| 块大小 | 128KB64 × 2048 |
| 总块数 | 2048 |
| SPI 模式 | Mode 0CPOL=0, CPHA=0 |
| SPI 时钟 | 42MHzAPB2=84MHz, 分频=2 |
| MID | 0xC8 |
| DID | 0x52 |
### 4.2 引脚连接
| 信号 | STM32 引脚 | 方向 | 说明 |
|------|-----------|------|------|
| CS# | PE0 | OUT | 片选,低有效 |
| SCLK | PB3 | OUT | SPI1 时钟 |
| MOSI | PB5 | OUT | SPI1 主出从入 |
| MISO | PB4 | IN | SPI1 主入从出 |
| WP# | PB8 | OUT | 写保护,低有效(拉高禁用) |
| HOLD# | PE1 | OUT | 保持,低有效(拉高禁用) |
### 4.3 GPIO 初始化
CubeMX 中需确保以下 GPIO 上电默认电平:
- **CS#** (PE0):上电输出 **高电平**(未选中)
- **HOLD#** (PE1):上电输出 **高电平**(不保持)
- **WP#** (PB8):上电输出 **高电平**(不禁用写保护)
> 若 GPIO 初始电平错误SPI 通信将完全无响应。
## 5. 分区规划
| 分区名 | 设备 | 偏移 | 大小 | 用途 |
|--------|------|------|------|------|
| fdb_kvdb1 | gd5f2gq5ue | 0 | 64MB | KVDB 键值数据库 |
| fdb_tsdb1 | gd5f2gq5ue | 64MB | 64MB | TSDB 时序数据库 |
| (未分配) | - | 128MB | 128MB | 剩余空间 |
共使用 128MB / 256MB剩余 128MB 可扩展。
## 6. 初始化流程
```
1. HAL_Init()
2. SystemClock_Config()
3. MX_GPIO_Init() ← CS#/HOLD#/WP# 初始电平
4. MX_USART1_UART_Init() ← 调试串口
5. MX_SPI2_Init() ← CH395F 以太网
6. MX_SPI1_Init() ← GD5F2GQ5UE NAND
7. gd5f2gq5ue_init() ← NAND 初始化
8. fdb_kvdb_init() ← FlashDB KVDB 初始化
```
### 6.1 gd5f2gq5ue_init() 内部步骤
```
1. CS#/WP#/HOLD# 拉高
2. 发送复位命令FFh等待 5ms
3. 读取芯片 ID9Fh校验 MID=0xC8, DID=0x52
4. 使能内部 ECCSET_FEATURE(B0h, 10h)
5. 解除块保护SET_FEATURE(A0h, 00h)
```
> **关键点**SET_FEATURE 命令前必须先发写使能06h否则设置不生效。
## 7. KVDB 使用方法
### 7.1 初始化
```c
#include "flashdb.h"
static struct fdb_kvdb kvdb;
/* path 参数对应分区表中的分区名 "fdb_kvdb1" */
/* default_kv 传 NULL 表示不使用默认键值 */
int ret = fdb_kvdb_init(&kvdb, "db", "fdb_kvdb1", NULL, NULL);
if (ret != FDB_NO_ERR) {
/* 初始化失败处理 */
}
```
### 7.2 字符串读写
```c
/* 写入字符串 */
fdb_kv_set(&kvdb, "device_name", "STM32F407-DTU");
/* 读取字符串(返回内部缓存指针,下次写入同一 key 后失效) */
char *val = fdb_kv_get(&kvdb, "device_name");
if (val) {
printf("device_name = %s\n", val);
}
```
### 7.3 二进制数据读写Blob
```c
#include <string.h>
/* 写入二进制数据 */
uint32_t temp = 2560;
fdb_kv_set_blob(&kvdb, "temperature",
fdb_blob_make(NULL, &temp, sizeof(temp)));
/* 读取二进制数据 */
uint32_t read_temp = 0;
fdb_kv_get_blob(&kvdb, "temperature",
fdb_blob_make(NULL, &read_temp, sizeof(read_temp)));
printf("temperature = %u\n", read_temp);
```
### 7.4 删除键
```c
fdb_kv_del(&kvdb, "device_name");
```
### 7.5 遍历所有键
```c
struct fdb_kv kv;
struct fdb_kv_iterator itr;
fdb_kv_iterator_init(&kvdb, &itr);
while (fdb_kv_iterate(&kvdb, &itr)) {
kv = itr.curr;
printf("key: %s\n", kv.name);
}
```
### 7.6 打印所有键值
```c
fdb_kv_print(&kvdb);
```
## 8. TSDB 使用方法
### 8.1 获取时间戳
FlashDB 需要用户提供时间戳获取函数。本项目使用 HAL_GetTick()(毫秒):
```c
static fdb_time_t get_time(void)
{
return (fdb_time_t)HAL_GetTick();
}
```
### 8.2 初始化
```c
static struct fdb_tsdb tsdb;
/* max_len 参数限制单条记录最大长度 */
int ret = fdb_tsdb_init(&tsdb, "tsdb", "fdb_tsdb1",
get_time, 256, NULL);
```
### 8.3 追加记录
```c
/* 写入二进制数据,自动附加当前时间戳 */
uint32_t sensor_val = 3200;
fdb_tsl_append(&tsdb,
fdb_blob_make(NULL, &sensor_val, sizeof(sensor_val)));
```
### 8.4 遍历记录
```c
/* 正序遍历 */
fdb_tsl_iter(&tsdb, tsl_cb_func, NULL);
/* 逆序遍历 */
fdb_tsl_iter_reverse(&tsdb, tsl_cb_func, NULL);
/* 按时间范围遍历 */
fdb_tsl_iter_by_time(&tsdb, from_time, to_time, tsl_cb_func, NULL);
```
回调函数原型:
```c
static void tsl_cb_func(struct fdb_tsl *tsl, void *arg)
{
struct fdb_blob blob;
uint32_t val;
/* 读取数据 */
fdb_blob_read((fdb_db_t)&tsl,
fdb_tsl_to_blob(tsl, fdb_blob_make(&blob, &val, sizeof(val))));
printf("ts=%lu, val=%lu\n", tsl->time, val);
}
```
### 8.5 查询记录数量
```c
size_t count = fdb_tsl_query_count(&tsdb, from_time, to_time,
FDB_TSL_STATUS_APPEND);
```
### 8.6 清理所有记录
```c
fdb_tsl_clean(&tsdb);
```
## 9. 配置文件说明
### 9.1 fdb_cfg.h
| 宏 | 说明 |
|----|------|
| `FDB_USING_KVDB` | 启用 KVDB |
| `FDB_USING_TSDB` | 启用 TSDB |
| `FDB_USING_FAL_MODE` | 使用 FAL 模式(非文件系统) |
| `FDB_WRITE_GRAN` | 写入粒度 8字节可编程 |
| `FDB_DEBUG_ENABLE` | 启用调试输出 |
| `FDB_KV_AUTO_UPDATE` | KVDB 版本变化时自动更新(默认关闭) |
### 9.2 fal_cfg.h
分区表定义,修改分区大小需同时调整偏移和大小:
```c
#define FAL_PART_TABLE \
{ \
{FAL_PART_MAGIC_WORD, "fdb_kvdb1", "gd5f2gq5ue", 0, 64*1024*1024, 0}, \
{FAL_PART_MAGIC_WORD, "fdb_tsdb1", "gd5f2gq5ue", 64*1024*1024, 64*1024*1024, 0}, \
}
```
字段顺序:`{魔数, 分区名, 设备名, 偏移, 大小, 标志}`
## 10. Keil 工程配置
### 10.1 头文件搜索路径
在 Keil 工程 Options → C/C++ → Include Paths 中添加:
```
../Drivers/BSP/GD5F2GQ5UE
../Lib/FlashDB/inc
../Lib/FlashDB/port/fal/inc
```
### 10.2 编译的源文件
需在 Keil 工程中添加以下源文件:
```
Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c
Drivers/BSP/GD5F2GQ5UE/fal_flash_gd5f2gq5ue.c
Lib/FlashDB/src/fdb.c
Lib/FlashDB/src/fdb_kvdb.c
Lib/FlashDB/src/fdb_tsdb.c
Lib/FlashDB/src/fdb_utils.c
Lib/FlashDB/port/fal/src/fal.c
Lib/FlashDB/port/fal/src/fal_flash.c
Lib/FlashDB/port/fal/src/fal_partition.c
```
### 10.3 全局宏定义
确保工程中定义了:
```
USE_HAL_DRIVER, STM32F407xx
```
## 11. 注意事项
### 11.1 NAND Flash 特性
1. **写前必须擦除**NAND 只能将 1→0不能 0→1。写入前目标块必须先擦除全部变为 0xFF
2. **擦除单位是块**:最小擦除单位 128KB不能按页擦除
3. **写入单位是页**单次写入不超过一页2048 字节),跨页需驱动层拆分
4. **Spare 区不可直接访问**:驱动已使能内部 ECCSpare 区由芯片硬件管理
### 11.2 驱动层注意事项
1. **每次 SPI 事务前**:必须 `CS_LOW()` 拉低片选
2. **每次 SPI 事务后**:必须 `CS_HIGH()` 拉高片选
3. **SET_FEATURE 前必须写使能**:先发 06h再发 1Fh + 地址 + 数据
4. **块擦除地址是字节地址**D8h 命令的参数 = 块编号 × 128KB不是块编号本身
5. **读取 ID 需跳过 dummy 字节**9Fh 返回 3 字节,第 0 字节无意义,第 1 字节 MID第 2 字节 DID
### 11.3 FlashDB 使用注意事项
1. **fdb_kv_get 返回值是内部缓存**:下次对同一 key 写入后,之前返回的指针失效
2. **TSDB 需要用户提供时间戳**:通过 `get_time` 回调,本项目使用 `HAL_GetTick()`
3. **fdb_kvdb_init 的 path 参数**:对应分区表中的分区名 `"fdb_kvdb1"`
4. **FlashDB 开启 FDB_DEBUG_ENABLE 后**:会通过 `fdb_print()` 输出调试信息,需确保有可用的输出(如 printf 重定向到串口)
5. **分区大小修改后**:需同步更新 `fal_cfg.h` 中的偏移和大小
### 11.4 常见问题
| 现象 | 可能原因 | 解决方法 |
|------|----------|----------|
| init 返回 -6 (ID_MISMATCH) | SPI 通信失败或芯片未上电 | 检查接线、GPIO 初始电平、虚焊 |
| KVDB init 失败 | 分区名不匹配 | 确认 `fdb_kvdb_init` 的 path 与 `fal_cfg.h` 一致 |
| 写入后读取为空 | 未擦除或写入失败 | 检查写入返回值,确认目标块已擦除 |
| 读取数据异常 | ECC 错误 | 检查 `gd5f2gq5ue_init` 是否成功使能 ECC |
| SET_FEATURE 未生效 | 缺少写使能命令 | 确认 `gd5f_set_feature` 中先调用 `gd5f_write_enable()` |
## 12. 调试方法
### 12.1 串口调试输出
FlashDB 调试输出通过 `fdb_print` 宏实现。在 `fdb_cfg.h` 中定义 `FDB_DEBUG_ENABLE`FlashDB 内部操作会自动输出到标准输出。
确保 Keil 工程中 `printf` 已重定向到 USART1PA9/PA10, 115200bps
### 12.2 NAND 驱动调试
可通过串口输出以下信息验证驱动工作正常:
```c
/* 读取芯片 ID */
uint8_t mid, did;
gd5f2gq5ue_read_id(&mid, &did);
printf("MID=0x%02X DID=0x%02X\n", mid, did);
/* 预期输出MID=0xC8 DID=0x52 */
/* 初始化测试 */
int ret = gd5f2gq5ue_init();
printf("init ret=%d\n", ret);
/* 预期输出init ret=0 */
```
### 12.3 FAL 设备注册验证
```c
#include "fal.h"
/* 检查 FAL 是否初始化成功 */
int ret = fal_init();
printf("fal_init ret=%d\n", ret);
/* 预期输出fal_init ret=0 */
```