前言

概述

本文档主要针对BS2X芯片中NV存储模块的使用进行介绍。用于指导工程人员能够快速使用NV模块进行二次开发。

产品版本

产品名称

产品版本

BS2X

V100

读者对象

本文档主要适用于以下工程师:

  • 技术支持工程师

  • 软件工程师

符号约定

在本文中可能出现下列标志,它们所代表的含义如下。

符号

说明

表示如不避免则将会导致死亡或严重伤害的具有高等级风险的危害。

表示如不避免则可能导致死亡或严重伤害的具有中等级风险的危害。

表示如不避免则可能导致轻微或中度伤害的具有低等级风险的危害。

用于传递设备或环境安全警示信息。如不避免则可能会导致设备损坏、数据丢失、设备性能降低或其它不可预知的结果。

“须知”不涉及人身伤害。

对正文中重点信息的补充说明。

“说明”不是安全警示信息,不涉及人身、设备及环境伤害信息。

修改记录

文档版本

发布日期

修改说明

03

2025-12-01

  • 新增“定制化NV结构使用介绍”章节内容。
  • 新增“通过NV自定义传输数据最大长度”章节内容。

02

2025-03-26

更新“新增NV项”章节内容。

01

2024-05-15

第一次正式版本发布。

00B02

2024-02-29

  • 更新“新增NV项”章节内容。
  • 更新“编译生成NV镜像”章节内容。

00B01

2024-02-05

第一次临时版本发布。

NV简介

NV模块用于在本地存储器中存储非易失性数据。NV中的每项数据以类似key-value的方式进行定义,数据项中包含唯一的索引key和自定义数据类型的value。

NV项可通过两种方式进行存储:编译预置和API写入。

  • 编译预置是指开发者可在代码编译阶段,通过修改NV头文件和NV配置文件的方式生成客制化的NV镜像,在镜像烧录的过程中统一烧录到存储介质中。预置的NV在代码运行阶段可通过API接口进行读取和更新。具体使用方法请参见“NV编译预置”章节。

  • API写入是指用户可直接在代码中调用API接口写入新的NV项,具体使用方法请参见“NV API指南”章节。

以Flash为例,NV项存储在Flash中时,以Flash器件的sector为单位进行管理,NV的每个页为一个sector大小(4096Byte),NV页的数量默认配置为2页。除去管理结构,单个非加密NV项的有效数据最大不应超过4060Byte。

NV编译预置

新增NV项

新增NV项流程

  1. 在头文件中新增kvalue的数据类型定义(非必须,如果是通用类型数据可忽略此步骤)。

  2. 在json文件中新增NV描述项。

新增kvalue数据类型

  • 通用数据类型:

    unit8_t、unit16_t、unit32_t、bool。

  • 自定义数据类型:

    支持自定义枚举(enum)类型和结构体(struct)类型。

  • 自定义数据类型存放路径:

    以bs21为例:middleware\chips\bs21\nv\nv_config\bs21_nv_default\include\common.h,其中bs21_nv_default和“build\config\target_config\bs21\config.py"中的'nv_cfg': 'bs21_nv_default'对应,模式配置为bs21_nv_default。

  • 当用户使用通用或已定义的数据类型时,不涉及本小节的修改;当用户要新增枚举或结构体类型时,需在上述文件中定义。

新增NV描述项

  • NV描述项文件路径:以bs21为例,middleware\chips\bs21\nv\nv_config\bs21_nv_default\cfg\acore\app.json,其中bs21_nv_default和“build\config\target_config\bs21\config.py"中的'nv_cfg': 'bs21_nv_default'对应,模式配置为bs21_nv_default。

  • 定义说明如表1所示。

    表 1 NV配置选项说明

    NV配置选项

    说明

    key_id

    NV项的ID。

    key_status

    NV项的状态。

    structure_type

    NV项的数据结构类型。

    attributions

    NV项的属性值。

    value

    NV项的数据。

    NV配置文件示例:

    {
        "common":{
            "module_id": "0x0",
            "host_config": {
                "key_id": "0x1",
                "key_status": "alive",
                "structure_type": "uint8_t",
                "attributions": 1,
                "value": 15
            },
            "sample1": {
                "key_id": "0x2",
                "key_status": "alive",
                "structure_type": "sample_type_t",
                "attributions": 1,
                "value": [1,2,3,4,5]    
            }
        }
    }
    

    在NV的配置中,每个字段的详细描述如下:

    • key_id:

      以十六进制形式给出的NV项ID。key_id必须唯一,不能重复,因此建议用户对key_id的取值进行管理,如:key_id由16bit组成,可规定高8bit为所在模块内的module_id,低8bit在模块内取值,避免不同模块使用NV互相影响。

    • key_status:

      用于标记是否将该项的NV值编到生成的bin文件中。该字段为“alive”,表示NV项生效,当前固件版本正在使用此key;若为其他的字段或空,则不生效。

    • structure_type:

      NV项的数据类型。具体说明请参见“新增kvalue数据类型”中详细描述。

    • attributions:NV项属性值。

      1 、2、4为互斥关系,三选一。

      1:Normal NV(普通NV)。

      2:Permanent NV(不可修改)。

      4:Un-upgrade NV(不随版本升级而修改,未使用到)。

    • value:如果value不是上述通用数据类型,任何结构都必须以列表的形式书写。

      value赋值有如下两种情况:

      1. 列表所有成员全部赋值。

      2. 只对列表前面若干个成员赋值。(末尾未赋值的成员默认赋值为0)。

      注:赋值只支持十进制格式。

新节点1

新增NV项示例

  • 以BS21为例,在middleware\chips\bs21\nv\nv_config\bs21_nv_default\include\common.h文件中,新增自定义结构体。新增自定义数据类型示例如下。

    • 新增结构体类型且结构体内都为基础类型:

      typedef struct {
          int8_t param1;
          int8_t param2;
          int8_t param3;
          int8_t param4;
          int8_t param5;
          uint32_t param6;
          uint32_t param7;
          int32_t param8;
          uint32_t param9;
          uint32_t param10;
          uint32_t param11;
          uint32_t param12;
          uint32_t param13;
          uint32_t param14;
          uint32_t param15;
          uint32_t param16;
          uint32_t param17;
      } sample_type_t;
      
    • 新增结构体类型且结构体内有数组类型:

      typedef struct {
          uint16_t param1; 
          uint16_t param2; 
          uint16_t param3;
          uint16_t param4[2];
      } sample_two;
      
    • 新增枚举类型:

      typedef enum {
          PARAM1,
          PARAM2,
          PARAM3,
          PARAM4
      } sample_three;
      
  • 以bs21为例,在middleware\chips\bs21\nv\nv_config\bs21_nv_default\cfg\acore\app.json文件中,添加新的NV项。

    • 当kvalue预置值类型是基础类型时,添加kvalue预置值如图1所示,可在app.json配置文件直接添加,无需在头文件中新增。

      图 1 添加kvalue预置值为基础类型

      "sample1": {
          "key_id": "0x1",
          "key_status": "alive",
          "structure_type": "uint8_t",
          "attributions": 1,
          "value": 0    
      }
      
    • 当kvalue预置值类型是结构体类型时,添加kvalue预置值如图2所示,该kvalue值要对应头文件中已有的结构体,如果没有需手动添加自定义结构体。

      图 2 添加kvalue预置值为结构体类型

      "sample2": {
          "key_id": "0x2",
          "key_status": "alive",
          "structure_type": "sample_type_t",
          "attributions": 1,
          "value": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]    
      }
      

NV位置调整

功能描述

NV位于Flash上的存储区域,其位置及大小支持自定义调整,相关配置文件如下(以BS21为例):

  • 位置及大小相关宏配置文件:

    middleware\chips\bs21\nv\include\nv_config.h

  • bin生成相关配置文件:

    middleware\chips\bs21\nv\nv_config\nv_target.json

  • 打包相关文件:

    tools\pkg\chip_packet\bs21\packet.py

配置修改

  1. 修改nv_config.h宏定义:

    #define KV_STORE_DATA_SIZE            NV_LENGTH
    #define KV_STORE_START_ADDR           (NV_STATR_ADDR)
    #define KV_STORE_PAGES_ACPU           1
    
    • KV_STORE_DATA_SIZE修改成目标NV的大小,SIZE大小为扇区(0x1000)的整数倍,最小SIZE为2×FLASH_PAGE_SIZE。

    • KV_STORE_START_ADDR修改为目标位置映射的内存空间。

    • KV_STORE_PAGES_ACPU修改A核扇区的数量,目前BS2X NV操作在A核进行,只适配ACPU即可。

  2. 修改nv_target.json生成信息:

    修改flash_size为KV_STORE_DATA_SIZE大小,同时修改page_nums,使其与 KV_STORE_PAGES_ACPU对应。

    {
        "size":{
            "flash_size" : "0x2000",
            "page_size" : "0x1000"
        },
        "cores" :{
            "app" : {
                "page_nums" : 4,
                "page_id_start" : "0x254D"
            }
        },
        "acore_test_nv":{
            "TYPE" :  "nv",
            "CHIP" :  "bs21",
            "CORE" :  "app",
            "KERNEL_BIN" : "acore",
            "COMPONENT" : ["app"]
        }
    }
    
  3. 修改packet.py打包脚本:

    修改打包脚本中的NV bin打包地址。

        nv = os.path.join(SDK_DIR, "interim_binary", "bs21", "bin", "bs21_all_nv.bin")
        nv_bx = nv + "|0x8c5fc000|0x4000|100"
    

    NV bin代码打包格式为nv_bx = nv + "|地址|大小|下载类型",用户完成NV地址和大小的修改后,需要将代码中的地址、大小和nv_config.h里面的地址大小对应,下载类型保持不变。

编译生成NV镜像

目前SDK NV bin会随APP的编译一起更新,修改完NV的参数以及地址信息后,重新编译APP,NV bin会自动更新到(以bs21为例)interim_binary\bs21\bin\bs21_all_nv.bin路径下,并且打包到tools\pkg\fwpkg\bs21\bs21_all.fwpkg路径下,用户使用Burntool下载即可。

NV API指南

功能描述

NV API主要提供以下几种功能:

  • NV项写入:

    保存需要存储的格式化数据,同时可以定义写入数据的三种属性,分别为是否永久存储、是否加密存储和是否不可升级。

  • NV项读取:

    从本地存储器读取NV数据。

  • NV信息查询:

    • 查询NV是否已存储于本地存储器中。

    • 查询NV空间的使用状态。

NV项的写入接口可设置NV项的属性,对于通过API动态添加的NV项,可在其写入的接口中传入其所拥有的特殊属性。

接口说明

NV模块主要提供以下API:

uapi_nv_init

初始化NV模块,包括nv区域map,必须在使用nv函数之前调用。

NULL

NULL

uapi_nv_write

写入NV数据项,默认属性Normal,无回调函数。

key

要写入的NV项的key ID,用于索引。

*kvalue

指向要写入的NV项的值的指针。

kvalue_length

写入数据的长度,单位:Byte。

uapi_nv_write_with_attr

写入NV数据项,并根据业务需求配置属性及回调函数。

key

要写入的NV项的key ID,用于索引。

*kvalue

指向要写入的NV项的值的指针。

kvalue_length

写入数据的长度,单位:Byte。

*attr

要配置的NV项的属性。

func

kvalue写入Flash后调用的回调函数。

uapi_nv_read

读取指定NV数据项的值,默认不获取key的属性值。

key

要读取的NV项的key ID,用于索引。

kvalue_max_length

允许存储数据的最大长度,单位:Byte。

*kvalue_length

实际读取到的数据长度(读取以四字节对齐)。

*kvalue

指向保存读取数据的buffer的指针。

uapi_nv_read_with_attr

读取指定NV数据项的值,同时获取key的属性值

key

要读取的NV项的key ID,用于索引。

kvalue_max_length

允许存储数据的最大长度,单位:Byte。

*kvalue_length

实际读取到的数据长度(读取以四字节对齐)。

*kvalue

指向保存读取数据的buffer的指针。

*attr

获取到的NV项的属性。

uapi_nv_get_store_status

获取NV存储空间使用情况。

*status

指向保存NV状态数据的指针。

开发指引

以下步骤为NV读写操作步骤指引:

  1. 写入默认Normal类型NV。

    uint8_t *test_nv_value; /* 要写入的NV value保存在test_nv_value中 */
    uint32_t test_len = 15; /* 长度为test_len ,例中为15*/
    
    uint16_t key = TEST_KEY; /* TEST_KEY 为该Key的ID*/
    uint16_t key_len= test_len;
    uint8_t *write_value = uapi_malloc(key_len);
    (void)memcpy_s(write_value, key_len, test_nv_value, key_len);
    errcode_t nv_ret_value = uapi_nv_write(key, write_value, key_len);
    if (nv_ret_value != ERRCODE_SUCC) {
        /* ERROR PROCESS */
        uapi_free(wrt_value);
        return ERRCODE_FAIL;
    }
    /* APP PROCESS */
    uapi_free(wrt_value);
    return ERRCODE_SUCC;
    
  2. 写入带属性NV(配置加密属性,其他略)。

    uint8_t *test_nv_value; /* 要写入的NV value保存在test_nv_value中 */
    uint32_t test_len = 15; /* 长度为test_len,例中为15 */
    
    uint16_t key = TEST_KEY;
    uint16_t key_len= test_len;
    
    nv_key_attr_t attr = {0};
    attr.permanent = false;
    attr.encrypted = true; /* 加密属性设为true */
    attr.non_upgrade = false
    uint8_t *write_value= uapi_malloc(key_len);
    (void)memcpy_s(write_value, key_len, test_nv_value, key_len);
    errcode_t nv_ret_value = uapi_nv_write_with_attr(key, write_value, key_len, &attr, NULL);
    if (nv_ret_value != ERRCODE_SUCC) {
        /* ERROR PROCESS */
        uapi_free(write_value);
        return ERRCODE_FAIL;
    }
    /* APP PROCESS */
    uapi_free(write_value);
    return ERRCODE_SUCC;
    
  3. 读取NV。

    uint16_t key = TEST_KEY;
    uint16_t key_len= test_len;
    uint16_t real_len= 0;
    uint8_t *read_value = uapi_malloc(key_len);
    if (uapi_nv_read(key, key_len, &real_len, read_value) != ERRCODE_SUCC) {
        /* ERROR PROCESS */
        uapi_free(read_value);
        return ERRCODE_FAIL;
    }
    /* APP PROCESS */
    uapi_free(read_value);
    return ERRCODE_SUCC;
    
  4. 读取NV及属性。

    uint16_t key = TEST_KEY;
    uint16_t key_len = test_len;
    uint16_t real_len = 0;
    uint8_t *read_value = uapi_malloc(key_len);
    nv_key_attr_t attr = {false, false, false, 0};
    ext_errno nv_ret = uapi_nv_read_with_attr(key, key_len, &real_len, read_value, &attr);
    if (nv_ret != ERRCODE_SUCC ) {
        /* ERROR PROCESS */
        uapi_free(read_value);
        return ERRCODE_FAIL;
    } 
    /* APP PROCESS */
    uapi_free(read_value);
    return ERRCODE_SUCC;
    
  5. 查询NV空间状态。

    nv_store_status status;
    if (uapi_nv_get_store_status(&status) == ERRCODE_SUCC) {
        /* APP PROCESS */
        printf("Total:      %d Bytes\r\n", status.total_space);
        printf("used:       %d Bytes\r\n", status.used_space);
        printf("reclaimable:%d Bytes\r\n", status.reclaimable_space);
        printf("corrupted:  %d Bytes\r\n", status.corrupted_space);
        printf("max_key:    %d Bytes\r\n", status.max_key_space);
    }
    

说明: 开发指引只是API接口的测试用例,为用户提供简单的sample参考,sample中省略了宏、部分变量、回调函数的定义过程和业务处理过程。

注意事项

  • uapi_nv_write:默认不对所存储的key添加额外属性(是否永久存储、是否加密存储等)。

  • uapi_nv_write_with_attr:可同时配置key属性和注册回调函数。目前NV代码中没有使用到回调函数,传NULL忽略即可。

  • NV属性结构体和NV空间状态结构体说明详见nv.h文件。

  • NV write和read接口使用到信号量同步获取,禁止在中断回调中使用。

定制化NV结构使用介绍

bth_ble_nv_reserved_struct_t

表 1 bth_ble_nv_reserved_struct_t结构体基本信息

key_id

0xE

结构体名称

bth_ble_nv_reserved_struct_t

长度

128字节(每个0是一个字节,默认值为十进制数)

表 2 字节使用记录

字节偏移

字节描述

功能描述

0

customize_flag

定制化使能flag,每个bit对应一个定制化开关。

1

customize_flag

2

gfsk power

GFSK(BLE和GLE帧1)调制类型的功率定制化。

3

psk power

GLE PSK调制类型的功率定制化。

4

em_customized_data_tx_size低8位

星闪低时延传输时业务需要的最大发送包长。

5

em_customized_data_tx_size高8位

星闪低时延传输时业务需要的最大发送包长。

6

max_nb_active_link

星闪低时延传输时业务需要的最大链接数量。

7

fem switch

RT201 fem管脚适配开关。

8

ctrim_flag

XO ctrim电容值写过flash标志位。

9

ctrim_value

XO ctrim电容值。

10

em_customized_data_rx_size低8位

rx em buffer大小的低8位。

11

em_customized_data_rx_size高8位

rx em buffer大小的高8位。

12

em_customized_acl_txbuff_nb

定制化ACL EM DATA TXBUFF个数。

13

em_customized_acl_data_size

定制化ACL EM DATA大小。

14

g_max_temp

记录芯片最高温度和最低温度。芯片最高温度初始设置为-40,最低温度初始设置为125。在nv中保存使用的数据类型和实际使用中不同,-40经过转换为216。因此在nv中最高温度初始值实际配置为216。

15

g_min_temp

通过NV自定义传输数据最大长度

NV根据字节使用记录表可以设置需要发送的数据长度,修改如图1所示。

图 1 NV自定义传输数据长度60字节示例

需要注意的是

  • 对于low latency模式,不同回报率下支持发送的最大数据长度的能力不同,8K回报率最多支持用户发送5Byte,4K最多支持用户发送16Byte,2K最多支持用户发送36Byte,1K最多支持用户发送250Byte。

  • 对于非low latency模式,最多支持用户发送255Byte。

  • em_customized_data_tx_size和em_customized_data_rx_size根据需要设置为传输数据的最大长度。

  • max_nb_active_link需设置为非0,最大值为8。