FlashDB 嵌入式数据库从移植到上手:KVDB/TSDB 双模式基础操作指南
简介
FlashDB 是 armink 开源的一款超轻量级嵌入式数据库,专为嵌入式产品的数据存储场景设计。它不像传统数据库依赖文件系统,而是直接结合 Flash 存储特性,在保证极低资源占用的前提下,提供高性能、高可靠的数据存储。
FlashDB 提供两种数据库模式:
- KVDB(键值数据库):非关系型数据库,数据以 Key-Value 键值对存储,操作简洁、扩展性强。适用于产品参数存储、用户配置信息管理、小文件管理等场景。
- TSDB(时序数据库):数据按时间顺序存储,支持大量数据的高速插入与查询。适用于传感器采集数据记录、设备运行日志存储、异常告警记录等场景。
核心特性
FlashDB 在设计上针对嵌入式 Flash 的特点做了深度优化:
资源占用极低。在 STM32F4 平台下,IAR 编译器编译后整个库仅占用约 7KB ROM,RAM 占用几乎为零。内部使用静态分配内存,不使用 malloc。
磨损平衡。Flash 的擦写次数有限,FlashDB 通过扇区轮转写入机制让擦写操作均匀分布在整个分区上,避免热点扇区过早失效。
掉电保护。写入机制类比 OTA 的 AB 分区:新数据写到扇区空白区域,旧数据原地不动。写完后用一次 Flash 编程脉冲把一个 bit 从 1 翻成 0,这一个 bit 就是提交信号。NOR Flash 的物理规则是擦除后全为 1,编程只能把 1 变 0,且单个 bit 的翻转是硬件保证的最小原子操作——要么打上去,要么没打,不存在"翻了一半"。于是只有两种结果:bit 还是 1(没提交,等效于没写过),bit 已经 0(提交成功),没有中间态。断电发生在翻转前——旧数据完好;发生在翻转后——新数据生效。
多分区、多实例。一个系统中可以同时存在多个 KVDB 或 TSDB 实例,各自坐落在不同的 Flash 分区上。当数据量大时,可按业务细分到不同分区以降低检索时间。
增量升级。启用 FDB_KV_AUTO_UPDATE 后,固件升级时 KVDB 能自动将新的默认 KV 集合合并到已有数据库中,无需手动迁移。
两种 KV 类型。支持字符串(string)和二进制(blob)两种值类型,blob 可兼容任意结构体数据。
快速上手:在 Linux 上运行 Demo
FlashDB 项目自带一个 Linux 平台的 demo,不依赖硬件,可以直接在 PC 上体验。demo 位于 demos/linux 目录下。
# 克隆项目
git clone https://github.com/armink/FlashDB.git
cd FlashDB/demos/linux
# 编译
make
# 多次运行查看效果
cd out
./FlashDBLinuxDemo
./FlashDBLinuxDemo每次运行时,demo 会执行 KVDB 和 TSDB 的基本操作并输出日志。第二次运行时可以看到 KVDB 中之前写入的 boot_count 值已被持久化保留。
配置文件说明
使用 FlashDB 前,需要在项目中创建 fdb_cfg.h 配置文件。项目提供了模板文件 inc/fdb_cfg_template.h,把它复制并重命名后按需修改即可。
#ifndef _FDB_CFG_H_
#define _FDB_CFG_H_
/* 启用 KVDB 功能 */
#define FDB_USING_KVDB
/* 可选:启用 KV 增量升级 */
/* #define FDB_KV_AUTO_UPDATE */
/* 启用 TSDB 功能 */
#define FDB_USING_TSDB
/* 使用 FAL 存储模式(直接操作 Flash) */
#define FDB_USING_FAL_MODE
#ifdef FDB_USING_FAL_MODE
/* Flash 写粒度,单位 bit
* nor flash: 1, stm32f2/f4: 8, stm32f1: 32, stm32l4: 64 */
#define FDB_WRITE_GRAN 8
#endif
/* 或者使用文件模式(二选一) */
/* #define FDB_USING_FILE_LIBC_MODE */
/* #define FDB_USING_FILE_POSIX_MODE */
/* 调试信息开关 */
#define FDB_DEBUG_ENABLE
#endif /* _FDB_CFG_H_ */关键配置项说明:
| 配置项 | 说明 |
|---|---|
FDB_USING_KVDB |
编译 KVDB 模块,按需开启 |
FDB_USING_TSDB |
编译 TSDB 模块,按需开启 |
FDB_USING_FAL_MODE |
Flash 抽象层模式,直接操作 Flash,性能最优 |
FDB_USING_FILE_POSIX_MODE |
POSIX 文件模式,通过 open/read/write/close 操作 |
FDB_USING_FILE_LIBC_MODE |
C 标准库文件模式,通过 fopen/fread/fwrite/fclose 操作 |
FDB_WRITE_GRAN |
Flash 写粒度,必须与所用 Flash 芯片一致 |
FDB_DEBUG_ENABLE |
调试日志开关,移植阶段建议开启 |
FDB_KV_AUTO_UPDATE |
KV 增量升级开关 |
FDB_BIG_ENDIAN |
大端模式,默认小端 |
FDB_PRINT(...) |
自定义打印宏,默认使用 printf |
FAL 模式和文件模式只能二选一。FAL 模式直接操作 Flash,性能更好;文件模式则不受硬件限制,可以存储在 SD 卡、文件系统等任意介质上。
KVDB 基础操作
KVDB 的所有操作都围绕 Key-Value 展开。在代码中需要先定义一个 fdb_kvdb_t 类型的数据库实例并完成初始化。
初始化 KVDB
#include <flashdb.h>
/* 定义默认 KV 集合:首次初始化时自动写入 */
static struct fdb_default_kv_node default_kv_set[] = {
{ .key = "device_id", .value = NULL, .value_len = 0 },
{ .key = "boot_count", .value = NULL, .value_len = 0 },
};
static struct fdb_default_kv default_kv = {
.kvs = default_kv_set,
.num = sizeof(default_kv_set) / sizeof(default_kv_set[0]),
};
/* 定义 KVDB 实例 */
static struct fdb_kvdb kvdb;
void kvdb_init_example(void)
{
fdb_err_t result;
/* FAL 模式:"kv_db" 是分区表中的分区名 */
result = fdb_kvdb_init(&kvdb, "env", "kv_db", &default_kv, NULL);
if (result != FDB_NO_ERR) {
printf("KVDB init failed: %d\n", result);
return;
}
printf("KVDB init ok\n");
}初始化参数说明:
&kvdb:数据库实例指针"env":数据库名称,用于日志标识"kv_db":FAL 模式下为分区名,文件模式下为存储路径&default_kv:默认 KV 集合,首次初始化时写入,不需要则传 NULLNULL:用户自定义数据指针
字符串类型 KV
字符串类型操作最简单,直接使用 char * 指针即可。
void kvdb_string_ops(struct fdb_kvdb *db)
{
char *val;
/* 新增 / 修改 KV —— fdb_kv_set 自动判断 key 是否存在 */
fdb_kv_set(db, "wifi_ssid", "MyWiFi_2.4G");
/* 读取 KV —— 返回值在内部 buffer,建议立即拷贝 */
val = fdb_kv_get(db, "wifi_ssid");
if (val != NULL) {
printf("wifi_ssid = %s\n", val);
}
/* 修改已有 KV */
fdb_kv_set(db, "wifi_ssid", "MyWiFi_5G");
/* 删除 KV */
fdb_kv_del(db, "wifi_ssid");
}注意:fdb_kv_get 返回的指针指向内部缓冲区,不可重入,建议立即用 strcpy 或 strdup 拷贝到用户内存中。
Blob 类型 KV
Blob 类型支持任意二进制数据,如结构体、数组等。
struct device_config {
uint32_t baud_rate;
uint8_t slave_id;
uint8_t parity;
uint16_t timeout_ms;
};
void kvdb_blob_ops(struct fdb_kvdb *db)
{
struct fdb_blob blob;
struct device_config cfg_write, cfg_read;
/* 写入 blob */
cfg_write.baud_rate = 115200;
cfg_write.slave_id = 0x01;
cfg_write.parity = 0;
cfg_write.timeout_ms = 500;
fdb_kv_set_blob(db, "dev_cfg",
fdb_blob_make(&blob, &cfg_write, sizeof(cfg_write)));
/* 读取 blob */
memset(&cfg_read, 0, sizeof(cfg_read));
fdb_kv_get_blob(db, "dev_cfg",
fdb_blob_make(&blob, &cfg_read, sizeof(cfg_read)));
if (blob.saved.len > 0) {
printf("baud_rate = %u\n", cfg_read.baud_rate);
}
}fdb_blob_make 的三步操作合为一句:
- 用
value_buf和buf_len初始化 blob 对象 - 返回构造好的
fdb_blob_t指针 - 直接传给
fdb_kv_set_blob或fdb_kv_get_blob
读取 blob 后通过 blob.saved.len > 0 判断是否成功获取到数据。
遍历所有 KV
void kvdb_iterate_all(struct fdb_kvdb *db)
{
struct fdb_kv_iterator itr;
struct fdb_blob blob;
char value_buf[128];
fdb_kv_iterator_init(db, &itr);
while (fdb_kv_iterate(db, &itr)) {
fdb_kv_t kv = &itr.curr_kv;
/* 读取当前 KV 的值 */
fdb_kv_to_blob(kv, fdb_blob_make(&blob, value_buf, sizeof(value_buf)));
fdb_blob_read((fdb_db_t)db, &blob);
printf("KV: name=%-20s value_len=%u status=%d\n",
kv->name, (unsigned)kv->value_len, kv->status);
}
printf("遍历完成:共 %u 条 KV, 占用 %zu 字节\n",
(unsigned)itr.iterated_cnt, itr.iterated_obj_bytes);
}重置为默认值
/* 将 KVDB 中所有 KV 重置为初始化时传入的 default_kv */
fdb_kv_set_default(&kvdb);打印数据库信息
/* 输出当前 KVDB 中所有 KV 的概要信息 */
fdb_kv_print(&kvdb);TSDB 基础操作
TSDB 使用时序日志(TSL)作为存储单元,每条 TSL 包含时间戳和用户数据。
初始化 TSDB
static struct fdb_tsdb tsdb;
/* 获取当前时间戳的回调函数 */
static fdb_time_t get_current_time(void)
{
return time(NULL);
}
void tsdb_init_example(void)
{
fdb_err_t result;
result = fdb_tsdb_init(&tsdb, "sensor_log", "ts_db",
get_current_time, /* 获取时间戳函数 */
128, /* 每条 TSL 最大长度 */
NULL); /* 用户数据 */
if (result != FDB_NO_ERR) {
printf("TSDB init failed: %d\n", result);
return;
}
printf("TSDB init ok\n");
}几个关键参数:
get_current_time:时间戳获取函数,嵌入式平台可对接 RTC128:每条 TSL 数据部分的最大长度,超出会返回错误- 可选配置:通过
fdb_tsdb_control设置扇区大小、翻转模式等
追加 TSL
struct sensor_data {
float temperature;
float humidity;
uint32_t timestamp;
};
void tsdb_append_example(struct fdb_tsdb *db)
{
struct fdb_blob blob;
struct sensor_data data;
data.temperature = 26.5f;
data.humidity = 65.2f;
data.timestamp = get_current_time();
fdb_tsl_append(db, fdb_blob_make(&blob, &data, sizeof(data)));
printf("已追加一条传感器数据\n");
}追加操作会自动打上时间戳。如果需要使用指定时间戳(如从 RTC 读取历史数据回填),使用 fdb_tsl_append_with_ts:
fdb_tsl_append_with_ts(db, fdb_blob_make(&blob, &data, sizeof(data)),
custom_timestamp);查询 TSL
TSDB 的查询通过迭代器回调实现。回调函数返回 true 时停止迭代。
/* 回调函数:每条 TSL 调用一次 */
static bool query_cb(fdb_tsl_t tsl, void *arg)
{
struct fdb_blob blob;
struct sensor_data data;
fdb_blob_read((fdb_db_t)arg,
fdb_tsl_to_blob(tsl, fdb_blob_make(&blob, &data, sizeof(data))));
printf("TSL: time=%d temp=%.1f humi=%.1f status=%d\n",
(int)tsl->time, data.temperature, data.humidity, tsl->status);
return false; /* 返回 false 继续迭代 */
}
/* 遍历全部 TSL */
void tsdb_query_all(struct fdb_tsdb *db)
{
printf("===== 全部 TSL =====\n");
fdb_tsl_iter(db, query_cb, db);
}
/* 按时间范围遍历 */
void tsdb_query_by_time(struct fdb_tsdb *db,
fdb_time_t from, fdb_time_t to)
{
printf("===== %d ~ %d 范围内的 TSL =====\n", (int)from, (int)to);
fdb_tsl_iter_by_time(db, from, to, query_cb, db);
/* 同时查询满足条件的 TSL 数量 */
size_t count = fdb_tsl_query_count(db, from, to, FDB_TSL_WRITE);
printf("范围内 TSL 数量: %zu\n", count);
}
/* 反向遍历(从最新到最旧) */
void tsdb_query_reverse(struct fdb_tsdb *db)
{
printf("===== 反向遍历 =====\n");
fdb_tsl_iter_reverse(db, query_cb, db);
}设置 TSL 状态
每条 TSL 都有状态标记,可用于数据管理。状态切换必须按顺序:
FDB_TSL_WRITE → FDB_TSL_USER_STATUS1 → FDB_TSL_DELETED → FDB_TSL_USER_STATUS2
static bool set_status_cb(fdb_tsl_t tsl, void *arg)
{
struct fdb_tsdb *db = (struct fdb_tsdb *)arg;
/* 将已写入状态改为用户自定义状态1 */
if (tsl->status == FDB_TSL_WRITE) {
fdb_tsl_set_status(db, tsl, FDB_TSL_USER_STATUS1);
printf("TSL time=%d 状态已更新\n", (int)tsl->time);
}
return false;
}
void tsdb_set_status_example(struct fdb_tsdb *db)
{
fdb_tsl_iter(db, set_status_cb, db);
}两个用户自定义状态(USER_STATUS1 / USER_STATUS2)可用于实现"已读/未读"、"已上报/未上报"等业务逻辑。
其他控制操作
/* 设置 TSDB 翻卷模式:最旧数据可被覆盖(默认) */
fdb_tsdb_control(&tsdb, FDB_TSDB_CTRL_SET_ROLLOVER, (void *)true);
/* 查询上次存储时间 */
fdb_time_t last_time;
fdb_tsdb_control(&tsdb, FDB_TSDB_CTRL_GET_LAST_TIME, &last_time);移植指南
FlashDB 的移植工作集中在 FAL(Flash Abstraction Layer)一端。FAL 是 RT-Thread 开源的 Flash 抽象层软件包,也支持裸机运行,不依赖任何 RTOS。
移植的整体架构如下:
┌─────────────────────────────────┐
│ KVDB / TSDB (FlashDB) │ ← 上层 API
├─────────────────────────────────┤
│ FAL (Flash Abstraction Layer) │ ← 分区 & 设备管理
├─────────────────────────────────┤
│ Flash Driver (片内/片外) │ ← 硬件驱动
└─────────────────────────────────┘第一步:集成 FAL
将 FAL 源码添加到工程。FAL 仓库地址:
https://github.com/RT-Thread-packages/fal将 fal/inc 和 fal/src 目录加入工程头文件路径和源文件列表。
第二步:实现 Flash 驱动操作函数
根据目标平台实现四个操作函数。以下以 STM32F4 片内 Flash 为例:
#include <fal.h>
#include <stm32f4xx_hal.h>
static int stm32_onchip_init(void)
{
/* 片内 Flash 通常无需额外初始化 */
return 1;
}
static int stm32_onchip_read(long offset, uint8_t *buf, size_t size)
{
size_t i;
uint32_t addr = STM32_FLASH_START_ADDR + offset;
for (i = 0; i < size; i++, addr++, buf++) {
*buf = *(uint8_t *)addr;
}
return size;
}
static int stm32_onchip_write(long offset, const uint8_t *buf, size_t size)
{
size_t i;
uint32_t addr = STM32_FLASH_START_ADDR + offset;
HAL_FLASH_Unlock();
for (i = 0; i < size; i++, addr++, buf++) {
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, addr, *buf) != HAL_OK) {
HAL_FLASH_Lock();
return i;
}
}
HAL_FLASH_Lock();
return size;
}
static int stm32_onchip_erase(long offset, size_t size)
{
uint32_t err_sector;
uint32_t start_addr = STM32_FLASH_START_ADDR + offset;
FLASH_EraseInitTypeDef erase_cfg;
erase_cfg.TypeErase = FLASH_TYPEERASE_SECTORS;
erase_cfg.Sector = stm32_get_sector(start_addr);
erase_cfg.NbSectors = size / FLASH_SECTOR_SIZE;
erase_cfg.VoltageRange = FLASH_VOLTAGE_RANGE_3;
HAL_FLASH_Unlock();
if (HAL_FLASHEx_Erase(&erase_cfg, &err_sector) != HAL_OK) {
HAL_FLASH_Lock();
return -1;
}
HAL_FLASH_Lock();
return size;
}
/* 定义 Flash 设备对象 */
const struct fal_flash_dev stm32_onchip_flash = {
.name = "stm32_onchip",
.addr = 0x08000000,
.len = 1024 * 1024, /* 1MB */
.blk_size = 128 * 1024, /* 最大扇区大小 128KB */
.ops = { stm32_onchip_init,
stm32_onchip_read,
stm32_onchip_write,
stm32_onchip_erase },
.write_gran = 8, /* STM32F4 写粒度: 8 bit */
};操作函数说明:
| 函数 | 功能 | 返回值 |
|---|---|---|
init |
可选初始化,不需要可置 NULL | ≥0 成功 |
read |
从 Flash 偏移处读取数据 | 实际读取字节数 |
write |
向 Flash 偏移处写入数据 | 实际写入字节数 |
erase |
擦除 Flash 指定区域 | 实际擦除字节数 |
write_gran 写粒度必须准确设置——它决定了 FlashDB 内部按什么粒度对齐写入数据。常见 Flash 写粒度:
| Flash 类型 | write_gran |
|---|---|
| NOR Flash (SPI) | 1 bit |
| STM32F2 / STM32F4 | 8 bit |
| STM32F1 | 32 bit |
| STM32L4 | 64 bit |
| STM32H5 | 128 bit |
| STM32H7 | 256 bit |
第三步:创建 fal_cfg.h
在工程目录下新建 fal_cfg.h 文件,定义 Flash 设备表和分区表:
#ifndef _FAL_CFG_H_
#define _FAL_CFG_H_
#include <fal.h>
/* 声明 Flash 设备 */
extern const struct fal_flash_dev stm32_onchip_flash;
/* ============ Flash 设备表 ============ */
#define FAL_FLASH_DEV_TABLE \
{ \
&stm32_onchip_flash, \
}
/* ============ Flash 分区表 ============ */
#define FAL_PART_TABLE \
{ \
{FAL_PART_MAGIC_WORD, "boot", "stm32_onchip", 0, 64*1024, 0}, \
{FAL_PART_MAGIC_WORD, "app", "stm32_onchip", 64*1024, 256*1024, 0}, \
{FAL_PART_MAGIC_WORD, "kv_db", "stm32_onchip", 320*1024, 128*1024, 0}, \
{FAL_PART_MAGIC_WORD, "ts_db", "stm32_onchip", 448*1024, 512*1024, 0}, \
}
#endif /* _FAL_CFG_H_ */分区表定义了 4 个分区:
| 分区名 | 设备 | 偏移 | 大小 | 用途 |
|---|---|---|---|---|
| boot | stm32_onchip | 0 | 64KB | Bootloader |
| app | stm32_onchip | 64KB | 256KB | 应用程序 |
| kv_db | stm32_onchip | 320KB | 128KB | KVDB 数据库 |
| ts_db | stm32_onchip | 448KB | 512KB | TSDB 数据库 |
注意:
- 分区名不可重复
- 关联的 Flash 设备必须在设备表中已定义且名称一致
- 分区起始地址和大小不能超出 Flash 设备的地址范围
- 每个分区记录末尾保留
0作为保留字段
第四步:应用层初始化
在 main 函数中依次初始化 FAL 和 FlashDB:
#include <flashdb.h>
#include <fal.h>
static struct fdb_kvdb kvdb;
static struct fdb_tsdb tsdb;
int main(void)
{
fdb_err_t result;
/* 1. 初始化 FAL */
fal_init();
/* 2. 初始化 KVDB —— 分区名对应 fal_cfg.h 中的 "kv_db" */
result = fdb_kvdb_init(&kvdb, "env", "kv_db", NULL, NULL);
if (result != FDB_NO_ERR) {
printf("KVDB init failed: %d\n", result);
}
/* 3. 初始化 TSDB —— 分区名对应 fal_cfg.h 中的 "ts_db" */
result = fdb_tsdb_init(&tsdb, "sensor_log", "ts_db",
get_current_time, 128, NULL);
if (result != FDB_NO_ERR) {
printf("TSDB init failed: %d\n", result);
}
/* 4. 正常使用 */
fdb_kv_set(&kvdb, "fw_version", "1.0.0");
printf("fw_version = %s\n", fdb_kv_get(&kvdb, "fw_version"));
while (1) {
/* 业务循环 */
}
}文件模式移植(无需 Flash 硬件)
如果开发阶段没有 Flash 硬件,或需要将数据库存储在 SD 卡/文件系统中,可以使用文件模式:
在 fdb_cfg.h 中选择文件模式:
/* 使用 POSIX 文件模式(Linux / ESP-IDF 等) */
#define FDB_USING_FILE_POSIX_MODE
/* 注意:同时关闭 FAL 模式 */
/* #define FDB_USING_FAL_MODE */初始化时通过 control 接口设置文件模式参数:
/* 设置文件模式 */
fdb_kvdb_control(&kvdb, FDB_KVDB_CTRL_SET_FILE_MODE, NULL);
/* 设置扇区大小(字节) */
uint32_t sec_size = 4096;
fdb_kvdb_control(&kvdb, FDB_KVDB_CTRL_SET_SEC_SIZE, &sec_size);
/* 设置数据库最大大小 */
uint32_t max_size = 256 * 1024; /* 256KB */
fdb_kvdb_control(&kvdb, FDB_KVDB_CTRL_SET_MAX_SIZE, &max_size);
/* 路径参数传入文件目录 */
fdb_kvdb_init(&kvdb, "env", "/data/flashdb_kv", NULL, NULL);C 标准库模式 (FDB_USING_FILE_LIBC_MODE) 与 POSIX 模式类似,区别在于底层使用 fopen/fread/fwrite/fclose 接口。
加锁保护
在多线程或中断环境中需要保护 Flash 操作,通过 control 接口注册加锁/解锁函数:
static void db_lock(fdb_db_t db)
{
mutex_lock(&flash_mutex);
}
static void db_unlock(fdb_db_t db)
{
mutex_unlock(&flash_mutex);
}
/* 初始化前注册 */
fdb_kvdb_control(&kvdb, FDB_KVDB_CTRL_SET_LOCK, (void *)db_lock);
fdb_kvdb_control(&kvdb, FDB_KVDB_CTRL_SET_UNLOCK, (void *)db_unlock);常见移植问题
分区找不到:检查
fdb_kvdb_init传入的分区名与fal_cfg.h中的分区名是否完全一致,以及fal_init()是否在数据库初始化之前调用。写入后读取数据不匹配:检查
FDB_WRITE_GRAN写粒度是否与 Flash 芯片一致。nor flash 通常为 1,STM32 系列因型号不同而异。扇区大小对齐问题:KVDB/TSDB 的扇区大小必须是 Flash 块大小的整数倍。如果数据库保存的数据较大,可通过
FDB_KVDB_CTRL_SET_SEC_SIZE调大扇区。编译提示找不到
fal_cfg.h:确认文件已创建在工程目录下且头文件路径已加入编译器 include path。
API 速查表
KVDB API
| 函数 | 功能 |
|---|---|
fdb_kvdb_init(db, name, path, default_kv, user_data) |
初始化 KVDB |
fdb_kvdb_deinit(db) |
反初始化 |
fdb_kvdb_control(db, cmd, arg) |
控制操作(设置扇区大小、锁等) |
fdb_kv_set(db, key, value) |
写入字符串类型 KV |
fdb_kv_get(db, key) |
读取字符串类型 KV(返回内部 buffer 指针) |
fdb_kv_set_blob(db, key, blob) |
写入 blob 类型 KV |
fdb_kv_get_blob(db, key, blob) |
读取 blob 类型 KV |
fdb_kv_get_obj(db, key, kv) |
获取 KV 对象(不读数据) |
fdb_kv_del(db, key) |
删除指定 KV |
fdb_kv_set_default(db) |
重置为默认 KV 集合 |
fdb_kv_print(db) |
打印 KVDB 信息 |
fdb_kv_iterator_init(db, itr) |
初始化 KV 迭代器 |
fdb_kv_iterate(db, itr) |
遍历下一个 KV,返回 false 表示结束 |
TSDB API
| 函数 | 功能 |
|---|---|
fdb_tsdb_init(db, name, path, get_time, max_len, user_data) |
初始化 TSDB |
fdb_tsdb_deinit(db) |
反初始化 |
fdb_tsdb_control(db, cmd, arg) |
控制操作(翻卷、扇区大小等) |
fdb_tsl_append(db, blob) |
追加 TSL(自动时间戳) |
fdb_tsl_append_with_ts(db, blob, timestamp) |
追加 TSL(指定时间戳) |
fdb_tsl_iter(db, cb, arg) |
遍历全部 TSL |
fdb_tsl_iter_reverse(db, cb, arg) |
反向遍历全部 TSL |
fdb_tsl_iter_by_time(db, from, to, cb, arg) |
按时间范围遍历 |
fdb_tsl_query_count(db, from, to, status) |
查询符合条件 TSL 数量 |
fdb_tsl_set_status(db, tsl, status) |
设置 TSL 状态 |
fdb_tsl_clean(db) |
清空 TSDB |
Blob 工具 API
| 函数 | 功能 |
|---|---|
fdb_blob_make(blob, buf, len) |
构造 blob 对象 |
fdb_blob_read(db, blob) |
读取 blob 数据到用户 buffer |
fdb_kv_to_blob(kv, blob) |
KV 对象转 blob |
fdb_tsl_to_blob(tsl, blob) |
TSL 对象转 blob |
KVDB 控制命令
| 命令 | 说明 |
|---|---|
FDB_KVDB_CTRL_SET_SEC_SIZE |
设置扇区大小(初始化前) |
FDB_KVDB_CTRL_GET_SEC_SIZE |
获取扇区大小 |
FDB_KVDB_CTRL_SET_LOCK |
设置加锁函数 |
FDB_KVDB_CTRL_SET_UNLOCK |
设置解锁函数 |
FDB_KVDB_CTRL_SET_FILE_MODE |
启用文件模式(初始化前) |
FDB_KVDB_CTRL_SET_MAX_SIZE |
设置数据库最大大小(文件模式,初始化前) |
FDB_KVDB_CTRL_SET_NOT_FORMAT |
禁自动格式化(初始化前) |
TSDB 控制命令
| 命令 | 说明 |
|---|---|
FDB_TSDB_CTRL_SET_SEC_SIZE |
设置扇区大小(初始化前) |
FDB_TSDB_CTRL_SET_ROLLOVER |
设置翻卷模式(初始化后) |
FDB_TSDB_CTRL_GET_ROLLOVER |
获取翻卷模式 |
FDB_TSDB_CTRL_GET_LAST_TIME |
获取最后一次存储时间 |
FDB_TSDB_CTRL_SET_FILE_MODE |
启用文件模式(初始化前) |
FDB_TSDB_CTRL_SET_MAX_SIZE |
设置数据库最大大小(文件模式,初始化前) |
与 LittleFS 的设计对比
FlashDB 和 LittleFS 同为嵌入式 Flash 存储方案,都遵循 Copy-on-Write + 原子提交的设计原则,但抽象层级和适用场景完全不同。
共同的设计哲学:
两者都"不信任 Flash"——不用原地修改,每次写入走新位置;两者都靠 Flash 的单向翻转特性做原子提交——写完后改一个标志位或版本号,改完即提交,断在中间就当没写;两者都不依赖 OS,静态分配内存,裸机和 RTOS 都能跑。
提交机制的对比:
FlashDB 用单 bit 翻转做提交。写入一条 KV 后,把记录头部一个 bit 从 1 翻成 0,这条记录就生效了。bit 是 Flash 编程的最小原子单位,断电时只有翻之前(回退旧数据)和翻之后(新数据生效)两种状态。
LittleFS 用双 metadata block 交替做提交。因为它管理的是整个文件系统的目录树——一次操作可能牵扯多个 inode、目录项、文件大小更新。一个 bit 表达不了这么多改动的"一致性提交",于是它用两个 metadata block 轮换写入,每次写新的、让 revision count 递增,上电时比较两个 block,取 revision 更大且 CRC 正确的那个。
本质是同一个 AB 切换思想,FlashDB 缩到了一个 bit,LittleFS 扩到了两个 block。不是谁更聪明,是各自要保护的数据复杂度不同。
抽象层级:FlashDB 是记录导向的数据库——接口是 set("key", value),它理解数据的语义,能做遍历、状态过滤、增量升级。LittleFS 是流导向的文件系统——接口是 fopen/fwrite,只管理字节序列和目录结构,不关心文件里存了什么。
选择依据:
| 场景 | 推荐 |
|---|---|
| 存产品参数、用户配置 | FlashDB KVDB |
| 记录传感器日志、运行日志 | FlashDB TSDB |
| 存固件 OTA 包、资源文件、任意格式文件 | LittleFS |
| 需要 PC 端熟悉的目录/文件操作模式 | LittleFS |
| 资源极度受限(几 KB RAM) | FlashDB |
也可以一起用:FlashDB 的文件模式底层跑在 LittleFS 之上,LittleFS 管 Flash 分区,FlashDB 把数据库当做文件存在上面。
总结
FlashDB 为嵌入式设备提供了一套开箱即用的数据库方案。KVDB 解决键值存储需求,TSDB 解决时序数据记录需求,二者共享同一套底层 Flash 管理机制。移植过程的核心在 FAL 层——只要实现 init/read/write/erase 四个 Flash 操作函数并配置好分区表,即可在裸机或 RTOS 环境下运行。对于没有 Flash 硬件的开发阶段,文件模式可以无缝切换到 PC 端进行调试。
本文由 AI 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。