# FlashDB 使用说明 ## 1. 概述 本项目使用 [FlashDB](https://github.com/armink/FlashDB) 嵌入式数据库,提供两种数据库: - **KVDB**(键值数据库):存储键值对,类似 Redis 简化版 - **TSDB**(时序数据库):存储带时间戳的日志记录,支持按时间范围查询 底层存储介质为 GD5F2GQ5UE SPI NAND Flash(256MB),通过 FAL(Flash 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 芯片参数 | 参数 | 值 | |------|-----| | 型号 | GD5F2GQ5UE(GigaDevice) | | 总容量 | 2Gbit = 256MB | | 页大小 | 2048 字节 | | Spare 区 | 64 字节 | | 每块页数 | 64 页 | | 块大小 | 128KB(64 × 2048) | | 总块数 | 2048 | | SPI 模式 | Mode 0(CPOL=0, CPHA=0) | | SPI 时钟 | 42MHz(APB2=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. 读取芯片 ID(9Fh),校验 MID=0xC8, DID=0x52 4. 使能内部 ECC:SET_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 /* 写入二进制数据 */ 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 区不可直接访问**:驱动已使能内部 ECC,Spare 区由芯片硬件管理 ### 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` 已重定向到 USART1(PA9/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 */ ```