Skip to content

概念先知道

  • 掉电保存(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 = 3name = "PSM")是必须配置的部分。

操作步骤

1
进入例程目录

本页不需要额外接线。在终端进入 SDK 的 EasyFlash 例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/easyflash
2
编译工程

执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可(例程依赖 CONFIG_BFLB_MTDCONFIG_PARTITIONCONFIG_LITTLEFS,SDK 已在该例程的 defconfig 中打开):

make CHIP=bl616 BOARD=bl616dk
3
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
运行验证

打开串口助手(波特率 2000000)。程序先打印 easyflash_init test pass.,随后写入并读回 wifi.ssidwifi.passwdg/hwaddr/mac_aabb/root/aa/bbb/ 等键值(打印 ssid:helloworldpasswd: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() 之后调用。

参数:无

返回值EfErrCodeEF_NO_ERR 表示成功

ef_set_and_save_env(key, value)

写入键值并立即保存到 Flash(组合了 ef_set_envef_save_env)。

参数

  • key:键名,如 "wifi.ssid"
  • value:字符串值

返回值EfErrCodeEF_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_foreach_env(cb, arg)

遍历全部键,逐个调用回调 cb(name, arg)

参数

  • cb:回调函数
  • arg:透传给回调的参数(例程用来计数)

返回值EfErrCode

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 完整代码
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_MTDCONFIG_PARTITIONCONFIG_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

Released under the MIT License. Build Time 2026-09-11 14:52:23