概念先知道
- 掉电保存(KV 存储):把“键值对”数据(如 Wi-Fi 账号密码、设备配置)写进 Flash,掉电不丢失,重启后能读回来。
- EasyFlash:一款轻量级 KV 数据库组件,SDK 把它适配到 LittleFS 之上,接口以
ef_开头。 - PSM 分区:EasyFlash 只能操作分区表中名为 PSM 的分区(例程注释里给出了分区表配置示例),分区大小不足时会返回存储满错误。
- 先写内存再落盘:
ef_set_env只改内存缓存,ef_save_env才真正写 Flash;ef_set_and_save_env是“改完立即保存”的组合接口。
例程功能简介
本页对应博流官方 SDK 的 easyflash 例程(examples/easyflash),演示 KV 键值数据的写入、读取、遍历与清空:
- 初始化 MTD 与 EasyFlash,验证 PSM 分区可用;
- 写入 4 组键值:
wifi.ssid/wifi.passwd/g/hwaddr/mac_aabb//root/aa/bbb/; - 用
ef_get_env_blob读回并校验,ef_get_env_blob_offset演示从偏移位置读取; ef_foreach_env遍历全部键,ef_print_env打印当前全部键值;ef_env_set_default()清空全部键值并验证遍历计数归零。- 该例程依赖 MTD / 分区表 / LittleFS 组件,正文代码即例程自身
main.c。
注意
EasyFlash 只能操作分区表中的 PSM 分区。如果板子固件/分区表不含 PSM 分区,easyflash_init 会失败;例程 main.c 注释中的分区表配置片段(type = 3、name = "PSM")是必须配置的部分。
操作步骤
本页不需要额外接线。在终端进入 SDK 的 EasyFlash 例程目录(前提:已按快速开始(Linux)或Windows搭建好环境):
cd examples/easyflash执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可(例程依赖 CONFIG_BFLB_MTD、CONFIG_PARTITION、CONFIG_LITTLEFS,SDK 已在该例程的 defconfig 中打开):
make CHIP=bl616 BOARD=bl616dk用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):
make flash CHIP=bl616 COMX=/dev/ttyUSB0打开串口助手(波特率 2000000)。程序先打印 easyflash_init test pass.,随后写入并读回 wifi.ssid、wifi.passwd、g/hwaddr/mac_aabb、/root/aa/bbb/ 等键值(打印 ssid:helloworld、passwd:helloworld2023 test pass. 等),最后遍历全部键(共 4 个)并调用 ef_env_set_default() 清空,打印 easyflash case success。复位或断电再上电后,写入的键值仍然存在(这正是掉电保存的意义)。
代码执行流程
例程从启动到完成 KV 验证的完整流程如下(图中的菱形判断表示逐项检查):
例程调用的 API 介绍
bflb_mtd_init()
初始化 MTD(Memory Technology Device,Flash 分区抽象层),EasyFlash 依赖它访问 PSM 分区。
参数:无
返回值:成功返回 0;失败返回负值错误码
easyflash_init()
初始化 EasyFlash KV 存储(挂载 PSM 分区)。必须在 bflb_mtd_init() 之后调用。
参数:无
返回值:EfErrCode,EF_NO_ERR 表示成功
ef_set_and_save_env(key, value)
写入键值并立即保存到 Flash(组合了 ef_set_env 与 ef_save_env)。
参数:
key:键名,如"wifi.ssid"value:字符串值
返回值:EfErrCode,EF_NO_ERR 表示成功
ef_get_env_blob(key, buf, len, saved_len)
按键读回数据(二进制安全,例程读字符串)。
参数:
key:键名buf:接收缓冲区len:缓冲区大小saved_len:返回实际保存长度(可为NULL)
返回值:读取到的数据长度;键不存在或失败返回 0
ef_get_env_blob_offset(key, buf, len, saved_len, offset)
从键值的指定偏移位置开始读取,例程用它验证偏移读取功能。
参数:
key:键名buf:接收缓冲区len:缓冲区大小saved_len:返回实际保存长度(可为NULL)offset:起始偏移
返回值:读取到的数据长度;失败返回 0
ef_print_env / ef_env_set_default()
ef_print_env 打印当前全部键值;ef_env_set_default 清空全部键值(恢复出厂默认)。
参数:无
返回值:ef_print_env 无;ef_env_set_default 返回 EfErrCode
完整代码
以下为 easyflash/main.c 完整源码,与官方示例(examples/easyflash)逐字一致,默认折叠,点击展开:
📜 点击展开 easyflash/main.c 完整代码
#include "bflb_mtimer.h"
#include "board.h"
#include "bflb_mtd.h"
#include "easyflash.h"
uint8_t test_data[] = { "1234567890" };
uint8_t read_buffer[100];
#define WIFI_SSID_KEY "wifi.ssid"
#define WIFI_PASSWD_KEY "wifi.passwd"
#define TEST_KEY1 "g/hwaddr/mac_aabb"
#define TEST_KEY2 "/root/aa/bbb/"
static EfErrCode env_foreach_cb(const char *name, void *arg) {
uint32_t *count = (uint32_t *)arg;
printf("foreach key %d: %s\n", (*count)++, name);
return EF_NO_ERR;
}
int main(void)
{
EfErrCode ret;
board_init();
/* Partition and boot2 must be use, and we can only operate partition **psm** with easyflash
*
* partition_cfg with psm:
*
[[pt_entry]]
type = 3
name = "PSM"
device = 0
address0 = 0x3E9000
size0 = 0x8000
address1 = 0
size1 = 0
# compressed image must set len,normal image can left it to 0
len = 0
# If header is 1, it will add the header.
header = 0
# If header is 1 and security is 1, It will be encrypted.
security= 0
*/
bflb_mtd_init();
if (easyflash_init() == EF_NO_ERR) {
printf("easyflash_init test pass.\n");
} else {
printf("errno: %d\r\n", errno);
printf("easyflash_init test failed.\n");
}
memset(read_buffer, 0, sizeof(read_buffer));
ret = ef_set_and_save_env(WIFI_SSID_KEY, (const char *)"helloworld");
if (ret != EF_NO_ERR) {
printf("test case %d failed.\n", __LINE__);
while(1);
}
ret = ef_set_and_save_env(WIFI_PASSWD_KEY, (const char *)"helloworld2023");
if (ret != EF_NO_ERR) {
printf("test case %d failed.\n", __LINE__);
while(1);
}
ret = ef_set_and_save_env(TEST_KEY1, (const char *)"11223344");
if (ret != EF_NO_ERR) {
printf("test case %d failed.\n", __LINE__);
while(1);
}
ret = ef_set_and_save_env(TEST_KEY2, (const char *)"deadbeef");
if (ret != EF_NO_ERR) {
printf("test case %d failed.\n", __LINE__);
while(1);
}
ret = ef_save_env();
if (ret != EF_NO_ERR) {
printf("test case %d failed.\n", __LINE__);
while(1);
}
char ssid[33];
char passwd[65];
char hwaddr[33];
ret = ef_get_env_blob(WIFI_SSID_KEY, ssid, sizeof(ssid), NULL);
if (ret > 0) {
ssid[ret] = 0;
printf("ssid:%s, test pass.\r\n", ssid);
} else {
printf("test case %d failed.\n", __LINE__);
while(1);
}
ret = ef_get_env_blob(WIFI_PASSWD_KEY, passwd, sizeof(passwd), NULL);
if (ret > 0) {
passwd[ret] = 0;
printf("passwd:%s test pass.\r\n", passwd);
} else {
printf("test case %d failed.\n", __LINE__);
while(1);
}
ret = ef_get_env_blob(TEST_KEY1, hwaddr, sizeof(hwaddr), NULL);
hwaddr[ret] = 0;
if (ret == 0) {
printf("read key1 failed\r\n");
while(1);
} else {
printf(TEST_KEY1 ":%s, pass\r\n", hwaddr);
}
ret = ef_get_env_blob_offset(TEST_KEY1, hwaddr, sizeof(hwaddr), NULL, 2);
hwaddr[ret] = 0;
if (ret == 0) {
printf("read key1 failed\r\n");
while(1);
} else {
printf(TEST_KEY1 "+2:%s, pass\r\n", hwaddr);
}
ret = ef_get_env_blob_offset(TEST_KEY1, hwaddr, sizeof(hwaddr), NULL, 3);
hwaddr[ret] = 0;
if (ret == 0) {
printf("read key1 failed\r\n");
while(1);
} else {
printf(TEST_KEY1 "+3:%s, pass\r\n", hwaddr);
}
ret = ef_get_env_blob_offset(TEST_KEY1, hwaddr, sizeof(hwaddr), NULL, 100);
hwaddr[ret] = 0;
if (ret == 0) {
printf("test case %d pass.\n", __LINE__);
} else {
printf(TEST_KEY1 "+100:%s, failed\r\n", hwaddr);
while(1);
}
ret = ef_get_env_blob("aa/bb", hwaddr, sizeof(hwaddr), NULL);
hwaddr[ret] = 0;
if (ret == 0) {
printf("test non-exists key pass\r\n");
} else {
printf("aa/bb:%s, failed!\r\n", hwaddr);
while(1);
}
ret = ef_get_env_blob(TEST_KEY2, hwaddr, sizeof(hwaddr), NULL);
hwaddr[ret] = 0;
if (ret == 0) {
printf("read key2 failed\r\n");
while(1);
} else {
printf(TEST_KEY2 ":%s, pass\r\n", hwaddr);
}
printf("foreach all env:\n");
uint32_t count = 0;
ef_foreach_env(env_foreach_cb, &count);
printf("foreach all env: done, total: %d\n", count);
if (count == 4) {
printf("ef_foreach_env test pass.\n");
} else {
printf("ef_foreach_env test failed.\n");
while(1);
}
ef_print_env();
printf("clear all kv\r\n");
/* reset all kv */
ef_env_set_default();
ef_print_env();
count = 0;
ef_foreach_env(env_foreach_cb, &count);
if (count == 0) {
printf("ef_env_set_default test pass.\n");
} else {
printf("ef_env_set_default test failed.\n");
while(1);
}
printf("easyflash case success\r\n");
while (1) {
}
}FAQ
easyflash_init test failed
最常见原因是分区表缺少 PSM 分区(EasyFlash 只能操作 PSM)。检查 partition_cfg 是否包含例程注释中的 name = "PSM" 条目,并确认 CONFIG_BFLB_MTD、CONFIG_PARTITION、CONFIG_LITTLEFS 已打开。
写入很多键后提示存储满
PSM 分区容量有限(例程示例为 0x8000 = 32KB),写满后返回 EF_ENV_FULL。按需扩大 PSM 分区大小,或定期清理不再需要的键。
断电后键值丢失
确认写入用的是 ef_set_and_save_env(或 ef_set_env 后调用 ef_save_env);只调用 ef_set_env 而不保存的话,数据只存在内存缓存中,掉电即丢。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

