一、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 CSPA5 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协议进行升级,敬请期待。
暂无评论内容