FlashDB 嵌入式数据库从移植到上手:KVDB/TSDB 双模式基础操作指南

简介

FlashDB 是 armink 开源的一款超轻量级嵌入式数据库,专为嵌入式产品的数据存储场景设计。它不像传统数据库依赖文件系统,而是直接结合 Flash 存储特性,在保证极低资源占用的前提下,提供高性能、高可靠的数据存储。

FlashDB 提供两种数据库模式:

核心特性

FlashDB 在设计上针对嵌入式 Flash 的特点做了深度优化:

资源占用极低。在 STM32F4 平台下,IAR 编译器编译后整个库仅占用约 7KB ROM,RAM 占用几乎为零。内部使用静态分配内存,不使用 malloc。

磨损平衡。Flash 的擦写次数有限,FlashDB 通过扇区轮转写入机制让擦写操作均匀分布在整个分区上,避免热点扇区过早失效。

掉电保护。写入机制类比 OTA 的 AB 分区:新数据写到扇区空白区域,旧数据原地不动。写完后用一次 Flash 编程脉冲把一个 bit 从 1 翻成 0,这一个 bit 就是提交信号。NOR Flash 的物理规则是擦除后全为 1,编程只能把 10,且单个 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");
}

初始化参数说明:

字符串类型 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 返回的指针指向内部缓冲区,不可重入,建议立即用 strcpystrdup 拷贝到用户内存中。

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 的三步操作合为一句:

  1. value_bufbuf_len 初始化 blob 对象
  2. 返回构造好的 fdb_blob_t 指针
  3. 直接传给 fdb_kv_set_blobfdb_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");
}

几个关键参数:

追加 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/incfal/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 数据库

注意:

第四步:应用层初始化

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);

常见移植问题

  1. 分区找不到:检查 fdb_kvdb_init 传入的分区名与 fal_cfg.h 中的分区名是否完全一致,以及 fal_init() 是否在数据库初始化之前调用。

  2. 写入后读取数据不匹配:检查 FDB_WRITE_GRAN 写粒度是否与 Flash 芯片一致。nor flash 通常为 1,STM32 系列因型号不同而异。

  3. 扇区大小对齐问题:KVDB/TSDB 的扇区大小必须是 Flash 块大小的整数倍。如果数据库保存的数据较大,可通过 FDB_KVDB_CTRL_SET_SEC_SIZE 调大扇区。

  4. 编译提示找不到 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 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。