嵌入式微型数据库-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大佬的开源贡献!

相关推荐
yejqvow122 小时前
如何分析RAC启动挂起_crond与ohasd进程启动依赖链排查
jvm·数据库·python
Alice-YUE2 小时前
【前端面试之ai概念】大白话讲清 Agent、MCP、Skill、Function Calling、RAG
前端·人工智能·学习·aegnt
2401_835956812 小时前
c++怎么解析二进制存储的BMP位图调色板数据及每一行的像素偏移【详解】
jvm·数据库·python
2301_815279522 小时前
JavaScript中闭包结合代理模式Proxy实现数据监听
jvm·数据库·python
2401_837163892 小时前
mysql如何禁止用户创建新表_撤销CREATE与ALTER表权限
jvm·数据库·python
明天再做行么2 小时前
C++教程资源合集(第二辑)
经验分享
m0_640309302 小时前
如何解决phpMyAdmin导出空文件的问题_权限检查与表是否损坏排查
jvm·数据库·python
2401_837163892 小时前
Golang怎么设置响应状态码_Golang如何用WriteHeader返回404或500状态【基础】
jvm·数据库·python
2301_773553622 小时前
如何配置Data Guard的重做路由Redo Routing_级联备库Cascaded Standby架构
jvm·数据库·python