存储与配置系统
AC63 系列蓝牙 SoC SDK 的持久化存储与系统配置子系统,以 SPI NOR Flash 上的 VM(Virtual Memory,虚拟存储器) 为核心,为蓝牙协议栈、RTC、EQ、用户自定义配置等提供键值对(index → value)形式的掉电不丢失存储能力,并配套 SFC/norflash 驱动、OTP、写保护、OTA 备份恢复等基础设施。
Purpose and Scope
本页完整介绍 AC63 BT SDK 的存储与配置体系,涵盖:
- VM 虚拟存储器:核心数据结构(
vm_hdl、struct vm_table)、初始化/读写/擦除 API、错误码语义、批量读写、缓存机制与碎片整理; - Flash 访问抽象:SFC 驱动接口(
sfc_read/sfc_write/sfc_erase/sfc_erase_zone)、norflash 设备接口、OTP/UUID 读取、写保护; - 系统配置集成:
syscfg_vm_enable、get_syscfg_vm_ops、cfg_vm、bt_vm_interface等库级接线点; - RTC 持久化:虚拟 RTC 通过 VM 保存时间/闹钟/纳秒计数的接口族(
rtc_save_time_vm等); - 应用层配置项:
custom_cfg.c中定义的配置项表头与联合体; - OTA 相关维护:
vm_backup_for_update、vm_defrag_for_update、vm_need_recover、vm_update_recover。
以下内容属于兄弟页面,不在本页展开:电源管理/低功耗(low_power_*、power_* 接口族)、OTA 升级流程本身(本页仅涉及其调用 VM 备份恢复的部分)、音频/EQ 算法与蓝牙协议栈(本页只说明它们如何通过 VM 存储配对信息)。
说明:VM 的算法实现位于预编译库
cpu/br23/liba/cpu.a、cpu/br25/liba/cpu.a中(符号表可见vm_init、vm_read、vm_write、vm_open、vm_close、vm_status等导出),仓库中可读的权威接口定义见 include_lib/system/device/vm.h。本页对内部算法(扇区分配、碎片整理、备份恢复)的描述以头文件契约与符号导出为准。
Overview
在 AC63 这类资源受限的蓝牙 SoC 上,运行期产生的状态(蓝牙配对地址、音量、EQ 参数、系统时间、用户自定义配置等)必须保存在外部 SPI NOR Flash 中,且要求掉电不丢失、频繁改写时磨损可控、读写速度快、占用 RAM 小。
VM 子系统正是为此设计的索引化 KV 存储:
- 每个配置项用一个
vm_hdl(u16)作为句柄,等价于"索引号"; - 应用通过
vm_read(hdl, buf, len)/vm_write(hdl, buf, len)读写; - 系统在
vm_init(dev_hdl, vm_addr, vm_len, vm_mode)时划定 Flash 上的 VM 分区(地址 + 长度 + 模式); - 长度 ≤ 4 字节的配置项会被缓存到
struct vm_table.value中,读操作直接命中 RAM,避免每次读 Flash; - 提供批量接口
vm_api_read_mult/vm_api_write_mult支持多索引连续操作(如一次保存整组系统配置); - 提供
vm_eraser(整区擦除)、vm_check_all(上电校验)、以及 OTA 专用的备份/恢复/整理接口。
库符号还显示 Flash 子系统包含 norflash_read/write/erase/ioctl、OTP(norflash_get_otp_info、norflash_read_otp、sys_cfg_read_otp)、UUID(norflash_get_uuid、get_norflash_uuid)、写保护(norflash_set_write_protect、norflash_write_protect_config、update_norflash_write_protect、sfc_protect)等能力,与 VM 共同构成完整的"存储与配置"基础设施。
SDK 还随包提供了 VM 兼容性修复补丁(patch_release/AC63系列VM兼容性修复补丁_20250224),说明 VM 数据布局存在跨版本兼容性约束,升级固件时必须谨慎对待 VM 区的格式变化。
Architecture
flowchart TD
subgraph sg_App["应用层 (Apps)"]
AppCfg["custom_cfg 配置项<br/>(cfg_item_head_t / ex_cfg_item_u)"]
RTC["虚拟 RTC<br/>(rtc_save_time_vm 等)"]
BT["蓝牙协议栈<br/>(bt_vm_interface)"]
OTA["OTA 升级<br/>(vm_backup_for_update)"]
end
subgraph sg_VM["VM 虚拟存储器层 (预编译库)"]
VMAPI["vm_init / vm_read / vm_write"]
VMMULT["vm_api_read_mult / vm_api_write_mult"]
VMMNT["vm_eraser / vm_check_all / vm_defrag_for_update"]
VMBACK["vm_backup_for_update / vm_need_recover / vm_update_recover"]
VMSYS["syscfg_vm_enable / get_syscfg_vm_ops / cfg_vm"]
end
subgraph sg_FLASHDRV["Flash 驱动层 (SFC / norflash)"]
SFC["sfc_read / sfc_write / sfc_erase"]
SFCZONE["sfc_erase_zone / sfc_protect"]
NOR["norflash_read / norflash_write / norflash_ioctl"]
OTP["norflash_read_otp / sys_cfg_read_otp / UUID"]
end
FLASH[("SPI NOR Flash<br/>(VM 分区 + 固件区 + OTP)")]
AppCfg --> VMAPI
RTC --> VMAPI
BT --> VMAPI
OTA --> VMBACK
VMSYS --> VMAPI
VMAPI --> SFC
VMMULT --> VMAPI
VMMNT --> SFC
VMBACK --> SFC
SFC --> FLASH
SFCZONE --> FLASH
NOR --> FLASH
OTP --> FLASH
架构分四层:
- 应用层:蓝牙协议栈、RTC、OTA、第三方 profile 的
custom_cfg等模块通过 VM API 读写各自配置。custom_cfg.c定义了可扩展的配置项描述结构(cfg_item_head_t、ex_cfg_item_u)。 - VM 层:预编译库提供 KV 语义。对外暴露
vm_init、vm_read、vm_write、vm_eraser、vm_check_all、vm_api_read_mult、vm_api_write_mult以及 OTA 专用维护接口;syscfg_vm_enable/get_syscfg_vm_ops/cfg_vm表明库内部还有一套"系统配置 VM"的封装(工厂模式:通过 ops 结构体对外提供系统配置操作)。 - Flash 驱动层:
sfc_*是硬件 SPI Flash 控制器(SFC)的直接操作;norflash_*是更上层的设备抽象(含 4bit/2bit 模式、DMA 写、OTP、写保护、UUID)。vm_dma_write/sfc_dma_write符号说明 VM 写入支持 DMA 通道。 - 物理层:SPI NOR Flash 按地址划分固件区、VM 分区、OTP 区;VM 分区由
vm_init的vm_addr/vm_len参数圈定。
VM 核心机制
句柄与配置表结构
VM 使用 16 位句柄 vm_hdl 标识配置项,通过 struct vm_table 描述"索引 → 长度 → 缓存值"的映射:
// VM define and api
typedef u16 vm_hdl;
struct vm_table {
u16 index;
u16 value_byte;
int value; //cache value which value_byte <= 4
};
Source: vm.h
设计意图:
index是配置项的全局唯一索引(即vm_hdl),应用层通过它读写;value_byte声明该项最大长度;value是缓存字段,注释明确"cache value which value_byte <= 4"——长度不超过 4 字节的配置项(音量、开关标志、短状态值等)在写入后直接缓存在 RAM 中,读取时零 Flash 访问。这是对"频繁读、少改动"的配置场景(如音量、EQ 索引)的关键性能优化;vm_hdl为u16意味着索引空间上限 65536,实际受vm_init划定的分区大小约束。
vm.h 中另有两组 IOCTL 常量(IOCTL_SET_VM_INFO / IOCTL_GET_VM_INFO,命令字 'V'),表明 VM 分区信息可通过设备 ioctl 方式查询/设置,与 vm_open/vm_close/vm_status 等设备化接口配合使用(库符号中可见 vm_open、vm_close、vm_status、vm_test)。
VM 生命周期
VM 子系统的生命周期分为四个阶段:
- 分区划定:
vm_init(void *dev_hdl, u32 vm_addr, u32 vm_len, u8 vm_mode)指定 Flash 设备句柄、VM 区起始地址、长度与工作模式。vm_mode用于表达分配策略(如扇区对齐方式、是否启用备份区等),具体取值由库内部定义。 - 上电校验:
vm_check_all(u8 level)在启动阶段扫描 VM 区,校验索引表与数据完整性(level 默认传 0)。配合get_vm_statu()可查询当前 VM 状态。 - 运行期读写:
vm_read/vm_write按句柄访问;批量场景使用vm_api_read_mult/vm_api_write_mult。 - 维护/回收:
vm_eraser()整区擦除(恢复出厂);OTA 场景走vm_backup_for_update→ 升级 →vm_need_recover/vm_update_recover→vm_defrag_for_update的完整流程。
错误码语义
typedef enum _vm_err {
VM_ERR_NONE = 0,
VM_INDEX_ERR = -0x100,
VM_INDEX_EXIST, //0xFF
VM_DATA_LEN_ERR, //0xFE
VM_READ_NO_INDEX, //0xFD
VM_READ_DATA_ERR, //0xFC
VM_WRITE_OVERFLOW, //0xFB
VM_NOT_INIT,
VM_INIT_ALREADY,
VM_DEFRAG_ERR,
VM_ERR_INIT,
VM_ERR_PROTECT
} VM_ERR;
Source: vm.h
错误码从 -0x100 起按位递减(-0x100, -0xFF, -0xFE, ...),相邻错误码只差 1,便于在日志中以短整数形式打印排查。各错误码含义与触发场景:
| 错误码 | 含义 | 典型触发场景 |
|---|---|---|
VM_ERR_NONE | 成功 | 所有正常路径返回 |
VM_INDEX_ERR | 索引非法 | 传入的 vm_hdl 超出分区/表范围 |
VM_INDEX_EXIST | 索引已存在 | 重复创建同一索引项 |
VM_DATA_LEN_ERR | 数据长度非法 | len 与 vm_table.value_byte 不匹配或超限 |
VM_READ_NO_INDEX | 读时索引不存在 | 读取从未写入的配置项 |
VM_READ_DATA_ERR | 读数据校验失败 | Flash 数据损坏、CRC 校验不过 |
VM_WRITE_OVERFLOW | 写入溢出 | 分区剩余空间不足以容纳新项,触发自动整理仍不足 |
VM_NOT_INIT | VM 未初始化 | 未调用 vm_init 就读写 |
VM_INIT_ALREADY | 重复初始化 | vm_init 被调用两次 |
VM_DEFRAG_ERR | 碎片整理失败 | 整理过程中 Flash 操作异常 |
VM_ERR_INIT | 初始化失败 | vm_init 参数非法或 Flash 访问失败 |
VM_ERR_PROTECT | 写保护错误 | Flash 处于写保护状态(sfc_protect/norflash_set_write_protect 生效中)无法写入 |
VM API 参考
vm.h 声明的全部公开接口:
// vm api
VM_ERR vm_eraser(void);
VM_ERR vm_init(void *dev_hdl, u32 vm_addr, u32 vm_len, u8 vm_mode);
//VM_ERR vm_db_create_table(const struct vm_table *table, int num);
void vm_check_all(u8 level); //level : default 0
u8 get_vm_statu(void);
// io api
//s32 vm_read(vm_hdl hdl, void *data_buf, u16 len);
//s32 vm_write(vm_hdl hdl, const void *data_buf, u16 len);
void spi_port_hd(u8 level);
bool sfc_erase_zone(u32 addr, u32 len);
void vm_api_write_mult(u16 start_id, u16 end_id, void *buf, u16 len, u32 delay);
int vm_api_read_mult(u16 start_id, u16 end_id, void *buf, u16 len);
Source: vm.h
要点:
vm_read/vm_write在头文件中被注释掉,说明正式入口是vm_api_read_mult/vm_api_write_mult(库符号仍导出vm_read/vm_write,供内部与兼容代码使用)。应用层应优先使用批量接口,保证一组配置项的整体一致性;vm_db_create_table同样被注释,表结构改为在库内以索引号约定维护;sfc_erase_zone(addr, len)是 SFC 层的按区间擦除接口,VM 与上层均可直接调用;spi_port_hd(level)用于切换 SPI 端口保持(低功耗场景下保存/恢复 SPI 引脚状态,符号表中save_spi_port与之配套)。
批量读写与数据一致性
vm_api_write_mult
void vm_api_write_mult(u16 start_id, u16 end_id, void *buf, u16 len, u32 delay);
int vm_api_read_mult(u16 start_id, u16 end_id, void *buf, u16 len);
Source: vm.h
start_id/end_id:连续索引区间 [start_id, end_id],对应一段内存视图(如整个系统配置结构体);buf/len:内存缓冲与长度;delay(仅写接口):写入防抖/合并延迟,单位为系统 tick。设计意图:上层(如按键音量调节)可能连续快速触发多次配置变更,delay让 VM 把一段时间内的多次写合并为一次 Flash 写,显著降低 Flash 擦写次数、延长寿命,同时避免高频擦写阻塞主流程;vm_api_read_mult返回int,负数表示错误(可对照VM_ERR值域),非负表示成功读取的长度。
库符号显示 vm_api_write_mult 内部有 remain_len、counter 等局部变量,说明其按"剩余长度 + 分段计数器"方式逐索引写入;vm_api_read_mult 内部带 crc_tmp、err 变量,说明批量读会对每个索引做 CRC 校验(对应 VM_READ_DATA_ERR)。
写路径控制流
flowchart TD
Start([应用发起写入]) --> Init{"vm_init 已完成?"}
Init -->|"否"| ErrNotInit["返回 VM_NOT_INIT"]
Init -->|"是"| ChkIdx{"index 合法?"}
ChkIdx -->|"否"| ErrIdx["返回 VM_INDEX_ERR"]
ChkIdx -->|"是"| ChkLen{"数据长度合法?"}
ChkLen -->|"否"| ErrLen["返回 VM_DATA_LEN_ERR"]
ChkLen -->|"是"| Cache{"value_byte <= 4?"}
Cache -->|"是"| UpdCache["更新 RAM 缓存 value"]
Cache -->|"否"| FlashW["擦写 Flash 扇区"]
UpdCache --> RamChk["vm_write_ram_list_check 检查 RAM 列表"]
FlashW --> RamChk
RamChk --> Ok["返回 VM_ERR_NONE"]
ErrNotInit --> End([结束])
ErrIdx --> End
ErrLen --> End
Ok --> End
说明:
vm_write_ram_list_check(库导出符号)在写入前检查 RAM 中的待写列表,配合delay实现合并写;- ≤4 字节的写入先更新 RAM 缓存,再异步落盘,读取时直接命中缓存;
4 字节的写入直接操作 Flash 扇区,需要先擦后写(NOR Flash 特性),因此大配置项应避免高频改写。
Flash 抽象层(SFC / norflash)
库符号表揭示了完整的 Flash 访问栈:
| 层 | 代表符号 | 职责 |
|---|---|---|
| SFC 控制器 | sfc_read、sfc_write、sfc_erase、sfc_erase_zone、sfc_protect、sfc_suspend、get_sfc_status、spi_cache_way_switch | 直接操作 SPI Flash 控制器,支持 DMA(sfc_dma_write)、缓存路切换、保护与挂起 |
| norflash 设备 | norflash_init、norflash_open、norflash_read、norflash_write、norflash_dma_write、norflash_erase、norflash_ioctl | 设备化抽象,封装 SPI 模式切换(flash_enter_4bit_mode/flash_exit_2bit_mode 等)与协议细节 |
| 写保护 | norflash_set_write_protect、norflash_write_protect_config、update_norflash_write_protect、dump_flash_wp_info | Flash 写保护配置,与 VM_ERR_PROTECT 错误码对应 |
| OTP/UUID | norflash_get_otp_info、norflash_get_otp_size、norflash_read_otp、norflash_otp_lock、sys_cfg_read_otp、norflash_get_uuid、get_norflash_uuid、read_flash_id、check_flash_type | 一次性可编程区、芯片唯一标识、Flash 型号识别 |
设计意图:
- 分层隔离:VM 只依赖 SFC 层的擦读写原语,不关心具体 Flash 型号;
norflash_ioctl统一承载模式切换、OTP、UUID 等控制面操作,应用层通过get_sfc_status/read_flash_id识别 Flash 能力; - 写保护是 VM 的威胁:若 Flash 被
sfc_protect或norflash_set_write_protect保护,vm_write会失败并返回VM_ERR_PROTECT,因此固件在启用写保护时需为 VM 分区保留写入权限; - 挂起机制:
sfc_suspend允许高优先级任务(如蓝牙中断)临时挂起正在进行的 Flash 操作,避免长擦写阻塞实时任务。
系统配置集成与 RTC 持久化
系统配置封装(cfg_vm)
库符号 syscfg_vm_enable、get_syscfg_vm_ops、cfg_vm、__initcall_check_otp_data 表明库内部存在一套"系统配置 VM"封装:
get_syscfg_vm_ops返回一个 ops 结构体(工厂模式),系统配置模块通过 ops 读写系统级配置;syscfg_vm_enable控制该系统配置 VM 是否启用;__initcall_check_otp_data是__initcall机制的启动钩子(SDK 的初始化调用框架),在系统启动早期检查 OTP 数据——说明 OTP 中的校准/配置数据在 VM 初始化之前就被读取。
虚拟 RTC 的 VM 持久化
虚拟 RTC 在无独立 RTC 电源的平台上依赖 VM 保存时间,相关符号族:
| 接口(库导出) | 作用 |
|---|---|
rtc_save_time_vm / rtc_get_time_vm | 保存/读取系统时间到 VM |
rtc_save_alm_vm / rtc_get_alm_vm | 保存/读取闹钟到 VM |
rtc_save_sum_nsec_vm / rtc_get_sum_nsec_vm | 保存/读取亚秒纳秒累计值(提高时间精度) |
vir_set_vm_id | 为虚拟 RTC 分配 VM 索引号 |
set_virtual_rtc_tick / vir_rtc_trim | 虚拟 RTC 走时与校准 |
设计意图:AC63 低功耗休眠时主电源关闭,唤醒后通过"VM 中保存的时间 + 休眠时长"重建当前时间。时间类配置属于"≤4 字节缓存"的典型受益者——每次休眠唤醒只读 RAM 缓存,避免擦写 Flash。rtc_save_sum_nsec_vm 的存在说明 VM 保存的是"秒 + 纳秒"双字段,保证跨休眠的时间连续性。
应用层配置项(custom_cfg)
第三方 profile 公共目录下的 apps/common/third_party_profile/common/custom_cfg.c 展示了应用层如何组织可扩展配置项。配置项采用"表头 + 联合体"的经典布局:
typedef union _ex_cfg_item_u {
adv_data_cfg_t adv_data_cfg;
...
hid_param_cfg_t hid_param_cfg;
} ex_cfg_item_u;
Source: custom_cfg.c
typedef struct _cfg_item_head_t {
u16 index;
...
u8 name[16];
} cfg_item_head_t;
Source: custom_cfg.c
typedef struct _cfg_item_description {
u8 *item_name;
...
} cfg_item_description;
Source: custom_cfg.c
设计意图:
cfg_item_head_t以u16 index开头,直接对应 VM 的vm_hdl索引空间;name[16]允许为配置项命名,便于调试与工具(如产测/上位机)按名字定位;ex_cfg_item_u是一个可扩展联合体:新增一种配置(如 HID 参数hid_param_cfg、广播数据adv_data_cfg)只需在联合体中追加成员,无需改动 VM 核心——这是 SDK 将"存储机制(VM)"与"配置内容(custom_cfg)"解耦的体现;cfg_item_description提供配置项的描述元数据(名字指针等),用于枚举/导出配置。
OTA 与 VM 维护流程
OTA 升级会改写 Flash 固件区,而 VM 分区在升级后可能因固件版本变化需要迁移或恢复。库符号提供了一组专用接口:
| 接口 | 阶段 | 作用 |
|---|---|---|
vm_backup_for_update | 升级前 | 将当前 VM 配置备份到安全区域,防止升级过程中被破坏 |
vm_need_recover | 升级后 | 检测新固件是否需要从备份恢复配置(如 VM 布局变更) |
vm_update_recover | 升级后 | 执行配置恢复:把备份的配置迁移到新 VM 布局 |
vm_defrag_for_update | 升级后 | 对升级后的 VM 区做碎片整理,回收废弃扇区 |
flowchart TD
Start([OTA 升级开始]) --> Backup["vm_backup_for_update<br/>备份 VM 配置"]
Backup --> Update["固件写入 Flash"]
Update --> NeedRecover{"vm_need_recover<br/>需要恢复?"}
NeedRecover -->|"是"| Recover["vm_update_recover<br/>迁移并恢复配置"]
NeedRecover -->|"否"| Defrag["vm_defrag_for_update<br/>整理碎片"]
Recover --> Check["vm_check_all 校验"]
Defrag --> Check
Check --> End([升级完成])
设计意图:升级固件时 VM 数据布局可能变化(索引新增、字段加长),直接沿用旧数据会导致错位。备份 → 检测 → 迁移的流程保证升级前后配置尽量保留,且不因布局变化产生脏数据。这与随包发布的"AC63 系列 VM 兼容性修复补丁"(patch_release/AC63系列VM兼容性修复补丁_20250224)相互印证——VM 的跨版本兼容性是需要主动维护的约束。
失败模式、边界与并发考虑
依据 VM_ERR 枚举与库符号,可归纳以下风险点:
- 未初始化访问:任何
vm_read/vm_write在vm_init之前调用都会返回VM_NOT_INIT。应用应在board_init早期(__initcall阶段)完成vm_init与vm_check_all。 - Flash 写保护:
VM_ERR_PROTECT表明写保护(sfc_protect/norflash_set_write_protect)会直接阻断 VM 写入。启用写保护的方案必须为 VM 分区单独放行。 - 空间耗尽与碎片:
VM_WRITE_OVERFLOW在分区满时返回;vm_defrag_for_update/VM_DEFRAG_ERR说明库依赖碎片整理回收空间。配置项总量应控制在分区容量内,避免高频改写大结构体。 - 掉电一致性:批量写接口的
delay合并机制 + 升级场景的备份/恢复机制,都是为了缓解"写一半掉电"导致的配置损坏;vm_api_read_mult的 CRC 校验(crc_tmp)与VM_READ_DATA_ERR提供了读取侧的数据完整性防线。 - 并发/中断:
sfc_suspend允许 Flash 操作被高优先级中断挂起;vm_write_ram_list_check表明写请求先进 RAM 列表(可被中断上下文安全追加),由后台任务统一落盘,从而避免擦写期间长时间关中断。 - RAM 缓存一致性:≤4 字节配置项写入后 RAM 缓存先更新,若落盘失败(如掉电)缓存与 Flash 可能短暂不一致;重启后以
vm_check_all校验结果为准。
性能与运维要点
- 读路径:≤4 字节项直接命中
struct vm_table.valueRAM 缓存,读 Flash 仅发生在冷启动首次访问或大项读取;因此"经常读"的配置(音量、EQ 号、开关位)应保持单字段 ≤4 字节。 - 写路径:批量写 +
delay合并是控制 Flash 磨损的主要手段;vm_dma_write/sfc_dma_write符号表明大块写入走 DMA,降低 CPU 占用。 - 启动开销:
vm_check_all与 OTP 校验(__initcall_check_otp_data)在启动早期执行,属于固定成本;VM 分区过大或索引项过多会线性增加校验时间。 - 排障手段:库提供
vm_test、vm_status、get_vm_statu、dump_flash_wp_info等调试接口,可结合VM_ERR数值(-0x100起)快速定位是索引、长度、校验还是保护问题。
扩展点
- 新增配置项:在
custom_cfg.c的ex_cfg_item_u联合体追加成员,并在cfg_item_head_t之后注册索引与长度,即完成"存储机制零改动、配置内容可扩展"。 - 系统配置 ops:
get_syscfg_vm_ops返回的 ops 结构体是系统级配置的抽象层,可替换实现以接入自己的存储后端。 - 蓝牙配置:
bt_vm_interface是蓝牙协议栈与 VM 之间的适配接口,配对信息(地址、链接密钥)通过它落盘。 - 虚拟 RTC 索引:
vir_set_vm_id允许为虚拟 RTC 指定 VM 索引,多实例场景(如 TWS 双耳)可为左右耳分配不同索引避免冲突。
测试
预编译库导出 vm_test 符号,表明库内自带 VM 自检程序(写读回环 + 校验);vm_check_all 可视为启动期的"只读自检"。SDK 侧仓库未包含 VM 的单元测试源码(实现封闭在库中),建议在产测固件中调用 vm_test 验证 Flash 分区健康度。
配置选项
VM 子系统的运行参数集中在 vm_init 调用点与库编译选项(预编译库已固定)。应用可配置项如下:
| 配置项 | 类型 | 默认/典型值 | 说明 |
|---|---|---|---|
vm_addr | u32 | 由板级 linker 脚本决定 | VM 分区在 Flash 中的起始地址(与固件区、资源区划分互斥) |
vm_len | u32 | 数 KB ~ 数十 KB | VM 分区长度,决定可容纳的配置项总量;过大增加 vm_check_all 启动耗时 |
vm_mode | u8 | 0 | VM 工作模式(库内定义:扇区对齐、备份区策略等) |
vm_check_all 的 level | u8 | 0 | 上电校验深度,默认 0 |
vm_api_write_mult 的 delay | u32 | 按调用方指定(如 20~100 tick) | 批量写合并延迟,越大越省 Flash 寿命,但配置落盘越滞后 |
spi_port_hd 的 level | u8 | 0 | SPI 端口保持级别(低功耗保存/恢复) |
| 配置项索引 | u16 | 各模块自行约定 | 通过 cfg_item_head_t.index / vir_set_vm_id 等分配,注意与系统索引空间不冲突 |
API 参考(汇总)
VM_ERR vm_init(void *dev_hdl, u32 vm_addr, u32 vm_len, u8 vm_mode)
初始化 VM 分区。参数: dev_hdl Flash 设备句柄;vm_addr 分区起始地址;vm_len 分区长度;vm_mode 工作模式。返回: VM_ERR_NONE 成功;VM_INIT_ALREADY 重复初始化;VM_ERR_INIT 参数/硬件错误。注意: 必须在首次读写前调用,且只需调用一次。
void vm_check_all(u8 level)
上电后校验 VM 区完整性与索引表。参数: level 校验深度(默认 0)。返回: 无(状态经 get_vm_statu 查询)。
u8 get_vm_statu(void)
查询 VM 当前状态(是否初始化、是否有待恢复数据等)。
VM_ERR vm_eraser(void)
整区擦除 VM 分区(恢复出厂设置)。返回: VM_ERR_NONE 成功;VM_ERR_PROTECT Flash 被写保护。
void vm_api_write_mult(u16 start_id, u16 end_id, void *buf, u16 len, u32 delay)
批量写入连续索引区间。参数: start_id/end_id 索引区间;buf 数据缓冲;len 缓冲长度;delay 合并延迟(tick)。返回: 无(错误经状态查询)。典型用法: 一次性保存整个系统配置结构体。
int vm_api_read_mult(u16 start_id, u16 end_id, void *buf, u16 len)
批量读取连续索引区间并做 CRC 校验。返回: 成功读取长度;负值为 VM_ERR 错误(如 VM_READ_NO_INDEX、VM_READ_DATA_ERR)。
bool sfc_erase_zone(u32 addr, u32 len)
按地址区间擦除 Flash(SFC 层原语,VM 与上层共用)。返回: true 成功。
void spi_port_hd(u8 level)
低功耗场景下保存/恢复 SPI 端口状态,防止休眠后 Flash 访问失效。
使用示例
示例 1:定义配置表结构(内核头文件)
typedef u16 vm_hdl;
struct vm_table {
u16 index;
u16 value_byte;
int value; //cache value which value_byte <= 4
};
Source: vm.h
示例 2:应用层扩展配置项(第三方 profile)
typedef union _ex_cfg_item_u {
adv_data_cfg_t adv_data_cfg;
...
hid_param_cfg_t hid_param_cfg;
} ex_cfg_item_u;
typedef struct _cfg_item_head_t {
u16 index;
...
u8 name[16];
} cfg_item_head_t;
Sources:
- custom_cfg.c(
ex_cfg_item_u联合体)- custom_cfg.c(
cfg_item_head_t表头)
示例 3:批量保存系统配置(模式代码,基于头文件契约)
// 一次性写入/读取连续索引区间 [CFG_BEGIN, CFG_END] 对应的配置结构体
void cfg_save(sys_cfg_t *cfg) {
vm_api_write_mult(CFG_BEGIN, CFG_END, cfg, sizeof(*cfg), 50 /*tick*/);
}
int cfg_load(sys_cfg_t *cfg) {
return vm_api_read_mult(CFG_BEGIN, CFG_END, cfg, sizeof(*cfg));
}
Source(接口声明): vm.h
注:以上
cfg_save/cfg_load为演示vm_api_*_mult用法的示意代码(仓库中该层实现位于预编译库),实际调用模式与custom_cfg.c的配置项组织方式一致。
Related Links
- VM 接口头文件 vm.h — 本页所有 API 的权威定义
- custom_cfg.c(第三方 profile 公共配置项) — 应用层配置项组织示例
- AC63 系列 VM 兼容性修复补丁说明 — VM 跨版本兼容性维护
- 兄弟页面:电源管理/低功耗(
low_power_*、power_*接口族,与spi_port_hd/RTC 休眠持久化相关)、OTA 升级(vm_backup_for_update等接口的完整升级流程)、蓝牙协议栈(bt_vm_interface配对信息存储)、系统启动流程(__initcall机制与vm_init时序)