FlashDB数据库移植与使用介绍

一、FlashDB数据库简介

FlashDB 是一款超轻量级的嵌入式数据库,专注于提供嵌入式产品的数据存储方案。FlashDB 不仅支持传统的基于文件系统的数据库模式,而且结合了 Flash 的特性,具有较强的性能及可靠性。并在保证极低的资源占用前提下,尽可能延长 Flash 使用寿命。

FlashDB 提供两种数据库模式:

键值数据库 :是一种非关系数据库,它将数据存储为键值(Key-Value)对集合,其中键作为唯一标识符。KVDB 操作简洁,可扩展性强。
时序数据库 :时间序列数据库 (Time Series Database , 简称 TSDB),它将数据按照时间顺序存储 。TSDB 数据具有时间戳,数据存储量大,插入及查询性能高。

欢迎 Star&Fork :FlashDB: 一款支持 KV 数据和时序数据的超轻量级数据库

1、使用场景

键值数据库 :

产品参数存储(如:键-单一参数)
用户配置信息存储(如:键-字符串参数)
小文件管理(如:键-结构体参数)

时序数据库 :

存储动态产生的结构化数据:如 温湿度传感器采集的环境监测信息,智能手环实时记录的人体健康信息等
记录运行日志:存储产品历史的运行日志,异常告警的记录等

        按照时间顺序存储 :数据的保存均是以时间戳增长为标记存储数据,如果当前保存数据的时间戳小于该数据库里该类型数据的时间戳,时间戳会自增1后,再将数据存到Flash中。

2、主要特性

资源占用极低,内存占用几乎为 0 ;
支持 多分区,多实例 。数据量大时,可细化分区,降低检索时间,同一扇区可保存不同键值或时序数据;
支持 磨损平衡 ,延长 Flash 寿命;
支持 掉电保护 功能,可靠性高;
支持 字符串及 blob 两种 KV 类型,方便用户操作;
支持 KV 增量升级 ,产品固件升级后, KVDB 内容也支持自动升级;
支持 修改每条 TSDB 记录的状态,方便用户进行管理;

3、基本概念

键值数据库(KVDB):是一种非关系数据库,它将数据存储为键值(Key-Value)对集合,其中键作为唯一标识符。KVDB 操作简洁,可扩展性强。
时序数据(TSDB) :时间序列数据库 (Time Series Database , 简称 TSDB),它将数据按照 时间顺序存储 。TSDB 数据具有时间戳,数据存储量大,插入及查询性能高。
时序记录(TSL) :TSL (Time series log),是 TSDB 中每条记录的简称。
Blob :在计算机中,blob 常常是数据库中用来存储二进制文件的字段类型。在 FlashDB 中, KV 和 TSL 都使用 blob 类型来存储,该类型可以兼容任意变量类型。
迭代器(iterator):它可以让用户透过特定的接口巡访容器中的每一个元素,而不用了解底层的实现。 TSDB 和 KVDB 都支持通过迭代器对数据库进行遍历访问。

4、功能框图

5、FlashDB框架

FlashDB依赖于fal硬件抽象层(实现对flash芯片的分区管理),fal依赖于sfud硬件驱动层(实现对flash芯片的驱动)。所以在移植FlashDB前,需要先将fal与sfud事先移植好。下面按照顺序依次讲解移植步骤。

参考链接:[FlashDB]基于GD32纯代码裸机移植FlashDB数据库——一、介绍-CSDN博客


二、SFUD驱动库移植与使用

1、SFUD驱动库简介

SFUD 是一款开源的串行 SPI Flash 通用驱动库。几乎支持市面上所有norflash存储芯片。作者是RT-Thread的技术总监—armink。

主要特点:支持 SPI/QSPI 接口、面向对象(同时支持多个 Flash 对象)、可灵活裁剪、扩展性强、支持 4 字节地址。
资源占用

标准占用:RAM:0.2KB ROM:5.5KB
最小占用:RAM:0.1KB ROM:3.6KB

设计思路:

什么是 SFDP :它是 JEDEC (固态技术协会)制定的串行 Flash 功能的参数表标准,最新版 V1.6B (点击这里查看)。该标准规定了,每个 Flash 中会存在一个参数表,该表中会存放 Flash 容量、写粒度、擦除命令、地址模式等 Flash 规格参数。目前,除了部分厂家旧款 Flash 型号会不支持该标准,其他绝大多数新出厂的 Flash 均已支持 SFDP 标准。所以该库在初始化时会优先读取 SFDP 表参数。
不支持 SFDP 怎么办 :如果该 Flash 不支持 SFDP 标准,SFUD 会查询配置文件 ( /sfud/inc/sfud_flash_def.h ) 中提供的 Flash 参数信息表 中是否支持该款 Flash。如果不支持,则可以在配置文件中添加该款 Flash 的参数信息(添加方法详细见 2.5 添加库目前不支持的 Flash)。获取到了 Flash 的规格参数后,就可以实现对 Flash 的全部操作。

2、软硬件环境

N32G457VEL7,Freertos,W25Q128BV,SPI1

IO引脚                SPI接口                norflash芯片引脚
PA4                    SPI1_NSS            CS

PA5                    SPI1_SCK            CLK

PA6                    SPI1_MISO          DO

PA7                    SPI1_MOSI          DI

3、移植

①源码下载:SFUD: 一款使用 JEDEC SFDP 标准的串行 (SPI) Flash 通用驱动库

②将..SFUD-mastersfud里面的.c和.h文件均添加到项目中

③移植文件如下,与官方略有差别,我已忘记是从哪移植的,大家相互对比选择合适的代码,此处贴出移植代码:

sfud_port.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: Portable interface for each platform.
 * Created on: 2016-04-23
 */

#include <sfud.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include "n32g45x.h"

typedef struct {
    SPI_Module *spix;
    GPIO_Module *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 void rcc_configuration(spi_user_data_t spi) {
    if (spi->spix == SPI1) {
        RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_SPI1, ENABLE);
        RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA, ENABLE);
    } else if (spi->spix == SPI2) {
        /* you can add SPI2 code here */
    }
}

static void gpio_configuration(spi_user_data_t spi) {
    GPIO_InitType GPIO_InitStructure;

    if (spi->spix == SPI1) {
        /* SCK:PA5  MISO:PA6  MOSI:PA7 */
        GPIO_InitStructure.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);
        /* CS: PA4 */
        GPIO_InitStructure.Pin = GPIO_PIN_4;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);
        GPIO_SetBits(GPIOA, GPIO_PIN_4);
    } else if (spi->spix == SPI2) {
        /* you can add SPI2 code here */
    }
}

static void spi_configuration(spi_user_data_t spi) {
    SPI_InitType SPI_InitStructure;

    SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;    //SPI全双工模式
    SPI_InitStructure.SpiMode = SPI_MODE_MASTER;                        //SPI1工作在主设备模式下,与之相对的是W25Q128工作在从模式
    SPI_InitStructure.DataLen = SPI_DATA_SIZE_8BITS;                    //SPI1每次传输一个字节
    SPI_InitStructure.CLKPOL = SPI_CLKPOL_LOW;                          //设置时钟极性为低电平空闲状态
    SPI_InitStructure.CLKPHA = SPI_CLKPHA_FIRST_EDGE;                   //数据捕获于第一个时钟沿
    //TODO 以后可以尝试硬件CS
    SPI_InitStructure.NSS = SPI_NSS_SOFT;                               //设置片选信号由软件控制
    SPI_InitStructure.BaudRatePres = SPI_BR_PRESCALER_4;                //设置波特率预分频为4,将SPI1的时钟信号四分频,主模式需要配置串行时钟波特率
    SPI_InitStructure.FirstBit = SPI_FB_MSB;                            //设置数据传输的起始位为最高有效位
    SPI_InitStructure.CRCPoly = 7;                                      //设置CRC多项式为7

    SPI_I2S_DeInit(spi->spix);
    SPI_Init(spi->spix, &SPI_InitStructure);

    SPI_EnableCalculateCrc(spi->spix, DISABLE);
    SPI_Enable(spi->spix, ENABLE);
}


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;
    uint8_t send_data, read_data;
  
    spi_user_data_t spi_dev = (spi_user_data_t) spi->user_data;
    if (write_size) {
        SFUD_ASSERT(write_buf);
    }
    if (read_size) {
        SFUD_ASSERT(read_buf);
    }    
    GPIO_ResetBits(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin);
    /* 开始读写数据 */
    for (size_t i = 0, retry_times; i < write_size + read_size; i++) {
        /* 先写缓冲区中的数据到SPI总线,数据写完后,再写dummy(0XFF)到SPI总线 */
        if (i < write_size) {
            send_data = *write_buf++;
        } else {
            send_data = SFUD_DUMMY_DATA;
        }
        /* 发送数据 */
        retry_times = 1000;
        while (SPI_I2S_GetStatus(spi_dev->spix, SPI_I2S_TE_FLAG) == RESET) {
            SFUD_RETRY_PROCESS(NULL, retry_times, result);
        }
        if (result != SFUD_SUCCESS) {
            goto exit;
        }
        SPI_I2S_TransmitData(spi_dev->spix, send_data);
        /* 接收数据 */
        retry_times = 1000;
        while (SPI_I2S_GetStatus(spi_dev->spix, SPI_I2S_RNE_FLAG) == RESET) {
            SFUD_RETRY_PROCESS(NULL, retry_times, result);
        }
        if (result != SFUD_SUCCESS) {
            goto exit;
        }
        read_data = SPI_I2S_ReceiveData(spi_dev->spix);
        /* 写缓冲区中的数据发完后,在读取SPI总线中的数据到读缓冲区 */
        if (i >= write_size) {
            *read_buf++ = read_data;
        }
    }

exit:
    GPIO_SetBits(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin);

    return result;
}

#ifdef SFUD_USING_QSPI
/**
 * read flash data by QSPI
 */
static sfud_err qspi_read(const struct __sfud_spi *spi, uint32_t addr, sfud_qspi_read_cmd_format *qspi_read_cmd_format,
        uint8_t *read_buf, size_t read_size) {
    sfud_err result = SFUD_SUCCESS;

    /**
     * add your qspi read flash data code
     */

    return result;
}
#endif /* SFUD_USING_QSPI */

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

static spi_user_data spi1 = { .spix = SPI1, .cs_gpiox = GPIOA, .cs_gpio_pin = GPIO_PIN_4 };
sfud_err sfud_spi_port_init(sfud_flash *flash) {
    sfud_err result = SFUD_SUCCESS;

    /**
     * add your port spi bus and device object initialize code like this:
     * 1. rcc initialize
     * 2. gpio initialize
     * 3. spi device initialize
     * 4. flash->spi and flash->retry item initialize
     *    flash->spi.wr = spi_write_read; //Required
     *    flash->spi.qspi_read = qspi_read; //Required when QSPI mode enable
     *    flash->spi.lock = spi_lock;
     *    flash->spi.unlock = spi_unlock;
     *    flash->spi.user_data = &spix;
     *    flash->retry.delay = null;
     *    flash->retry.times = 10000; //Required
     */
    switch (flash->index) {
    case SFUD_W25Q_DEVICE_INDEX: {
        /* RCC 初始化 */
        rcc_configuration(&spi1);
        /* GPIO 初始化 */
        gpio_configuration(&spi1);
        /* SPI 外设初始化 */
        spi_configuration(&spi1);
        /* 同步 Flash 移植所需的接口及数据 */
        flash->spi.wr = spi_write_read;
        flash->spi.lock = spi_lock;
        flash->spi.unlock = spi_unlock;
        flash->spi.user_data = &spi1;
        /* about 100 microsecond delay */
        flash->retry.delay = retry_delay_100us;
        /* adout 60 seconds timeout */
        flash->retry.times = 60 * 10000;

        break;
    }
    }
    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
", 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
", log_buf);
    va_end(args);
}

sfud_cfg.h

/*
 * 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_

#define SFUD_DEBUG_MODE     //使能SFUD的打印日志

#define SFUD_USING_SFDP     //使能SFDP:JEDEC标准(JESD216)标准接口注意:关闭后只会查询该库在 
														// /sfud/inc/sfud_flash_def.h 中提供的 Flash 信息表。这样虽然会降低软件的适配性,但减少代码量。

// #define SFUD_USING_FAST_READ

#define SFUD_USING_FLASH_INFO_TABLE //是否使用该库自带的 Flash 参数信息表注意:关闭后该库只驱动支持 SFDP 规范的 Flash,
																		//也会适当的降低部分代码量。另外 2.3.2 及 2.3.3 这两个宏定义至少定义一种,也可以两种方式都选择

enum {
    SFUD_W25Q_DEVICE_INDEX = 0,
};

#define SFUD_FLASH_DEVICE_TABLE                                                
{                                                                              
    [SFUD_W25Q_DEVICE_INDEX] = {.name = "W25Q128BV", .spi.name = "SPI1"},      
}

//#define SFUD_USING_QSPI

#endif /* _SFUD_CFG_H_ */

4、修改要点

如果是使用官方源码移植,仅实现下面标红文件即可,其他未标红文件可直接使用官方源码。

1、sfud_sfdp.c,参照移植文件;

2、sfud.c,参照移植文件;

3、sfud_port.c,主要是实现rcc_configuration,gpio_configuration,spi_configuration,spi_write_read,sfud_spi_port_init函数,根据实际接线情况修改static spi_user_data spi1 的定义,参照移植文件;

可参考..SFUD-masterdemostm32f10x_non_oscomponentssfud路径下的sfud_port.c文件设置;

4、sfud_flash_def.h,参照移植文件;

5、sfud_def.h,参照移植文件;

6、sfud_cfg.h,主要是修改SFUD_FLASH_DEVICE_TABLE设备表定义,参照移植文件。

5、演示代码

参考..SFUD-masterdemostm32f10x_non_osappsrcapp.c文件
main.c

/*****************************************************************************
 * Copyright (c) 2019, Nations Technologies Inc.
 *
 * All rights reserved.
 * ****************************************************************************
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Nations' name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY NATIONS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL NATIONS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************/

/**
 * @file main.c
 * @author Nations
 * @version v1.0.0
 *
 * @copyright Copyright (c) 2019, Nations Technologies Inc. All rights reserved.
 */
#include <stdio.h>
#include "drv_log.h"
#include "drv_rcc.h"
#include "drv_log_segger.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "misc.h"
#include "cmsis_os.h"
#include <sfud.h>

/** @addtogroup N32G45X_StdPeriph_Examples
 * @{
 */
void vApplicationStackOverflowHook(TaskHandle_t xTask, char* pcTaskName)
{
    printf("%s stack overflow
",pcTaskName);
}

void vApplicationMallocFailedHook(void)
{
    printf("malloc failed
");
}
/** @addtogroup USART_Printf
 * @{
 */

#define SFUD_DEMO_TEST_BUFFER_SIZE                     1024
static void sfud_demo(uint32_t addr, size_t size, uint8_t *data);
static uint8_t sfud_demo_test_buf[SFUD_DEMO_TEST_BUFFER_SIZE];
/***********************************task init**********************************/
#define START_TASK_PRIO         osPriority_8     			//8 
#define START_STK_SIZE          ((unsigned short)128)      
TaskHandle_t StartThread_Handler;           
static void StartThread(void const * argument);     

#define SFUD_DMEO_TASK_PRIO     osPriority_14         		//14
#define SFUD_DMEO_STK_SIZE      ((unsigned short)128)     
TaskHandle_t SfudThread_Handler;
static void SfudThread(void const * argument);   

#define LED_TASK_PRIO           osPriority_13         		//13
#define LED_STK_SIZE            ((unsigned short)128)     
TaskHandle_t LedThread_Handler; 
static void LedThread(void const * argument);          

/***********************************task end***********************************/
//uint32_t PreviousWakeTime;

/**
 * @brief  Main program
 */
int main(void)
{
	//drv_SysClockInit_144MHZ();
    RCC_ClocksType clks;
	
	drv_log_init();
	drv_log_segger_Init();

    /* Output a message on Hyperterminal using printf function */
    RCC_GetClocksFreqValue(&clks);
	print_log("SYSCLK: %d
", clks.SysclkFreq);
    print_log("HCLK: %d
", clks.HclkFreq); 
    print_log("PCLK1: %d
", clks.Pclk1Freq);
    print_log("PCLK2: %d
", clks.Pclk2Freq);
    print_log("AdcPllClkFreq: %d
", clks.AdcPllClkFreq);	
    print_log("AdcHclkFreq: %d
", clks.AdcHclkFreq);
	
    log_info("SYSCLK: %d
", clks.SysclkFreq);
    log_info("HCLK: %d
", clks.HclkFreq); 
    log_info("PCLK1: %d
", clks.Pclk1Freq);
    log_info("PCLK2: %d
", clks.Pclk2Freq);
    log_info("AdcPllClkFreq: %d
", clks.AdcPllClkFreq);	
    log_info("AdcHclkFreq: %d
", clks.AdcHclkFreq);
	
	/* Initialize all configured peripherals */
    osThreadDef(Start_Thread, StartThread, START_TASK_PRIO, 0, START_STK_SIZE);
    StartThread_Handler  = osThreadCreate(osThread(Start_Thread), NULL);

	uint16_t u16Val = 0;
	u16Val = uxTaskGetNumberOfTasks();
	printf("In all tasks:%d
",u16Val);
	print_log("In all tasks:%d
",u16Val);
	
    /* Start scheduler */
	osKernelStart();	
		
    while (1)
    {
        
    }
}

static void StartThread(void const * argument)
{	
	UBaseType_t uxHighWaterMark;
	uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );  
	printf("1---StartThread: uxHighWaterMark:%ld
",uxHighWaterMark);	
	print_log("1---StartThread: uxHighWaterMark:%ld
",uxHighWaterMark);	
	
	
	for(int i=0;i<5;i++)
	{
		osDelay(500);
		printf("StartThread
");
		print_log("StartThread
");
	}
	uint8_t UIDbuf[0x0C];
	GetUID(UIDbuf);
	printf("GetUID:%s
",UIDbuf);	
	//print_log("GetUID:%s
",UIDbuf);	
	
	/* Initialize all configured peripherals */
    osThreadDef(Sfud_Thread, SfudThread, SFUD_DMEO_TASK_PRIO, 0, SFUD_DMEO_STK_SIZE);
    SfudThread_Handler  = osThreadCreate(osThread(Sfud_Thread), NULL);	
	
    osThreadDef(Led_Thread, LedThread, LED_TASK_PRIO, 0, LED_STK_SIZE);
    LedThread_Handler  = osThreadCreate(osThread(Led_Thread), NULL);	
	
	vTaskDelete(xTaskGetCurrentTaskHandle());
}	

static void SfudThread(void const * argument)
{	
    /* SFUD initialize */
    if (sfud_init() == SFUD_SUCCESS) {
        sfud_demo(0, sizeof(sfud_demo_test_buf), sfud_demo_test_buf);
    }	
	while(1){osDelay(100);}		
}

static void LedThread(void const * argument)
{
	while(1)
	{
		osDelay(1000);
		printf("LedThread is running
");
		print_log("LedThread is running
");
	}		
}

/**
 * SFUD demo for the first flash device test.
 *
 * @param addr flash start address
 * @param size test flash size
 * @param size test flash data buffer
 */
static void sfud_demo(uint32_t addr, size_t size, uint8_t *data) {
    sfud_err result = SFUD_SUCCESS;
    const sfud_flash *flash = sfud_get_device_table() + 0;
    size_t i;
    /* prepare write data */
    for (i = 0; i < size; i++) {
        data[i] = i ;
    }
    /* erase test */
    result = sfud_erase(flash, addr, size);
    if (result == SFUD_SUCCESS) {
        printf("Erase the %s flash data finish. Start from 0x%08X, size is %ld.
", flash->name, addr,
                size);
    } else {
        printf("Erase the %s flash data failed.
", flash->name);
        return;
    }
    /* write test */
    result = sfud_write(flash, addr, size, data);
    if (result == SFUD_SUCCESS) {
        printf("Write the %s flash data finish. Start from 0x%08X, size is %ld.
", flash->name, addr,
                size);
    } else {
        printf("Write the %s flash data failed.
", flash->name);
        return;
    }
    /* read test */
    result = sfud_read(flash, addr, size, data);
    if (result == SFUD_SUCCESS) {
        printf("Read the %s flash data success. Start from 0x%08X, size is %ld. The data is:
", flash->name, addr,
                size);
        printf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
");
        for (i = 0; i < size; i++) {
            if (i % 16 == 0) {
                printf("[%08X] ", addr + i);
            }
            printf("%02X ", data[i]);
            if (((i + 1) % 16 == 0) || i == size - 1) {
                printf("
");
            }
        }
        printf("
");
    } else {
        printf("Read the %s flash data failed.
", flash->name);
    }
    /* data check */
    for (i = 0; i < size; i++) {
        if (data[i] != i % 256) {
            printf("Read and check write data has an error. Write the %s flash data failed.
", flash->name);
			break;
        }
    }
    if (i == size) {
        printf("The %s flash test is success.
", flash->name);
    }
}

6、演示效果与源码

测试成功,演示代码链接(待上传)

7、QSPI使用介绍

参照:..SFUD-masterdemostm32l475_non_os_qspi

当 SFUD 开启 QSPI 模式后,SFUD 中的 Flash 驱动支持使用 QSPI 总线进行通信。相比传统的 SPI 模式,使用 QSPI 能够加速 Flash 数据的读取,但当数据需要写入时,由于 Flash 本身的数据写入速度慢于 SPI 传输速度,所以 QSPI 模式下的数据写入速度提升并不明显。

所以 SFUD 对于 QSPI 模式的支持仅限于快速读命令。通过该函数可以配置 Flash 所使用的 QSPI 总线的实际支持的数据线最大宽度,例如:1 线(默认值,即传统的 SPI 模式)、2 线、4 线。

设置后,SFUD 会去结合当前设定的 QSPI 总线数据线宽度,去 QSPI Flash 扩展信息表 中匹配最合适的、速度最快的快速读命令,之后用户在调用 sfud_read() 时,会使用 QSPI 模式的传输函数发送该命令。

sfud_err sfud_qspi_fast_read_enable(sfud_flash *flash, uint8_t data_line_width)

此处不对QSPI进行演示,有需要的同学可自行研究。

8、相关链接

SFUD源码浅析

SFUD驱动库移植与适配详解

[FlashDB]基于GD32纯代码裸机移植FlashDB数据库


三、FAL硬件抽象层移植与使用

1、FAL硬件抽象层简介

FAL (Flash Abstraction Layer) Flash 抽象层,是对 Flash 及基于 Flash 的分区进行管理、操作的抽象层,对上层统一了 Flash 及 分区操作的 API (框架图如下所示),并具有以下特性:

支持静态可配置的分区表,并可关联多个 Flash 设备;
分区表支持 自动装载 。避免在多固件项目,分区表被多次定义的问题;

代码精简,对操作系统 无依赖 ,可运行于裸机平台,比如对资源有一定要求的 Bootloader;

统一的操作接口。保证了文件系统、OTA、NVM(例如:EasyFlash) 等对 Flash 有一定依赖的组件,底层 Flash 驱动的可重用性;
自带基于 Finsh/MSH 的测试命令,可以通过 Shell 按字节寻址的方式操作(读写擦) Flash 或分区,方便开发者进行调试、测试;

2、移植

①源码下载, 官方链接: RT-Thread-Mirror/fal

②添加源码到项目中

因为我使用的是SPI-FLASH所以使用fal_flash_sfud_port.c这个.C文件,如果是片内flash,就选择其他的端口文件。

③移植文件如下

fal.h

//#include <rtconfig.h>    //无该头文件,屏蔽掉

fal_partition.c

MCU无”FalPartTable”分区,屏蔽并改用如图所示代码

/* partition table definition */
//USED static const struct fal_partition partition_table_def[] SECTION("FalPartTable") = FAL_PART_TABLE;
static const struct fal_partition partition_table_def[] = FAL_PART_TABLE;

fal_cfg.h

/*
 * 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 FAL_DEBUG 1    //启动打印
#define FAL_PART_HAS_TABLE_CFG    //启动设备表
#define FAL_USING_SFUD_PORT    //使用sfud通用串行flash

#define NOR_FLASH_DEV_NAME             "norflash0"

/* ===================== Flash device Configuration ========================= */
extern struct fal_flash_dev nor_flash0;

/* flash device table */
#define FAL_FLASH_DEV_TABLE                                          
{                                                                    
    &nor_flash0,                                                     
}
/* ====================== Partition Configuration ========================== */
#ifdef FAL_PART_HAS_TABLE_CFG
/* partition table */
#define FAL_PART_TABLE                                                               
{                                                                                    
    {FAL_PART_MAGIC_WORD, "KVDB", NOR_FLASH_DEV_NAME,           0, 1*1024*1024, 0}, 
    {FAL_PART_MAGIC_WORD, "TSDB", NOR_FLASH_DEV_NAME, 1*1024*1024, 4*1024*1024, 0}, 
}
#endif /* FAL_PART_HAS_TABLE_CFG */

#endif /* _FAL_CFG_H_ */

fal_flash_sfud_port.c

/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-01-26     armink       the first version
 */

#include <fal.h>
#include <sfud.h>

#ifdef FAL_USING_SFUD_PORT
#ifdef RT_USING_SFUD
#include <spi_flash_sfud.h>
#endif

#ifndef FAL_USING_NOR_FLASH_DEV_NAME
#define FAL_USING_NOR_FLASH_DEV_NAME             "norflash0"
#endif

static int init(void);
static int read(long offset, uint8_t *buf, size_t size);
static int write(long offset, const uint8_t *buf, size_t size);
static int erase(long offset, size_t size);

static sfud_flash_t sfud_dev = NULL;
struct fal_flash_dev nor_flash0 =
{
    .name       = FAL_USING_NOR_FLASH_DEV_NAME,
    .addr       = 0,
    .len        = 16 * 1024 * 1024,
    .blk_size   = 4096,
    .ops        = {init, read, write, erase},
    .write_gran = 1
};

static int init(void)
{

#ifdef RT_USING_SFUD
    /* RT-Thread RTOS platform */
    sfud_dev = rt_sfud_flash_find_by_dev_name(FAL_USING_NOR_FLASH_DEV_NAME);
#else
    /* bare metal platform */
    //extern sfud_flash sfud_norflash0;
    //sfud_dev = &sfud_norflash0;
    sfud_dev = sfud_get_device(SFUD_W25Q_DEVICE_INDEX);
#endif

    if (NULL == sfud_dev)
    {
        return -1;
    }

    /* update the flash chip information */
    nor_flash0.blk_size = sfud_dev->chip.erase_gran;
    nor_flash0.len = sfud_dev->chip.capacity;

    return 0;
}

static int read(long offset, uint8_t *buf, size_t size)
{
    assert(sfud_dev);
    assert(sfud_dev->init_ok);
    sfud_read(sfud_dev, nor_flash0.addr + offset, size, buf);

    return size;
}

static int write(long offset, const uint8_t *buf, size_t size)
{
    assert(sfud_dev);
    assert(sfud_dev->init_ok);
    if (sfud_write(sfud_dev, nor_flash0.addr + offset, size, buf) != SFUD_SUCCESS)
    {
        return -1;
    }

    return size;
}

static int erase(long offset, size_t size)
{
    assert(sfud_dev);
    assert(sfud_dev->init_ok);
    if (sfud_erase(sfud_dev, nor_flash0.addr + offset, size) != SFUD_SUCCESS)
    {
        return -1;
    }

    return size;
}
#endif /* FAL_USING_SFUD_PORT */

3、修改要点

1、fal_flash_sfud_port.c

定义 flash 设备表

struct fal_flash_dev nor_flash0 =
{
    .name       = FAL_USING_NOR_FLASH_DEV_NAME,
    .addr       = 0,
    .len        = 16 * 1024 * 1024,
    .blk_size   = 4096,
    .ops        = {init, read, write, erase},
    .write_gran = 1
};

FAL_USING_NOR_FLASH_DEV_NAME : Flash 设备的名字。
0: 对 Flash 操作的起始地址。
16*1024*1024:Flash 的总大小(16MB)。
4096:Flash 块/扇区大小
{init, read, write, erase} :Flash 的操作函数。 如果没有 init 初始化过程,第一个操作函数位置可以置空。
8 : 设置写粒度,单位 bit, 0 表示未生效(默认值为 0 ),该成员是 fal 版本大于 0.4.0 的新增成员。各个 flash 写入粒度不尽相同,可通过该成员进行设置,以下列举几种常见 Flash 写粒度:

nor flash: 1 bit
stm32f2/f4: 8 bit
stm32f1: 32 bit
stm32l4: 64 bit


2、fal_cfg.h

①关联的设备名

关联的 Flash 设备 务必已经在 Flash 设备表中定义好 ,并且与名称(FAL_USING_NOR_FLASH_DEV_NAME)一致 ,否则会出现无法找到 Flash 设备的错误;

②初始化的设备驱动表与fal_flash_sfud_port.c中定义的设备驱动对应上;

③魔术字,默认即可;


分区名保证 
不能重复

⑤设备名;


分区的起始地址,
不能超过 Flash 设备的地址范围;

⑦分区大小,
不能超过 Flash 设备的地址范围;

⑧默认,保留功能;

4、演示代码

main.c

/*****************************************************************************
 * Copyright (c) 2019, Nations Technologies Inc.
 *
 * All rights reserved.
 * ****************************************************************************
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Nations' name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY NATIONS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL NATIONS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************/

/**
 * @file main.c
 * @author Nations
 * @version v1.0.0
 *
 * @copyright Copyright (c) 2019, Nations Technologies Inc. All rights reserved.
 */
#include <stdio.h>
#include <string.h>
#include "drv_log.h"
#include "drv_rcc.h"
#include "drv_log_segger.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "misc.h"
#include "cmsis_os.h"
#include <sfud.h>
#include "fal.h"

/** @addtogroup N32G45X_StdPeriph_Examples
 * @{
 */
void vApplicationStackOverflowHook(TaskHandle_t xTask, char* pcTaskName)
{
    printf("%s stack overflow
",pcTaskName);
}

void vApplicationMallocFailedHook(void)
{
    printf("malloc failed
");
}
/** @addtogroup USART_Printf
 * @{
 */

#define SFUD_DEMO_TEST_BUFFER_SIZE                     1024
static void __attribute__((unused)) sfud_demo(uint32_t addr, size_t size, uint8_t *data);
static uint8_t __attribute__((unused)) sfud_demo_test_buf[SFUD_DEMO_TEST_BUFFER_SIZE];

#define BUF_SIZE 1024
static int fal_test(const char *partiton_name);
/***********************************task init**********************************/
#define START_TASK_PRIO         osPriority_8     			//8 
#define START_STK_SIZE          ((unsigned short)128)      
TaskHandle_t StartThread_Handler;           
static void StartThread(void const * argument);     

#define FAL_DMEO_TASK_PRIO      osPriority_13         		//14
#define FAL_DMEO_STK_SIZE       ((unsigned short)3*128)     
TaskHandle_t FalThread_Handler;
static void FalThread(void const * argument);  

#define SFUD_DMEO_TASK_PRIO     osPriority_14         		//14
#define SFUD_DMEO_STK_SIZE      ((unsigned short)128)     
TaskHandle_t SfudThread_Handler;
static void SfudThread(void const * argument);   

#define LED_TASK_PRIO           osPriority_13         		//13
#define LED_STK_SIZE            ((unsigned short)128)     
TaskHandle_t LedThread_Handler; 
static void LedThread(void const * argument);          

/***********************************task end***********************************/
//uint32_t PreviousWakeTime;

/**
 * @brief  Main program
 */
int main(void)
{
	//drv_SysClockInit_144MHZ();
    RCC_ClocksType clks;
	
	drv_log_init();
	drv_log_segger_Init();
    sfud_init();
    /* Output a message on Hyperterminal using printf function */
    RCC_GetClocksFreqValue(&clks);
	print_log("SYSCLK: %d
", clks.SysclkFreq);
    print_log("HCLK: %d
", clks.HclkFreq); 
    print_log("PCLK1: %d
", clks.Pclk1Freq);
    print_log("PCLK2: %d
", clks.Pclk2Freq);
    print_log("AdcPllClkFreq: %d
", clks.AdcPllClkFreq);	
    print_log("AdcHclkFreq: %d
", clks.AdcHclkFreq);
	
    log_info("SYSCLK: %d
", clks.SysclkFreq);
    log_info("HCLK: %d
", clks.HclkFreq); 
    log_info("PCLK1: %d
", clks.Pclk1Freq);
    log_info("PCLK2: %d
", clks.Pclk2Freq);
    log_info("AdcPllClkFreq: %d
", clks.AdcPllClkFreq);	
    log_info("AdcHclkFreq: %d
", clks.AdcHclkFreq);
	
	/* Initialize all configured peripherals */
    osThreadDef(Start_Thread, StartThread, START_TASK_PRIO, 0, START_STK_SIZE);
    StartThread_Handler  = osThreadCreate(osThread(Start_Thread), NULL);

	uint16_t u16Val = 0;
	u16Val = uxTaskGetNumberOfTasks();
	printf("In all tasks:%d
",u16Val);
	print_log("In all tasks:%d
",u16Val);
	
    /* Start scheduler */
	osKernelStart();	
		
    while (1)
    {
        
    }
}

static void StartThread(void const * argument)
{	
	UBaseType_t uxHighWaterMark;
	uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );  
	printf("1---StartThread: uxHighWaterMark:%ld
",uxHighWaterMark);	
	print_log("1---StartThread: uxHighWaterMark:%ld
",uxHighWaterMark);	
	
	
	for(int i=0;i<5;i++)
	{
		osDelay(500);
		printf("StartThread
");
		print_log("StartThread
");
	}
	uint8_t UIDbuf[0x0C];
	GetUID(UIDbuf);
	printf("GetUID:%s
",UIDbuf);	
	//print_log("GetUID:%s
",UIDbuf);	
	
	/* Initialize all configured peripherals */
    osThreadDef(Fal_Thread, FalThread, FAL_DMEO_TASK_PRIO, 0, FAL_DMEO_STK_SIZE);
    FalThread_Handler  = osThreadCreate(osThread(Fal_Thread), NULL);

    osThreadDef(Sfud_Thread, SfudThread, SFUD_DMEO_TASK_PRIO, 0, SFUD_DMEO_STK_SIZE);
    SfudThread_Handler  = osThreadCreate(osThread(Sfud_Thread), NULL);	
	
    osThreadDef(Led_Thread, LedThread, LED_TASK_PRIO, 0, LED_STK_SIZE);
    LedThread_Handler  = osThreadCreate(osThread(Led_Thread), NULL);	
	
	vTaskDelete(xTaskGetCurrentTaskHandle());
}	


static void FalThread(void const * argument)
{	
    fal_init();

    if (fal_test("KVDB") == 0)
    {
        log_info("Fal partition (%s) test success!", "KVDB");
    }
    else
    {
        log_error("Fal partition (%s) test failed!", "KVDB");
    }

    if (fal_test("TSDB") == 0)
    {
        log_info("Fal partition (%s) test success!", "TSDB");
    }
    else
    {
        log_error("Fal partition (%s) test failed!", "TSDB");
    }   
	while(1)
    {
        osDelay(800);
        printf("FalThread is running
");
		print_log("FalThread is running
");
    }		
}

static void SfudThread(void const * argument)
{	
#if 0    
    /* SFUD initialize */
    if (sfud_init() == SFUD_SUCCESS) {
        sfud_demo(0, sizeof(sfud_demo_test_buf), sfud_demo_test_buf);
    }
#endif	
	while(1){osDelay(100);}		
}

static void LedThread(void const * argument)
{
	while(1)
	{
		osDelay(1000);
		printf("LedThread is running
");
		print_log("LedThread is running
");
	}		
}

static int fal_test(const char *partiton_name)
{
    int ret;
    int i, j, len;
    uint8_t buf[BUF_SIZE];
    const struct fal_flash_dev *flash_dev = NULL;
    const struct fal_partition *partition = NULL;

    if (!partiton_name)
    {
        log_error("Input param partition name is null!");
        return -1;
    }

    partition = fal_partition_find(partiton_name);
    if (partition == NULL)
    {
        log_error("Find partition (%s) failed!", partiton_name);
        ret = -1;
        return ret;
    }

    flash_dev = fal_flash_device_find(partition->flash_name);
    if (flash_dev == NULL)
    {
        log_error("Find flash device (%s) failed!", partition->flash_name);
        ret = -1;
        return ret;
    }

    log_info("Flash device : %s   "
          "Flash size : %dK   "
          "Partition : %s   "
          "Partition size: %dK", 
           partition->flash_name, 
           flash_dev->len/1024,
           partition->name,
           partition->len/1024);

    /* 擦除 `partition` 分区上的全部数据 */
    ret = fal_partition_erase_all(partition);
    if (ret < 0)
    {
        log_error("Partition (%s) erase failed!", partition->name);
        ret = -1;
        return ret;
    }
    log_info("Erase (%s) partition finish!", partiton_name);

    /* 循环读取整个分区的数据,并对内容进行检验 */
    for (i = 0; i < partition->len;)
    {
        memset(buf, 0x00, BUF_SIZE);
        len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);

        /* 从 Flash 读取 len 长度的数据到 buf 缓冲区 */
        ret = fal_partition_read(partition, i, buf, len);
        if (ret < 0)
        {
            log_error("Partition (%s) read failed!", partition->name);
            ret = -1;
            return ret;
        }
        for(j = 0; j < len; j++)
        {
            /* 校验数据内容是否为 0xFF */
            if (buf[j] != 0xFF)
            {
                log_error("The erase operation did not really succeed!");
                ret = -1;
                return ret;
            }
        }
        i += len;
    }

    /* 把 0 写入指定分区 */
    for (i = 0; i < partition->len;)
    {
        /* 设置写入的数据 0x00 */
        memset(buf, 0x00, BUF_SIZE);
        len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);

        /* 写入数据 */
        ret = fal_partition_write(partition, i, buf, len);
        if (ret < 0)
        {
            log_error("Partition (%s) write failed!", partition->name);
            ret = -1;
            return ret;
        }
        i += len;
    }
    log_info("Write (%s) partition finish! Write size %d(%dK).", partiton_name, i, i / 1024);

    /* 从指定的分区读取数据并校验数据 */
    for (i = 0; i < partition->len;)
    {
        /* 清空读缓冲区,以 0xFF 填充 */
        memset(buf, 0xFF, BUF_SIZE);
        len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);

        /* 读取数据到 buf 缓冲区 */
        ret = fal_partition_read(partition, i, buf, len);
        if (ret < 0)
        {
            log_error("Partition (%s) read failed!", partition->name);
            ret = -1;
            return ret;
        }
        for(j = 0; j < len; j++)
        {
            /* 校验读取的数据是否为步骤 3 中写入的数据 0x00 */
            if (buf[j] != 0x00)
            {
                log_error("The write operation did not really succeed!");
                ret = -1;
                return ret;
            }
        }
        i += len;
    }

    ret = 0;
    return ret;
}

/**
 * SFUD demo for the first flash device test.
 *
 * @param addr flash start address
 * @param size test flash size
 * @param size test flash data buffer
 */
static void __attribute__((unused)) sfud_demo(uint32_t addr, size_t size, uint8_t *data) {
    sfud_err result = SFUD_SUCCESS;
    const sfud_flash *flash = sfud_get_device_table() + 0;
    size_t i;
    /* prepare write data */
    for (i = 0; i < size; i++) {
        data[i] = i ;
    }
    /* erase test */
    result = sfud_erase(flash, addr, size);
    if (result == SFUD_SUCCESS) {
        printf("Erase the %s flash data finish. Start from 0x%08X, size is %ld.
", flash->name, addr,
                size);
    } else {
        printf("Erase the %s flash data failed.
", flash->name);
        return;
    }
    /* write test */
    result = sfud_write(flash, addr, size, data);
    if (result == SFUD_SUCCESS) {
        printf("Write the %s flash data finish. Start from 0x%08X, size is %ld.
", flash->name, addr,
                size);
    } else {
        printf("Write the %s flash data failed.
", flash->name);
        return;
    }
    /* read test */
    result = sfud_read(flash, addr, size, data);
    if (result == SFUD_SUCCESS) {
        printf("Read the %s flash data success. Start from 0x%08X, size is %ld. The data is:
", flash->name, addr,
                size);
        printf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
");
        for (i = 0; i < size; i++) {
            if (i % 16 == 0) {
                printf("[%08X] ", addr + i);
            }
            printf("%02X ", data[i]);
            if (((i + 1) % 16 == 0) || i == size - 1) {
                printf("
");
            }
        }
        printf("
");
    } else {
        printf("Read the %s flash data failed.
", flash->name);
    }
    /* data check */
    for (i = 0; i < size; i++) {
        if (data[i] != i % 256) {
            printf("Read and check write data has an error. Write the %s flash data failed.
", flash->name);
			break;
        }
    }
    if (i == size) {
        printf("The %s flash test is success.
", flash->name);
    }
}

5、演示效果与源码

测试成功,演示代码链接(待上传)

6、相关链接

FlashDB】第一步 FlashDB 移植到 STM32L475 使用QSPI驱动外部 flash W25Q64之FAL移植

四、移植Fal_gd32f470移植flashdb-CSDN博客

STM32通用FLASH管理软件包——SFUD/FAL_sfud 博雅-CSDN博客


四、FlashDB数据库移植与使用

1、移植

①源码下载,朱天龙 (Armink)/FlashDB – 码云 – 开源中国

②添加源码到项目中

2、修改要点

fdb_cfg.h

#ifdef FDB_USING_FAL_MODE
/* the flash write granularity, unit: bit
 * only support 1(nor flash)/ 8(stm32f2/f4)/ 32(stm32f1)/ 64(stm32f7)/ 128(stm32h5) */
#define FDB_WRITE_GRAN          1      /* @note you must define it for a value */
#endif

FDB_WRITE_GRAN 因为是norflash,所以定义写粒度为1。

3、演示代码

参考..FlashDB-mastersamples下的四个示例文件件
main.c

/*****************************************************************************
 * Copyright (c) 2019, Nations Technologies Inc.
 *
 * All rights reserved.
 * ****************************************************************************
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Nations' name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY NATIONS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL NATIONS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************/

/**
 * @file main.c
 * @author Nations
 * @version v1.0.0
 *
 * @copyright Copyright (c) 2019, Nations Technologies Inc. All rights reserved.
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "drv_log.h"
#include "drv_rcc.h"
#include "drv_log_segger.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "misc.h"
#include "cmsis_os.h"
#include "sfud.h"
#include "fal.h"
#include "flashdb.h"

/** @addtogroup N32G45X_StdPeriph_Examples
 * @{
 */
void vApplicationStackOverflowHook(TaskHandle_t xTask, char* pcTaskName)
{
    printf("%s stack overflow
",pcTaskName);
}

void vApplicationMallocFailedHook(void)
{
    printf("malloc failed
");
}
/** @addtogroup USART_Printf
 * @{
 */

#define SFUD_DEMO_TEST_BUFFER_SIZE                     1024
static void __attribute__((unused)) sfud_demo(uint32_t addr, size_t size, uint8_t *data);
static uint8_t __attribute__((unused)) sfud_demo_test_buf[SFUD_DEMO_TEST_BUFFER_SIZE];

#define BUF_SIZE 1024
static int __attribute__((unused)) fal_test(const char *partiton_name);
/***********************************task init**********************************/
#define START_TASK_PRIO         osPriority_8     			//8 
#define START_STK_SIZE          ((unsigned short)128)      
TaskHandle_t StartThread_Handler;           
static void StartThread(void const * argument);     

#define FLASHDB_DMEO_TASK_PRIO  osPriority_13         		//14
#define FLASHDB_DMEO_STK_SIZE   ((unsigned short)2*128)     
TaskHandle_t FlashdbThread_Handler;
static void FlashdbThread(void const * argument);  

#define FAL_DMEO_TASK_PRIO      osPriority_13         		//14
#define FAL_DMEO_STK_SIZE       ((unsigned short)2*128)     
TaskHandle_t FalThread_Handler;
static void FalThread(void const * argument);  

#define SFUD_DMEO_TASK_PRIO     osPriority_14         		//14
#define SFUD_DMEO_STK_SIZE      ((unsigned short)128)     
TaskHandle_t SfudThread_Handler;
static void SfudThread(void const * argument);   

#define LED_TASK_PRIO           osPriority_13         		//13
#define LED_STK_SIZE            ((unsigned short)128)     
TaskHandle_t LedThread_Handler; 
static void LedThread(void const * argument);          
/***********************************task end***********************************/
//uint32_t PreviousWakeTime;
#define FDB_LOG_TAG "[main]"

static uint32_t boot_count = 0;
static time_t boot_time[10] = {0, 1, 2, 3};
/* default KV nodes */
static struct fdb_default_kv_node default_kv_table[] = {
        {"username", "heyl", 0}, /* string KV */
        {"password", "2486", 0}, /* string KV */
        {"boot_count", &boot_count, sizeof(boot_count)}, /* int type KV */
        {"boot_time", &boot_time, sizeof(boot_time)},    /* int array type KV */
};
/* KVDB object */
static struct fdb_kvdb kvdb = { 0 };
/* TSDB object */
struct fdb_tsdb tsdb = { 0 };
/* counts for simulated timestamp */
static int counts = 0;

extern void kvdb_basic_sample(fdb_kvdb_t kvdb);
extern void kvdb_type_string_sample(fdb_kvdb_t kvdb);
extern void kvdb_type_blob_sample(fdb_kvdb_t kvdb);
extern void tsdb_sample(fdb_tsdb_t tsdb);
static int8_t flashdb_test_kvdb(void);
static int8_t flashdb_test_tsdb(void);
/**
 * @brief  Main program
 */
int main(void)
{
	//drv_SysClockInit_144MHZ();
    RCC_ClocksType clks;
	
	drv_log_init();
	drv_log_segger_Init();
    sfud_init();
    fal_init();
    /* Output a message on Hyperterminal using printf function */
    RCC_GetClocksFreqValue(&clks);
	print_log("SYSCLK: %d
", clks.SysclkFreq);
    print_log("HCLK: %d
", clks.HclkFreq); 
    print_log("PCLK1: %d
", clks.Pclk1Freq);
    print_log("PCLK2: %d
", clks.Pclk2Freq);
    print_log("AdcPllClkFreq: %d
", clks.AdcPllClkFreq);	
    print_log("AdcHclkFreq: %d
", clks.AdcHclkFreq);
	
    log_info("SYSCLK: %d
", clks.SysclkFreq);
    log_info("HCLK: %d
", clks.HclkFreq); 
    log_info("PCLK1: %d
", clks.Pclk1Freq);
    log_info("PCLK2: %d
", clks.Pclk2Freq);
    log_info("AdcPllClkFreq: %d
", clks.AdcPllClkFreq);	
    log_info("AdcHclkFreq: %d
", clks.AdcHclkFreq);
	
	/* Initialize all configured peripherals */
    osThreadDef(Start_Thread, StartThread, START_TASK_PRIO, 0, START_STK_SIZE);
    StartThread_Handler  = osThreadCreate(osThread(Start_Thread), NULL);

	uint16_t u16Val = 0;
	u16Val = uxTaskGetNumberOfTasks();
	printf("In all tasks:%d
",u16Val);
	print_log("In all tasks:%d
",u16Val);
	
    /* Start scheduler */
	osKernelStart();	
		
    while (1)
    {
        
    }
}

static void StartThread(void const * argument)
{	
	UBaseType_t uxHighWaterMark;
	uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );  
	printf("1---StartThread: uxHighWaterMark:%ld
",uxHighWaterMark);	
	print_log("1---StartThread: uxHighWaterMark:%ld
",uxHighWaterMark);	
	
	
	for(int i=0;i<5;i++)
	{
		osDelay(500);
		printf("StartThread
");
		print_log("StartThread
");
	}
	uint8_t UIDbuf[0x0C];
	GetUID(UIDbuf);
	printf("GetUID:%s
",UIDbuf);	
	//print_log("GetUID:%s
",UIDbuf);	
	
	/* Initialize all configured peripherals */
    osThreadDef(FlashdbThread, FlashdbThread, FLASHDB_DMEO_TASK_PRIO, 0, FLASHDB_DMEO_STK_SIZE);
    FlashdbThread_Handler  = osThreadCreate(osThread(FlashdbThread), NULL);

    osThreadDef(Fal_Thread, FalThread, FAL_DMEO_TASK_PRIO, 0, FAL_DMEO_STK_SIZE);
    FalThread_Handler  = osThreadCreate(osThread(Fal_Thread), NULL);

    osThreadDef(Sfud_Thread, SfudThread, SFUD_DMEO_TASK_PRIO, 0, SFUD_DMEO_STK_SIZE);
    SfudThread_Handler  = osThreadCreate(osThread(Sfud_Thread), NULL);	
	
    osThreadDef(Led_Thread, LedThread, LED_TASK_PRIO, 0, LED_STK_SIZE);
    LedThread_Handler  = osThreadCreate(osThread(Led_Thread), NULL);	
	
	  vTaskDelete(xTaskGetCurrentTaskHandle());
}	

static void FlashdbThread(void const * argument)
{
	/* kvdb测试 */
	flashdb_test_kvdb();
	/* tsdb测试 */
	flashdb_test_tsdb();

    while(1) 
	{
		log_info("Flashdb test is successed!!!
");            //注意不要使用中文感叹符号!
        osDelay(500);
    }
}

static void FalThread(void const * argument)
{	
#if 0        
    if (fal_test("KVDB") == 0)
    {
        log_info("Fal partition (%s) test success!", "KVDB");
    }
    else
    {
        log_error("Fal partition (%s) test failed!", "KVDB");
    }

    if (fal_test("TSDB") == 0)
    {
        log_info("Fal partition (%s) test success!", "TSDB");
    }
    else
    {
        log_error("Fal partition (%s) test failed!", "TSDB");
    }  
#endif 
	while(1)
    {
        osDelay(800);
        printf("FalThread is running
");
		print_log("FalThread is running
");
    }		
}

static void SfudThread(void const * argument)
{	
#if 0    
    /* SFUD initialize */
    if (sfud_init() == SFUD_SUCCESS) {
        sfud_demo(0, sizeof(sfud_demo_test_buf), sfud_demo_test_buf);
    }
#endif	
	while(1){osDelay(100);}		
}

void LedThread(void const * argument)
{
	while(1)
	{
		osDelay(1000);
		printf("LedThread is running
");
		print_log("LedThread is running
");
	}		
}

static int __attribute__((unused)) fal_test(const char *partiton_name)
{
    int ret;
    int i, j, len;
    uint8_t buf[BUF_SIZE];
    const struct fal_flash_dev *flash_dev = NULL;
    const struct fal_partition *partition = NULL;

    if (!partiton_name)
    {
        log_error("Input param partition name is null!");
        return -1;
    }

    partition = fal_partition_find(partiton_name);
    if (partition == NULL)
    {
        log_error("Find partition (%s) failed!", partiton_name);
        ret = -1;
        return ret;
    }

    flash_dev = fal_flash_device_find(partition->flash_name);
    if (flash_dev == NULL)
    {
        log_error("Find flash device (%s) failed!", partition->flash_name);
        ret = -1;
        return ret;
    }

    log_info("Flash device : %s   "
          "Flash size : %dK   "
          "Partition : %s   "
          "Partition size: %dK", 
           partition->flash_name, 
           flash_dev->len/1024,
           partition->name,
           partition->len/1024);

    /* 擦除 `partition` 分区上的全部数据 */
    ret = fal_partition_erase_all(partition);
    if (ret < 0)
    {
        log_error("Partition (%s) erase failed!", partition->name);
        ret = -1;
        return ret;
    }
    log_info("Erase (%s) partition finish!", partiton_name);

    /* 循环读取整个分区的数据,并对内容进行检验 */
    for (i = 0; i < partition->len;)
    {
        memset(buf, 0x00, BUF_SIZE);
        len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);

        /* 从 Flash 读取 len 长度的数据到 buf 缓冲区 */
        ret = fal_partition_read(partition, i, buf, len);
        if (ret < 0)
        {
            log_error("Partition (%s) read failed!", partition->name);
            ret = -1;
            return ret;
        }
        for(j = 0; j < len; j++)
        {
            /* 校验数据内容是否为 0xFF */
            if (buf[j] != 0xFF)
            {
                log_error("The erase operation did not really succeed!");
                ret = -1;
                return ret;
            }
        }
        i += len;
    }

    /* 把 0 写入指定分区 */
    for (i = 0; i < partition->len;)
    {
        /* 设置写入的数据 0x00 */
        memset(buf, 0x00, BUF_SIZE);
        len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);

        /* 写入数据 */
        ret = fal_partition_write(partition, i, buf, len);
        if (ret < 0)
        {
            log_error("Partition (%s) write failed!", partition->name);
            ret = -1;
            return ret;
        }
        i += len;
    }
    log_info("Write (%s) partition finish! Write size %d(%dK).", partiton_name, i, i / 1024);

    /* 从指定的分区读取数据并校验数据 */
    for (i = 0; i < partition->len;)
    {
        /* 清空读缓冲区,以 0xFF 填充 */
        memset(buf, 0xFF, BUF_SIZE);
        len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);

        /* 读取数据到 buf 缓冲区 */
        ret = fal_partition_read(partition, i, buf, len);
        if (ret < 0)
        {
            log_error("Partition (%s) read failed!", partition->name);
            ret = -1;
            return ret;
        }
        for(j = 0; j < len; j++)
        {
            /* 校验读取的数据是否为步骤 3 中写入的数据 0x00 */
            if (buf[j] != 0x00)
            {
                log_error("The write operation did not really succeed!");
                ret = -1;
                return ret;
            }
        }
        i += len;
    }

    ret = 0;
    return ret;
}

/**
 * SFUD demo for the first flash device test.
 *
 * @param addr flash start address
 * @param size test flash size
 * @param size test flash data buffer
 */
static void __attribute__((unused)) sfud_demo(uint32_t addr, size_t size, uint8_t *data) 
{
    sfud_err result = SFUD_SUCCESS;
    const sfud_flash *flash = sfud_get_device_table() + 0;
    size_t i;
    /* prepare write data */
    for (i = 0; i < size; i++) {
        data[i] = i ;
    }
    /* erase test */
    result = sfud_erase(flash, addr, size);
    if (result == SFUD_SUCCESS) {
        printf("Erase the %s flash data finish. Start from 0x%08X, size is %ld.
", flash->name, addr,size);
    } else {
        printf("Erase the %s flash data failed.
", flash->name);
        return;
    }
    /* write test */
    result = sfud_write(flash, addr, size, data);
    if (result == SFUD_SUCCESS) {
        printf("Write the %s flash data finish. Start from 0x%08X, size is %ld.
", flash->name, addr,size);
    } else {
        printf("Write the %s flash data failed.
", flash->name);
        return;
    }
    /* read test */
    result = sfud_read(flash, addr, size, data);
    if (result == SFUD_SUCCESS) {
        printf("Read the %s flash data success. Start from 0x%08X, size is %ld. The data is:
", flash->name, addr,size);
        printf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
");
        for (i = 0; i < size; i++) {
            if (i % 16 == 0) {
                printf("[%08X] ", addr + i);
            }
            printf("%02X ", data[i]);
            if (((i + 1) % 16 == 0) || i == size - 1) {
                printf("
");
            }
        }
        printf("
");
    } else {
        printf("Read the %s flash data failed.
", flash->name);
    }
    /* data check */
    for (i = 0; i < size; i++) {
        if (data[i] != i % 256) {
            printf("Read and check write data has an error. Write the %s flash data failed.
", flash->name);
			break;
        }
    }
    if (i == size) {
        printf("The %s flash test is success.
", flash->name);
    }
}


/******************* flashdb ********************/
static void lock(fdb_db_t db)
{
    __disable_irq();
}

static void unlock(fdb_db_t db)
{
    __enable_irq();
}

static fdb_time_t get_time(void)
{
    /* Using the counts instead of timestamp.
     * Please change this function to return RTC time.
     */
    return ++counts;
}

static int8_t flashdb_test_kvdb(void)
{ /* KVDB Sample */
	
	fdb_err_t result;
	struct fdb_default_kv default_kv;

	default_kv.kvs = default_kv_table;
	default_kv.num = sizeof(default_kv_table) / sizeof(default_kv_table[0]);
	/* set the lock and unlock function if you want */
	fdb_kvdb_control(&kvdb, FDB_KVDB_CTRL_SET_LOCK, (void *)lock);
	fdb_kvdb_control(&kvdb, FDB_KVDB_CTRL_SET_UNLOCK, (void *)unlock);
	/* Key-Value database initialization
	 *
	 *       &kvdb: database object
	 *       "env": database name
	 * "fdb_kvdb1": The flash partition name base on FAL. Please make sure it's in FAL partition table.
	 *              Please change to YOUR partition name.
	 * &default_kv: The default KV nodes. It will auto add to KVDB when first initialize successfully.
	 *        NULL: The user data if you need, now is empty.
	 */
	result = fdb_kvdb_init(&kvdb, "env", "KVDB", &default_kv, NULL);

	if (result != FDB_NO_ERR) {
		return -1;
//		printf("
 FDB_NO_ERR = SET !!! 
");
	}

	/* run basic KV samples */
	kvdb_basic_sample(&kvdb);
	/* run string KV samples */
	kvdb_type_string_sample(&kvdb);
	/* run blob KV samples */
	kvdb_type_blob_sample(&kvdb);
	return 0;
}

static int8_t flashdb_test_tsdb(void)
{ /* TSDB Sample */
	fdb_err_t result;
	/* set the lock and unlock function if you want */
	fdb_tsdb_control(&tsdb, FDB_TSDB_CTRL_SET_LOCK, (void *)lock);
	fdb_tsdb_control(&tsdb, FDB_TSDB_CTRL_SET_UNLOCK, (void *)unlock);
	/* Time series database initialization
	 *
	 *       &tsdb: database object
	 *       "log": database name
	 * "fdb_tsdb1": The flash partition name base on FAL. Please make sure it's in FAL partition table.
	 *              Please change to YOUR partition name.
	 *    get_time: The get current timestamp function.
	 *         128: maximum length of each log
	 *        NULL: The user data if you need, now is empty.
	 */
	result = fdb_tsdb_init(&tsdb, "log", "TSDB", get_time, 128, NULL);
	/* read last saved time for simulated timestamp */
	fdb_tsdb_control(&tsdb, FDB_TSDB_CTRL_GET_LAST_TIME, &counts);

	if (result != FDB_NO_ERR) 
  {
		return -1;
        //printf("
 FDB_NO_ERR = SET !!! 
");
	}

	/* run TSDB sample */
	tsdb_sample(&tsdb);
	return 0;
}

4、演示效果与源码

测试成功,演示代码链接(待上传)

5、当前时间戳小于Flash中保存的时间戳

1、gettime函数如下
static fdb_time_t get_time(void)
{
    /* Using the counts instead of timestamp.
     * Please change this function to return RTC time.
     */
    return ++counts;
}
2、修改代码并将fal与sfud的打印使能关闭

屏蔽KV样例 flashdb_test_kvdb();

static void FlashdbThread(void const * argument)
{
    /* kvdb测试 */
    //flashdb_test_kvdb();        //屏蔽kvdb测试
    /* tsdb测试 */
    flashdb_test_tsdb();
    while(1)
    {
        log_info("Flashdb test is successed!!!
");            //注意不要使用中文感叹符号!
        osDelay(1000);
    }
}

修改添加的数据,并烧录固件,将MCU复位查看效果

FDB_INFO("==================== tsdb_sample ====================
");
    { /* APPEND new TSL (time series log) */
        struct env_status status;
        /* append new log to TSDB */
        status.temp = 136;
        status.humi = 185;
        fdb_tsl_append(tsdb, fdb_blob_make(&blob, &status, sizeof(status)));
        FDB_INFO("append the new status.temp (%d) and status.humi (%d)
", status.temp, status.humi);
        status.temp = 138;
        status.humi = 190;
        fdb_tsl_append(tsdb, fdb_blob_make(&blob, &status, sizeof(status)));
        FDB_INFO("append the new status.temp (%d) and status.humi (%d)
", status.temp, status.humi);
    }
3、验证效果如下

4、结论:

从上面分析可知,MCU复位后,时间从0开始计算,但存到MCU中的时间戳,会自增1后,再将数据存到MCU中。

 6、相关链接

FlashDB 移植-CSDN博客

五、使用要点

1、static fdb_time_t get_time(void)

        要重写该函数,get_time应该获取的是实际时间,比如从RTC获取到的时间或是网络时间,并使用如c语言中的Cmktime函数将其转为时间戳返回。
2、#define FAL_PART_TABLE

        不同类型数据应该存放在不同分区内,所以尽可能的多分区,数据量大时,可细化分区,降低检索时间。
3、static void lock(fdb_db_t db)与static void unlock(fdb_db_t db)

      在任务中不一定非得使用开关中断的方式,也可以使用互斥锁的方式实现原子操作。

后续更新如何在ARM中使用CAN协议进行升级,敬请期待。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
莲菜的头像 - 宋马
评论 抢沙发

请登录后发表评论

    暂无评论内容