1. 为什么选择FlashDB?

第一次接触嵌入式数据库时,我也纠结过该选哪个方案。当时手头的STM32F103项目需要存储设备配置参数和运行日志,试过直接读写Flash,但维护起来太痛苦。后来发现了FlashDB这个轻量级键值数据库,实测下来确实很适合资源有限的MCU。

FlashDB最大的优势是针对嵌入式场景做了极致优化。它不像传统数据库那样需要文件系统支持,直接操作Flash物理层,节省了中间环节的开销。我对比过几种常见方案:

  • FatFs:适合需要完整文件系统的场景,比如SD卡存储
  • EasyFlash:简单易用但功能较基础
  • FlashDB:提供了更丰富的数据库功能,支持事务和快速查询

在STM32F103这类Cortex-M3芯片上,FlashDB的内存占用可以控制在2KB以内,这对只有20KB RAM的芯片来说非常关键。我实测写入100条记录(每条128字节)耗时约15ms,完全能满足大多数嵌入式场景的需求。

2. 环境准备与源码获取

2.1 硬件准备

我用的开发板是STM32F103VET6(512KB Flash),这个型号很常见,淘宝几十块就能买到。关键要注意Flash的物理特性:

  • 页大小:2KB(大容量型号)
  • 写粒度:32位(必须正确配置)
  • 擦除次数:约1万次(需做磨损均衡)

建议准备个ST-Link调试器,方便观察运行状态。如果要做长时间稳定性测试,最好接个逻辑分析仪监测Flash操作时序。

2.2 软件准备

FlashDB的源码托管在Gitee:

git clone https://gitee.com/Armink/FlashDB.git

项目结构很清晰:

├── demos    # 示例工程
├── docs     # 文档
├── inc      # 头文件
├── port     # 硬件适配层
├── samples  # 示例代码
└── src      # 核心源码

我建议先用Keil MDK创建一个空白工程,然后按需添加文件。重点要关注port/fal目录下的硬件抽象层,这是移植的关键。

3. 移植实战步骤

3.1 工程配置

首先在Keil中添加必要文件:

  1. incsrc下的所有文件加入工程
  2. 添加port/fal下的硬件适配文件
  3. 在工程选项的C/C++选项卡添加头文件路径

关键配置项:

  • Define:添加FAL_PART_HAS_TABLE_CFG
  • Optimization:建议选择-O2优化
  • Debug信息:建议开启,方便排查问题

3.2 硬件适配修改

找到fal_flash_stm32f1_port.c文件,根据芯片型号修改:

#define PAGE_SIZE 2048  // STM32F103VET6的页大小

const struct fal_flash_dev stm32_onchip_flash = {
    .name = "stm32_onchip",
    .addr = 0x08000000,
    .len = 512*1024,
    .blk_size = 2*1024,
    .ops = {NULL, read, write, erase}, // 不需要初始化函数
    .write_gran = 32  // STM32F1的写粒度
};

特别注意write_gran参数,STM32F1系列必须设为32,否则会出现写入错位。这个坑我踩过,现象是随机出现数据损坏。

3.3 分区表配置

修改fal_cfg.h定义存储分区:

#define FAL_PART_TABLE \
{ \
    {FAL_PART_MAGIC_WORD, "tsdb", "stm32_onchip", 256*1024, 64*1024, 0}, \
    {FAL_PART_MAGIC_WORD, "kvdb", "stm32_onchip", 320*1024, 64*1024, 0}, \
}

这里有几个经验:

  1. 分区起始地址要避开程序存储区(查看链接脚本确认)
  2. 保留至少16KB空间给Bootloader(如果需要)
  3. 日志分区建议不小于32KB

4. 功能验证与性能优化

4.1 基础功能测试

先写个简单的测试程序验证基本功能:

void test_kvdb(void) {
    struct fdb_kvdb kvdb;
    fdb_kvdb_init(&kvdb, "config", "kvdb", NULL, NULL);
    
    // 写入测试
    fdb_kv_set(&kvdb, "device_id", "STM32F103");
    fdb_kv_set_blob(&kvdb, "calibration", &calib_data, sizeof(calib_data));
    
    // 读取验证
    char value[32];
    fdb_kv_get(&kvdb, "device_id", value, sizeof(value));
    printf("Device ID: %s\n", value);
}

常见问题排查:

  • 如果初始化失败,检查分区地址是否合法
  • 写入返回错误时,确认write_gran设置正确
  • 数据读取异常可能是Flash未擦除导致

4.2 性能优化技巧

通过实测发现几个优化点:

  1. 批量写入:攒够一页数据再写入,减少擦除次数
  2. 缓存热点数据:频繁读取的数据可以缓存在RAM
  3. 合理设置GC阈值:建议保留至少25%空闲空间

在我的项目中,优化后写入速度提升3倍,Flash寿命预计延长5倍。具体参数需要根据实际使用场景调整。

5. 高级功能应用

5.1 时间序列数据库

FlashDB的TSDB功能非常适合存储传感器数据:

struct env_data {
    float temperature;
    float humidity;
    uint32_t timestamp;
};

void log_sensor_data(void) {
    struct fdb_tsdb tsdb;
    fdb_tsdb_init(&tsdb, "sensor", "tsdb", get_timestamp, sizeof(struct env_data), NULL);
    
    struct env_data data = {
        .temperature = 25.6,
        .humidity = 60.2,
        .timestamp = HAL_GetTick()
    };
    fdb_tsl_append(&tsdb, &data);
}

查询数据时可以使用时间范围过滤:

fdb_tsl_iter_by_time(&tsdb, start_time, end_time, iter_cb, NULL);

5.2 掉电保护实现

利用FlashDB的事务特性实现掉电安全:

fdb_kvdb_transaction_begin(&kvdb);
fdb_kv_set(&kvdb, "config1", value1);
fdb_kv_set(&kvdb, "config2", value2);
fdb_kvdb_transaction_commit(&kvdb);

我在产品中实测,即使在写入过程中突然断电,数据一致性也能得到保证。关键是要合理设置事务超时时间。

6. 常见问题解决方案

6.1 数据损坏问题

遇到过几次数据异常,总结出以下排查步骤:

  1. 检查Flash驱动函数的返回值
  2. 确认擦除和写入操作符合芯片规格
  3. 使用CRC校验关键数据
  4. 在RTOS中确保操作互斥

6.2 内存不足处理

当RAM紧张时,可以:

  1. 减小FDB_KV_CACHE_SIZE(默认256字节)
  2. 关闭调试日志FAL_DEBUG=0
  3. 使用静态内存分配替代动态分配

6.3 长期运行稳定性

经过三个月连续运行测试,发现:

  • 需要定期检查存储碎片率
  • 建议每月执行一次完整GC
  • 关键数据建议双备份存储

这些经验都是在实际项目中踩坑后总结的,希望能帮你少走弯路。移植过程中遇到具体问题,可以查看FlashDB的issue区,很多常见问题都有讨论。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐