本帖最后由 yang377156216 于 2022-5-13 15:10 编辑
#申请原创# @21小跑堂
整体概览
如今,物联网产品种类越来越多,运行时产生的数据种类及总量及也在不断变大,通过前面 2 篇内容已经对内嵌 Flash 用于存储用户数据的场景十分熟悉了,如果还不清晰,请先翻看前面 2 篇内容介绍。今天来聊聊另外一种对 Flash 操作的上层应用介质 —— FlashDB 。它其实是 EasyFlash 的升级版本,也是为了能够更加贴合实际工程应用而做的优化。为了配合该组件的实现,RTT 大佬朱总还特意另外再开源了个叫做 FAL 的小组件。本文将记录我移植开源的 FAL+ FlashDB 组件,使用内嵌 Flash 存储空间来实现用户数据存储记录的功能的全部过程。主要分为以下几个部分内容:
- FAL 以及 FlashDB 是什么
- CmBacktrace 是什么
- FlashDB 两大功能在 MM32F0144C6P 上实现
- 没有硬件 RTC 怎么实现时间戳
- 附件内容
- 参考资源
一、FAL 以及 FlashDB 是什么
FAL 是 Flash Abstraction Layer 的缩写,指的是 Flash 抽象层。它是对 Flash 及基于 Flash 的分区进行管理、操作的抽象层,对上层统一了 Flash 及分区操作的 API,并具有以下特性:
- 支持静态可配置的分区表,并可关联多个 Flash 设备,尤其指的是可以并联外部 SPI NOR Flash 和内嵌 eFlash;
- 分区表支持自动装载 ,也就是说要用到哪个分区的时候再加载哪个分区描述,避免在多固件项目,分区表被多次定义的问题;
- 代码精简,对操作系统无依赖 ,可运行于裸机平台,比如对资源有一定要求的 Bootloader;
- 统一的操作接口。保证了文件系统、OTA、NVM(例如之前说的 EasyFlash 和 下面提的 FlashDB) 等对 Flash 有一定依赖的组件,底层 Flash 驱动的可重用性;
- 可以通过 Shell 按字节寻址的方式操作(读写擦) Flash 或分区,方便开发者进行调试、测试,下面我会将原始的基于 Finsh/MSH 的命令移植到 nr-micro shell 上。
FAL 框架图如下所示:
FAL 提供的 API 接口如下所示:
有了 FAL 做嫁衣,FlashDB 也就可以专注于干它自己擅长的事情了。FlashDB 是一款超轻量级的嵌入式数据库,专注于提供嵌入式产品的数据存储方案。与传统的基于文件系统的数据库不同,FlashDB 结合了 Flash 的特性,具有较强的性能及可靠性。并在保证极低的资源占用前提下,尽可能延长 Flash 使用寿命。 它提供两种数据库模式: 键值数据库 :是一种非关系数据库,它将数据存储为键值(Key-Value)对集合,其中键作为唯一标识符。KVDB 操作简洁,可扩展性强,相当于 EasyFlash 的 ENV 功能,它适用于以下场景:
时序数据库 :时间序列数据库 (Time Series Database , 简称 TSDB),它将数据按照时间顺序存储。TSDB 数据具有时间戳,数据存储量大,插入及查询性能高,相当于 EasyFlash 的 log 功能,它适用于以下场景:
整体而言,FlashDB 主要有以下特性: 资源占用极低,内存占用几乎为 0 ; 支持 多分区,多实例 。数据量大时,可细化分区,降低检索时间; 支持 磨损平衡 ,延长 Flash 寿命; 支持 掉电保护 功能,可靠性高; 支持 字符串及 blob 两种 KV 类型,方便用户操作; 支持 KV 增量升级 ,产品固件升级后, KVDB 内容也支持自动升级; 支持 修改每条 TSDB 记录的状态,方便用户进行管理;
FlashDB 底层的 Flash 管理及操作依赖于 FAL ,它们的关系结构如下图所示: FlashDB 向应用层提供的 API 较丰富,具体可以查看源文档,链接贴在这:http://armink.gitee.io/flashdb/#/zh-cn/api二、CmBacktrace 是什么CmBacktrace 是一款针对 ARM Cortex-M 系列 MCU 的错误代码自动追踪、定位,错误原因自动分析的开源库,作者同样是 RTT 的 armink,目前收获 611 个 star,遵循 MIT 开源许可协议。它对标于 SEGGER 提供的 SEGGER_HardFaultHandle 组件。目前 CmBacktrace支持以下功能:- 支持断言(assert)和故障(HardFault);
- 故障原因自动诊断;
- 输出错误现场的函数调用栈;
- 适配 Cortex-M0/M3/M4/M7 MCU;
- 支持 IAR、KEIL、GCC 编译器;
之前有提到,在项目中可以加入故障定位分析组件于 FlashDB 中,这样就可以实现对故障代码分析 log 信息的掉电保存了,所以推荐以组合形式应用到自己项目中。下面记录 CmBacktrace 的简单移植和测试过程。移植非常简单,主要包括添加源码到裸机工程中,去除原有的 HardFault_Handler 函数定义,或者将其定义为 __WEAK__ 形式,以及实现串口打印功能即可。接着进行配置,所有配置参数均在 cmb_cfg.h 中实现: 以适配自己的平台填入合适的参数即可。使用 CmBacktrace 时,需要引用 cm_backtrace.h 头文件,以及调用 cm_backtrace_init 函数来进行初始化,初始化时需要传入三个参数包括 固件名称、硬件版本号、软件版本号,这些自己定义好即可。此时需要人为定义一个会触发故障的函数,这里特意提醒一下,M0 产品中将所有硬件故障都统一归为 HardFault,而 M3/M4 等其它内核会将错误种类更加细分。实际做了个指针非法访问的触发故障函数: /**
* [url=home.php?mod=space&uid=247401]@brief[/url] hwfault_test_cmd
*/
void hwfault_test_cmd(char argc, char *argv)
{
#if 0
volatile int * SCB_CCR = (volatile int *) 0xE000ED14; // SCB->CCR
int x, y, z;
*SCB_CCR |= (1 << 4); /* bit4: DIV_0_TRP. */
x = 10;
y = 0;
z = x / y;
printf("z:%d\n", z);
#endif
*(uint32_t *)0x32 = 888 ;
}
实际测试调用该函数后,串口会有一下打印,并且结合自带的 addr2line 分析软件可以定位到触发故障的那一条代码语句,我发现除 0 运算并不会触发 HardFault,另外需要分析的 .axf 文件需要置于工具的同级目录中: CmBacktrace 作为一个与底层汇编指令打交道的库,适配非常完善,源代码在条件编译上也做得非常优雅,更深入地学习可以参见源码链接:https://github.com/armink/CmBacktrace
三、FlashDB 两大功能在 MM32F0144C6P 上实现硬件资源如下: 软件资源如下: FAL 源码包(获取地址:https://github.com/RT-Thread/rt-thread/tree/master/components/fal ) FlashDB 源码包(获取地址:https://github.com/armink/FlashDB ) 一份已经调试好的 KEIL template 工程代码包
这里顺便提一下,FAL 源码包目前没有单独的工程仓,而是作为 RTT 的一个组件包,那如何从庞大的系统软件工程仓中单独下载想要的 FAL 源码包呢?可以使用该链接:https://minhaskamal.github.io/DownGit/#/home 在GitHub 上下载指定文件夹的方法,亲测好用。 由于 FlashDB 底层依赖于 FAL ,所以需要先将所用到的 Flash 对接到 FAL ,再加入 FlashDB 应用层即可完成整个移植工作。 首先对下面的框图要有一个认知,明确之后再进行 FAL 的移植。
2. 配置定义。
- 定义 flash 设备。由于不使用片外的 spi flash,只需要定义好片内的 eFlash 设备对象,具体需要根据自己的 Flash 情况分别实现 `init`、 `read`、 `write`、 `erase` 这些底层操作 Flash 的函数。这里的 init 函数为可选操作,其它几个必须实现。由于考虑到操作 Flash 需要耗费时间,可能会影响到系统层面触发看门狗复位,所以可以预留喂狗操作(使用了看门狗的前提下)。另外,还需要注意最小读操作单位为 1 个字节,最小写操作单位为 4 个字节,最小擦除单位为 1024 个字节(1 页)。具体代码如下:
/*
* 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
* 2022-05-06 yang377156216 to match mm32f0144c6p
*/
#include <fal.h>
#include <string.h>
#include "mm32_device.h"
#include "hal_conf.h"
#define PAGE_SIZE 1024 /* 页大小为 1K */
static int ef_err_port_cnt = 0;
int on_ic_read_cnt = 0;
int on_ic_write_cnt = 0;
void feed_dog(void)
{
/* do nothing now */
}
static int init(void)
{
/* do nothing now */
return 0;
}
/* 最小读取单位为: 1 字节 */
static int read(long offset, uint8_t *buf, size_t size)
{
size_t i;
uint32_t addr = mm32f0144_onchip_flash.addr + offset;
for (i = 0; i < size; i++, addr++, buf++)
{
*buf = *(uint8_t *) addr;
}
on_ic_read_cnt++;
return size;
}
/* 最小写入单位为: 4 字节 */
static int write(long offset, const uint8_t *buf, size_t size)
{
size_t i;
uint32_t addr = mm32f0144_onchip_flash.addr + offset;
__align(4) uint32_t write_data ;
__align(4) uint32_t read_data ;
if (addr % 4 != 0)
ef_err_port_cnt++;
FLASH_Unlock();
FLASH_ClearFlag(\
FLASH_FLAG_EOP | FLASH_FLAG_OPTERR | FLASH_FLAG_WRPRTERR \
| FLASH_FLAG_PGERR | FLASH_FLAG_BSY);
for (i = 0; i < size; i += 4, buf += 4, addr += 4)
{
/* 用以保证 FLASH_ProgramWord 的第二个参数是内存首地址对齐 */
memcpy(&write_data, buf, 4);
FLASH_ProgramWord(addr, write_data);
read_data = *(uint32_t *)addr;
/* You can add your code under here. */
if (read_data != write_data)
{
FLASH_Lock();
return -1;
}
else
{
/* FLash操作可能非常耗时,如果有看门狗需要喂狗,以下代码由用户实现*/
feed_dog();
}
}
FLASH_Lock();
on_ic_write_cnt++;
return size;
}
/* 最小擦除单位为: 1页 = 1024 字节 */
static int erase(long offset, size_t size)
{
size_t erase_pages, i;
uint32_t addr = mm32f0144_onchip_flash.addr + offset;
erase_pages = size / PAGE_SIZE;
if (size % PAGE_SIZE != 0)
{
erase_pages++;
}
/* start erase */
FLASH_Unlock();
FLASH_ClearFlag(\
FLASH_FLAG_EOP | FLASH_FLAG_OPTERR | FLASH_FLAG_WRPRTERR \
| FLASH_FLAG_PGERR | FLASH_FLAG_BSY);
/* it will stop when erased size is greater than setting size */
for (i = 0; i < erase_pages; i++)
{
if (FLASH_ErasePage(addr + (PAGE_SIZE * i)) != FLASH_COMPLETE)
{
FLASH_Lock();
return -1;
}
else
{
/*FLash操作可能非常耗时,如果有看门狗需要喂狗,以下代码由用户实现*/
feed_dog();
}
}
FLASH_Lock();
return size;
}
const struct fal_flash_dev mm32f0144_onchip_flash =
{
.name = "mm32f0144_onchip_flash",
.addr = 0x08000000,
.len = 64 * 1024,
.blk_size = 1 * 1024,/* 最小的擦除操作块为 1 页即为 1K */
.ops = {init, read, write, erase},
.write_gran = 32 /* 最小的写操作粒度为 32 bit */
};
/* ===================== Flash device Configuration ========================= */
extern const struct fal_flash_dev mm32f0144_onchip_flash;
/* flash device table */
#define FAL_FLASH_DEV_TABLE \
{ \
&mm32f0144_onchip_flash, \
}
#define FAL_PART_HAS_TABLE_CFG
/* ====================== Partition Configuration ========================== */
#ifdef FAL_PART_HAS_TABLE_CFG
/* partition table */
#define FAL_PART_TABLE \
{ \
{FAL_PART_MAGIC_WORD, "fdb_tsdb1", "mm32f0144_onchip_flash", 48*1024, 8*1024, 0}, \
{FAL_PART_MAGIC_WORD, "fdb_kvdb1", "mm32f0144_onchip_flash", 56*1024, 8*1024, 0}, \
}
#endif /* FAL_PART_HAS_TABLE_CFG */
/*
* Copyright (c) 2006-2018, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2018-06-23 armink the first version
* 2019-08-22 MurphyZhao adapt to none rt-thread case
* 2022-05-09 yang377156216 adapt to nr micro shell case
*/
#include "fal.h"
#define FAL_DEBUG 1
#define USING_SHELL 1
#ifdef USING_SHELL
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include "nr_micro_shell.h"
extern int fal_init_check(void);
static void fal_test(char argc, char *argv) {
#define __is_print(ch) ((unsigned int)((ch) - ' ') < 127u - ' ')
#define HEXDUMP_WIDTH 16
#define CMD_PROBE_INDEX 0
#define CMD_READ_INDEX 1
#define CMD_WRITE_INDEX 2
#define CMD_ERASE_INDEX 3
#define CMD_BENCH_INDEX 4
int result;
static const struct fal_flash_dev *flash_dev = NULL;
static const struct fal_partition *part_dev = NULL;
size_t i = 0, j = 0;
const char* help_info[] =
{
[CMD_PROBE_INDEX] = "fal probe [dev_name|part_name] - probe flash device or partition by given name",
[CMD_READ_INDEX] = "fal read addr size - read 'size' bytes starting at 'addr'",
[CMD_WRITE_INDEX] = "fal write addr data1 ... dataN - write some bytes 'data' starting at 'addr'",
[CMD_ERASE_INDEX] = "fal erase addr size - erase 'size' bytes starting at 'addr'",
[CMD_BENCH_INDEX] = "fal bench <blk_size> - benchmark test with per block size",
};
if (fal_init_check() != 1)
{
FAL_PRINTF("\n[Warning] FAL is not initialized or failed to initialize!\n\n");
return;
}
if (argc < 2)
{
FAL_PRINTF("Usage:\n");
for (i = 0; i < sizeof(help_info) / sizeof(char*); i++)
{
FAL_PRINTF("%s\n", help_info[i]);
}
FAL_PRINTF("\n");
}
else
{
// const char *operator = argv[1];
uint32_t addr, size;
if (!strcmp("probe", &argv[argv[1]]))
// if (!strcmp(operator, "probe"))
{
if (argc >= 3)
{
char *dev_name = &argv[argv[2]];
if ((flash_dev = fal_flash_device_find(dev_name)) != NULL)
{
part_dev = NULL;
}
else if ((part_dev = fal_partition_find(dev_name)) != NULL)
{
flash_dev = NULL;
}
else
{
FAL_PRINTF("Device %s NOT found. Probe failed.\n", dev_name);
flash_dev = NULL;
part_dev = NULL;
}
}
if (flash_dev)
{
FAL_PRINTF("Probed a flash device | %s | addr: %ld | len: %d |.\n", flash_dev->name,
(long)flash_dev->addr, flash_dev->len);
}
else if (part_dev)
{
FAL_PRINTF("Probed a flash partition | %s | flash_dev: %s | offset: %ld | len: %d |.\n",
part_dev->name, part_dev->flash_name, part_dev->offset, part_dev->len);
}
else
{
FAL_PRINTF("No flash device or partition was probed.\n");
FAL_PRINTF("Usage: %s.\n", help_info[CMD_PROBE_INDEX]);
fal_show_part_table();
}
}
else
{
if (!flash_dev && !part_dev)
{
FAL_PRINTF("No flash device or partition was probed. Please run 'fal probe'.\n");
return;
}
if (!strcmp("read", &argv[argv[1]]))
// if (!strcmp(operator, "read"))
{
if (argc < 4)
{
FAL_PRINTF("Usage: %s.\n", help_info[CMD_READ_INDEX]);
return;
}
else
{
addr = strtol(&argv[argv[2]], NULL, 0);
size = strtol(&argv[argv[3]], NULL, 0);
uint8_t *data = malloc(size);
if (data)
{
if (flash_dev)
{
result = flash_dev->ops.read(addr, data, size);
}
else if (part_dev)
{
result = fal_partition_read(part_dev, addr, data, size);
}
if (result >= 0)
{
FAL_PRINTF("Read data success. Start from 0x%08X, size is %ld. The data is:\n", addr,
(long)size);
FAL_PRINTF("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n");
for (i = 0; i < size; i += HEXDUMP_WIDTH)
{
FAL_PRINTF("[%08X] ", addr + i);
/* dump hex */
for (j = 0; j < HEXDUMP_WIDTH; j++)
{
if (i + j < size)
{
FAL_PRINTF("%02X ", data[i + j]);
}
else
{
FAL_PRINTF(" ");
}
}
/* dump char for hex */
for (j = 0; j < HEXDUMP_WIDTH; j++)
{
if (i + j < size)
{
FAL_PRINTF("%c", __is_print(data[i + j]) ? data[i + j] : '.');
}
}
FAL_PRINTF("\n");
}
FAL_PRINTF("\n");
}
free(data);
}
else
{
FAL_PRINTF("Low memory!\n");
}
}
}
else if (!strcmp("write", &argv[argv[1]]))
// else if (!strcmp(operator, "write"))
{
if (argc < 4)
{
FAL_PRINTF("Usage: %s.\n", help_info[CMD_WRITE_INDEX]);
return;
}
else
{
addr = strtol(&argv[argv[2]], NULL, 0);
size = argc - 3;
uint8_t *data = malloc(size);
if (data)
{
for (i = 0; i < size; i++)
{
data[i] = strtol(&argv[argv[3+i]], NULL, 0);
}
if (flash_dev)
{
result = flash_dev->ops.write(addr, data, size);
}
else if (part_dev)
{
result = fal_partition_write(part_dev, addr, data, size);
}
if (result >= 0)
{
FAL_PRINTF("Write data success. Start from 0x%08X, size is %ld.\n", addr, (long)size);
FAL_PRINTF("Write data: ");
for (i = 0; i < size; i++)
{
FAL_PRINTF("%d ", data[i]);
}
FAL_PRINTF(".\n");
}
free(data);
}
else
{
FAL_PRINTF("Low memory!\n");
}
}
}
else if (!strcmp("erase", &argv[argv[1]]))
// else if (!rt_strcmp(operator, "erase"))
{
if (argc < 4)
{
FAL_PRINTF("Usage: %s.\n", help_info[CMD_ERASE_INDEX]);
return;
}
else
{
addr = strtol(&argv[argv[2]], NULL, 0);
size = strtol(&argv[argv[3]], NULL, 0);
if (flash_dev)
{
result = flash_dev->ops.erase(addr, size);
}
else if (part_dev)
{
result = fal_partition_erase(part_dev, addr, size);
}
if (result >= 0)
{
FAL_PRINTF("Erase data success. Start from 0x%08X, size is %ld.\n", addr, (long)size);
}
}
}
else
{
FAL_PRINTF("Usage:\n");
for (i = 0; i < sizeof(help_info) / sizeof(char*); i++)
{
FAL_PRINTF("%s\n", help_info[i]);
}
FAL_PRINTF("\n");
return;
}
if (result < 0) {
FAL_PRINTF("This operate has an error. Error code: %d.\n", result);
}
}
}
}
#ifdef NR_SHELL_USING_EXPORT_CMD
NR_SHELL_CMD_EXPORT(fal, fal_test);
#endif
#endif /* defined(USING_SHELL)*/
在初始化时调用 fal_init() 即完成了 FAL 组件和其 shell 命令的移植,接着进行测试,对 Flash 的读取、擦除以及写入进行简单测试,正常情况下会有如下打印信息输出:
已经完成了关键一步了,接着进行 FlashDB 的添加。
/*
* Copyright (c) 2020, Armink, <armink.ztl@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* [url=home.php?mod=space&uid=247401]@brief[/url] configuration file
*/
#ifndef _FDB_CFG_H_
#define _FDB_CFG_H_
/* using KVDB feature */
#define FDB_USING_KVDB
#ifdef FDB_USING_KVDB
/* Auto update KV to latest default when current KVDB version number is changed. [url=home.php?mod=space&uid=8537]@see[/url] fdb_kvdb.ver_num */
/* #define FDB_KV_AUTO_UPDATE */
#endif
/* using TSDB (Time series database) feature */
#define FDB_USING_TSDB
/* Using FAL storage mode */
#define FDB_USING_FAL_MODE
#ifdef FDB_USING_FAL_MODE
/* the flash write granularity, unit: bit
* only support 1(nor flash)/ 8(stm32f2/f4)/ 32(mm32f0144) */
#define FDB_WRITE_GRAN 32 /* [url=home.php?mod=space&uid=536309]@NOTE[/url] you must define it for a value */
#endif
/* MCU Endian Configuration, default is Little Endian Order. */
/* #define FDB_BIG_ENDIAN */
/* log print macro. default EF_PRINT macro is printf() */
/* #define FDB_PRINT(...) my_printf(__VA_ARGS__) */
/* print debug information */
#define FDB_DEBUG_ENABLE
#endif /* _FDB_CFG_H_ */
#define FDB_LOG_TAG "[main]"
static uint32_t boot_count = 0;
static time_t boot_time[10] = {8, 8, 8, 8};
/* default KV nodes */
static struct fdb_default_kv_node default_kv_table[] = {
{"username", "mm32f0144c6p", 0}, /* string KV */
{"password", "mm32", 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 __IO u32 gulSystickCnt;
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 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);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief This function is main entrance.
/// @param None.
/// @retval 0.
////////////////////////////////////////////////////////////////////////////////
s32 main(void)
{
static unsigned int Count_Down = 10 ;
fdb_err_t result;
LED_Init();
DELAY_Init();
CONSOLE_Init(115200);
cm_backtrace_init(APPNAME, HARDWARE_VERSION, SOFTWARE_VERSION);
cm_backtrace_firmware_info();
#ifdef __GNUC__
my_printf("\r\n\r\n MM32F0144C6P %s %s\r\n\r\n", __DATE__, __TIME__);
my_printf("\r\n\r\n Test project-generator\r\n\r\n");
my_printf("\r\n\r\n LED is blinking ...\r\n\r\n");
#else
printf("\r\n\r\n MM32F0144C6P %s %s\r\n\r\n", __DATE__, __TIME__);
printf("\r\n\r\n Test FAL_FlashDB.\r\n\r\n");
#endif
while (Count_Down --)
{
LED1_TOGGLE();
LED2_TOGGLE();
LED3_TOGGLE();
LED4_TOGGLE();
DELAY_Ms(200);
}
#ifdef FDB_USING_KVDB
{ /* KVDB Sample */
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", "fdb_kvdb1", &default_kv, NULL);
if (result != FDB_NO_ERR) {
return -1;
}
/* 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);
}
#endif /* FDB_USING_KVDB */
#ifdef FDB_USING_TSDB
{ /* TSDB Sample */
/* 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.
*/
/* 如果是POR/PDR 复位了,那么模拟时间戳计数器应该清0 且不能运行 TSDB sample*/
if(((RCC->CSR) & RCC_CSR_PORRSTF) != RESET)
{
gulSystickCnt = 0;
printf("need erase fdb_tsdb1 area");
}
else
{
result = fdb_tsdb_init(&tsdb, "log", "fdb_tsdb1", 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;
}
/* run TSDB sample */
tsdb_sample(&tsdb);
}
}
#endif /* FDB_USING_TSDB */
while (1)
{
}
}
到此已经完成了 FAL+FlashDB+CmBacktrace 组件的完整移植,编译下载程序后可以看到如下打印,表示组件有正常工作:
实际测试还遇到一个问题, tsdb 样例流程中复位后会打印出 warning,说是当前获取的时间比之前保存的时间戳要更小,分析下来,应该是由于没有将 get_time() 这个函数正确实现的缘故,如果改为 return (++ counts); 的话,是不会报警告的,但却不是真实的时间戳了而是一个模拟的+1计数器。
四、没有硬件 RTC 怎么实现时间戳上面发现的问题,归根到底是由于 MM32F0144C6P 没有硬件 RTC ,导致我无法在 get_time() 中获取到可用的时间戳数据给到 tsdb 应用。那么怎么办呢? 想到一个方案,就是用硬件定时器作为时基,然后使用软件方式模拟实现 RTC 日历和闹钟等功能,最后每隔一段时间使用 FlashDB 将当前时间戳保存到 Flash 内,或者在 PVD 监测到掉电到阈值范围内后进行保存。这样能够实现掉电保存的 RTC 功能,考虑到有些场景和芯片使用的是不支持掉电没有 VBAT 供电的 RTC ,这样的场景下软件模拟 RTC 时不需要将时间保存到 Flash 中,而可以将其放到 SRAM 中,因为 SRAM 不掉电的话也是非易失的存储器,但是要注意配置变量为 noinit 型的。 由于使用的为 KEIL IDE,下面几步为对应的设置和定义,让 SRAM 中能够存储一个变量而且软复位后依旧保持。 /* 用于模拟时间戳的计数器 ,设置成 noinit 类型全局变量*/
__IO u32 gulSystickCnt __attribute__((at(0x20000000), zero_init));
////////////////////////////////////////////////////////////////////////////////
/// @brief SysTick_Handler is call from interrupt map
/// [url=home.php?mod=space&uid=536309]@NOTE[/url] Call delay count function.
/// @param None.
/// @retval None.
////////////////////////////////////////////////////////////////////////////////
void SysTick_Handler(void)
{
gulSystickCnt ++;
TimingDelayDecrement();
}
可以看到时间戳功能是实现了,并且实际测试也不报警告了。工程中只实现了时基参数的保存,未实现 RTC 日历以及闹钟等上层功能,刚好发现了 RTT package 组件包中也包含了一个算法实现,大家可以参考。另外还未实现将 CmBacktrace log 信息保存到 Flash 中,大家可以自己完成,将原本 printf 输出的数据全部以 tsdb 数据库形式存储。 全部过程记录如上,后面将会记录一些基于文件系统保存数据的方法,提供给大家使用。
五、附件内容附件资源包中有以下内容可供参考下载: FlashDB 全功能代码包 —— 1. MM32F0144C6P_FAL_FlashDB_CmBacktrace_Shell.zip RTT package 包中 driver 层软件模拟 RTC 参考 —— 2. RTT_Software_RTC_Ref.zip 软件模拟 RTC 参考2 —— 3. M031BSP_Software_RTC-master.zip
六、参考资源本文创作参阅学习了以下下资源,在此声明感谢! https://blog.csdn.net/Mculover666/article/details/106373147/ https://blog.csdn.net/zzssdd2/article/details/111457836 http://armink.gitee.io/flashdb/#/zh-cn/quick-started
1. MM32F0144C6P_FAL_FlashDB_CmBacktrace_Shell.zip
(1.84 MB)
2. RTT_Software_RTC_Ref.zip
(36.94 KB)
3. M031BSP_Software_RTC-master.zip
(1.43 MB)
|
灵活使用开源FAL+ FlashDB组件,并实现使用MCU内嵌Flash实现数据储存。同时借助CmBacktrace 进行故障追踪,同时巧用SysTick定时器模拟时间戳,以较完整的方式实现整个测试过程。