嵌入式微型数据库-FlashDB

嵌入式微型数据库-FlashDB

开源链接 FlashDB

一、简介

FlashDB 是一款超轻量级的嵌入式数据库,提供两种数据库模式:

键值数据库 :是一种非关系数据库,它将数据存储为键值(Key-Value)对集合,其中键作为唯一标识符。KVDB 操作简洁,可扩展性强。

时序数据库 :时间序列数据库 (Time Series Database , 简称 TSDB),它将数据按照 时间顺序存储 。TSDB 数据具有时间戳,数据存储量大,插入及查询性能高。

KVDB :键值对数据库,可以存储我们常用的配置参数,数据类型可以是多种样式u8、u16、u32、字符串、结构体等都能以键值对的防止进行存储。
TSDB:时序数据库,带时间戳的数据记录。可以存储我们实时的采集数据,保存我们的工作日志等需要带时间戳的数据类型。数据类型可以是多种样式u8、u16、u32、字符串、结构体等。

感谢原创Armink朱天龙先生的开源贡献!

二、移植

本示例使用SMT32F105RB系列单片机,使用25Q128外部FLASH芯片,连接SPI2接口。

1、文件

(1)源码获取

FlashDB源码请自行在Github上下载,此处不再赘述。

所需文件如下:

(2)工程导入

此处以keil-MDK软件(基于STM32F105)工程为例,目录如下:

并同步包含头文件路径。

2、接口移植

(1)准备硬件SPI驱动

此处使用SP2,文件名"bsp_spi.c"和"bsp_spi.h"
bsp_spi.h

c 复制代码
#ifndef __SPI_H
#define __SPI_H
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
#include "includes.h" //修改成你自己的头文件
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
void SPIx_InitFlash(void);			 //初始化SPI口
void SPIx_SetSpeedFlash(u8 SpeedSet); //设置SPI速度
u8 SPIx_ReadWriteByteFlash(u8 TxData);//SPI总线读写一个字节
void sfud_SPIx_WriteByte(SPI_TypeDef * spix,u8 TxData); //FlashDB移植接口
u8 sfud_SPIx_ReadByte(SPI_TypeDef * spix,u8 TxData); //FlashDB移植接口
u8 sfud_SPIx_ReadWriteByte(SPI_TypeDef * spix, u8 TxData); //FlashDB移植接口
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
#endif

bsp_spi.c

c 复制代码
#include "bsp_spi.h"
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
#define FLASH_SPIx SPI2
#define LCD_SPIx SPI3
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
/*******************************************************************************
* 函数名称:SPIx_InitFlash
* 函数功能:Flash存储芯片SPI口初始化
* 入口参数:无
* 返回数值:无
* 其他说明:SPI模块的初始化代码,配置成主机模式,访问SD Card/W25Q64/NRF24L01
*******************************************************************************/
void SPIx_InitFlashFlash(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //PORTA时钟使能,FLASH_SPIx时钟使能

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PA5/6/7复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO

    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//设置SPI工作模式:设置为主SPI
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//设置SPI的数据大小:SPI发送接收8位帧结构
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//串行同步时钟的空闲状态为高电平
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//串行同步时钟的第二个跳变沿(上升或下降)数据被采样
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;		//定义波特率预分频的值:波特率预分频值为256
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
    SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
    SPI_Init(FLASH_SPIx, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设FLASH_SPIx寄存器

    SPI_Cmd(FLASH_SPIx, ENABLE); //使能SPI外设

    SPIx_ReadWriteByteFlash(0xff);//启动传输
}
/*******************************************************************************
* 函数名称:SPIx_SetSpeedFlash
* 函数功能:SPI 速度设置函数
* 入口参数:SPI速度
* 返回数值:无
* 其他说明:
SpeedSet:
SPI_BaudRatePrescaler_2   2分频
SPI_BaudRatePrescaler_8   8分频
SPI_BaudRatePrescaler_16  16分频
SPI_BaudRatePrescaler_256 256分频
*******************************************************************************/
void SPIx_SetSpeedFlash(u8 SPI_BaudRatePrescaler)
{
    assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
    FLASH_SPIx->CR1 &= 0XFFC7;
    FLASH_SPIx->CR1 |= SPI_BaudRatePrescaler;	//设置FLASH_SPIx速度
    SPI_Cmd(FLASH_SPIx, ENABLE);
}
/*******************************************************************************
* 函数名称:SPIx_ReadWriteByteFlash
* 函数功能:FLASH_SPIx 读写一个字节
* 入口参数:TxData:要写入的字节
* 返回数值:读取到的字节
* 其他说明:SPI模块的初始化代码,配置成主机模式,访问SD Card/W25Q64/NRF24L01
*******************************************************************************/
u8 SPIx_ReadWriteByteFlash(u8 TxData)
{
    u8 retry = 0;
    while(SPI_I2S_GetFlagStatus(FLASH_SPIx, SPI_I2S_FLAG_TXE) == RESET)  //检查指定的SPI标志位设置与否:发送缓存空标志位
    {
        retry++;
        if(retry > 200)return 0;
    }
    SPI_I2S_SendData(FLASH_SPIx, TxData); //通过外设FLASH_SPIx发送一个数据
    retry = 0;

    while(SPI_I2S_GetFlagStatus(FLASH_SPIx, SPI_I2S_FLAG_RXNE) == RESET)  //检查指定的SPI标志位设置与否:接受缓存非空标志位
    {
        retry++;
        if(retry > 200)return 0;
    }
    return SPI_I2S_ReceiveData(FLASH_SPIx); //返回通过FLASH_SPIx最近接收的数据
}
/*******************************************************************************
* 函数名称:sfud_SPIx_ReadWriteByte
* 函数功能:FLASH_SPIx 读写一个字节
* 入口参数:TxData:要写入的字节
* 返回数值:读取到的字节
* 其他说明:无
*******************************************************************************/
u8 sfud_SPIx_ReadWriteByte(SPI_TypeDef * spix, u8 TxData)
{
    u8 retry = 0;
    while(SPI_I2S_GetFlagStatus(spix, SPI_I2S_FLAG_TXE) == RESET)  //检查指定的SPI标志位设置与否:发送缓存空标志位
    {
        retry++;
        if(retry > 200)return 0;
    }
    SPI_I2S_SendData(spix, TxData); //通过外设FLASH_SPIx发送一个数据
    retry = 0;

    while(SPI_I2S_GetFlagStatus(spix, SPI_I2S_FLAG_RXNE) == RESET)  //检查指定的SPI标志位设置与否:接受缓存非空标志位
    {
        retry++;
        if(retry > 200)return 0;
    }
    return SPI_I2S_ReceiveData(spix); //返回通过FLASH_SPIx最近接收的数据
}
/*******************************************************************************
* 函数名称:sfud_SPIx_ReadByte
* 函数功能:FLASH_SPIx 读一个字节
* 入口参数:TxData:要写入的字节
* 返回数值:读取到的字节
* 其他说明:无
*******************************************************************************/
u8 sfud_SPIx_ReadByte(SPI_TypeDef * spix, u8 TxData)
{
    u8 retry = 0;
    SPI_I2S_SendData(spix, TxData); //通过外设FLASH_SPIx发送一个数据

    while(SPI_I2S_GetFlagStatus(spix, SPI_I2S_FLAG_RXNE) == RESET)  //检查指定的SPI标志位设置与否:接受缓存非空标志位
    {
        retry++;
        if(retry > 200)return 0;
    }
    return SPI_I2S_ReceiveData(spix); //返回通过FLASH_SPIx最近接收的数据
}
/*******************************************************************************
* 函数名称:sfud_SPIx_ReadByte
* 函数功能:FLASH_SPIx写一个字节
* 入口参数:TxData:要写入的字节
* 返回数值:无
* 其他说明:无
*******************************************************************************/
void sfud_SPIx_WriteByte(SPI_TypeDef * spix, u8 TxData)
{
    u8 retry = 0;
    while(SPI_I2S_GetFlagStatus(spix, SPI_I2S_FLAG_TXE) == RESET)  //检查指定的SPI标志位设置与否:发送缓存空标志位
    {
        retry++;
        if(retry > 200)
            return;
    }
    SPI_I2S_SendData(spix, TxData); //通过外设FLASH_SPIx发送一个数据
}

(2)移植修改sfud_port.c

sfud_port.c

c 复制代码
/*
 * This file is part of the Serial Flash Universal Driver Library.
 *
 * Copyright (c) 2016, Armink, <armink.ztl@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * 'Software'), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Function: Portable interface for each platform.
 * Created on: 2016-04-23
 */

#include <sfud.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "bsp_spi.h"
/******************************************************************************/
typedef struct {
    SPI_TypeDef *spix;
    SPI_InitTypeDef *spi_handle;
    GPIO_TypeDef *cs_gpiox;
    uint16_t cs_gpio_pin;
} spi_user_data, *spi_user_data_t;
/******************************************************************************/
static char log_buf[256];
/******************************************************************************/
void sfud_log_debug(const char *file, const long line, const char *format, ...);
/******************************************************************************/
static SPI_InitTypeDef hspi2;
static void spi_configuration(spi_user_data_t spi)
{
    SPI_InitTypeDef *spi_handle = spi->spi_handle;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
/******************************************************************************/
/******************************************************************************/
    /* USER CODE END SPI2_Init 1 */
    /* SPI1 parameter configuration*/
    spi_handle->SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
    spi_handle->SPI_Mode = SPI_Mode_Master;		//设置SPI工作模式:设置为主SPI
    spi_handle->SPI_DataSize = SPI_DataSize_8b;		//设置SPI的数据大小:SPI发送接收8位帧结构
    spi_handle->SPI_CPOL = SPI_CPOL_High;		//串行同步时钟的空闲状态为高电平
    spi_handle->SPI_CPHA = SPI_CPHA_2Edge;	//串行同步时钟的第二个跳变沿(上升或下降)数据被采样
    spi_handle->SPI_NSS = SPI_NSS_Soft;		//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
    spi_handle->SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;		//定义波特率预分频的值:波特率预分频值为256
    spi_handle->SPI_FirstBit = SPI_FirstBit_MSB;	//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
    spi_handle->SPI_CRCPolynomial = 7;	//CRC值计算的多项式
    SPI_Init(spi->spix, spi_handle);  //根据SPI_InitStruct中指定的参数初始化外设FLASH_SPIx寄存器

    SPI_Cmd(spi->spix, ENABLE); //使能SPI外设

    SPIx_ReadWriteByteFlash(0xff);//启动传输
}

static void spi_lock(const sfud_spi *spi) {
    __disable_irq();
}

static void spi_unlock(const sfud_spi *spi) {
    __enable_irq();
}

/**
 * SPI write data then read data
 */
static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
        size_t read_size) {
    sfud_err result = SFUD_SUCCESS;
    spi_user_data_t spi_dev = (spi_user_data_t) spi->user_data;
    u16 i;
    //HAL_StatusTypeDef state = HAL_OK;

    if (write_size) {
        SFUD_ASSERT(write_buf);
    }
    if (read_size) {
        SFUD_ASSERT(read_buf);
    }

    //CS拉低
    GPIO_ResetBits(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin);

    if (write_size) {
        //state = HAL_SPI_Transmit(spi_dev->spi_handle, (uint8_t *)write_buf, write_size, 1000);
        //while (HAL_SPI_GetState(spi_dev->spi_handle) != HAL_SPI_STATE_READY);
        for(i = 0; i < write_size; i++)
        {
            sfud_SPIx_ReadWriteByte(spi_dev->spix, write_buf[i]);
        }
        
    }

//    if (state != HAL_OK) {
//        goto __exit;
//    }

    if (read_size) {
        memset((uint8_t *)read_buf, 0xFF, read_size);
        //state = HAL_SPI_Receive(spi_dev->spi_handle, read_buf, read_size, 1000);
        //while (HAL_SPI_GetState(spi_dev->spi_handle) != HAL_SPI_STATE_READY);
        for(i = 0; i < read_size; i++)
        {
            read_buf[i] = sfud_SPIx_ReadWriteByte(spi_dev->spix, 0xFF);
        }
    }

//__exit:

    //CS拉高
    GPIO_SetBits(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin);

    return result;
}

/* about 100 microsecond delay */
static void retry_delay_100us(void) {
    uint32_t delay = 120;
    while(delay--);
}

static spi_user_data spi2 = { .spix = SPI2, .cs_gpiox = GPIOB, .cs_gpio_pin = GPIO_Pin_12, .spi_handle = &hspi2};
sfud_err sfud_spi_port_init(sfud_flash *flash) {
    sfud_err result = SFUD_SUCCESS;

    if (!strcmp(flash->spi.name, "SPI2"))
    {
        GPIO_InitTypeDef GPIO_InitStructure;

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //PORTA时钟使能,FLASH_SPIx时钟使能
        
        /* SPI 外设初始化 */
        spi_configuration(&spi2);

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PA5/6/7复用
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
    
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //PA4输出
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
        //CS拉高
        GPIO_SetBits(spi2.cs_gpiox, spi2.cs_gpio_pin);
        
        
        /* 同步 Flash 移植所需的接口及数据 */
        flash->spi.wr = spi_write_read;
        flash->spi.lock = spi_lock;
        flash->spi.unlock = spi_unlock;
        flash->spi.user_data = &spi2;
        /* about 100 microsecond delay */
        flash->retry.delay = retry_delay_100us;
        /* adout 60 seconds timeout */
        flash->retry.times = 60 * 10000;
    }

    return result;
}

/**
 * This function is print debug info.
 *
 * @param file the file which has call this function
 * @param line the line number which has call this function
 * @param format output format
 * @param ... args
 */
void sfud_log_debug(const char *file, const long line, const char *format, ...) {
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    printf("[SFUD](%s:%ld) ", file, line);
    /* must use vprintf to print */
    vsnprintf(log_buf, sizeof(log_buf), format, args);
    printf("%s\r\n", log_buf);
    va_end(args);
}

/**
 * This function is print routine info.
 *
 * @param format output format
 * @param ... args
 */
void sfud_log_info(const char *format, ...) {
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    printf("[SFUD]");
    /* must use vprintf to print */
    vsnprintf(log_buf, sizeof(log_buf), format, args);
    printf("%s\r\n", log_buf);
    va_end(args);
}

(3)修改数据库配置

修改sfud_cfg.h
c 复制代码
/*
 * This file is part of the Serial Flash Universal Driver Library.
 *
 * Copyright (c) 2016-2018, Armink, <armink.ztl@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * 'Software'), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Function: It is the configure head file for this library.
 * Created on: 2016-04-23
 */

#ifndef _SFUD_CFG_H_
#define _SFUD_CFG_H_

#ifdef DEBUG
#define SFUD_DEBUG_MODE
#endif

#define SFUD_USING_SFDP

#define SFUD_USING_FLASH_INFO_TABLE

enum {
    SFUD_W25Q128_DEVICE_INDEX = 0,
};

/*
请在此处修改你的FLASH芯片名称,以及使用的SPI硬件名称。
*/
#define SFUD_FLASH_DEVICE_TABLE                                                \
{                                                                              \
    [SFUD_W25Q128_DEVICE_INDEX] = {.name = "W25Q128BV", .spi.name = "SPI2"},   \
}

#endif /* _SFUD_CFG_H_ */

查看支持的FLALSH芯片,在文件sfud_flash_def.h 中,如下图所示:

常用的芯片基本都有。

修改分区配置文件fal_cfg.h
c 复制代码
/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-05-17     armink       the first version
 */

#ifndef _FAL_CFG_H_
#define _FAL_CFG_H_

#define NOR_FLASH_DEV_NAME             "norflash0"
#define FAL_PART_HAS_TABLE_CFG
#define FAL_DEBUG 1
/* ===================== Flash device Configuration ========================= */
extern const struct fal_flash_dev stm32f2_onchip_flash;
extern struct fal_flash_dev nor_flash0;

/* flash device table */
//#define FAL_FLASH_DEV_TABLE                                          \
//{                                                                    \
//    &stm32f2_onchip_flash,                                           \
//    &nor_flash0,                                                     \
//}
/*
由于我只是用的是外部FLASH芯片,所有我把官方示例直接屏幕掉,改为只支持nor_flash0。
*/
#define FAL_FLASH_DEV_TABLE                                          \
{                                                                    \
    &nor_flash0,                                                     \
}

/* ====================== Partition Configuration ========================== */
#ifdef FAL_PART_HAS_TABLE_CFG

/* partition table */
/*          备用               分区名        Flash设备名      偏移大小    大小   备用*/
//#define FAL_PART_TABLE                                                               \
//{                                                                                    \
//    {FAL_PART_MAGIC_WORD,        "bl",     "stm32_onchip",         0,   64*1024, 0}, \
//    {FAL_PART_MAGIC_WORD,       "app",     "stm32_onchip",   64*1024,  704*1024, 0}, \
//    {FAL_PART_MAGIC_WORD, "easyflash", NOR_FLASH_DEV_NAME,         0, 1024*1024, 0}, \
//    {FAL_PART_MAGIC_WORD,  "download", NOR_FLASH_DEV_NAME, 1024*1024, 1024*1024, 0}, \
//}
/*
	修改分区表,我直接屏蔽类官方的示例,复制一份进行自定义。
	分区表名称:"config_DB",是键值对数据库KVDB,主要存储配置参数,存储空间0-64K。
	分区表名称:"log_DB",是时序数据库TSLDB,主要存储日志文件,存储空间64K-15000K。
	其他参数默认就行。
*/
//总共15625K 
#define FAL_PART_TABLE                                                               \
{                                                                                    \
    {FAL_PART_MAGIC_WORD,  "config_DB",     NOR_FLASH_DEV_NAME, 0,         64 * 1024,   0},       \
    {FAL_PART_MAGIC_WORD,  "log_DB",         NOR_FLASH_DEV_NAME, 64 * 1024,  15000 * 1024,   0},       \
}

#endif /* FAL_PART_HAS_TABLE_CFG */

#endif /* _FAL_CFG_H_ */
分区表初始化
c 复制代码
///* TSDB object */
struct fdb_tsdb tsdb_Log = { 0 }; //数据库------工作日志
///* KVDB object */
struct fdb_kvdb kvdb_Config = { 0 }; //数据库-参数配置
/*******************************************************************************
* 函数名称:lock
* 函数功能:加锁
* 入口参数:无
* 返回数值:无
* 其他说明:无
*******************************************************************************/
static void lock(fdb_db_t db)
{
    __disable_irq();
}
/*******************************************************************************
* 函数名称:lock
* 函数功能:解锁
* 入口参数:无
* 返回数值:无
* 其他说明:无
*******************************************************************************/
static void unlock(fdb_db_t db)
{
    __enable_irq();
}
/*******************************************************************************
* 函数名称:clearLogDB
* 函数功能:清空日志
* 入口参数:无
* 返回数值:无
* 其他说明:无
*******************************************************************************/
void clearLogDB(void)
{
    //清空记录数据
    fdb_tsl_clean(&tsdb_Log); 
}
/*******************************************************************************
* 函数名称:clearConfigDB
* 函数功能:清空配置数据库
* 入口参数:无
* 返回数值:无
* 其他说明:无
*******************************************************************************/
void clearConfigDB(void)
{
    //清空记录数据
    fdb_kv_set_default(&kvdb_Config); 
}
/*******************************************************************************
* 函数名称:fdb_time_t
* 函数功能:得到当前系统时间 单位秒
* 入口参数:无
* 返回数值:无
* 其他说明:
* 这个是后去系统时间的函数,TSLDB数据库需要获取系统时间作为时间戳。你可以使用单片机内部RTC或者
* 外部时钟芯片提供时间,请自行实现吧!!!!!!!!!后续会单独写一篇文章关于STM32的RTC时钟驱
* 动的代码。
*******************************************************************************/
static fdb_time_t get_time(void)
{
    /* Using the counts instead of timestamp.
     * Please change this function to return RTC time.
     */
    
    return (time(NULL) + time_offset);
}
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
/*******************************************************************************
* 函数名称:Init_UserKVDB
* 函数功能:初始化用户KVDB数据库
* 入口参数:无
* 返回数值:-1:失败 0:成功
* 其他说明:无
*******************************************************************************/
s8 Init_UserKVDB(void)
{
    fdb_err_t result;
    struct fdb_default_kv default_kv;
	//添加加锁和解锁回调函数
	fdb_kvdb_control(&kvdb_Config, FDB_KVDB_CTRL_SET_LOCK, (void *)lock);
	fdb_kvdb_control(&kvdb_Config, FDB_KVDB_CTRL_SET_UNLOCK, (void *)unlock);
	/* 键值对数据库初始化
	 *             &tsdb: 数据库对象
     *        "information": 数据库名称
     *        "config": 分区表
	 *          get_time: 系统时间的回调函数.
	 *         128: 日志的最大输出长度
	 *        NULL: 保留
	 */
	result = fdb_kvdb_init(&kvdb_Config, "DB_Config", "config_DB", &default_kv, NULL);

	if (result != FDB_NO_ERR) 
    {
		return -1;
	}
	return 0;
}
/*******************************************************************************
* 函数名称:Init_UserTSDB_Log
* 函数功能:初始化TSDB数据库,用于保存测量数据
* 入口参数:无
* 返回数值:-1:失败 0:成功
* 其他说明:无
*******************************************************************************/
s8 Init_UserTSDB_Log(void)
{
    fdb_err_t result;
    
	//添加加锁和解锁回调函数
	fdb_tsdb_control(&tsdb_Log, FDB_TSDB_CTRL_SET_LOCK, (void *)lock);
	fdb_tsdb_control(&tsdb_Log, FDB_TSDB_CTRL_SET_UNLOCK, (void *)unlock);
    
	/* 时间序列数据库初始化
	 *             &tsdb: 数据库对象
     *        "DB_SystemLog": 数据库名称
     *        "senserDB": 分区表
	 *          get_time: 系统时间的回调函数.
	 *         128: 日志的最大输出长度
	 *        NULL: 保留
	 */
	result = fdb_tsdb_init(&tsdb_Log, "DB_log", "log_DB", get_time, 128, NULL);
	/* 读取最后一次保存数据的时间戳*/
	//fdb_tsdb_control(&tsdb, FDB_TSDB_CTRL_GET_LAST_TIME, &counts);

	if (result != FDB_NO_ERR) 
    {
		return -1;
	}
	return 0;
}

3、初始化数据库

main.h中包含头文件

c 复制代码
#include "sfud.h"
#include "fal.h"
#include "flashdb.h"

然后初始化调用:

c 复制代码
sfud_init();  //SFUD初始化
fal_init(); //初始化FAL库
Init_UserTSDB_Log();
Init_UserKVDB();

初次初始化时间会比较长,我大概等了4-5分钟左右才初始化完,初始化完毕后后续再初始化就会非常快了。

建议你再main主函数内部增加一个心跳LED灯,初始化完成后能看出来,或者初始化完毕后增加串口打印日志。

注意:在初始化函数调用前最好不用启用中断类函数,以免出现异常,导致初始化卡死。
请看初始化成功后的打印日志

建议增加SEGGER_RTT 的调试代码,然后重映射printf函数,通过J-LINK RTT View软件查看,详细使用请自行查找吧望山文章挺多的。

三、使用

1、KVDB键值对数据

(1)存储u8 u16 u32 String类型

存储
c 复制代码
/*******************************************************************************
* 函数名称:saveKVDB_u32
* 函数功能:保存u32类型数据
* 入口参数:键值名称,键值内容
* 返回数值:无
* 其他说明:无
*******************************************************************************/
static void saveKVDB_u32(const char *key, u32 text)
{
    struct fdb_blob blob;
    
    fdb_kv_set_blob(&kvdb_Config, key, fdb_blob_make(&blob, &text, sizeof(text)));
    IWDG_Feed();
}
/*******************************************************************************
* 函数名称:saveKVDB_u16
* 函数功能:保存u16类型数据
* 入口参数:键值名称,键值内容
* 返回数值:无
* 其他说明:无
*******************************************************************************/
static void saveKVDB_u16(const char *key, u16 text)
{
    struct fdb_blob blob;
    
    fdb_kv_set_blob(&kvdb_Config, key, fdb_blob_make(&blob, &text, sizeof(text)));
    IWDG_Feed();
}

/*******************************************************************************
* 函数名称:saveKVDB_u8
* 函数功能:保存u8类型数据
* 入口参数:键值名称,键值内容
* 返回数值:无
* 其他说明:无
*******************************************************************************/
static void saveKVDB_u8(const char *key, u8 text)
{
    struct fdb_blob blob;
    
    fdb_kv_set_blob(&kvdb_Config, key, fdb_blob_make(&blob, &text, sizeof(text)));
    IWDG_Feed();
}

/*******************************************************************************
* 函数名称:saveKVDB_float
* 函数功能:保存float类型数据
* 入口参数:键值名称,键值内容
* 返回数值:无
* 其他说明:无
*******************************************************************************/
static void saveKVDB_float(const char *key, float text)
{
    struct fdb_blob blob;
    
    fdb_kv_set_blob(&kvdb_Config, key, fdb_blob_make(&blob, &text, sizeof(text)));
    IWDG_Feed();
}
/*******************************************************************************
* 函数名称:saveKVDB_String
* 函数功能:保存字符串键值对
* 入口参数:键值名称,键值内容
* 返回数值:无
* 其他说明:无
*******************************************************************************/
static void saveKVDB_String(const char *key, char *ptext)
{
    fdb_kv_set(&kvdb_Config, key, ptext);
    IWDG_Feed();
}
查询
c 复制代码
/*******************************************************************************
* 函数名称:queryKVDB_u32
* 函数功能:查询u32类型键值对
* 入口参数:键值名称, 查询返回的数据指针
* 返回数值:-1:失败 0:成功 
* 其他说明:无
*******************************************************************************/
static s8 queryKVDB_u32(const char *key, u32* ptext)
{
    u16 return_value;
    struct fdb_blob blob;

    IWDG_Feed();
    fdb_kv_get_blob(&kvdb_Config, key, fdb_blob_make(&blob, &return_value, sizeof(return_value)));
    if (blob.saved.len > 0) 
    {
        *ptext = return_value;
        return 0;
    }
    else
    {
        return -1;
    }
    
}
/*******************************************************************************
* 函数名称:queryKVDB_u16
* 函数功能:查询u16类型键值对
* 入口参数:键值名称, 查询返回数据指针
* 返回数值:-1:失败 0:成功 
* 其他说明:无
*******************************************************************************/
static s8 queryKVDB_u16(const char *key, u16* ptext)
{
    u16 return_value;
    struct fdb_blob blob;

    IWDG_Feed();
    fdb_kv_get_blob(&kvdb_Config, key , fdb_blob_make(&blob, &return_value, sizeof(return_value)));
    if (blob.saved.len > 0) 
    {
        *ptext = return_value;
        return 0;
    }
    else
    {
        return -1;
    }
    
}
/*******************************************************************************
* 函数名称:queryKVDB_u8
* 函数功能:查询u8类型键值对
* 入口参数:键值名称, 查询返回数据指针
* 返回数值:-1:失败 0:成功 
* 其他说明:无
*******************************************************************************/
static s8 queryKVDB_u8(const char *key, u8* ptext)
{
    u8 return_value;
    struct fdb_blob blob;

    fdb_kv_get_blob(&kvdb_Config, key, fdb_blob_make(&blob, &return_value, sizeof(return_value)));
    if (blob.saved.len > 0) 
    {
        *ptext = return_value;
        return 0;
    }
    else
    {
        return -1;
    }
}
/*******************************************************************************
* 函数名称:queryKVDB_float
* 函数功能:查询float类型键值对
* 入口参数:键值名称, 查询返回数据指针
* 返回数值:-1:失败 0:成功 
* 其他说明:无
*******************************************************************************/
static s8 queryKVDB_float(const char *key, float* ptext)
{
    float return_value;
    struct fdb_blob blob;
    
    IWDG_Feed();
    fdb_kv_get_blob(&kvdb_Config, key, fdb_blob_make(&blob, &return_value, sizeof(return_value)));
    if (blob.saved.len > 0) 
    {
        *ptext = return_value;
        return 0;
    }
    else
    {
        return -1;
    }
    
}
使用示例
c 复制代码
//存储电压值
u16 vol1 = 12;
saveKVDB_u16("Power_Vol", vol1 ); //其他数据格式存储使用与此相同。
//查询电压值
u16 vol2 = 0;
queryKVDB_u16("Power_Vol", &vol2 );//其他数据格式存储使用与此相同。

(2)存储结构体数据

存储
c 复制代码
定义一个数据个构体和变量,结构体的数据格式我是随便定义的。
```c
typedef struct test
{
	u8 index; 
	char name[6]; 
	float vol;
	
}test_t;
/*******************************************************************************
* 函数名称:saveKVDB_Test
* 函数功能:保存结构体键值对
* 入口参数:键值名称,键值内容
* 返回数值:无
* 其他说明:无
*******************************************************************************/
static void saveKVDB_Test(const char *key, test_t * test)
{
	struct fdb_blob blob;
    
    fdb_kv_set_blob(&kvdb_Config, key, fdb_blob_make(&blob, test, sizeof(test_t )));
    
    IWDG_Feed();
}
查询
c 复制代码
/*******************************************************************************
* 函数名称:queryKVDB_Test
* 函数功能:查询结构体类型键值对
* 入口参数:键值名称, 数据指针
* 返回数值:-1:失败 0:成功 
* 其他说明:无
*******************************************************************************/
static s8 queryKVDB_Test(const char *key, test_t* ptext)
{
    test_t return_value;
    struct fdb_blob blob;

    IWDG_Feed();
    fdb_kv_get_blob(&kvdb_Config, key, fdb_blob_make(&blob, &return_value, sizeof(test_t));
    if (blob.saved.len > 0) 
    {
        memcpy(ptext, &return_value, sizeof(test_t ));
        return 0;
    }
    else
    {
        return -1;
    }
    
}
使用示例
c 复制代码
u8 index; 
	char name[6]; 
	float vol;
//定义一个结构体变量
test_t myTest1 = {.index = 1, .name = {1,2,3,4,5,6}, .vol=12.5};
saveKVDB_Test("myTest", &myTest1.index);
//保存数据

//查询数据
test_t myTest2;
queryKVDB_Test("myTest", &myTest2)

2、TSLDB时序数据

c 复制代码
typedef struct savelog
{
    u32 time; //时间戳; //时间戳
    u8 powerOn;
    float vol;
    
}SaveLog_t;

存储

c 复制代码
/*******************************************************************************
* 函数名称:saveTSDB_Log
* 函数功能:保存日志数据
* 入口参数:数据库句柄,参数
* 返回数值:无
* 其他说明:无
*******************************************************************************/
void saveTSDB_Log(SaveLog_t logdb)
{
    struct fdb_blob blob;
    SaveLog_t logDB = {0};
    fdb_err_t fdbErr;
    
    os_memcpy((u8 *)&logDB, (u8 *)&logdb, sizeof(SaveLog_t)); //拷贝数据
    logDB.time = time(NULL); //获取当前时间
    
    //追加数据到数据库
    fdbErr = fdb_tsl_append(&tsdb_Log, fdb_blob_make(&blob, &logDB, sizeof(SaveLog_t)));
    
    if(fdbErr == FDB_NO_ERR)
    {
        #ifdef DEBUG
            printf("write log ok \r\n");
        #endif
    }
    else
    {
        #ifdef DEBUG
            printf("write log ERROR:%d \r\n", fdbErr);
        #endif
    }
    
    IWDG_Feed();
}

查询

c 复制代码
/*******************************************************************************
* 函数名称:query_cb_Log
* 函数功能:查询数据回调函数
* 入口参数:数据库句柄,参数
* 返回数值:-1:失败 0:成功
* 其他说明:无
*******************************************************************************/
char strLog[128]={0};
bool query_cb_Log(fdb_tsl_t tsl, void *arg)
{
    struct fdb_blob blob;
    SaveLog_t status = {0};
    fdb_tsdb_t db = arg;
    
    memset(strLog, 0, sizeof(strLog));
    fdb_blob_read((fdb_db_t) db, fdb_tsl_to_blob(tsl, fdb_blob_make(&blob, &status, sizeof(SaveLog_t))));

        struct tm ts = {0};
        // 转为本地时间
        ts = *localtime((time_t*)&status.time);

    //通过串口发送日志数据
    sprintf(strLog, "Data: %04d-%02d-%02d %02d:%02d:%02d powerON:%d vol:%.3f \r\n", 
            ts.tm_year + 1900, ts.tm_mon+1, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, status.powerOn, status.vol);
    rsxx2_write_wait((const u8*)strLog, strlen(strLog));
    
    IWDG_Feed(); //如果有看门狗,一定要喂狗,查询数据量大的话会导致看门狗复位

    return false;
}
/*******************************************************************************
* 函数名称:queryTSDB_Log_SelectDate
* 函数功能:查询数据记录,根据选择的日期
* 入口参数:从fromtime到totime
* 返回数值:无
* 其他说明:无
*******************************************************************************/
void queryTSDB_Log_SelectDate(u32 fromtime, u32 totime)
{
    size_t len;

    //查询条数
    len = fdb_tsl_query_count(&tsdb_Log, fromtime, totime, FDB_TSL_WRITE);
    if(len > 0)
    {

        //查询数
        //fdb_tsl_iter(&tsdb_Data, query_cb_Data, &tsdb_Data);
        fdb_tsl_iter_by_time(&tsdb_Log, fromtime, totime, query_cb_Log, &tsdb_Log);
        #ifdef DEBUG
            printf("[mydb_save.c]data total fdb size:%d \r\n", len);
        #endif
    }
    else
    {
        #ifdef DEBUG
            printf("[mydb_save.c]data total fdb size:0 \r\n");
        #endif
    }
}

使用示例

c 复制代码
/*******************************************************************************
 * 函数名称:queryLogRecordAll
 * 函数功能:查询日志记录
 * 入口参数:无
 * 返回数值:无
 * 其他说明:无
*******************************************************************************/
void queryLogRecordAll(void)
{   
    const u32 startTime = 1735660800; //2025-1-1 0:0:0
    const u32 endTime = 2524579200; ////2050-1-1 0:0:0
    //const u32 timediff = 28800; //8小时时差
    
    //开始日期
    u32 fromTime = startTime;
    //结束日期
    u32 toTime = endTime;
        
    queryTSDB_Log_SelectDate(fromTime, toTime);
}

四、结束语

编译过程中报数据类型错误,自行解决就行,有的是数据类型未定义,例如找不到int16_t等错误。
再次感谢Armink大佬的开源贡献!

相关推荐
陈天伟教授几秒前
TraeWork初体验-生成研究报告
大数据·数据库·人工智能
大黄说说5 分钟前
SQL Server 执行计划怎么看?快速定位 SQL 慢的根源
java·linux·数据库
专注API从业者24 分钟前
告别人工盯品!Open Claw 搭建京东商品全自动监控与数据分析系统(附完整可运行代码)
大数据·数据库·数据分析
杨连江30 分钟前
基于多维度数学模型的股票投资预测体系构建与实证分析
经验分享
Bernice橘子30 分钟前
职场英语学习计划Day034
学习
雪之下雪乃的代码日记43 分钟前
Python快速入门(Java开发者版)
java·开发语言·笔记·python
IT小白杨1 小时前
2026年短视频平台风控技术解析:设备指纹体系、行为建模与环境隔离的边界在哪里
chrome·经验分享·架构·音视频·安全架构·指纹浏览器
磊灬泽1 小时前
【从零学习 MQTT】发布/订阅、主题、QoS 与报文解析
学习
zzh___zzh1 小时前
Java Stream API 常用方法笔记
java·笔记
捷智算云服务2 小时前
训练大模型为什么选择A100/H100服务器租赁?
经验分享