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