配置与参数存储 (app_config / VM)
本文档介绍 AW30N BLE SDK 中的两大基础机制:app_config 配置体系(编译期宏配置 app_config.h + 运行期全局配置变量 app_config.c)与 VM 参数存储系统(旧 vm 接口与新 new_vm 接口),说明它们如何协作,为整个 SDK 提供"板级参数定义"与"掉电保存的用户参数存储"能力。
Purpose and Scope
本页覆盖以下内容:
- app_config 配置体系:
sdk/apps/app/src/mbox_flash/app_config.h中成百上千个TCFG_*/KEY_*等编译期宏,以及app_config.c中定义的运行期全局配置变量(中断优先级、日志开关、升级使能等)。 - VM 参数存储:包括旧 VM 接口(
vm.h/vm_api.c,通过vm_buff_alloc向 VM 库提供索引缓冲)与新 VM 接口(new_vm.h/nvm_api.c,由预编译库new_vm_lib.a实现),覆盖其缓存结构、双备份区、磨损均衡与掉电保护机制。
本页不展开的内容(属于其它目录页):
- 具体蓝牙业务的配置变体,如
hid_app_config.h、spple_app_config.h、test_app_config.h,它们只是本页所述配置体系在各自业务模块上的裁剪实例。 - OTA 升级、测试盒通信等具体业务实现(仅在本页提及
CONFIG_UPDATE_*使能开关)。 - 补丁包中针对特定版本的修复细节(如 v1.3.3→v1.3.4 的 new_vm 格式整理修复),仅在本页"故障模式"中作为已知问题引用。
Overview
为什么需要 app_config
AW30N 是一颗低功耗蓝牙 SoC,同一份 SDK 需要适配多种产品形态(不同时钟、按键矩阵、充电方案、音频配置)。app_config.h 将这些可变项收敛为编译期宏:通过修改宏即可裁剪模块、调整 IO 与参数,而无需改动各模块源码。app_config.c 则存放运行期只读的全局配置变量(中断优先级、日志标签开关、升级功能使能等),供 SDK 各库在运行时查询。
为什么需要 VM
VM(Virtual Memory,虚拟内存)是 JieLi SDK 在 SPI NOR Flash 上实现的参数存储系统,用于保存掉电后仍需保留的数据:蓝牙配对信息、音量、EQ、用户自定义参数等。与直接读写 Flash 相比,VM 提供:
- 按 ID 寻址:应用通过
id读写参数,无需关心物理地址。 - 磨损均衡:参数频繁改写时分散到不同物理块,延长 Flash 寿命。
- 双备份区与掉电保护:数据区分为 A/B 两块,配合 CRC 与格式整理(
format_another),降低掉电导致的数据损坏概率。 - 缓存加速:通过
NVM_CACHE在 RAM 中维护 ID→偏移映射,避免每次读写都扫描 Flash。
Architecture
flowchart TD
subgraph sg_Config["配置层 (app_config)"]
H["app_config.h<br/>TCFG_* 编译期宏"]
C["app_config.c<br/>const 全局配置变量"]
end
subgraph sg_App["应用层"]
APP["业务模块<br/>配对 / 音量 / 自定义参数"]
end
subgraph sg_VM["参数存储层 (VM)"]
OLD["旧 VM 接口<br/>vm.h + vm_api.c (vm_buff_alloc)"]
NEW["新 VM 接口<br/>new_vm.h + nvm_api.c (nvm_*_api)"]
CACHE["NVM_CACHE / NVM_ENTRY<br/>RAM 索引缓存"]
end
subgraph sg_Lib["预编译库"]
LIB["new_vm_lib.a<br/>nvm_init / nvm_read / nvm_write ..."]
end
subgraph sg_Flash["硬件层"]
SFC["SFC 控制器 (vm_sfc.h)"]
FLASH[("SPI NOR Flash<br/>双备份区 A/B")]
end
H -->|"裁剪/定义"| C
C -->|"提供常量"| APP
APP -->|"nvm_read_api / nvm_write_api"| NEW
APP -->|"vm_read / vm_write"| OLD
NEW --> CACHE
NEW --> LIB
OLD --> LIB
LIB --> SFC
SFC --> FLASH
架构说明:
- 配置层:
app_config.h的宏在编译期决定模块是否编译、参数默认值;app_config.c的const变量在运行期被 SDK 库读取。两者共同构成"板级配置"的唯一入口。 - 参数存储层:新 VM(
new_vm)是当前推荐的参数存储方案,提供nvm_*_api应用接口;旧 VM(vm)为兼容接口,vm_api.c通过vm_buff_alloc为 VM 库分配索引缓冲(含 CRC 空间)。两者底层都经由预编译库new_vm_lib.a访问 SFC 控制器,最终读写 SPI NOR Flash。 - 缓存:
NEW_VM_OBJ内嵌NVM_CACHE,保存 ID→偏移的映射,避免每次读写都全盘扫描。
核心数据结构
new_vm.h 定义了 VM 的核心数据结构,是整个参数存储系统的基础:
typedef struct __nvm_entry {
u16 id;
u16 rw_cnt;
u32 offset;
} NVM_ENTRY;
typedef struct __nvm_cache {
u16 rw_cnt;
u16 number_entry;
NVM_ENTRY *entries;
} NVM_CACHE;
Source: new_vm.h
NVM_ENTRY:缓存表项,id为参数 ID,rw_cnt为该 ID 的读写计数(用于磨损均衡/整理判断),offset为参数在当前块中的偏移。NVM_CACHE:缓存整体,rw_cnt为总读写计数,number_entry为表项个数,entries指向NVM_ENTRY数组。
NEW_VM_OBJ 是 VM 运行期对象,通过位域压缩存储关键状态:
typedef struct __new_vm_obj {
void *device; // 底层存储设备句柄
NVM_CACHE *cache; // RAM 缓存
u32 addr; // VM 区域起始地址
u32 reserve : 7; // 保留位
u32 bool_block : 1; // 当前使用块标记
u32 block_size : 24; // 块大小(bitfield 24 位)
u32 area_len; // VM 区域总长度
u32 w_offset; // 当前写偏移
u16 pre_sec_a; // 预擦除区 A
u16 pre_sec_b; // 预擦除区 B
u16 id; // 最近操作 ID
u16 offset; // 最近操作偏移
} NEW_VM_OBJ;
Source: new_vm.h
设计意图:VM 区域被划分为两块(由 bool_block 标记当前使用块),pre_sec_a / pre_sec_b 记录两块的"预擦除"状态,w_offset 追踪当前写位置。这种双块设计配合 nvm_format_another 实现磨损均衡与掉电安全(详见下文核心流程)。
app_config 配置体系详解
编译期宏配置 (app_config.h)
app_config.h 是 SDK 顶层板级配置头文件,位于 sdk/apps/app/src/mbox_flash/app_config.h。它通过大量 #define 提供两类开关:
- 模块使能开关:
ENABLE_THIS_MOUDLE(1) /DISABLE_THIS_MOUDLE(0),以及通用ENABLE/DISABLE;NO_CONFIG_PORT定义为(-1),表示"无对应端口"的占位值。 - 参数宏:以
TCFG_前缀命名的具体配置项。
#define ENABLE_THIS_MOUDLE 1
#define DISABLE_THIS_MOUDLE 0
#define ENABLE 1
#define DISABLE 0
#define NO_CONFIG_PORT (-1)
/*---------系统时钟配置---------------------*/
#define TCFG_SYS_PLL_CLK 96000000
#define TCFG_PLL_SEL PLL_D1p0_192M
#define TCFG_PLL_DIV PLL_DIV2
#define TCFG_HSB_DIV HSB_DIV1
Source: app_config.h
配置项按功能分区组织,典型的区段包括:
| 区段 | 关键宏示例 | 作用 |
|---|---|---|
| 系统时钟 | TCFG_SYS_PLL_CLK=96000000、TCFG_PLL_SEL=PLL_D1p0_192M、TCFG_PLL_DIV=PLL_DIV2、TCFG_HSB_DIV=HSB_DIV1 | 配置 PLL 频率与分频,决定 CPU 主频 |
| Cache | CPU_USE_CACHE_WAY_NUMBER=4 | 配置 Cache 路数(2~4),影响取指/数据缓存容量 |
| UART | TCFG_UART_TX_PORT=IO_PORTA_05、TCFG_UART_BAUDRATE=1000000 | 串口打印引脚与波特率(默认 1Mbps) |
| 按键 | KEY_DOUBLE_CLICK_EN、KEY_IO_EN=0、KEY_AD_EN=1、AD_KEY_IO=IO_PORTA_08 等 | 使能按键类型并指定引脚 |
| 低功耗 | TCFG_CONFIG_LOWPOWER_SEL=ENABLE | 低功耗功能总开关 |
| 软关机 | TCFG_SHUTDOWN_TIME=5000 | 软关机判定时间(单位 ms) |
| 充电 | TCFG_CHARGE_ENABLE=0、TCFG_CHARGE_FULL_V=CHARGE_FULL_V_4199、TCFG_CHARGE_MA=CHARGE_mA_90 | 充电使能与满电电压/充电电流挡位 |
| 音频 | SR_DEFAULT=32000、AMM_RS_INSIDE_ENABLE、TCFG_AUDIO_AUTO_MUTE_ENABLE=0 | 默认采样率、MIC 选择、自动 mute |
| DAC 音量 | TCFG_DAC_VOL_FADE_EN、TCFG_DAC_VOL_FADE_STEP、TCFG_DAC_VOL_FADE_SLOW=15 | 音量淡入淡出开关与步进 |
| EQ 调音 | TCFG_CFG_TOOL_ENABLE=DISABLE、TCFG_COMM_TYPE=TCFG_USB_COMM | 在线调音工具使能与通信方式(USB) |
| RF PA | RF_PA_EN=DISABLE、RF_PA_POWER_SUPPLY=DISABLE | 外置 PA 使能与供电控制 |
设计意图:所有可变硬件参数集中在一个头文件,使同一 SDK 可以仅通过改宏适配不同硬件方案;同时宏在编译期求值,不产生运行时开销,便于编译器优化裁剪。
运行期全局配置变量 (app_config.c)
app_config.c 定义 SDK 各库在运行期读取的 const 全局变量,典型内容:
- 中断优先级表:为系统各外设/蓝牙中断分配固定优先级(数值越小优先级越高,
IRQ_DECODER_IP=0最高):
//系统使用到的
const int IRQ_IRTMR_IP = 6;
const int IRQ_AUDIO_IP = 5;
const int IRQ_DECODER_IP = 0;
const int IRQ_WFILE_IP = 2;
const int IRQ_RF_QUEUE_IP = 2;
const int IRQ_ADC_IP = 3;
const int IRQ_ENCODER_IP = 1;
const int IRQ_TICKTMR_IP = 3;
const int IRQ_USB_IP = 4;
const int IRQ_SD_IP = 3;
const int IRQ_CTMU_IP = 2;
const int IRQ_STREAM_IP = 4;
const int IRQ_SPEAKER_IP = 1;
const int IRQ_LEDC_IP = 1;
const int IRQ_SLCD_IP = 2;
const int IRQ_PMU_TMR1_IP = 3;
const int IRQ_ALINK_IP = 3;
//BT
const int IRQ_BT_TIMEBASE_IP = 6; //BT TIMEBASE
const int IRQ_BLE_EVENT_IP = 6; //BT RX_EVT
const int IRQ_BLE_RX_IP = 6; //BT RX
const int IRQ_BTSTACK_MSG_IP = 3; //BT STACK
Source: app_config.c
- 系统行为开关:内存断言、内核异常打印、SFC 代码缓存、音频 ADC、RTC 等:
//内存管理malloc内部断言
const char MM_ASSERT = TRUE;
//内核异常打印
const u8 config_asser = 1;
const u8 config_spi_code_user_cache = 1;//sfc放code区
const u8 config_audio_adc_enable = 1;
#if RTC_EN
const int config_rtc_enable = 1;
#else
const int config_rtc_enable = 0;
#endif
Source: app_config.c
- 升级/产测功能使能:
CONFIG_UPDATE_STORAGE_DEV_EN(存储设备升级)、CONFIG_UPDATE_TESTBOX_UART_EN(测试盒 UART)、CONFIG_UPDATE_APP_OTA_EN(手机 App OTA)、CONFIG_UPDATE_TESTBOX_BLE_EN(测试盒 BLE):
////////////////////////////update control///////////////////////////////////////////
const int CONFIG_UPDATE_STORAGE_DEV_EN = 1;
const int CONFIG_UPDATE_TESTBOX_UART_EN = 1;
const int CONFIG_UPDATE_APP_OTA_EN = 1;
const int CONFIG_UPDATE_TESTBOX_BLE_EN = 1;
/////////////////////////////////////////////////////////////////////////////////////
Source: app_config.c
- 日志总开关与标签:
libs_debug配合CONFIG_DEBUG_LIBS(X)宏控制各日志标签(如MAIN、KEYM、PMU、WKUP、RTC、P33、LRC)的 i/d/e/c 级打印是否生效,存放在.LOG_TAG_CONST段以便链接器统一管理:
const char libs_debug AT(.LOG_TAG_CONST) = TRUE; //打印总开关
#define CONFIG_DEBUG_LIBS(X) (X & libs_debug)
const char log_tag_const_i_MAIN AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIBS(1);
const char log_tag_const_d_MAIN AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIBS(1);
const char log_tag_const_e_MAIN AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIBS(1);
Source: app_config.c
设计意图:这些变量以 const 存储于 Flash(而非 RAM),避免占用稀缺的 RAM;运行时只需读取常量即可获得全部板级信息。中断优先级集中定义则保证了系统中断(如音频、USB、RF)与用户中断之间的确定性抢占关系。
各业务模块的配置变体
SDK 在每个业务模块目录下提供独立的 app_config 变体头文件,例如:
sdk/apps/app/bsp/common/bt_common/hid/include/hid_app_config.h— HID 业务配置sdk/apps/app/bsp/common/bt_common/spp_and_le/include/spple_app_config.h— SPP/LE 业务配置sdk/apps/app/bsp/common/bt_common/ble_test/test_app_config.h— BLE 测试配置
它们遵循与本页相同的"宏裁剪"模式,但只涉及各自业务参数,细节请参见对应业务功能页。
VM 参数存储系统详解
旧 VM 接口 (vm.h / vm_api.c)
旧 VM 接口由头文件 sdk/apps/include_lib/common/vm.h 声明(定义 VM_INDEX_BUFF 等索引缓冲类型),sdk/apps/app/bsp/common/vm/vm_api.c 负责为 VM 库提供内存缓冲回调:
#define LABEL_INDEX_LEN_CRC_SIZE (4)
static u8 vm_buff[sizeof(VM_INDEX_BUFF) + LABEL_INDEX_LEN_CRC_SIZE];
u16 vm_buff_alloc(u8 **buf)
{
if (buf == NULL) {
return 0;
}
*buf = vm_buff;
return sizeof(vm_buff);
}
Source: vm_api.c
设计意图:VM 库需要在 RAM 中维护一份"索引缓冲"(VM_INDEX_BUFF),用于记录 Flash 上各参数的位置。vm_buff_alloc 是库回调(nvm_buf_for_lib 的同源模式),由应用层静态分配缓冲区以避免运行时 malloc;缓冲区末尾额外预留 LABEL_INDEX_LEN_CRC_SIZE(4 字节)存放 CRC,用于校验索引数据的完整性。
新 VM 接口 (new_vm.h / nvm_api.c)
新 VM 是当前推荐方案,接口全部声明在 sdk/apps/include_lib/common/new_vm.h,实现位于预编译库 new_vm_lib.a(源码路径 sdk/apps/include_lib/liba/bd49/mbox_flash/new_vm_lib.a),应用层适配在 sdk/apps/app/bsp/common/vm/nvm_api.c。
新 VM 的设计参数:
#define BIT_MAP (32 * 16) // 512 bit 位图
#define BIT_MAP_SIZE (BIT_MAP / 8) // 64 字节
#define NVM_MAX_LEN 128 // 单条参数最大长度(字节)
#define NVM_BUFF_SIZE (NVM_MAX_LEN + BIT_MAP_SIZE) // 192 字节
Source: new_vm.h
- 每条参数最大 128 字节(
NVM_MAX_LEN);块内以位图(BIT_MAP)记录每个槽位的占用/有效状态,位图本身占 64 字节,与单条参数合计为 192 字节的内部缓冲。 NVM_MULTIPLE_READ宏默认被注释,即默认关闭多读路径,NEW_VM_OBJ中的id/offset字段在关闭时仍保留(由// #ifdef注释可见),便于调试。
接口分三层:
库接口(由 new_vm_lib.a 实现,应用通常不直接调用):
u32 nvm_init(NEW_VM_OBJ *p_nvm, u32 addr, u32 size);
u32 nvm_format_another(NEW_VM_OBJ *p_nvm);
u32 nvm_read(NEW_VM_OBJ *p_nvm, u32 id, u8 *buf, u32 len);
u32 nvm_write(NEW_VM_OBJ *p_nvm, u32 id, u8 *buf, u32 len);
void nvm_pre_erasure_next(NEW_VM_OBJ *p_nvm, u16 using_next, u16 idle_next);
u32 nvm_format_another_ignore(NEW_VM_OBJ *p_nvm, u32 *ignore_map, u32 ignore_bits);
u32 nvm_get_half_addr(NEW_VM_OBJ *p_nvm);
u32 nvm_get_half_len(NEW_VM_OBJ *p_nvm);
u32 nvm_get_cur_date_len(NEW_VM_OBJ *p_nvm);
Source: new_vm.h
库回调(由应用层实现,库在需要缓冲时调用):
void *nvm_buf_for_lib(NEW_VM_OBJ *p_nvm, u32 *p_len);
Source: new_vm.h
应用接口(最常用,由 nvm_api.c 适配层封装):
u32 nvm_init_api(u32 addr, u32 size);
u32 nvm_format_anotheri_api(void);
u32 nvm_read_api(u32 id, u8 *buf, u32 len);
u32 nvm_write_api(u32 id, u8 *buf, u32 len);
void nvm_erasure_next_api(void);
void nvm_format_another_ignore_api(u32 *delete_map, u32 delete_bits);
Source: new_vm.h
缓存辅助接口(供上层维护 ID→偏移映射):
u32 nvm_cache_cnt(NVM_ENTRY *entries, u32 len);
u32 nvm_write_cache(NVM_CACHE *cache, u16 id, u32 offset);
u32 nvm_read_cache(NVM_CACHE *cache, u16 id);
u32 nvm_clear_cache(NVM_CACHE *cache);
Source: new_vm.h
设计意图:接口分层使预编译库与工程代码解耦——库只依赖 NEW_VM_OBJ 结构与 nvm_buf_for_lib 回调,内存由应用层供给;_api 后缀的适配函数隐藏了对象实例管理细节,业务代码只需"初始化一次,之后按 ID 读写"。
参数写入与磨损均衡流程
flowchart TD
Start([应用调用 nvm_write_api]) --> Lookup["查 NVM_CACHE<br/>找到/分配 NVM_ENTRY"]
Lookup --> Space{"当前块<br/>剩余空间足够?"}
Space -->|"是"| Write["在当前块 w_offset 处写入数据<br/>更新位图与 cache"]
Space -->|"否"| Format["nvm_format_another<br/>把有效数据整理到另一块"]
Format --> Erase["nvm_pre_erasure_next<br/>预擦除空闲块"]
Erase --> Write2["写入新块<br/>切换 bool_block"]
Write --> Check{"该 ID 旧记录<br/>需要清理?"}
Write2 --> Check
Check -->|"是"| Mark["标记旧记录失效(位图置 0)"]
Check -->|"否"| Done
Mark --> Done([返回写入结果])
- 按 ID 追加写:VM 采用"追加写 + 位图标记"策略,每次写不覆盖旧位置,而是在当前块末尾追加新记录并更新 cache,旧记录通过位图标记为失效。这避免了 Flash 的擦-写-擦循环,是磨损均衡的基础。
- 块切换:当当前块写满时,
nvm_format_another将仍有效的记录(根据 cache 与位图)整理到另一块,随后nvm_pre_erasure_next预擦除旧块,bool_block翻转表示当前使用块切换。若某些 ID 需要被删除,可调用nvm_format_another_ignore并传入ignore_map/ignore_bits,在整理时直接丢弃这些 ID。 - 掉电安全:由于两块中始终有一块是完整可用的,即使整理中途掉电,下次
nvm_init也能基于位图/CRC 恢复到一致状态。这也是仓库中"new_vm 格式整理失败导致概率数据错误"补丁(v1.3.3→v1.3.4)所针对的关键路径——整理过程本身的原子性直接影响数据可靠性。
读写时序
sequenceDiagram
participant App as 业务模块
participant Api as nvm_api.c (应用接口)
participant Lib as new_vm_lib.a
participant Cache as NVM_CACHE (RAM)
participant SFC as SFC 控制器
participant F as SPI NOR Flash (A/B 区)
App->>Api: nvm_write_api(id, buf, len)
Api->>Lib: nvm_write(p_nvm, id, buf, len)
Lib->>Cache: nvm_read_cache 查映射 / 新建 NVM_ENTRY
Lib->>SFC: 在当前块 w_offset 追加写数据(含位图更新)
SFC->>F: 页编程
F-->>SFC: 写完成
SFC-->>Lib: 成功
Lib->>Cache: nvm_write_cache 更新 id→offset
Lib-->>Api: 返回结果
Api-->>App: 返回结果
App->>Api: nvm_read_api(id, buf, len)
Api->>Lib: nvm_read(p_nvm, id, buf, len)
Lib->>Cache: nvm_read_cache 查 id→offset
Cache-->>Lib: offset
Lib->>SFC: 读取该偏移数据
SFC->>F: 页读
F-->>SFC: 数据
SFC-->>Lib: 数据
Lib-->>Api: 返回结果
Api-->>App: 数据写入 buf
配置选项
app_config 关键配置项
| 配置宏 | 类型/取值 | 默认值 | 说明 |
|---|---|---|---|
TCFG_SYS_PLL_CLK | 数值(Hz) | 96000000 | 系统 PLL 输出频率 |
TCFG_PLL_SEL | 枚举 | PLL_D1p0_192M | PLL 档位选择 |
TCFG_PLL_DIV | 枚举 | PLL_DIV2 | PLL 分频(决定 CPU 主频) |
TCFG_HSB_DIV | 枚举 | HSB_DIV1 | HSB 总线分频 |
CPU_USE_CACHE_WAY_NUMBER | 数值 | 4 | Cache 路数,范围 2~4 |
TCFG_UART_TX_PORT | IO 枚举 | IO_PORTA_05 | 串口打印 TX 引脚 |
TCFG_UART_BAUDRATE | 数值(bps) | 1000000 | 串口打印波特率 |
KEY_DOUBLE_CLICK_EN | 0/1 | DISABLE | 按键多击使能 |
KEY_IO_EN / KEY_AD_EN | 0/1 | 0 / 1 | IO 键 / AD 键使能 |
AD_KEY_IO | IO 枚举 | IO_PORTA_08 | AD 键采样引脚 |
TCFG_CONFIG_LOWPOWER_SEL | 0/1 | ENABLE | 低功耗功能开关 |
TCFG_SHUTDOWN_TIME | 数值(ms) | 5000 | 软关机判定时间 |
TCFG_CHARGE_ENABLE | 0/1 | 0 | 充电功能使能 |
TCFG_CHARGE_FULL_V | 枚举 | CHARGE_FULL_V_4199 | 满电电压挡位 |
TCFG_CHARGE_MA | 枚举 | CHARGE_mA_90 | 充电电流挡位 |
SR_DEFAULT | 数值(Hz) | 32000 | 默认音频采样率 |
TCFG_DAC_VOL_FADE_EN | 0/1 | DISABLE | 硬件 DAC 音量淡入淡出 |
TCFG_DAC_VOL_FADE_STEP | 数值(0~15) | 1 | 淡入淡出步进 |
TCFG_DAC_VOL_FADE_SLOW | 数值(0~15) | 15 | 淡入淡出慢速系数 |
TCFG_CFG_TOOL_ENABLE | 0/1 | DISABLE | 在线调音工具使能 |
TCFG_COMM_TYPE | 枚举 | TCFG_USB_COMM | 调音通信方式(仅 USB) |
RF_PA_EN | 0/1 | DISABLE | 外置 RF PA 使能 |
来源:app_config.h(配置宏均为编译期常量,修改后需重新编译)。
运行期全局配置变量
| 变量 | 类型 | 默认值 | 说明 |
|---|---|---|---|
IRQ_IRTMR_IP / IRQ_AUDIO_IP / IRQ_DECODER_IP 等 | const int | 0~6 | 各中断优先级(0 最高) |
IRQ_BT_TIMEBASE_IP / IRQ_BLE_EVENT_IP / IRQ_BLE_RX_IP | const int | 6 | 蓝牙相关中断优先级 |
MM_ASSERT | const char | TRUE | malloc 内部断言开关 |
config_asser | const u8 | 1 | 内核异常打印开关 |
config_spi_code_user_cache | const u8 | 1 | SFC 是否放 code 区 |
config_audio_adc_enable | const u8 | 1 | 音频 ADC 使能 |
config_rtc_enable | const int | 随 RTC_EN | RTC 使能(宏条件编译) |
CONFIG_UPDATE_STORAGE_DEV_EN | const int | 1 | 存储设备升级使能 |
CONFIG_UPDATE_TESTBOX_UART_EN | const int | 1 | 测试盒 UART 升级使能 |
CONFIG_UPDATE_APP_OTA_EN | const int | 1 | App OTA 升级使能 |
CONFIG_UPDATE_TESTBOX_BLE_EN | const int | 1 | 测试盒 BLE 升级使能 |
libs_debug | const char | TRUE | 日志打印总开关 |
来源:app_config.c。
VM 存储参数
| 参数 | 值 | 说明 |
|---|---|---|
NVM_MAX_LEN | 128 字节 | 单条参数最大长度 |
BIT_MAP | 512 bit | 块内槽位占用位图 |
BIT_MAP_SIZE | 64 字节 | 位图占用的存储大小 |
NVM_BUFF_SIZE | 192 字节 | 内部缓冲(参数+位图) |
LABEL_INDEX_LEN_CRC_SIZE | 4 字节 | 旧 VM 索引缓冲 CRC 长度 |
| 双备份区 | A/B 两块 | 由 bool_block 标记当前使用块,nvm_format_another 切换 |
API 参考
新 VM 库接口 (new_vm.h)
u32 nvm_init(NEW_VM_OBJ *p_nvm, u32 addr, u32 size)
初始化 VM 对象,绑定 Flash 起始地址 addr 与区域长度 size;首次使用或检测到数据不一致时自动完成格式整理。
参数:
p_nvm(NEW_VM_OBJ*):VM 对象指针,内存由调用方提供。addr(u32):VM 区域在 Flash 中的起始地址。size(u32):VM 区域大小(应为两块的总长)。
返回: 0 表示成功,非 0 表示初始化/整理出错。
u32 nvm_read(NEW_VM_OBJ *p_nvm, u32 id, u8 *buf, u32 len)
按 ID 读取参数到 buf。
参数: p_nvm 为对象指针;id 为参数 ID;buf 为输出缓冲区;len 为期望读取长度(≤ NVM_MAX_LEN)。
返回: 读取的字节数;未找到返回 0(或错误码)。
u32 nvm_write(NEW_VM_OBJ *p_nvm, u32 id, u8 *buf, u32 len)
按 ID 写入参数,内部执行"追加写 + 位图更新 + cache 更新",必要时触发 format_another 块切换。
返回: 写入的字节数;失败返回 0。
u32 nvm_format_another(NEW_VM_OBJ *p_nvm) / u32 nvm_format_another_ignore(NEW_VM_OBJ *p_nvm, u32 *ignore_map, u32 ignore_bits)
将有效数据整理到另一块并预擦除旧块;_ignore 变体在整理时丢弃 ignore_map 指定的 ID(按位标记,共 ignore_bits 位)。
返回: 0 表示成功。
void nvm_pre_erasure_next(NEW_VM_OBJ *p_nvm, u16 using_next, u16 idle_next)
预擦除下一个空闲块,由库在块切换前后调用,把耗时的擦除操作提前,减少写入时延。
u32 nvm_get_half_addr(NEW_VM_OBJ *p_nvm) / u32 nvm_get_half_len(NEW_VM_OBJ *p_nvm) / u32 nvm_get_cur_date_len(NEW_VM_OBJ *p_nvm)
查询单块地址、单块长度与当前块有效数据长度,用于上层判断空间与统计。
新 VM 应用接口 (nvm_api.c 封装)
| 函数 | 说明 |
|---|---|
u32 nvm_init_api(u32 addr, u32 size) | 初始化全局 VM 实例(内部管理 NEW_VM_OBJ) |
u32 nvm_read_api(u32 id, u8 *buf, u32 len) | 按 ID 读参数 |
u32 nvm_write_api(u32 id, u8 *buf, u32 len) | 按 ID 写参数 |
void nvm_erasure_next_api(void) | 主动触发下一块预擦除 |
void nvm_format_another_ignore_api(u32 *delete_map, u32 delete_bits) | 整理并删除指定 ID |
void *nvm_buf_for_lib(NEW_VM_OBJ *p_nvm, u32 *p_len) | 向库提供 RAM 缓冲(应用层回调实现) |
缓存辅助接口
| 函数 | 说明 |
|---|---|
u32 nvm_cache_cnt(NVM_ENTRY *entries, u32 len) | 统计给定 entry 数组的有效项数 |
u32 nvm_write_cache(NVM_CACHE *cache, u16 id, u32 offset) | 写入/更新 id→offset 映射 |
u32 nvm_read_cache(NVM_CACHE *cache, u16 id) | 查询 id 对应的 offset |
u32 nvm_clear_cache(NVM_CACHE *cache) | 清空整个缓存 |
旧 VM 缓冲回调 (vm_api.c)
u16 vm_buff_alloc(u8 **buf)
为旧 VM 库分配索引缓冲。
参数: buf (u8**):输出参数,接收静态缓冲指针。
返回: 缓冲长度(sizeof(VM_INDEX_BUFF) + 4);buf 为 NULL 时返回 0。
说明: 缓冲为静态数组,无 malloc 开销,末尾 4 字节供 CRC 使用。
故障模式、边界情况与并发
掉电与数据一致性
VM 的双备份区 + 位图 + CRC 设计旨在保证掉电安全,但格式整理(format_another)过程本身是脆弱的:整理需要"读有效记录 → 写入另一块 → 擦除旧块",若中途掉电,两块都可能处于中间状态。仓库补丁目录 补丁包/v1.3.3升级至v1.3.4相关new_vm和uart问题修复补丁 中即包含"解决 new_vm 格式整理失败导致的概率数据错误"的 new_vm_lib.a 替换包,说明该路径曾出现概率性数据错误,升级固件时需同步替换预编译库。
参数长度与数量边界
- 单条参数最大 128 字节(
NVM_MAX_LEN),超长写入会被截断或失败。 - 位图 512 bit,即每块最多可标记 512 个槽位;参数数量与每条占用长度共同决定块容量。
w_offset到达块尾时触发format_another,频繁写满会引入擦除/整理开销,应合理规划参数 ID 与写入频率。
并发与中断上下文
app_config.c 中的配置变量均为 const,运行期只读,无并发问题。VM 读写接口未在头文件中体现互斥保护,意味着调用方应避免在多个任务/中断中并发读写同一 VM 实例(或由 nvm_api.c 适配层加锁);蓝牙中断(如 IRQ_BLE_RX_IP)与主循环同时访问 Flash 时,应确保 SFC 访问串行化,否则可能出现索引与数据不一致。
已知约束
- 旧 VM(
vm.h)与新 VM(new_vm.h)使用不同的缓冲与索引格式,二者不可混用于同一 Flash 区域。 NVM_MULTIPLE_READ默认关闭,NEW_VM_OBJ中id/offset字段仍存在,但多读路径未启用。- VM 区域地址
addr/size必须与链接脚本中的 Flash 分区一致,错误配置会破坏其它分区(如固件区、资源区)。
性能与运维考虑
- RAM 占用小:VM 索引缓冲由静态数组提供(
vm_buff_alloc/nvm_buf_for_lib),避免堆分配;NVM_ENTRY单表项仅 8 字节。 - 读路径快:
NVM_CACHE将 ID→offset 映射常驻 RAM,读操作无需扫描 Flash;nvm_read_cache命中后直接按偏移页读。 - 写路径优化:追加写避免频繁擦除;
nvm_pre_erasure_next将擦除操作提前到空闲时刻,降低写时延抖动。 - 升级维护:
new_vm_lib.a为预编译库,修复(如格式整理问题)通过替换.a文件发布;升级时应核对补丁包版本与 SDK 版本匹配(仓库中存在多个版本补丁目录)。 - 调试:通过
app_config.c的日志总开关(libs_debug)与各模块log_tag_const_*标签可单独开关 VM/SFC 相关打印,便于定位读写异常。
扩展点
- 新增参数 ID:应用层在
nvm_init_api之后即可使用任意u32作为 ID 调用nvm_read_api/nvm_write_api,无需注册表;ID 的分配约定由业务层维护。 - 自定义缓冲:通过实现
nvm_buf_for_lib回调可替换 VM 库的 RAM 缓冲来源(静态数组或专用内存池)。 - 整理策略定制:
nvm_format_another_ignore允许在整理时按位图丢弃指定 ID(例如恢复出厂设置、删除配对信息),无需先读后删。 - 旧/新 VM 并存:
vm_buff_alloc(旧)与nvm_*_api(新)接口可分别服务不同参数集合,但 Flash 分区必须隔离。