发新帖本帖赏金 120.00元(功能说明)我要提问
返回列表

[MM32生态] FAL+FlashDB 组件是什么?怎么应用于不带 RTC 的芯片上?

[复制链接]
209|3
手机看帖
扫描二维码
随时随地手机跟帖
yang377156216|  楼主 | 2022-5-13 15:09 | 显示全部楼层 |阅读模式
本帖最后由 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 结构框图.png
FAL 提供的 API 接口如下所示:
FAL API.png
有了 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 ,它们的关系结构如下图所示:
FAL 与 FlashDB的关系结构图.png
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 中实现:
cmb 配置接口.png
以适配自己的平台填入合适的参数即可。使用 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 log .png
CmBacktrace 作为一个与底层汇编指令打交道的库,适配非常完善,源代码在条件编译上也做得非常优雅,更深入地学习可以参见源码链接:https://github.com/armink/CmBacktrace


三、FlashDB  两大功能在 MM32F0144C6P 上实现
硬件资源如下:
  • 一块 MM32F0144C6P 芯片的核心板,这里选用灵动微官方提供的红色最小系统板
  • 一个 DAP-Link 调试器,我选用的是创芯工坊的 PW-Link ,既能供电又能虚拟出串口

软件资源如下:
  • 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 的移植。
FAL 移植思路框架.png
  • 先解压上面下载好的源码包,将源文件 .c .h 以及 port 相关的 .c 文件夹拷贝到项目中,这里将所有涉及到的文件全部放到示例工程中去,并且添加 \fal\inc\ 文件夹到编译的头文件目录列表中,可以看到文件的目录结构大致如下:

fal 移植目录结构.png

fal keil 工程目录.png
  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 设备表。需要自己新建一个 fal_cfg.h 配置文件,并且添加到工程目录中且添加路径,定义内容如下:

/* ===================== Flash device Configuration ========================= */
extern const struct fal_flash_dev mm32f0144_onchip_flash;

/* flash device table */
#define FAL_FLASH_DEV_TABLE                                          \
{                                                                    \
    &mm32f0144_onchip_flash,                                         \
}
  • 定义 flash 分区表。Flash 分区基于 Flash 设备,每个 Flash 设备又可以有 N 个分区,这些分区的集合就是分区表。在配置分区表前,务必保证已定义好 Flash 设备 及设备表。现在将用户数据区分为 2 个 区域,1 个分区名叫 fdb_tsdb1 挂在 mm32f0144_onchip_flash 设备上,起始地址为 48K 位置,大小为 8K;1 分区名个叫 fdb_kvdb1,挂在 mm32f0144_onchip_flash 设备上,起始地址为 56K 位置,大小为 8K,具体分区定义内容如下:

#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 */
  • 适配 nr_micro_shell 测试命令 fal。源代码中提供的是基于 FinSH/MSH 的,而我工程中未使用 RTT 操作系统,所以不能直接使用,需要将 fal_test 接口重新适配好,包括了 probe、read、write、erase这几条命令,bench 命令暂时未适配。在做一些基于 Flash 的应用开发、调试时,这些命令会非常实用。它可以准确的写入或者读取指定位置的原始 Flash 数据,快速的验证 Flash 驱动的完整性。详细代码参见以下内容:

/*
* 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 的读取、擦除以及写入进行简单测试,正常情况下会有如下打印信息输出:




FAL shell test1.png fal erase_write ok.png fal read ok.png
已经完成了关键一步了,接着进行 FlashDB  的添加。
  • 将 FlashDB  源码中所有文件夹拷贝到项目中,FlashDB 的测试样例代码也添加进工程,并且添加好头文件路径到编译目录中,下图为整个工程的目录结构:

keil整体目录工程结构.png

  • 将 FlashDB 组件与 FAL 组件进行关联配置,在 fdb_cfg.h 中进行配置,代码如下:

/*
* 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_ */
  • 实现好 lock、unlock、get_time 三个接口函数,再使用样例提供的测试 demo 进行初步测试,验证组件功能的完整性。由于 fdb_init() 中会调用 fal_init(),所以前面为了测试 FAL 组件时添加的初始化代码可以去除掉了。下面为 main.c 中关键代码:

#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 组件的完整移植,编译下载程序后可以看到如下打印,表示组件有正常工作:


timestamp warning .png
实际测试还遇到一个问题, 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 中能够存储一个变量而且软复位后依旧保持。
  • 定义时基变量,将其固定于 0x20000000 位置,并于硬件定时器中断里进行自加。

/* 用于模拟时间戳的计数器 ,设置成 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();
}
  • Memorry Areas 设置,相当于 .sct 分散加载文件的配置。

SRAM 设置.png


  • 进行 debug 实验测试,观看 0x20000000 地址上变量的情况。由于首次使用该变量时,会是一个任意值,考虑到它的作用是提供时基,需要在 boot_count 为 0 的时候将其初始化为 0,在这个基础上运行模拟 RTC 时间戳。另外还结合了一些关于芯片复位标志的判断做了一些逻辑处理,可以完善这个 “不掉电软件模拟 RTC ”的功能。

SRAM count 初始化.png 模拟不掉电 RTC 方法.png

可以看到时间戳功能是实现了,并且实际测试也不报警告了。工程中只实现了时基参数的保存,未实现 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)

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 120.00 元 2022-05-13
理由:恭喜通过原创文章审核!请多多加油哦!

评论

21小跑堂 2022-5-13 16:55 回复TA
灵活使用开源FAL+ FlashDB组件,并实现使用MCU内嵌Flash实现数据储存。同时借助CmBacktrace 进行故障追踪,同时巧用SysTick定时器模拟时间戳,以较完整的方式实现整个测试过程。 
yangxiaor520| | 2022-5-13 18:36 | 显示全部楼层
码字不容易,谢谢分享。

使用特权

评论回复
gyh974| | 2022-5-17 08:31 | 显示全部楼层
谢谢分享

使用特权

评论回复
返回列表 发新帖 本帖赏金 120.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则