Skip to content

NV 使用文档

本文档介绍 YOpen SDK 中 NV(Non-Volatile,掉电保存数据)功能的整体架构、分区规划、demo 运行方式,以及两个配套工具:构建期工厂 NV 生成(nv_gen) 和 离线 NV 重打包工具(nv_pack_tools)。


1. 整体架构

NV 组件采用分层设计,应用层只面对简单统一的 KV 接口:

┌─────────────────────────────────────────────┐
│  应用代码(如 components/demo/demo_nv)    │
├─────────────────────────────────────────────┤
│  FlashDB KVDB   flashdb.h / fdb_kvdb.c  │  fdb_kv_set / fdb_kv_get / fdb_kv_set_blob ...
├─────────────────────────────────────────────┤
│  FAL 适配层 components/nv/src/nv_fal_port.c  │  把 flash 读写映射到 NV 分区
│             components/nv/src/fal_cfg.h  │  分区表:分区名 "nv",大小 FLASH_NV0_REGION_SIZE
├─────────────────────────────────────────────┤
│  底层 flash 驱动  FLASH_readSafe/writeSafe/eraseSafe│
└─────────────────────────────────────────────┘

应用层直接调用 FlashDB 的 fdb_kv_* 接口,无需额外封装。关键文件:

文件 作用
components/nv/FlashDB/inc/flashdb.h FlashDB 公开 API(fdb_kv_set/fdb_kv_get/fdb_kv_set_blob/fdb_kv_get_blob/fdb_kv_del 等)
components/nv/src/nv_fal_port.c flash 读写适配(XIP 地址源自动经 RAM 中转,避免 QSPI 总线冲突)
components/nv/src/fal_cfg.h FAL 分区表,绑定 NV 分区大小
components/nv/FlashDB/ FlashDB 开源库(KV 存储引擎)

2. demo_nv 使用

components/demo/demo_nv 是一个完整可跑的 NV 示例,项目配置见 project/nv.cmake。

2.1 编译运行

在仓库根目录打开 CMD 窗口,执行以下命令编译 nv 项目:

build.bat YM310_W09C.A60 nv

编译生成的文件位于仓库根目录的 out 文件夹。具体编译和烧录方法请查看仓库根目录的 README 和 build.bat 说明。

每次编译完成后,构建系统会自动运行 tools/analyze_mem_map.py,在 out/<项目>/bsp/<芯片>/ap/<项目>/ 下生成 partition_report.txt:

[All flash regions] sorted by address (ascending)

  name                                  start          end  size (human) (hex)
  ----------------------------------------------------------------------------------------
  HEAD                           0x00000000 0x00003000
  BL                             0x00003000 0x00019000  88 KiB (0x16000)
  RF BACKUP                      0x00019000 0x0001E000  20 KiB (0x5000)
  CP                             0x0001E000 0x00082000  400 KiB (0x64000)
  AP                             0x00082000 0x002C1000  2.25 MiB (0x23F000)
  HIB                            0x002C1000 0x002D9000  96 KiB (0x18000)
  NV                             0x002D9000 0x002DD000  16 KiB (0x4000)
  FS                             0x002DD000 0x003AD000  832 KiB (0xD0000)
  FOTA                           0x003AD000 0x003F1000  272 KiB (0x44000)
  RF                             0x003F1000 0x003FE000  52 KiB (0xD000)
  PLAT CONFIG                    0x003FE000 0x00400000
[OK] No partition overlaps detected.

report saved to: D:\code\yopen\out\EC7XX_YM310_W09C.A60_nv\bsp\ec7xx_0h00_ec718pm\ap\yopen_app\partition_report.txt
别名 宏名 用途
AP AP_FLASH_LOAD_SIZE ap 代码分区
NV FLASH_NV0_REGION_SIZE 出厂预置 + 运行期 KV 存储(本组件使用)
FS FLASH_FS_REGION_SIZE 文件系统分区
APP APP_FLASH_LOAD_SIZE 独立应用分区(demo_nv 项目大小为 0,未启用)
FREE 如果分配设置完后,还有剩余空间存在,就会显示该字段。
需要重新调整AP/APP/NV/FS的大小,保证空间完全利用

2.2 demo_nv.c

demo_nv.c 演示了 nv_gen 生成的访问函数与原生 FlashDB 接口的配合,流程如下:

  1. 初始化:fdb_kvdb_init() 打开 NV 数据库(含互斥锁回调设置)
  2. 字符串读取:nv_cfg_get_host() 读取 host(string[128])
  3. 二进制数组读取:nv_cfg_get_key0() 读取 key0(hex[8])
  4. 结构体读取:nv_cfg_get_http() 读取 HTTP(pack(1) 紧凑结构体,成员 HOST/PORT/PARAM1/PARAM2),多字节成员用 memcpy 取值
  5. 原生 blob 读写删:用临时 key temp_key 走一遍 fdb_kv_set_blob → fdb_kv_get_blob → fdb_kv_del 生命周期
  6. set 接口:nv_cfg_set_host() / nv_cfg_set_http() 改写后读回校验,再还原原值

第 2~4 步所用的键名 / 长度宏均来自生成的 nv.h:

#include "nv.h"

char host[NV_CFG_LEN_HOST + 1] = { 0 };
if (nv_cfg_get_host(&s_kvdb, host) == 0)          /* string[128] host */
{
    yopen_trace("[NV_DEMO] %s = %s", NV_CFG_KEY_HOST, host);
}

nv_http_t http = { 0 };
if (nv_cfg_get_http(&s_kvdb, &http) == 0)         /* struct HTTP */
{
    uint32_t port;
    memcpy(&port, &http.PORT, sizeof(port));      /* 紧凑结构体成员按非对齐访问 */
    yopen_trace("[NV_DEMO] %s.PORT=%u", NV_CFG_KEY_HTTP, port);
}

第 5 步用临时 key 演示原生 blob 接口的写 / 读 / 删:

static const char TMP_KEY[] = "temp_key";
uint8_t wr[4] = { 0xDE, 0xAD, 0xBE, 0xEF }, rd[4] = { 0 };
struct fdb_blob blob;

fdb_kv_set_blob(&s_kvdb, TMP_KEY, fdb_blob_make(&blob, wr, sizeof(wr)));
fdb_kv_get_blob(&s_kvdb, TMP_KEY, fdb_blob_make(&blob, rd, sizeof(rd)));
fdb_kv_del(&s_kvdb, TMP_KEY);

2.3 常用 API 速查

fdb_kvdb_init(&db, "nv", "nv", NULL, NULL);           /* 初始化(先调用) */
fdb_kv_set(&db, "key", "value");                       /* 写字符串 */
fdb_kv_get(&db, "key");                                /* 读字符串(返回 char*,NULL=不存在) */
fdb_kv_set_blob(&db, "key", fdb_blob_make(&blob, &cfg, sizeof(cfg)));  /* 写二进制 */
fdb_kv_get_blob(&db, "key", fdb_blob_make(&blob, &cfg, sizeof(cfg)));  /* 读二进制,返回实际长度 */
fdb_kv_get_obj(&db, "key", &kv);                       /* 查询 key 是否存在(NULL=不存在) */
fdb_kv_del(&db, "key");                                /* 删除 key */

约定:

  • 返回值 fdb_err_t,FDB_NO_ERR 为成功
  • fdb_kv_get 返回的 char* 指向 FlashDB 内部缓存,使用前应拷贝到本地 buffer
  • 多任务并发安全(初始化时设置锁/解锁回调),可在任意任务中调用

2.4 demo运行结果

Index   Message 

1574413 [FlashDB]   
1574414     
1574415 FlashDB V2.2.99 is initialize success.      
1574416 [FlashDB]   
1574417     
1574418 You can get the latest version on https://github.com/armink/FlashDB .   
1574419 [NV_DEMO] host = http://716.yuge-info.com/  
1574420 [NV_DEMO] key0 = 12 34 56 78 00 00 00 00    
1574421 [NV_DEMO] HTTP.HOST = http://716.yuge-info.com/     
1574422 [NV_DEMO] HTTP.PORT=80 PARAM1=-1 PARAM2=100     
1574425 [NV_DEMO] temp_key = DE AD BE EF    
1574426 [NV_DEMO] set host -> http://test.local/    
1574427 [NV_DEMO] set HTTP.PORT -> 8080     

3. 工厂预置 NV(nv_gen 工具)

设备出厂时希望 NV 分区里已带有服务器地址、SN 等数据。使用配套的 nv_gen 工具,从可读文本工程文件 .nvc 一键生成 NV 镜像与访问代码:

nv.nvc  --(nv_gen)-->  nv.bin       打入 binpkg 的 NV0 槽位
                   \-->  nv.h / nv.c  应用侧访问函数(参与编译)

nv_gen 位于 tools/nv_gen.exe(源码见 flashdb_win32/),提供 图形界面(GUI) 与 命令行(CLI) 两种用法。

3.1 .nvc 工程格式

.nvc 为可读文本格式,每行一条配置,语法为 [type[N]] key = value:

type 说明
string[N] N 字节字符数组,每成员 1 字符,不足补 0x00
hex[N] N 字节二进制数组,每成员两个十六进制字符(如 12 = 0x12)
u8 / u16 / u32 无符号整数(小端 1/2/4 字节)
i8 / i16 / i32 有符号整数(小端 1/2/4 字节)
struct 结构体,成员语法 name:type[N]=value;,以 ; 分隔

示例(components/demo/demo_nv/nv.nvc):

[string[128]] host = "http://716.yuge-info.com/"
[hex[8]] key0 = 12 34 56 78 00 00 00 00
[struct] HTTP = HOST:string[128]="http://716.yuge-info.com/"; PORT:u32=80; PARAM1:i32=-1; PARAM2:u32=100

约定:

  • # 开头为注释行
  • 字符串值建议用双引号包裹
  • 所有多字节整数与结构体成员均按 小端(little-endian) 存储
  • 结构体采用 #pragma pack(1) 紧凑布局,成员按声明顺序排列,不做字节对齐填充

3.2 图形界面使用

双击 tools/nv_gen.exe,或传入 .nvc 路径直接打开指定工程。示例工程的 .nvc 文件通常位于 components\demo\demo_nv 目录:

nv_gen.exe                      # 打开空白工程
nv_gen.exe <工程.nvc>           # 打开指定工程

nv_gen V1.0 打开 nv.nvc 工程

常用操作:

  • 右键列表 → 添加:选择类型(string / hex / u32 / u16 / u8 / i32 / i16 / i8 / struct)后填写 key、类型、长度、值
  • 右键 → 编辑 / 删除:修改或删除条目;双击条目可直接编辑
  • 列表按 name / type / value 三列显示,string / hex / struct 前有 + 号,可展开查看各元素 / 成员;结构体成员若为数组或结构体可逐层展开
  • 文件 → 导入 nvc 文件:载入已有 .nvc 继续编辑
  • 文件 → 保存 / 另存为:写出 .nvc(Ctrl+S 快捷保存;另存为只需填名称,会在所选目录下建同名目录并存放工程)
  • 文件 → 生成:在 .nvc 同级目录输出 nv.bin / nv.h / nv.c
  • 使用说明:菜单栏第二项,查看操作提示

3.3 命令行使用

nv_gen.exe <config.nvc> [output.bin] [size]
  • output.bin 省略时,在 .nvc 同目录生成同名 .bin
  • size 支持十进制或 0x 十六进制,省略则按内容自动计算
  • 生成 bin 的同时,在 bin 同目录输出 <base>.h 与 <base>.c
  • nv_gen.exe -h 或 --help 查看帮助

注意:重新烧录 binpkg 会整片重写 NV 分区,设备运行期保存的数据会被恢复为出厂预置内容,属于预期行为。

3.4 生成的代码(nv.h / nv.c)

nv.h 提供版本宏、键名宏、长度宏、结构体类型定义与 get/set 成对访问函数:

#define NV_CFG_VERSION  "V1.0"        /* 配置版本 */
#define NV_CFG_KEY_HOST "host"        /* KV 键名 */
#define NV_CFG_LEN_HOST 128           /* 数据长度(字节) */

#pragma pack(push, 1)                 /* 结构体紧凑布局 */
typedef struct {
    char     HOST[128];
    uint32_t PORT;
    int32_t  PARAM1;
    uint32_t PARAM2;
} nv_http_t;
#pragma pack(pop)

int nv_cfg_get_host(struct fdb_kvdb *db, char *out);          /* get / set 成对 */
int nv_cfg_set_host(struct fdb_kvdb *db, const char *val);
int nv_cfg_get_http(struct fdb_kvdb *db, nv_http_t *out);
int nv_cfg_set_http(struct fdb_kvdb *db, const nv_http_t *val);

应用侧直接调用(参考 components/demo/demo_nv/demo_nv.c):

#include "nv.h"

char host[NV_CFG_LEN_HOST + 1] = { 0 };
if (nv_cfg_get_host(&s_kvdb, host) == 0) { /* 读取成功 */ }

nv_http_t http = { 0 };
if (nv_cfg_get_http(&s_kvdb, &http) == 0) {
    uint32_t port;
    memcpy(&port, &http.PORT, sizeof(port));   /* 紧凑结构体成员为非对齐访问,用 memcpy 取值 */
}

约定:get 返回 0 表示成功且长度匹配,-1 表示失败(key 不存在或长度不符);set 返回 0 表示成功。

3.5 项目侧配置

在项目 cmake(project/nv.cmake 已配好)中指定 .nvc 路径,构建时(configure 阶段)自动生成 nv.bin 并打入 binpkg,同时生成 nv.h / nv.c:

set(NV0_NVC_DEFAULT_PATH ${YOPEN_TOP_DIR}/components/demo/demo_nv/nv.nvc)

生成物统一放在 .nvc 同级目录:nv.bin / nv.h / nv.c,其中 nv.c 由 nv.cmake 加入编译。


4. 自定义 NV 配置

不使用 SDK 自带的 NV 组件,客户使用自己的 NV 配置时,最多支持 4 个 NV 分区(NV0 ~ NV3),地址连续排列在 APP 之后。demo_nv 默认只启用 NV0,客户可按需启用更多分区。

4.1 NV 分区地址链

各 NV 分区的物理地址是链式推导的,无需手动指定:

FLASH_NV0_REGION_START = FLASH_NV_REGION_START          (固定起点)
FLASH_NV1_REGION_START = FLASH_NV_REGION_START + FLASH_NV0_REGION_SIZE
FLASH_NV2_REGION_START = FLASH_NV1_REGION_START + FLASH_NV1_REGION_SIZE
FLASH_NV3_REGION_START = FLASH_NV2_REGION_START + FLASH_NV2_REGION_SIZE

FLASH_NV_REGION_END = FLASH_NV3_REGION_START + FLASH_NV3_REGION_SIZE,FS 分区紧随其后。所以只需配置每个分区的大小,地址自动算好。

4.2 配置方法

在项目 cmake(如 project/nv.cmake)中配置:

启用分区(设置大小):

set(FLASH_NV0_REGION_SIZE 0x4000)    # 16KB,必须 > 0 才启用
#set(FLASH_NV1_REGION_SIZE 0x4000)    # 取消注释即启用 NV1
#set(FLASH_NV2_REGION_SIZE 0x4000)    # 取消注释即启用 NV2
#set(FLASH_NV3_REGION_SIZE 0x4000)    # 取消注释即启用 NV3

指定 NV 数据:

# NV0:nvc 工程方式(自动生成 nv.bin,同时生成 nv.h / nv.c)
set(NV0_NVC_DEFAULT_PATH ${YOPEN_TOP_DIR}/components/demo/demo_nv/nv.nvc)
# 不用 nvc 时可直接指定现成 bin:
# set(NV0_BIN_DEFAULT_PATH ${YOPEN_TOP_DIR}/components/demo/demo_nv/nv0.bin)

# NV1 ~ NV3:只能用 bin 方式
set(NV1_BIN_DEFAULT_PATH ${YOPEN_TOP_DIR}/components/demo/demo_nv/nv1.bin)
set(NV2_BIN_DEFAULT_PATH ${YOPEN_TOP_DIR}/components/demo/demo_nv/nv2.bin)
set(NV3_BIN_DEFAULT_PATH ${YOPEN_TOP_DIR}/components/demo/demo_nv/nv3.bin)

注意:FLASH_NVx_REGION_SIZE = 0(或未设置)的分区视为禁用,对应的 .bin 文件即使存在也不会打包。

4.3 代码侧使用多分区

默认 demo 只操作 NV0 分区(分区名 "nv",见 fal_cfg.h)。若需要读写 NV1~3,需扩展 FAL 分区表并初始化额外的 FlashDB 实例,参考 components/nv/src/fal_cfg.h 和 demo_nv.c 中的初始化方式。


5. 离线重打包工具(tools/nv_pack_tools)

5.1 用途

nv_pack_tools 是一个独立于软件工程的离线工具(整个目录可拷贝到任意 Windows 电脑使用,无需编译环境)。用于把客户准备好的 nv.bin 重新合入已经编译打包好的固件 .binpkg,典型场景:

  • 量产阶段为每批设备写入不同的 SN / 服务器配置
  • 售后更换 NV 内容,但不想重新编译整个固件

5.2 使用步骤(3 步)

第 1 步:准备 input 目录

打开 tools\nv_pack_tools。如果没有 input 文件夹,先手动创建,然后将下面 3 类文件放入 input:

文件 要求 默认来源
*.binpkg input 中只能放一个 out\EC7XX_YM310_W09C.A60_nv\bin
mem_map.txt 必须与 binpkg 来自同一次编译 out\EC7XX_YM310_W09C.A60_nv\bsp\ec7xx_0h00_ec718pm\ap\yopen_app
nv.bin 或 nv0.bin ~ nv3.bin 单分区使用 nv.bin;多分区使用自定义的 nv0.bin ~ nv3.bin NV 生成工具的输出目录

第 2 步:执行打包

双击 tools\nv_pack_tools\package_nv.bat,等待窗口显示打包完成。

第 3 步:获取结果

打开 tools\nv_pack_tools\output,检查下面两个输出文件:

输出文件 作用
<固件名>.binpkg 已包含新 NV 数据,可直接用于烧录
nv_partition.txt 记录各 NV 分区的地址和大小

使用 FlashTools 烧录时,在 package 一栏选择 output 目录中重新生成的 <固件名>.binpkg。下图红框是需要替换的位置:

FlashTools 替换重新打包后的 binpkg

5.3 目录结构

nv_pack_tools/
├── package_nv.bat    一键打包入口(双击运行)
├── input/            放固件 + mem_map.txt + nv.bin
├── output/           打包结果
└── tools/            打包程序(已编译 exe,无需 Python)
    ├── repack_pkg.exe    解包 / 合并 binpkg
    ├── dump_nv.exe       打印 NV 分区地址/大小
    └── fcelf.exe         底层打包工具

5.4 注意事项

  • NV 槽位必须在编译期已启用(mem_map.txt 中 FLASH_NVx_REGION_SIZE > 0),否则对应 nv 文件自动跳过
  • mem_map.txt 必须与 binpkg 来自同一次编译,否则地址对不上
  • input/ 中同时有 nv.bin 和 nv0.bin 时,NV0 以 nv0.bin 为准