杰理 SDK 文档中心
首页
首页
  • 概述

    • SDK 概览与产品定位
    • 支持芯片平台与蓝牙认证
    • SDK 架构与目录分层
  • 快速开始

    • 环境搭建与编译工具链
    • 编译构建系统
    • 板级工程与配置
    • 烧录与固件升级工具
  • 应用工程

    • 应用选择与工程总览
    • SPP + BLE 数传应用框架
    • 透传与 AT 指令示例
    • BLE 广播/中心与定位示例
    • 2.4G 私有协议与 Dongle 示例
    • 云平台接入示例
    • HID 人机交互应用框架
    • HID 示例工程(键盘/鼠标/遥控器/手柄)
    • Bluetooth Mesh 应用框架
    • Mesh 模型与 Mesh DFU 固件升级
    • Mesh 音频编解码演示
  • 芯片平台与硬件抽象

    • 芯片平台总览与差异
    • 音频编解码与时钟管理
    • 外设驱动接口(ADC/IIC/SPI/PWM/LED/充电)
    • 芯片配置工具与下载支持
  • 蓝牙协议栈

    • 蓝牙控制器层(btctrler)
    • 蓝牙协议栈与 Profile(btstack)
    • 蓝牙模块选择与配置
  • 媒体与音频框架

    • 音频流框架
    • 音频编解码与 A2DP 媒体
    • 音频效果处理(EQ/频谱/变调/环绕/超低音)
    • 本地 TWS 与音频同步
  • 系统服务与运行时

    • 实时操作系统与任务调度
    • 消息事件机制
    • 电源管理与低功耗
    • 存储与配置系统
    • 设备驱动框架(USB/RTC)
  • 应用公共组件

    • 音频应用组件
    • 设备外设抽象(按键/触摸/传感器/存储)
    • 蓝牙公共模块与消息联动
    • 调试与配置组件
    • 杰理关键词唤醒(jl_kws)
  • 第三方协议与云平台接入

    • 杰理 RCSP 私有协议
    • 低功耗蓝牙 Mesh 方案(llsync_mesh)
    • Sig Mesh 方案
    • 涂鸦协议接入
    • 腾讯连连接入
    • 华为 HiLink 接入
  • 固件升级与维护

    • OTA 升级机制
    • 升级补丁与版本维护
    • 升级工具链(BLE OTA / USB Dongle OTA)
  • 文档与开发资源

    • 数据手册与架构文档
    • 协议与云平台开发文档
    • 常见问题与技术支持

存储与配置系统

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

架构分四层:

  1. 应用层:蓝牙协议栈、RTC、OTA、第三方 profile 的 custom_cfg 等模块通过 VM API 读写各自配置。custom_cfg.c 定义了可扩展的配置项描述结构(cfg_item_head_t、ex_cfg_item_u)。
  2. 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 结构体对外提供系统配置操作)。
  3. Flash 驱动层:sfc_* 是硬件 SPI Flash 控制器(SFC)的直接操作;norflash_* 是更上层的设备抽象(含 4bit/2bit 模式、DMA 写、OTP、写保护、UUID)。vm_dma_write/sfc_dma_write 符号说明 VM 写入支持 DMA 通道。
  4. 物理层: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 子系统的生命周期分为四个阶段:

  1. 分区划定:vm_init(void *dev_hdl, u32 vm_addr, u32 vm_len, u8 vm_mode) 指定 Flash 设备句柄、VM 区起始地址、长度与工作模式。vm_mode 用于表达分配策略(如扇区对齐方式、是否启用备份区等),具体取值由库内部定义。
  2. 上电校验:vm_check_all(u8 level) 在启动阶段扫描 VM 区,校验索引表与数据完整性(level 默认传 0)。配合 get_vm_statu() 可查询当前 VM 状态。
  3. 运行期读写:vm_read / vm_write 按句柄访问;批量场景使用 vm_api_read_mult / vm_api_write_mult。
  4. 维护/回收: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_INITVM 未初始化未调用 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_infoFlash 写保护配置,与 VM_ERR_PROTECT 错误码对应
OTP/UUIDnorflash_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 枚举与库符号,可归纳以下风险点:

  1. 未初始化访问:任何 vm_read/vm_write 在 vm_init 之前调用都会返回 VM_NOT_INIT。应用应在 board_init 早期(__initcall 阶段)完成 vm_init 与 vm_check_all。
  2. Flash 写保护:VM_ERR_PROTECT 表明写保护(sfc_protect/norflash_set_write_protect)会直接阻断 VM 写入。启用写保护的方案必须为 VM 分区单独放行。
  3. 空间耗尽与碎片:VM_WRITE_OVERFLOW 在分区满时返回;vm_defrag_for_update/VM_DEFRAG_ERR 说明库依赖碎片整理回收空间。配置项总量应控制在分区容量内,避免高频改写大结构体。
  4. 掉电一致性:批量写接口的 delay 合并机制 + 升级场景的备份/恢复机制,都是为了缓解"写一半掉电"导致的配置损坏;vm_api_read_mult 的 CRC 校验(crc_tmp)与 VM_READ_DATA_ERR 提供了读取侧的数据完整性防线。
  5. 并发/中断:sfc_suspend 允许 Flash 操作被高优先级中断挂起;vm_write_ram_list_check 表明写请求先进 RAM 列表(可被中断上下文安全追加),由后台任务统一落盘,从而避免擦写期间长时间关中断。
  6. RAM 缓存一致性:≤4 字节配置项写入后 RAM 缓存先更新,若落盘失败(如掉电)缓存与 Flash 可能短暂不一致;重启后以 vm_check_all 校验结果为准。

性能与运维要点

  • 读路径:≤4 字节项直接命中 struct vm_table.value RAM 缓存,读 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 起)快速定位是索引、长度、校验还是保护问题。

扩展点

  1. 新增配置项:在 custom_cfg.c 的 ex_cfg_item_u 联合体追加成员,并在 cfg_item_head_t 之后注册索引与长度,即完成"存储机制零改动、配置内容可扩展"。
  2. 系统配置 ops:get_syscfg_vm_ops 返回的 ops 结构体是系统级配置的抽象层,可替换实现以接入自己的存储后端。
  3. 蓝牙配置:bt_vm_interface 是蓝牙协议栈与 VM 之间的适配接口,配对信息(地址、链接密钥)通过它落盘。
  4. 虚拟 RTC 索引:vir_set_vm_id 允许为虚拟 RTC 指定 VM 索引,多实例场景(如 TWS 双耳)可为左右耳分配不同索引避免冲突。

测试

预编译库导出 vm_test 符号,表明库内自带 VM 自检程序(写读回环 + 校验);vm_check_all 可视为启动期的"只读自检"。SDK 侧仓库未包含 VM 的单元测试源码(实现封闭在库中),建议在产测固件中调用 vm_test 验证 Flash 分区健康度。

配置选项

VM 子系统的运行参数集中在 vm_init 调用点与库编译选项(预编译库已固定)。应用可配置项如下:

配置项类型默认/典型值说明
vm_addru32由板级 linker 脚本决定VM 分区在 Flash 中的起始地址(与固件区、资源区划分互斥)
vm_lenu32数 KB ~ 数十 KBVM 分区长度,决定可容纳的配置项总量;过大增加 vm_check_all 启动耗时
vm_modeu80VM 工作模式(库内定义:扇区对齐、备份区策略等)
vm_check_all 的 levelu80上电校验深度,默认 0
vm_api_write_mult 的 delayu32按调用方指定(如 20~100 tick)批量写合并延迟,越大越省 Flash 寿命,但配置落盘越滞后
spi_port_hd 的 levelu80SPI 端口保持级别(低功耗保存/恢复)
配置项索引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 时序)
Prev
电源管理与低功耗
Next
设备驱动框架(USB/RTC)