# NV配置开发指南 ***Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.*** --- # 功能概述 ## JSON NV存储 在嵌入式设备及物联网模块中,非易失性(Non-Volatile, NV)参数的存储是系统的关键功能之一。UniRTOS提供了基于JSON格式的轻量级、高容错NV参数存储机制。相比传统二进制存储方式,它可以在一定程度上解决NV数据结构变动带来的兼容性问题,为设备提供一个安全、灵活、易于维护的“配置数据库”。本文主要描述如何通过相关API实现NV配置。 ## 痛点 在过去的方案中,NV参数的存储通常是 **将C语言中的配置结构体内存块直接以二进制形式写入文件系统**,读取时再原样拷贝回内存中。 这种基于“内存偏移量”的硬编码方式存在严重的向下兼容性问题: - **主要问题**:一旦在后续版本中修改了结构体(如在中间插入新变量、删除旧变量或改变变量类型),整个结构体的内存分布就会发生改变。 - **严重后果**:新固件在读取旧设备保存的配置文件时,数据无法与新结构体对齐,会导致参数读取失败、数据错位,甚至引发系统崩溃。 ## 解决方法 为了解决上述问题,UniRTOS引入了JSON键值对(Key-Value)模型,实现了 **数据存储与内存物理位置的彻底解耦**。 - **标识解耦**:为结构体中的每个变量赋予唯一的字符串标识(Key)。读取时“认名字不认位置”。无论结构体成员如何增删调整,只要Key匹配,就能精准还原数据。 - **映射绑定机制**:开发者只需定义一张 **参数节点表**,将自定义的Key与需要保存的结构体变量的内存地址及其大小进行绑定。底层读写接口会自动根据此表,完成JSON节点与内存数据的双向序列化与反序列化。 例如,某应用需要将串口的波特率和回显模式(即串口收到什么就回复什么)存储到NV中。 按照需求确定好配置结构体: ```c typedef struct { int baudrate; // 波特率 char echo_mode; // 回显模式 } uart_app_config_t; uart_app_config_t g_uart_app_cfg = { 115200, 1 }; // 当前内存中的值 ``` 在JSON NV方案中,上述配置结构体可映射为如下JSON键值对: ```c // 声明参数映射表 (将变量绑定到 JSON Key 上) qosa_nv_cfg_item_data_t uart_app_param_table[] = { // JSON Key名称 变量内存地址 变量大小 { "baudrate", &g_uart_app_cfg.baudrate, sizeof(g_uart_app_cfg.baudrate) }, { "echo_mode", &g_uart_app_cfg.echo_mode, sizeof(g_uart_app_cfg.echo_mode) } }; // 调用 API 写入 (设配置节点名为uart_app_cfg) qosa_nv_item_json_write("application_cfg.json/config/uart_app_cfg", uart_app_param_table, 2); // 数组元素个数为 2 ``` 执行完上述代码后,底层会生成对应的JSON文件内容: ```{image} images/image_HCKCb4Gv8oZYrYxSLTWcyo8onJd.webp :width: 1288px :height: 560px :align: center ``` ```{note} 115200的十六进制为0x0001C200,在小端模式下,内存字节表现为00 C2 01 00,即十进制的[0, 194, 1, 0]。 ``` ## 注意事项 JSON NV机制的设计初衷是解决大量零散且易变动的NV配置项存储需求,因此 **不适合存储空间占用较大的配置**。 如上述示例所示,同一JSON NV文件中可包含多个配置节,如 *example1_cfg*、*example2_cfg* 和 *uart_app_cfg* 等。实际使用时,通常会将不同应用的配置节放在同一个JSON NV文件中,以便集中管理多种应用的NV存储需求。 此场景下需控制JSON文件的大小。如果存放的配置数据过多,导致JSON文件过大,读写该文件将消耗更多存储资源。 不推荐将结构体整体绑定到一个Key上,示例如下: ```c {"sub_config_all", &g_cfg.sub_cfg, sizeof(g_cfg.sub_cfg)} ``` 以这种方式存储的JSON NV配置,整个结构体将变成一个超长数组。若后续 *sub_cfg* 中的结构体成员发生变动,该数组到结构体的映射会错乱,从而失去JSON映射的意义。 推荐将结构体拆解,参考如下示例,逐个映射到JSON对象中: ```c {"sub_param_A", &g_cfg.sub_cfg.param_A, sizeof(...) } {"sub_param_B", &g_cfg.sub_cfg.param_B, sizeof(...) } ``` 这种方式虽然会产生更多Key并增加配置表长度,但能确保结构体在后续版本中任意增删改时,依然保持向下兼容。 # NV配置API ## 头文件 *qosa_nvitem.h* ## 函数概览 | **函数** | **说明** | | --- | --- | | *qosa_nv_item_json_write()* | 写入JSON文件配置项 | | *qosa_nv_item_cfg_read()* | 读取指定JSON文件中保存的配置项,并将读取的值提取、还原后存入指定的内存地址中 | | *qosa_nv_item_cfg_delete()* | 删除JSON文件配置项 | ## 函数详解 ### qosa_nv_item_json_write - **功能描述** 写入JSON文件配置项。向指定的JSON文件中写入或更新配置项信息。系统会将传入的二进制数据转换为JSON格式的整型数组(IntArray)进行保存。若写入前检测到新数据与已有数据完全一致,则跳过写入操作,以保护底层Flash寿命。 - **函数原型** ```c qosa_nvm_error_e qosa_nv_item_json_write(char *name, qosa_nv_cfg_item_data_t *item_data, qosa_uint16_t item_count) ``` - **参数说明** | **参数名** | **输入/输出** | **类型** | **说明** | | --- | --- | --- | --- | | *name* | 输入 | char* | 目标节点路径 | | *item_data* | 输入 | *qosa_nv_cfg_item_data_t\** | 配置项映射表数组的首地址,数组元素包含Key、数据指针及数据大小;详见 [*qosa_nv_cfg_item_data_t*](#qosanvcfgitemdata_t) | | *item_count* | 输入 | qosa_uint16_t | *item_data* 数组中的元素个数 | *name* 用于指定操作的 **JSON NV配置文件** 名称及 **配置节** 名称。JSON NV配置文件默认保存在文件系统的 */config* 路径下,无需指定存储路径。请注意文件名不能与系统中的配置文件重名。系统当前使用的配置文件如下: - *atcmd_cfg.json* - *components_cfg.json* - *system_cfg.json* *name* 字符串格式请遵循如下规则:JSON NV配置文件名 + 根节点 + 配置节点名。同一JSON NV配置文件下的多个配置节点均应位于同一根节点下。 ```{image} images/image_HPSybRxNwowM28xo5avccd3ZnAb.webp :width: 1023px :height: 415px :align: center ``` - **返回值说明** *QOSA_NVM_CFG_OK*:函数执行成功 *QOSA_NVM_CFG_PARAM_ERROR*:参数输入错误 *QOSA_NVM_CFG_PATH_ERROR*:输入路径名不符合规范 *QOSA_NVM_CFG_WRITE_FILE_ERROR*:写入JSON文件失败 *QOSA_NVM_CFG_UNKNOWN_ERROR*:未知错误 ```{note} 1. 调用此函数前,SDK内部NV存储模块会自动检查目标JSON NV文件是否存在,若不存在,则自动创建。 2. 内置数据一致性校验逻辑(防冗余写入机制),频繁写入相同配置不会导致Flash过度磨损。 3. 数据的保存形式可转换为对应的字节级数组(IntArray),支持任意类型(包括结构体)的数据。 ``` ### qosa_nv_item_cfg_read - **功能描述** 读取指定JSON文件中保存的配置项。根据传入的路径及键名(Key),在文件中查找对应节点,并将其值提取、还原后存入指定的内存地址中。 - **函数原型** ```c qosa_nvm_error_e qosa_nv_item_cfg_read(char *name, qosa_nv_cfg_item_data_t *item_data, qosa_uint16_t item_count) ``` - **参数说明** | **参数名** | **输入/输出** | **类型** | **说明** | | --- | --- | --- | --- | | *name* | 输入 | char* | 目标节点路径(格式同 ***name字符串格式***) | | *item_data* | 输入/输出 | *qosa_nv_cfg_item_data_t\** | 配置项映射表数组的首地址,数组元素包含Key、数据指针及数据大小;详见 [*qosa_nv_cfg_item_data_t*](#qosanvcfgitemdata_t)
注:输入时指定要查询的Key及预期数据大小;输出时用于接收读取到的数据 | | *item_count* | 输入 | qosa_uint16_t | *item_data* 数组中的元素个数 | - **返回值说明** *QOSA_NVM_CFG_OK*:函数执行成功 *QOSA_NVM_CFG_PARAM_ERROR*:参数输入错误 *QOSA_NVM_CFG_NO_SEARCH_ERROR*:JSON中对应项名不存在 *QOSA_NVM_CFG_READ_FILE_ERROR*:读取JSON文件失败 *QOSA_NVM_CFG_MALLOC_ERROR*:内存分配失败 ```{note} 1. SDK在JSON文件中查找Key时,会区分字母的大小写。 2. 调用者必须正确设置 *item_data* 中的 *value_size*。如果文件中存储的数据长度与期望的 *value_size* 不一致,该函数将拒绝读取并返回 *QOSA_NVM_CFG_NO_SEARCH_ERROR*。 3. 如果某个Key读取失败,该函数将直接中断后续其他元素的读取并返回 *QOSA_NVM_CFG_NO_SEARCH_ERROR*。 ``` ### qosa_nv_item_cfg_delete - **功能描述** 删除JSON文件配置项。从指定的JSON文件中删除指定的配置项(Item节点)信息。删除后,更新后的JSON树将被重新写回文件系统。 - **函数原型** ```c qosa_nvm_error_e qosa_nv_item_cfg_delete(char *name, qosa_nv_cfg_item_data_t *item_data, qosa_uint16_t item_count) ``` - **参数说明** | **参数名** | **输入/输出** | **类型** | **说明** | | --- | --- | --- | --- | | *name* | 输入 | char* | 目标节点路径(格式同 ***name*****字符串格式**) | | *item_data* | 输入 | *qosa_nv_cfg_item_data_t\** | 配置项映射表数组的首地址,数组元素包含Key、数据指针及数据大小;详见 [*qosa_nv_cfg_item_data_t*](#qosanvcfgitemdata_t) | | *item_count* | 输入 | qosa_uint16_t | *item_data* 数组中的元素个数 | - **返回值说明** *QOSA_NVM_CFG_OK*:函数执行成功 *QOSA_NVM_CFG_PARAM_ERROR*:参数输入错误 *QOSA_NVM_CFG_NO_SEARCH_ERROR*:JSON中对应项名不存在 *QOSA_NVM_CFG_WRITE_FILE_ERROR*:写入JSON文件失败 *QOSA_NVM_CFG_UNKNOWN_ERROR*:未知错误 ```{note} 1. 该函数会将解析出的目标节点及其所有子数据整体删除。 2. 在删除操作中,*item_data* 不参与实际的数据处理,仅用于SDK内部的参数合法性校验。 ``` ## 数据结构定义 ### qosa_nv_cfg_item_data_t 配置项映射表数组结构体定义如下: ```cpp typedef struct { char *key; void *value; qosa_uint16_t value_size; } qosa_nv_cfg_item_data_t; ``` | **参数** | **类型** | **说明** | | --- | --- | --- | | *key* | char* | JSON键名,通常对应配置结构体的成员名称 | | *value* | void* | 结构体数据指针 | | *value_size* | qosa_uint16_t | 结构体数据大小 | *qosa_nv_cfg_item_data_t* 用于 ***qosa_nv_item_json_write()*** 和 ***qosa_nv_item_cfg_read()*** 传输配置数据。 在构建配置结构体时, 可使用宏 *NV_ITEM_CFG_DEF* 填充 *qosa_nv_cfg_item_data_t* 数组, 示例代码如下: ```c qosa_nv_cfg_item_data_t uart_app_param_table[] = { // JSON Key名称 变量内存地址 变量大小 { "baudrate", &g_uart_app_cfg.baudrate, sizeof(g_uart_app_cfg.baudrate) }, { "echo_mode", &g_uart_app_cfg.echo_mode, sizeof(g_uart_app_cfg.echo_mode) } }; //⬇️等同于如下写法⬇️ qosa_nv_cfg_item_data_t uart_app_param_table[] = { NV_ITEM_CFG_DEF("baudrate", g_uart_app_cfg.baudrate), NV_ITEM_CFG_DEF("echo_mode", g_uart_app_cfg.echo_mode) }; ``` ## 枚举定义 ### qosa_nvm_error_e 结果码枚举定义如下: ```cpp typedef enum { QOSA_NVM_CFG_OK = QOSA_OK, QOSA_NVM_CFG_MALLOC_ERROR = 1 | QOSA_ERRCODE_NVM_CFG_BASE, QOSA_NVM_CFG_PARAM_ERROR, QOSA_NVM_CFG_PATH_ERROR, QOSA_NVM_CFG_READ_FILE_ERROR, QOSA_NVM_CFG_NO_SEARCH_ERROR, QOSA_NVM_CFG_WRITE_FILE_ERROR, QOSA_NVM_CFG_UNKNOWN_ERROR, QOSA_NVM_CFG_ERROR_MAX } qosa_nvm_error_e; ``` | **成员** | **说明** | | --- | --- | | *QOSA_NVM_CFG_OK* | 函数执行成功 | | *QOSA_NVM_CFG_MALLOC_ERROR* | 内存分配失败 | | *QOSA_NVM_CFG_PARAM_ERROR* | 参数输入错误 | | *QOSA_NVM_CFG_PATH_ERROR* | 输入路径名不符合规范 | | *QOSA_NVM_CFG_READ_FILE_ERROR* | 读取JSON文件失败 | | *QOSA_NVM_CFG_NO_SEARCH_ERROR* | JSON中对应项名不存在 | | *QOSA_NVM_CFG_WRITE_FILE_ERROR* | 写入JSON文件失败 | | *QOSA_NVM_CFG_UNKNOWN_ERROR* | 未知错误 | # 应用逻辑流程图 ```{figure} images/board_UnJuw4eMNhmyPJbNsoHcIGDnnCb.jpg :align: center :alt: image ``` # 示例代码 完整示例代码请查看 https://github.com/UniRTOS/UniRTOS-Doc-Examples/blob/main/system/nv/nv_demo.c 。