一、开发环境
- 开发板:STM32F407ZG
- RT-Thread版本:V4.1.0
二、EasyFlash软件包介绍
EasyFlash是一款开源的轻量级嵌入式Flash存储器库,旨在简化基于Flash存储器的应用开发。它特别适用于智能家居、可穿戴设备、工控、医疗及物联网等需要断电存储功能的场景,具有资源占用低、支持多种MCU片上存储器的特点。
该库主要集成了三大核心功能:
-
ENV(环境变量):快速保存产品参数,支持写平衡(磨损平衡)及掉电保护。它不仅能够可靠地保存系统设定参数或运行日志,还提供了简洁的增、删、改、查接口,极大地降低了对产品参数的管理复杂度,并为产品后期升级提供了良好的扩展性。可以说,它将Flash变成了一个类似NoSQL(非关系型数据库)模型的轻量级键值(Key-Value)存储数据库。
-
IAP(在应用编程):封装了IAP功能常用的接口,支持CRC32校验,可同时用于Bootloader和Application的固件升级,让在线升级变得简单可靠。在嵌入式开发中,数据传输的完整性和可靠性至关重要,这涉及到诸如网络/系统层面的校验思想。
-
Log(日志存储):无需文件系统支持,即可直接将日志顺序存储到Flash中。这对于不带文件系统的小型设备非常实用,能帮助开发者快速定位系统崩溃或死机的原因。它可以与EasyLogger(一个超轻量级、高性能的C日志库)无缝配合,轻松实现日志的Flash存储功能。
三、移植概述
本文基于RT-Thread进行EasyFlash的移植。EasyFlash主要适配以下两种底层Flash驱动方案:
- FAL (Flash抽象层)
- SFUD (串行Flash通用驱动库)
如果你的项目已使用上述驱动之一,那么移植过程将非常简便。若未使用,请参考EasyFlash官方提供的移植文档。官方仓库中也提供了丰富的Demo可供参考。
本例将基于FAL (Flash抽象层)进行移植,并使用STM32的片上Flash。
四、详细配置步骤
4.1 FAL配置与测试
首先,需要在RT-Thread Env工具或Menuconfig中启用FAL组件。
- 开启FAL软件包。


- 开启片上Flash驱动。

- 修改
fal_cfg.h文件。此文件通常位于\board\ports目录下。关键修改是为EasyFlash指定专用的存储分区。
/* ====================== Partition Configuration ========================== */
#ifdef FAL_PART_HAS_TABLE_CFG
/* partition table */
#ifdef BSP_USING_SPI_FLASH_LITTLEFS
...
#else
#define FAL_PART_TABLE \
{ \
{FAL_PART_MAGIC_WROD, "easyflash", "onchip_flash_128k", 1* 128 * 1024 , 2* 128 * 1024, 0}, \
}
// 注意:2*128*1024 定义了ENV存储区大小。必须至少包含一个空闲扇区用于垃圾回收(GC),因此其大小必须大于或等于2个Flash扇区。
#endif
#endif /* FAL_PART_HAS_TABLE_CFG */
4.2 EasyFlash (V4.1.0) 配置
在配置工具中开启EasyFlash软件包。

主要配置项说明:
- ENV: 使能环境变量功能。
1.1 Auto update ENV: 环境变量版本号改变时自动更新。
1.2 ENV version number: 设置当前环境变量版本号。
- LOG: 使能日志存储功能,可将日志保存至Flash。
- IAP: 使能在线升级功能。
- Erase granularity: 擦除最小粒度,STM32F4片内Flash通常为128KB。
- Write granularity: 写入最小粒度,STM32F4通常为8bit。
- Start addr: EasyFlash存储区的起始偏移地址。
- Debug log: 使能调试日志输出。
修改ef_fal_port.c文件,确保分区名称与fal_cfg.h中定义的一致:
/* EasyFlash partition name on FAL partition table */
#define FAL_EF_PART_NAME "easyflash"
五、测试代码与验证
以下测试代码演示了如何使用EasyFlash进行不同类型环境变量的读写操作,并在RT-Thread这个实时操作系统中运行。
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include <stdlib.h>
#include <fal.h>
#include <easyflash.h>
/* defined the LED0 pin: PC3 */
#define LED0_PIN GET_PIN(C, 3)
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 init err, code is:%d\r\n", err);
return;
}
rt_kprintf("EasyFlash init ok!\r\n");
rt_kprintf("\r\n******************************\r\n");
rt_kprintf("ef_print_env 1\r\n");
ef_print_env(); // 打印当前所有环境变量
rt_kprintf("\r\n******************************\r\n");
// 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\r\n", 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\r\n", 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\r\n", 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\r\n", kv_int_key, int_val=atoi(str_value));
// 修改并重新写入
int_val += 1;
rt_kprintf("write KV[%s]: %d\r\n", 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\r\n", 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\r\n", kv_float_key,((int)(float_val*100))/100,((int)(float_val*100))%100);
// 修改并重新写入
float_val += 0.1f;
rt_kprintf("write KV[%s]: %d.%d\r\n", 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("\r\n******************************\r\n");
rt_kprintf("ef_print_env 2\r\n");
ef_print_env();
rt_kprintf("\r\n******************************\r\n");
rt_kprintf("EasyFlash test ok\r\n");
}
int main(void)
{
rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);
fal_init(); // 初始化FAL
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);
}
}
将代码编译并下载到设备后,首次运行输出如下:

重启设备后再次运行,可以看到之前存储的环境变量被正确读出,实现了数据的持久化存储,其功能类似于一个轻量的数据库/中间件。

参考资源