文章链接:https://mp.weixin.qq.com/s/-6FD1FFLsrVtwK3IVi5WDQ
一、开发环境
开发板:STM32F407ZG
RT-Thread版本:V4.1.0
二、EasyFlash 软件包介绍
EasyFlash是一款开源的轻量级嵌入式Flash存储器库,方便开发者更加轻松的实现基于Flash存储器的常见应用开发。非常适合智能家居、可穿戴、工控、医疗、物联网等需要断电存储功能的产品,资源占用极低,支持各种 MCU 片上存储器。
该库主要包括 三大实用功能 :
ENV 快速保存产品参数,支持 写平衡(磨损平衡) 及 掉电保护功能
EasyFlash不仅能够实现对产品的 设定参数 或 运行日志 等信息的掉电保存功能,还封装了简洁的 增加、删除、修改及查询 方法, 降低了开发者对产品参数的处理难度,也保证了产品在后期升级时拥有更好的扩展性。。
让Flash变为NoSQL(非关系型数据库)模型的小型键值(Key-Value)存储数据库
IAP 在线升级再也不是难事儿
该库封装了IAP(In-Application Programming)功能常用的接口,支持CRC32校验,同时支持Bootloader及Application的升级。
Log 无需文件系统,日志可直接存储在Flash上
非常适合应用在小型的不带文件系统的产品中,方便开发人员快速定位、查找系统发生崩溃或死机的原因。同时配合EasyLogger(我开源的超轻量级、高性能C日志库,它提供与EasyFlash的无缝接口)一起使用,轻松实现C日志的Flash存储功能。
三、移植
基于 RT-Thread 的移植参考。主要基于以下两种底层 Flash 驱动
fal : Flash 抽象层SFUD : 万能 SPI Flash 驱动库
如果 Flash 驱动使用的是上面中的一种,那么移植 EasyFlash 将会非常简单。如果没有使用上面的驱动,请参考 EasyFlash 的 移植文档 进行移植。在 EasyFlash 官方仓库 下有很多 demo,也可以参考。
本文章基于基于 fal : Flash 抽象层进行移植,使用片上flash。
四、配置
4.1 fal 配置和测试
开启 fal


开启 片上flash

fal_cfg.h 文件修改
在
fal_cfg.h 文件 目录下。
oardports
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2018-12-5 SummerGift first version
*/
#ifndef _FAL_CFG_H_
#define _FAL_CFG_H_
#include <rtthread.h>
#include <board.h>
#ifdef BSP_USING_SPI_FLASH_LITTLEFS
extern struct fal_flash_dev w25q128;
#else
#define FLASH_SIZE_GRANULARITY_16K (4 * 16 * 1024)
#define FLASH_SIZE_GRANULARITY_64K (64 * 1024)
#define FLASH_SIZE_GRANULARITY_128K (7 * 128 * 1024)
#define STM32_FLASH_START_ADRESS_16K STM32_FLASH_START_ADRESS
#define STM32_FLASH_START_ADRESS_64K (STM32_FLASH_START_ADRESS_16K + FLASH_SIZE_GRANULARITY_16K)
#define STM32_FLASH_START_ADRESS_128K (STM32_FLASH_START_ADRESS_64K + FLASH_SIZE_GRANULARITY_64K)
extern const struct fal_flash_dev stm32_onchip_flash_16k;
extern const struct fal_flash_dev stm32_onchip_flash_64k;
extern const struct fal_flash_dev stm32_onchip_flash_128k;
#endif
/* flash device table */
#ifdef BSP_USING_SPI_FLASH_LITTLEFS
#define FAL_FLASH_DEV_TABLE
{
&w25q128,
}
#else
#define FAL_FLASH_DEV_TABLE
{
&stm32_onchip_flash_16k,
&stm32_onchip_flash_64k,
&stm32_onchip_flash_128k,
}
#endif
/* ====================== Partition Configuration ========================== */
#ifdef FAL_PART_HAS_TABLE_CFG
/* partition table */
#ifdef BSP_USING_SPI_FLASH_LITTLEFS
#define FAL_PART_TABLE
{
{FAL_PART_MAGIC_WROD, "spiflash0", "W25Q128", 0 , 16 * 1024 * 1024, 0},
}
#else
#define FAL_PART_TABLE
{
{FAL_PART_MAGIC_WROD, "easyflash", "onchip_flash_128k", 1* 128 * 1024 , 2* 128 * 1024, 0},
}
// 2* 128 * 1024 : ENV area size. It's at least one empty sector for GC. So it's definition must more then or equal 2 flash sector size.
#endif
#endif /* FAL_PART_HAS_TABLE_CFG */
#endif /* _FAL_CFG_H_ */
4.2 easyflash 配置 版本 V4.1.0
开启 easyflash

ENV: Environment variables: 是否使能环境变量功能
1.1 Auto update ENV to latest default when current ENV version number is changed.:是否启用环境变量自动更新功能。启动这个功能后,环境变量将在其版本号发生变化时自动更新。
1.2 Setting current ENV version number : 当前环境变量版本号LOG: Save logs on flash:日志功能,可以将日志顺序保存至 Flash 中。还可以配合 EasyLogger 完成产品日志的掉电存储。IAP: In Application Programming:IAP 在线升级功能,开启后将提供一些 IAP 功能里常用的 API 。Erase minimum granularity:擦除的最小粒度,一般 SPI Flash 通常为 4KB,STM32F4 片内 Flash 通常为 128KB。Write minimum granularity:写数据的最小粒度,一般 SPI Flash 通常为 1bit,STM32F4 片内 Flash 通常为 8bit,详见具体选项。Start addr on flash or partition:EasyFlash 的整个存储区相对于 Flash 或者 分区 的偏移地址,视移植代码而定。Enable debug log output:是否使能调试日志输出。开启后将会看到更多调试日志信息。
ef_fal_port.c 文件修改如下:
/* EasyFlash partition name on FAL partition table */
#define FAL_EF_PART_NAME "easyflash"
五、代码和测试
测试代码
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include <stdlib.h>
#include <fal.h>
#include <easyflash.h>
/* defined the LED0 pin: PF9 */
#define LED0_PIN GET_PIN(C, 3)
/**
* EasyFlash核心测试函数
*/
static void easyflash_test(void)
{
EfErrCode err;
char str_value[127]={0};
int int_val = 0;
float float_val = 0.0f;
// 1. 初始化EasyFlash
err = easyflash_init();
if (err != EF_NO_ERR)
{
rt_kprintf("EasyFlash int err,code is:%d
", err);
return;
}
rt_kprintf("EasyFlash int ok!
");
rt_kprintf("
******************************
");
rt_kprintf("ef_print_env 1
");
ef_print_env();
rt_kprintf("
******************************
");
// 2. 测试字符串类型KV
const char *kv_str_key = "user_name";
const char *kv_str_def = "rt-thread";
char *kv_str_value = RT_NULL;
if ( (kv_str_value=ef_get_env(kv_str_key)) == NULL)
{
rt_kprintf("KV[%s],write: %s
", kv_str_key, kv_str_def);
ef_set_env_blob(kv_str_key, kv_str_def,rt_strlen(kv_str_def));
}
else
{
rt_kprintf("read KV[%s]: %s
", kv_str_key, kv_str_value);
}
// 3. 测试整数类型KV
const char *kv_int_key = "device_id";
int kv_int_def = 123456;
rt_memset(str_value,0,sizeof(str_value));
if( ef_get_env_blob(kv_int_key, str_value, sizeof(str_value) , NULL) == 0)
{
rt_kprintf("KV[%s],write: %d
", kv_int_key, kv_int_def);
rt_sprintf(str_value,"%d",kv_int_def);
ef_set_env_blob(kv_int_key, str_value, rt_strlen(str_value) );
}
else
{
rt_kprintf("read KV[%s]:%d
", kv_int_key, int_val=atoi(str_value));
// 修改并重新写入
int_val += 1;
rt_kprintf("wirte KV[%s]: %d
", kv_int_key, int_val);
rt_memset(str_value,0,sizeof(str_value));
rt_sprintf(str_value,"%d",int_val);
ef_set_env_blob(kv_int_key, str_value, rt_strlen(str_value) );
}
// 4. 测试浮点类型KV
const char *kv_float_key = "temperature";
float kv_float_def = 3.1f;
rt_memset(str_value,0,sizeof(str_value));
if ( ef_get_env_blob(kv_float_key, str_value, sizeof(str_value) , NULL) == 0)
{
rt_kprintf("KV[%s],write: 3.14
", kv_float_key);
rt_sprintf(str_value,"%d.%d",((int)(kv_float_def*100))/100,((int)(kv_float_def*100))%100);
ef_set_env_blob(kv_float_key, str_value, rt_strlen(str_value) );
}
else
{
float_val=atof(str_value);
rt_kprintf("read KV[%s]: %d.%d
", kv_float_key,((int)(float_val*100))/100,((int)(float_val*100))%100);
// 修改并重新写入
float_val += 0.1f;
rt_kprintf("write KV[%s]: %d.%d
", kv_float_key,((int)(float_val*100))/100,((int)(float_val*100))%100);
rt_memset(str_value,0,sizeof(str_value));
rt_sprintf(str_value,"%d.%d",((int)(float_val*100))/100,((int)(float_val*100))%100);
ef_set_env_blob(kv_float_key, str_value, rt_strlen(str_value) );
}
rt_kprintf("
******************************
");
rt_kprintf("ef_print_env 2
");
ef_print_env();
rt_kprintf("
******************************
");
// // 5. 测试删除KV(可选,注释掉,需测试时打开)
// err = ef_del_env(kv_str_key);
// if (err == EF_NO_ERR)
// rt_kprintf("del KV[%s] ok
", kv_str_key);
//
// rt_kprintf("
******************************
");
// rt_kprintf("ef_print_env 3
");
// ef_print_env();
// rt_kprintf("
******************************
");
rt_kprintf("EasyFlash test ok
");
}
int main(void)
{
/* set LED0 pin mode to output */
rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);
fal_init();
easyflash_test();
while (1)
{
rt_pin_write(LED0_PIN, PIN_HIGH);
rt_thread_mdelay(500);
rt_pin_write(LED0_PIN, PIN_LOW);
rt_thread_mdelay(500);
}
}

重启设备

参考
https://gitee.com/RT-Thread-Mirror/EasyFlash
https://gitee.com/RT-Thread-Mirror/EasyFlash/blob/master/docs/zh/api.md
