杰理 SDK 文档中心
首页
首页
  • 项目概览

    • AD23N SDK 概述与芯片平台
    • 工程结构与模块划分
  • 快速开始

    • 开发环境搭建与工具链
    • 编译构建指南
    • 烧录与固件升级工具
  • 应用框架与产品工作流

    • 应用入口与模式调度
    • 音乐播放应用
    • MIDI 解码与键盘演奏
    • 录音应用
    • LINEIN 与扩音应用
    • USB 从设备应用
    • 待机、软关机与空闲检测
    • 公共 UI 与 LED 显示
  • 音频子系统

    • 音频解码器框架
    • 音频编码器框架
    • 音效算法库
    • 音频管理与输出通路
  • 存储与文件系统

    • 文件系统层
    • NOR Flash 与虚拟机存储
    • 设备与设备管理
  • 系统服务与运行时

    • 消息机制与事件分发
    • 按键扫描与输入处理
    • 电源管理与低功耗控制
    • 定时器与系统任务
  • 外设驱动与平台

    • CPU 平台与启动流程
    • USB 协议栈与主机/设备驱动
    • SPI 与通用外设接口
  • 固件升级与构建工具

    • 固件升级机制
    • 编译后处理与镜像打包
    • 构建系统与命令行工具

NOR Flash 与虚拟机存储

本页介绍 AD23N 平台 SDK 中 NOR Flash 底层驱动与虚拟机(VM,Virtual Machine)键值存储的完整体系:从应用层通过 vm_read/vm_write 读写参数,到 VM 索引空间设计,再到 NOR Flash 驱动、SPI 命令集、互斥锁与平台配置的端到端工作机制。

Purpose and Scope

本页覆盖以下内容:

  • 虚拟机存储层(VM):sdk/app/bsp/common/vm/vm_api.h / vm_api.c 定义的参数持久化接口(syscfg_vm_init、vm_read、vm_write、vm_pre_erase、syscfg_read、syscfg_write)以及 VM_INDEX 逻辑索引空间的设计规则;新版虚拟机接口 sdk/app/bsp/common/vm/new_vm/nvm_api.c 的存在与定位。
  • NOR Flash 驱动层:sdk/app/bsp/common/norflash/norflash.h / norflash.c 定义的 Winbond WX25X 命令集、擦除粒度枚举、平台数据(SPI 通道/CS 引脚/分区配置)、设备操作表与无操作系统模式下的 flash_mutex 互斥机制。
  • 初始化参数区:ini_area_norflash.c 所承担的启动参数区功能(按文件定位说明,非本页重点)。

以下内容属于其它页面,本页不展开:

  • 文件系统式访问:norfs_dev_ops(NOR 文件系统)与上层文件系统/多媒体播放器的对接细节。
  • 具体业务参数的语义(如 FM 频道表、断点续播数据结构)属于各自业务模块的页面。

资料说明:本文全部接口契约、索引定义、命令字与配置结构均直接取自已核实的头文件源码;vm_api.c、nvm_api.c、norflash.c 的内部算法(如磨损均衡、掉电保护)在可验证范围内说明,未逐行展开的部分会明确标注。

Overview

为什么需要"虚拟机"存储

NOR Flash 物理特性决定了它只能把 1 写成 0,写入前必须先擦除(把 0 恢复为 1),且擦除以扇区(Sector)或块(Block)为最小单位;同时 Flash 存在擦写寿命限制。如果应用每次改一个音量值就直接擦整个扇区,会迅速耗尽寿命。

VM 层正是为解决这个问题而设计:它把逻辑 ID(如 VM_INDEX_VOL 音量)映射到物理存储区,由 VM 管理器统一承担"查表定位 → 擦除 → 编程"的流程,并向应用层暴露极简的 vm_read(id, buf, len) / vm_write(id, buf, len) 接口。应用只需关心"我要持久化哪个 ID 的数据",无需关心它在 Flash 中的物理位置。

系统级与用户级 ID 的划分

VM_INDEX 枚举(vm_api.h)是整个 VM 存储的"地址空间"。它被明确划分为两段:

  • 系统保留段(ID 0~32):由 SDK 内部使用,顺序不可修改,包括 RTC 累计/上次计数、闹钟时间、PMU 电压检测、Audio VBG 校准、PLL LDO 配置等。因为系统库可能按固定偏移访问这些 ID,任何顺序调整都会破坏兼容性。
  • 用户段(ID > 32):应用可自由添加/调整顺序,例如歌曲索引、音量、断点(BP)、mbox 各设备(U 盘/SD/内外部 Flash)的断点与索引、FM 频率与频道表、当前活跃设备等。

其中 VM_INDEX_FM_CHANNL 到 VM_INDEX_FM_CHANNL_END 之间是连续预留的 28 个频道槽位,头文件注释明确要求"其间不能插入其它 INDEX"——这是为 FM 频道表按固定偏移连续存储而保留的连续地址空间。

Architecture

flowchart TD
    subgraph sg_App["应用层 (App)"]
        App["应用程序<br/>(播放/设置/设备管理/RTC)"]
    end

    subgraph sg_VM["虚拟机存储层 (VM)"]
        VM_API["vm_api.c<br/>vm_read / vm_write"]
        NVM_API["new_vm/nvm_api.c<br/>新版 VM 接口"]
        VM_INDEX["VM_INDEX 索引表<br/>(vm_api.h)"]
        SYS_INIT["syscfg_vm_init<br/>(内存区注册)"]
    end

    subgraph sg_Dev["设备管理框架 (dev_mg)"]
        DEV_MGR["device.h 设备管理"]
    end

    subgraph sg_NorFlash["NOR Flash 驱动层 (norflash)"]
        NF_DRV["norflash.c<br/>norflash_dev_ops"]
        NF_FS["norfs_dev_ops<br/>(文件系统式访问)"]
        PLATFORM["norflash_dev_platform_data<br/>(SPI/CS/分区配置)"]
        MUTEX["flash_mutex 互斥锁"]
    end

    subgraph sg_HW["硬件层"]
        SPI["SPI1 / SPI2 控制器"]
        FLASH[("NOR Flash 芯片<br/>(Winbond WX25X 系列)")]
    end

    App -->|"vm_read/vm_write"| VM_API
    App --> NVM_API
    VM_API --> VM_INDEX
    VM_API -->|"设备写/读接口"| DEV_MGR
    NVM_API --> DEV_MGR
    SYS_INIT --> VM_API
    DEV_MGR -->|"打开 norflash 设备"| NF_DRV
    NF_DRV -->|"互斥保护"| MUTEX
    NF_DRV -->|"平台参数"| PLATFORM
    NF_DRV -->|"SPI 传输"| SPI
    NF_FS --> SPI
    SPI --> FLASH

架构说明

  • VM 层是"逻辑地址"入口:vm_api.h 中 VM_INDEX 定义逻辑 ID 空间;vm_api.c 把 id 映射到物理存储。vm_api.h 引入了 dev_mg/device.h,说明 VM 层通过设备管理框架访问底层设备,而不是直接操作寄存器——这是 SDK 统一设备抽象(device_operations)的体现。
  • 驱动层是"物理地址"出口:norflash.c 导出 norflash_dev_ops(块设备式操作)与 norfs_dev_ops(文件系统式操作)两张操作表(norflash.h)。norflash_dev_platform_data 决定驱动挂在哪个 SPI、用哪个 CS、读多宽、分区从哪里开始、多大。
  • 并发保护在驱动内:NORFLASH_NO_SYS 为 1 时(不使用操作系统),驱动用自旋式 flash_mutex 加锁,并在等待期间喂狗(wdt_clear)防止看门狗复位——这是裸机环境下保护 Flash 关键时序的典型手法。
  • 新版 VM(new_vm):nvm_api.c 与旧版 vm_api.c 并存于 vm/new_vm/ 目录,供新项目迁移使用,两者共用同一套 NOR Flash 驱动。

核心设计:VM 索引空间

VM 存储的核心抽象是把"持久化项"抽象为 (id, data, len) 三元组。id 即 VM_INDEX 枚举值,data 为数据缓冲,len 为长度(u16,单次最大 65535 字节)。索引空间的划分规则如下:

区间ID 范围归属约束
系统保留0 ~ 32SDK 内部库(RTC/PMU/AUDIO/PLL)顺序不可修改、不可删除
用户自由区33 起应用(歌曲/音量/断点/mbox/FM)可自由增删调整
FM 频道槽VM_INDEX_FM_CHANNL ~ +28FM 应用连续预留,其间不得插入其它 ID
typedef enum {
    VM_INDEX_DEMO = 0,

    //================系统内部 ,预留32个id,不可修改顺序===========================//
    /*RTC*/
    LIB_VM_INDEX_LP_SUM_CNT     = 1,
    LIB_VM_INDEX_LP_LAST_CNT    = 2,
    LIB_VM_INDEX_RTC_TIME       = 3,
    LIB_VM_INDEX_ALM_TIME       = 4,
    /*PMU VOLTAGE*/
    LIB_VM_PMU_VOLTAGE          = 5,    //电压检测
    /*AUDIO_VBG*/
    VM_INDEX_AUDIO_VBG_TRIM     = 6,    //AUDIO_VBG

    /*PLL_LDO*/
    LIB_VM_PLL_LDO              = 7,

    LIB_SYSMEM_END              = 32,
    // 系统lib使用结束
    //================================================================================//

    //================================================================================//
    // 以下用户可以任意修改顺序或添加
    VM_INDEX_SONG,
    VM_INDEX_ENG,
    VM_INDEX_POETRY,
    VM_INDEX_STORY,
    VM_INDEX_F1X,
    VM_INDEX_EXT_SONG,
    VM_INDEX_VOL,
    ...
    VM_INDEX_FM_CHANNL,//VM_INDEX_FM_CHANNL和VM_INDEX_FM_CHANNL_END之间不能插入其它INDEX
    VM_INDEX_FM_CHANNL_END = VM_INDEX_FM_CHANNL + 28,
    VM_INDEX_AUTO_BP,
} VM_INDEX;

Source: vm_api.h

设计意图:把系统库依赖的 ID 与用户 ID 物理隔离,使 SDK 升级不会破坏用户已有数据布局;同时保留 0~32 的固定偏移语义,让系统库可以安全地按序号访问。用户段不固定编号(自动递增),因此用户新增/删除枚举项不会引起系统段移位。

VM 接口契约

vm_api.h 暴露的公共接口非常精简——这正是 VM 层设计的核心价值:对上层隐藏一切 Flash 细节。应用只需初始化一次、之后按 ID 读写即可。

int syscfg_vm_init(u32 mem_addr, u32 mem_size);
int vm_read(u32 id, u8 *data_buf, u16 len);
int vm_write(u32 id, u8 *data_buf, u16 len);
void vm_pre_erase(void);

// 驱动内部使用接口
int syscfg_write(u16 item_id, const void *buf, u16 len);
int syscfg_read(u16 item_id, void *buf, u16 len);

Source: vm_api.h

各接口的语义与设计要点:

接口签名职责说明
syscfg_vm_initint (u32 mem_addr, u32 mem_size)初始化 VM 系统在指定内存区(mem_addr/mem_size)建立 VM 运行时上下文,通常在系统启动早期调用一次
vm_readint (u32 id, u8 *data_buf, u16 len)读取持久化项按 id 查表定位并拷贝 len 字节到 data_buf;返回值为读取结果
vm_writeint (u32 id, u8 *data_buf, u16 len)写入持久化项按 id 定位存储区,必要时触发擦除后编程;返回值为写入结果
vm_pre_erasevoid (void)预擦除在已知即将大量写入的场景(如升级、批量写参数)提前做擦除,把擦除耗时从关键路径移出
syscfg_read / syscfg_writeint (u16 item_id, const void *buf, u16 len)驱动内部读写头文件明确标注"驱动内部使用接口",供 VM 底层实现调用,应用不应直接使用

注意 vm_read/vm_write 的 id 是 u32,而 syscfg_read/syscfg_write 的 item_id 是 u16——两者服务于不同层级:前者是应用可见的逻辑 ID(VM_INDEX),后者是驱动内部更细粒度的条目 ID,两者之间的映射关系由 vm_api.c 内部完成(该映射的具体实现位于 sdk/app/bsp/common/vm/vm_api.c,本页仅核验到接口层)。

NOR Flash 驱动层

Winbond WX25X 命令集

驱动针对 Winbond WX25X 系列定义了完整的 SPI 命令字(norflash.h):

#define WINBOND_WRITE_ENABLE		0x06
#define WINBOND_READ_SR1			0x05
#define WINBOND_READ_SR2			0x35
#define WINBOND_WRITE_SR1			0x01
#define WINBOND_WRITE_SR2			0x31
#define WINBOND_READ_DATA			0x03
#define WINBOND_FAST_READ_DATA		0x0b
#define WINBOND_FAST_READ_DUAL_OUTPUT 0x3b
#define WINBOND_PAGE_PROGRAM		0x02
#define WINBOND_PAGE_ERASE			0x81
#define WINBOND_SECTOR_ERASE		0x20
#define WINBOND_BLOCK_ERASE			0xD8
#define WINBOND_CHIP_ERASE			0xC7
#define WINBOND_JEDEC_ID			0x9F
#define WINBOND_POWER_DOWN			0xB9
#define WINBOND_RELEASE_POWER_DOWN	0xAB

Source: norflash.h

按用途可归纳为四组:

  1. 状态/控制:WRITE_ENABLE(0x06)(每次编程/擦除前必须写使能)、READ_SR1/SR2(0x05/0x35)(轮询忙标志 WIP)、WRITE_SR1/SR2(配置状态寄存器)。
  2. 读取:READ_DATA(0x03)、FAST_READ(0x0B)、FAST_READ_DUAL_OUTPUT(0x3B)——普通读、快速读、双线输出读,配合平台配置中的 spi_read_width 选择线宽。
  3. 编程:PAGE_PROGRAM(0x02)——页编程,是唯一的写入命令。
  4. 擦除:PAGE_ERASE(0x81)、SECTOR_ERASE(0x20)、BLOCK_ERASE(0xD8)、CHIP_ERASE(0xC7)——四级擦除粒度。
  5. 其它:JEDEC_ID(0x9F) 用于上电识别芯片厂商/型号;POWER_DOWN/B9 与 RELEASE_POWER_DOWN/AB 用于低功耗管理(对应 _norflash_power_down() / _norflash_release_power_down())。

擦除粒度枚举

enum {
    FLASH_PAGE_ERASER,
    FLASH_SECTOR_ERASER,
    FLASH_BLOCK_ERASER,
    FLASH_CHIP_ERASER,
};

Source: norflash.h

该枚举把擦除能力抽象为四种粒度,驱动内部根据操作范围选择最合适的擦除命令。设计意图:页擦除(0x81)粒度最小、耗时最短但覆盖范围有限;块擦除(0xD8)覆盖大范围、单次命令开销低。VM 层在擦除策略上(例如只擦脏扇区、延迟合并等)会依赖驱动提供的这一粒度选择能力,以摊薄擦写寿命损耗。

平台数据:驱动与硬件的绑定点

struct norflash_dev_platform_data {
    s8 spi_hw_num;         //只支持SPI1或SPI2
    u8 spi_cs_port;        //cs的引脚
    u8 spi_read_width;     //flash读数据的线宽
    const struct spi_platform_data *spi_pdata;
    u32 start_addr;         //分区起始地址
    u32 size;               //分区大小,若只有1个分区,则这个参数可以忽略
};

Source: norflash.h

这是驱动实例化时的关键配置结构,头文件还提供了构造宏 NORFLASH_DEV_PLATFORM_DATA_BEGIN(data) / NORFLASH_DEV_PLATFORM_DATA_END(),用于在板级配置文件中以结构化方式声明设备。字段含义:

  • spi_hw_num:仅支持 SPI1 或 SPI2(s8),决定挂接的硬件 SPI 控制器。
  • spi_cs_port:片选引脚,驱动通过 GPIO 控制 CS。
  • spi_read_width:读数据线宽(1/2/4 线),对应命令集里的 FAST_READ_DUAL_OUTPUT 等扩展读模式。
  • spi_pdata:SPI 控制器自身的平台配置(时钟、模式等)。
  • start_addr / size:分区起始地址与大小——这是 NOR Flash 分区的物理基础,VM 存储区、文件系统区、OTA 区都通过该结构划分地址边界。

核心流程:一次参数写入的完整路径

以 vm_write(VM_INDEX_VOL, buf, len) 为例,展示从应用到 Flash 芯片的完整调用链。其中底层擦除/编程命令序列是 NOR Flash 的硬件要求(先写使能、再擦除、后编程),依据 norflash.h 命令集还原:

sequenceDiagram
    participant App as 应用代码
    participant VM as vm_api.c (VM 管理器)
    participant DM as 设备管理框架 (dev_mg)
    participant NF as norflash.c (设备驱动)
    participant SPI as SPI 控制器
    participant F as NOR Flash 芯片

    App->>VM: vm_write(VM_INDEX_VOL, buf, len)
    activate VM
    VM->>VM: 按 VM_INDEX 查表定位 item 存储区
    VM->>DM: 打开 norflash 设备 / 写接口
    activate DM
    DM->>NF: 调用 norflash_dev_ops 写操作
    activate NF
    NF->>NF: flash_mutex_pend 加锁 (死等/超时)
    NF->>SPI: WRITE_ENABLE (0x06)
    SPI->>F: 写使能命令
    NF->>SPI: SECTOR_ERASE (0x20) 擦除目标扇区
    SPI->>F: 擦除命令
    NF->>SPI: 轮询 READ_SR1 (0x05) 等 WIP 清除
    SPI->>F: 状态寄存器读取
    NF->>SPI: PAGE_PROGRAM (0x02) 写入数据
    SPI->>F: 页编程命令 + 数据
    NF->>NF: flash_mutex_post 解锁
    NF-->>DM: 返回写入结果
    deactivate NF
    DM-->>VM: 返回结果
    deactivate DM
    VM-->>App: 返回 len / 错误码
    deactivate VM

关键时序点说明:

  1. 先查表后写:VM 层把逻辑 id 转换为物理地址/条目位置——这是"虚拟机"名称的由来:逻辑地址与物理地址解耦,允许 VM 内部做重定位与磨损管理。
  2. 写使能是前置条件:NOR Flash 规定任何擦除/编程操作前必须先发 WRITE_ENABLE(0x06),否则命令被忽略;驱动每次操作都重新使能,避免遗留状态。
  3. 擦除与编程分离:编程(PAGE_PROGRAM)只能把 1 变 0,因此覆盖已有数据必须先擦除(SECTOR_ERASE)。vm_pre_erase() 的存在意味着 VM 允许把"擦除"从写路径中提前——例如升级时预擦除目标区,避免运行时擦除造成长时间阻塞。
  4. 忙检测:擦除/编程期间芯片置 WIP 位,驱动通过 READ_SR1(0x05) 轮询直至完成,期间在裸机模式下由 wdt_clear() 喂狗,防止长时间轮询触发看门狗复位。

互斥与并发:裸机下的临界区保护

norflash.h 在 NORFLASH_NO_SYS == 1(不使用操作系统)时,用软件信号量方式实现互斥(norflash.h):

typedef volatile u8 flash_mutex;
static inline void flash_mutex_create(flash_mutex *sem, u8 count)
{
    *sem = count;
}
static inline void flash_mutex_post(flash_mutex *sem)
{
    (*sem) = 1;
}
static inline s8 flash_mutex_pend(flash_mutex *sem, u32 timeout)
{
    u32 _timeout = timeout + jiffies;
    extern void wdt_clear();
    while (1) {
        if (*sem) {
            (*sem) = 0;
            break;
        }
        if ((timeout != 0) && (_timeout < jiffies)) {
            return -1;
        }
        wdt_clear();
    }
    return 0;
}

Source: norflash.h

设计要点:

  • flash_mutex_pend(sem, timeout):timeout == 0 时死等;非 0 时基于 jiffies 计算超时,超时返回 -1,避免因持锁方异常导致永久卡死。
  • 等待期间喂狗:循环内调用 wdt_clear(),因为擦除/编程可能持续数十毫秒,裸机中断上下文无法靠任务调度让步,只能主动喂狗防复位。
  • query_flash_mutex_pend:提供"查一次即返回"的非阻塞变体,适合中断上下文或无需等待的场合。
  • volatile u8 信号量:在单核裸机场景下,关中断/原子访问语义由 volatile 保证读改写;NORFLASH_NO_SYS == 0 时则走操作系统互斥路径(#else 分支)。

使用示例

示例 1:系统启动时初始化 VM 并读取参数

典型流程是:启动早期调用 syscfg_vm_init 指定 VM 工作内存,之后即可用 vm_read 读取各业务参数:

// 系统启动初始化
syscfg_vm_init((u32)vm_buf, sizeof(vm_buf));

// 读取音量(假设先前已写入)
u8 vol = 0;
if (vm_read(VM_INDEX_VOL, &vol, sizeof(vol)) == 0) {
    // 读取成功,vol 为持久化的音量
}

(接口签名见 vm_api.h;具体调用方式与缓冲区分配以 vm_api.c 中实现为准。)

示例 2:写入参数并配合预擦除

// 批量更新参数前预擦除,把擦除耗时移出写路径
vm_pre_erase();

u8 new_vol = 10;
vm_write(VM_INDEX_VOL, &new_vol, sizeof(new_vol));

// 记录当前活跃设备(mbox)
u8 dev = VM_INDEX_ACTIVE_DEV; // 示例值,实际为设备枚举
vm_write(VM_INDEX_ACTIVE_DEV, &dev, sizeof(dev));

(VM_INDEX_ACTIVE_DEV、VM_INDEX_VOL 等 ID 定义见 vm_api.h。)

示例 3:板级配置中声明 NOR Flash 平台数据

NORFLASH_DEV_PLATFORM_DATA_BEGIN(norflash0_data)
    .spi_hw_num    = 1,        // 使用 SPI1
    .spi_cs_port   = FLASH_CS_GPIO, // CS 引脚
    .spi_read_width = 1,       // 单线读
    .spi_pdata     = &spi1_data,
    .start_addr    = 0x0,      // 分区起始
    .size          = 0x200000, // 分区大小(示例)
NORFLASH_DEV_PLATFORM_DATA_END()

Source(结构定义与宏): norflash.h

配置选项

VM 层配置

配置项类型默认值说明
syscfg_vm_init(mem_addr, mem_size)函数参数无(由调用方指定)VM 运行时内存区地址与大小,启动早期必须调用一次
VM_INDEX 枚举项枚举见 vm_api.h逻辑 ID 空间;系统段 0~32 不可改序,用户段可自由扩展

NOR Flash 驱动层配置

配置项类型默认值说明
EXT_FLASH_EN宏(来自 app_config.h)未定义为 1 时使能 TCFG_NORFLASH_DEV_ENABLE,即启用外挂 NOR Flash
TCFG_NORFLASH_DEV_ENABLE宏未定义"启动 nor flash"开关(norflash.h)
NORFLASH_NO_SYS宏10=使用操作系统互斥,1=不使用操作系统(裸机自旋锁 + 喂狗)
spi_hw_nums8由平台数据决定挂接的 SPI 控制器,仅支持 SPI1/SPI2
spi_cs_portu8由平台数据决定CS 片选引脚
spi_read_widthu8由平台数据决定读数据线宽(对应双线/四线快速读)
spi_pdataconst struct spi_platform_data *由平台数据决定SPI 控制器平台配置(时钟等)
start_addru32由平台数据决定分区起始地址
sizeu32由平台数据决定分区大小;单分区时忽略

API 参考

int syscfg_vm_init(u32 mem_addr, u32 mem_size)

初始化 VM 存储系统,建立逻辑 ID → 物理存储的映射上下文。

参数:

  • mem_addr (u32):VM 工作内存起始地址(通常为静态数组或 RAM 池)
  • mem_size (u32):工作内存大小

返回: int,0 表示成功,非 0 表示失败。

int vm_read(u32 id, u8 *data_buf, u16 len)

按逻辑 ID 读取持久化数据。

参数:

  • id (u32):VM_INDEX 枚举值
  • data_buf (u8 *):读取数据的输出缓冲区
  • len (u16):期望读取的字节数

返回: int,0 表示成功;非 0 表示 ID 不存在或读取失败。

int vm_write(u32 id, u8 *data_buf, u16 len)

按逻辑 ID 写入持久化数据(内部可能触发擦除与页编程)。

参数:

  • id (u32):VM_INDEX 枚举值
  • data_buf (u8 *):待写入数据
  • len (u16):写入字节数(单次上限受 u16 约束)

返回: int,0 表示成功;非 0 表示写入失败(如擦除/编程超时)。

void vm_pre_erase(void)

预擦除接口:在批量写参数或升级前调用,把擦除耗时移出关键写入路径。无参数、无返回值。

int syscfg_write(u16 item_id, const void *buf, u16 len) / int syscfg_read(u16 item_id, void *buf, u16 len)

驱动内部使用接口(头文件明确标注),供 VM 底层实现调用;应用不应直接使用。参数为更细粒度的条目 ID 与数据缓冲。

void _norflash_power_down(void) / void _norflash_release_power_down(void)

NOR Flash 低功耗控制:断电前进入 Power Down(命令 0xB9),上电/唤醒时释放(命令 0xAB)。无参数、无返回值(norflash.h)。

设备操作表

符号类型说明
norflash_dev_opsconst struct device_operations块设备式 NOR Flash 操作表(norflash.h)
norfs_dev_opsconst struct device_operationsNOR 文件系统式操作表(norflash.h)

失败模式、边界情况与并发

写入中途掉电

NOR Flash 的"先擦后写"流程中若在擦除后、编程前掉电,该扇区会呈现全 0xFF(已擦除但未写入)或部分写入状态。VM 层需要具备幂等恢复能力:读侧遇到未写入条目应返回"数据不存在"而不是垃圾数据。这是 vm_api.c / nvm_api.c 内部需要保证的语义(实现细节位于实现文件中,本页核验到接口契约层)。应用侧建议采用"先写新值、确认成功后再依赖新值"的策略。

擦写寿命与磨损

Flash 扇区擦写寿命有限(典型 10 万次)。频繁写同一 VM_INDEX(如定时存 RTC 计数、断点)会集中磨损同一物理区。VM 层通过 syscfg_vm_init 划分的内存区与内部映射,通常采用"多槽位轮换/拷贝式更新"来摊薄磨损。若观察到 Flash 提前损坏,优先检查是否有高热路径在循环写同一个 ID,并考虑用 vm_pre_erase() 批量合并写操作。

并发访问

  • 裸机模式(NORFLASH_NO_SYS == 1)下,多个中断/主循环任务同时访问 Flash 时,必须经 flash_mutex_pend 保护;timeout=0 死等模式风险在于持锁方异常时无限等待,生产代码建议使用带超时的调用。
  • flash_mutex_pend 超时返回 -1 后,调用方不得继续执行擦除/编程命令,否则会与持锁方交叉操作造成数据破坏。
  • 中断内访问 Flash 应使用 query_flash_mutex_pend 非阻塞查询;若锁被占用则放弃本次操作(返回失败),避免在中断里长时间自旋。

边界情况

  • 长度上限:vm_read/vm_write 的 len 为 u16,单次最大 65535 字节;跨越大小的数据需分多次写入或用 syscfg_* 内部条目接口。
  • ID 越界:id 超过 VM_INDEX 枚举末尾时行为未定义,调用方应保证 ID 合法。
  • 索引顺序约束:LIB_VM_INDEX_LP_SUM_CNT ~ LIB_SYSMEM_END 之间禁止增删改序;VM_INDEX_FM_CHANNL ~ VM_INDEX_FM_CHANNL_END(28 个槽位)之间禁止插入其它 ID——违反会破坏既有数据的偏移兼容性。
  • 未初始化:未调用 syscfg_vm_init 就使用 vm_read/vm_write 属于未定义行为,典型表现为返回错误或访问非法内存。

故障定位建议

现象可能原因排查方向
vm_read 返回非 0ID 未写过 / VM 未初始化 / 存储区损坏确认 syscfg_vm_init 已调用;检查 ID 是否首次读取
写入后读回不一致擦除未完成即编程 / 互斥未生效检查写路径是否持锁;确认 READ_SR1 忙轮询完整
系统随机复位擦除耗时内看门狗超时确认裸机路径调用了 wdt_clear()(flash_mutex_pend 内已有)
Flash 提前损坏单一 ID 高频写入排查热路径写调用,评估磨损均衡

性能与运维

  • 擦除是耗时操作:扇区擦除(0x20)典型耗时数十毫秒,块擦除(0xD8)更长。vm_pre_erase() 的存在说明 VM 支持"预擦除"模式,把擦除从写关键路径中剥离——批量更新(升级、恢复出厂)前调用可显著降低写入阻塞时间。
  • 读路径轻量:READ_DATA(0x03) 读通常无状态等待,vm_read 可直接命中缓存或快速读出,适合高频读取场景。
  • 低功耗配合:空闲时可调用 _norflash_power_down() 进入 Power Down 省电,唤醒前调用 _norflash_release_power_down();注意切换 Power Down 状态会短暂增加唤醒延迟。
  • 喂狗与超时:裸机自旋等待期间驱动内主动 wdt_clear(),这是把 Flash 操作嵌入中断/主循环场景的关键保证;移植到新平台时务必保留。

扩展点

  1. 新增持久化项:在 VM_INDEX 用户段追加枚举值即可(注意 FM 频道槽位约束),无需改动 VM 实现。
  2. 新增分区:通过 norflash_dev_platform_data 的 start_addr/size 划分新的 NOR 分区;多分区场景需为每个分区注册独立平台数据与设备。
  3. 更换 Flash 型号:在 norflash.h/norflash.c 中扩展命令集(JEDEC ID 识别)与擦除粒度映射;norflash_dev_ops 保持稳定,上层 VM 无感。
  4. 从裸机迁移到 RTOS:将 NORFLASH_NO_SYS 置 0,驱动切换到操作系统互斥路径,flash_mutex_* 走 #else 分支。
  5. 新项目采用新版 VM:sdk/app/bsp/common/vm/new_vm/nvm_api.c 提供新版 VM 实现,接口风格与旧版一致(vm_read/vm_write 语义),迁移成本低。

Related Links

  • VM 接口定义与索引空间(vm_api.h)
  • VM 实现(vm_api.c)
  • 新版 VM 实现(new_vm/nvm_api.c)
  • NOR Flash 驱动头文件(norflash.h)
  • NOR Flash 驱动实现(norflash.c)
  • 初始化参数区(ini_area_norflash.c)
Prev
文件系统层
Next
设备与设备管理