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

    • SDK 简介与核心特性
    • 芯片平台与硬件资料
    • SDK 版本与发布信息
  • 快速开始

    • 环境搭建与工具链
    • 编译工程
    • 烧录与量产工具
  • 工程结构与构建系统

    • 工程目录布局
    • 构建与链接配置
  • 应用层开发

    • mbox_flash 应用框架
    • 板级支持包 (BSP)
    • 公共应用模块
    • UI 显示子系统
  • 蓝牙子系统

    • BLE 控制器、链路层与 HCI 传输
    • GATT 服务框架
    • BLE 应用示例:遥控器 / Dongle / 对讲机
    • 经典蓝牙支持
  • 音频子系统

    • 音频编解码器
    • 音频设备接口 (DAC / ADC / APA)
    • 音效处理与 EQ
    • 播放、录音与 MIO 工作流
  • 设备与文件系统

    • 存储设备驱动 (NorFlash / SDMMC / USB)
    • 文件系统 (FAT / nor_fs / SYDF)
    • 设备管理框架 (dev_mg)
  • 系统服务与电源管理

    • 消息机制 (msg / hot_msg)
    • 配置与参数存储 (app_config / VM)
    • 电源管理 (SOFT OFF / POWER DOWN)
  • 固件升级

    • 升级框架总览 (code_v1 / code_v2)
    • 双 Bank 升级机制
    • 升级通道:UART / 测试盒 / BLE OTA / USB / SD
  • 补丁包与版本维护

    • 版本升级补丁链 (v1.1.0 → v1.4.0)
    • 问题修复补丁
    • 固件裁剪与资源优化
  • 开发工具与支持

    • 辅助工具与脚本
    • 文档、配置说明与常见问题

mbox_flash 应用框架

mbox_flash 是 AW30N BLE SDK 中基于 Flash 运行(XIP,Execute-in-Place) 的轻量级应用框架,由 app.c 提供入口 mbox_flash_main(),负责系统初始化、工作模式装载(音乐/录音/Linein/MIDI/对箱/RC/RF Radio 等)、外设轮询(按键/ADC/充电/USB 热插拔)以及配套的链接脚本与内存布局管理。该框架专为从 SPI Flash 直接取指执行的低成本音箱(soundbox/mbox)类产品设计,是 BD49 平台应用侧的默认启动框架。

Purpose and Scope

本文档完整说明 mbox_flash 应用框架的实现机制,包括:

  • 应用入口 mbox_flash_main() 的启动序列与"设备更新完成"特殊流程
  • work_mode 工作模式系统的装载、记忆(SYSMEM_INDEX_SYSMODE)与切换语义
  • tick_timer_ram_loop() / app_timer_loop() 定时轮询循环及各类外设挂接点
  • 链接脚本 app_ld.c 的内存布局(app_code/ram0/cache_ram/boot_ram)与 overlay 条件编译
  • 工程构建产物(AW30N_mbox_flash.cbp、download_bat.c)与烧录流程

以下主题属于其他页面,不在本文展开:具体音乐解码(music_play)、USB 从机协议栈(usb_slave_mode)、录音算法(record_mode)、蓝牙协议栈(bt_ble)、VM 存储格式(vm_sfc)等具体模块的内部实现——本文只说明 mbox_flash 框架如何组织、装载和调度这些模块。

Overview

mbox_flash 应用框架解决的是"音箱产品固件如何组织"这一根本问题。与传统 MCU 上把全部代码烧进 ROM/内部 Flash 不同,AW30N 的 mbox_flash 方案把应用代码存放在 外部 SPI Flash 中,通过 #pragma code_seg(".app.text") 等段指令让代码直接在 Flash 上执行(XIP),从而把 RAM 留给数据、栈与蓝牙协议栈。

框架的核心设计可以概括为三点:

  1. 单一入口、循环调度:mbox_flash_main() 是整个应用的主入口,内部是一个 while(1) 大循环——每次循环先 clear_all_message() 清空消息队列,再读取/预擦除系统存储,最后按 work_mode 分发到对应模式的事件循环。模式切换通过写回 SYSMEM_INDEX_SYSMODE 并重启循环完成。

  2. 模式即模块:每个工作模式(音乐、录音、Linein、MIDI、对箱、RC、RF Radio、RTC 等)对应一个独立模块,通过 app_modules.h 统一头文件接入。框架本身不关心模式内部细节,只负责"装载哪个模式、在什么时机装载"。

  3. Tick 驱动的外设轮询:app_timer_loop() 以系统 tick 为节拍,按固定周期分派 ADC 扫描、USB 热插拔检测、SD 卡检测、充电管理、UAC 同步等任务,避免为每个外设单独建任务带来的 RAM 开销。

flowchart TD
    subgraph sg_Boot["启动链 (ROM/Boot)"]
        PowerOn["上电复位"] --> MaskROM["MaskROM 引导"]
        MaskROM --> AppEntry["_start (app_ld.c ENTRY)"]
    end

    subgraph sg_App["mbox_flash 应用框架 (app.c)"]
        AppEntry --> SysInit["app_system_init()"]
        SysInit --> Main["mbox_flash_main()"]
        Main -->|"get_up_suc_flag() == true"| UpdateEnd["wdt_close() + while(1)<br/>(升级完成等待断电)"]
        Main -->|"读取音量"| VolRestore["sysmem_read_api(SYSMEM_INDEX_VOL)<br/>dac_vol() 恢复音量"]
        Main -->|"读取模式"| ModeLoad["sysmem_read_api(SYSMEM_INDEX_SYSMODE)<br/>装载 work_mode"]
        ModeLoad --> Loop["while(1) 主循环<br/>clear_all_message() + sysmem_pre_erase_api()"]
        Loop --> ModeDispatch["按 work_mode 分发到模式模块"]
    end

    subgraph sg_Modules["工作模式模块 (app_modules.h)"]
        ModeDispatch --> Music["music_play<br/>(音乐)"]
        ModeDispatch --> Record["record_mode<br/>(录音)"]
        ModeDispatch --> Linein["linein_mode<br/>(AUX)"]
        ModeDispatch --> Midi["midi_dec_mode<br/>(MIDI)"]
        ModeDispatch --> Loudspk["loudspk_mode<br/>(对箱)"]
        ModeDispatch --> Rc["rc_app<br/>(遥控)"]
        ModeDispatch --> Radio["rf_radio_app<br/>(RF 收音)"]
    end

    subgraph sg_Tick["Tick 轮询层 (app.c)"]
        Loop --> TickLoop["tick_timer_ram_loop()<br/>LED5X7 扫描 (L2 cache)"]
        Loop --> AppTimer["app_timer_loop()<br/>ADC/USB/SD/充电/UAC"]
    end

    subgraph sg_Layout["链接布局 (app_ld.c)"]
        AppEntry -.->|"段指令 .app.text / .app.data"| Flash["app_code (SPI Flash XIP, 32M)"]
        TickLoop -.->|"AT(.tick_timer.text.cache.L2)"| CacheRam["cache_ram / ram0 (L1)"]
    end

架构说明:上电后由 MaskROM 引导到链接脚本定义的 _start,随后 app_system_init() 完成系统基础初始化,mbox_flash_main() 接管应用生命周期。模式分发后各模式模块通过 app_modules.h 统一暴露事件处理接口;tick 层与链接布局层分别保证外设轮询的实时性与 XIP 代码的热路径缓存命中。

入口与初始化流程

段指令与 XIP 执行模型

app.c 文件顶部用 5 条 #pragma 把整个应用的代码/数据/常量段重定向到专用段:

#pragma bss_seg(".app.data.bss")
#pragma data_seg(".app.data")
#pragma const_seg(".app.text.const")
#pragma code_seg(".app.text")
#pragma str_literal_override(".app.text.const")

Source: app.c

这些段与链接脚本 app_ld.c 中 app_code 存储区(ORIGIN = _SFC_MEMORY_START_ADDR + 0x100, LENGTH = 32M-0x100)对应,即代码、只读常量直接落在 SPI Flash 地址空间,CPU 取指时由 SFC(SPI Flash Controller)映射访问——这就是"mbox_flash(mbox 产品 + Flash 执行)"名称的由来。bss/data 段则落在 ram0(L1 SRAM,起始 0x3f00000 + MAX_IRQ_ENTRY_NUM * 4 + 0x200)。把只读数据与代码放在 Flash、把可写数据放在 RAM,是 XIP 方案平衡容量(Flash 大)与速度(RAM 快)的基本策略。

mbox_flash_main() 启动序列

mbox_flash_main() 是应用级入口,调用点位于 app_system_init() 之后(app.c 第 278 行 mbox_flash_main();):

void mbox_flash_main(void)
{
    log_info("Mbox-Flash App\n");
    if (get_up_suc_flag()) {
        log_info("----device update end----\n");
        wdt_close();
        while (1);
    }

    vm_isr_response_index_register(IRQ_TICKTMR_IDX);//vm擦写flash时响应tick_timer_ram_loop()函数
    delay_10ms(50);//等待系统稳定
    pa_mute(0);

#if 1//volume memory
    u8 vol = 0;
    u32 res = sysmem_read_api(SYSMEM_INDEX_VOL, &vol, sizeof(vol));
    if ((vol <= 31) && (res == sizeof(vol))) {
        dac_vol(0, vol);
        log_info("powerup set vol : %d\n", vol);
    }
#endif

    sysmem_read_api(SYSMEM_INDEX_SYSMODE, &work_mode, sizeof(work_mode));
    /* work_mode = MUSIC_MODE; */
    /* work_mode = RECORD_MODE; */
    ...
    while (1) {
        clear_all_message();
        //切换模式前做预擦除动作
        sysmem_pre_erase_api();

Source: app.c

启动序列分五个阶段,设计意图如下:

  1. 升级完成守卫:get_up_sup_flag() 检测到设备升级(OTA/量产烧录)刚结束时,直接 wdt_close() 并死循环等待断电。此时应用尚未运行,避免在固件更新后立即进入业务逻辑造成数据竞争或异常播放;用户重新上电后标志位已被升级流程清除,才进入正常启动。
  2. VM 擦写中断响应注册:vm_isr_response_index_register(IRQ_TICKTMR_IDX) 让 VM(虚拟存储)擦写 Flash 期间仍能响应 tick 定时器中断,保证 tick_timer_ram_loop() 中的 LED 扫描等短任务不因 Flash 忙而丢拍。这是 XIP + VM 同占 Flash 时典型的协调手段。
  3. 稳定等待与功放静音:delay_10ms(50)(约 500ms)等待电源/时钟稳定;pa_mute(0) 立即静音功放,防止上电爆音(pop noise)。
  4. 音量记忆恢复:从 SYSMEM_INDEX_VOL 读取上次音量(0~31 有效),调用 dac_vol(0, vol) 恢复 DAC 音量——注意先静音再设音量、最后才取消静音的顺序,避免音量突变。
  5. 工作模式装载:从 SYSMEM_INDEX_SYSMODE 读出 work_mode;注释中保留了各模式的手动强制赋值方式,便于调试时固定进入某一模式(如强制 RECORD_MODE 验证录音链路)。

主循环与模式预擦除

while(1) 是框架的事件分发核心:每次迭代先 clear_all_message() 清空所有待处理消息(防止旧消息污染新模式的初始状态),再调用 sysmem_pre_erase_api() 做 VM 擦写预擦除——把后续模式切换中大概率要用的 Flash 擦除动作提前完成,从而缩短模式切换的阻塞时间。循环体剩余部分(本次读取范围内未展开)负责向当前模式模块投递事件并执行其循环体,模式模块内部自行完成消息消费。

工作模式系统

work_mode 的装载与记忆

work_mode 是文件级全局变量(u8 work_mode;),由 SYSMEM_INDEX_SYSMODE 持久化。框架支持的模式在头文件 app_modules.h/app_config.h 中定义,包括:

模式宏含义对应模块
MUSIC_MODE音乐播放(本地/SD)music_play
RECORD_MODE录音record_mode
AUX_MODELinein 音频输入linein_mode
MIDI_DEC_MODEMIDI 解码播放midi_dec_mode
MIDI_KEYBOARD_MODEMIDI 键盘模式midi_keyboard_mode
SIMPLE_DEC_MODE简单解码模式simple_decode
REMOTECONTROL_MODE遥控器模式rc_app
LOUDSPEAKER_MODE对箱(TWS 音箱对)模式loudspk_mode
RTC_MODE时钟模式rtc_mode
RF_RADIO_MODERF 收音模式(RF_RADIO_EN 使能时)rf_radio_app

模式模块通过 app_modules.h 统一暴露给框架,每个模块实现自己的消息循环、按键处理和状态机。框架对模式的约束只有两条:入口函数签名一致(可被 work_mode 分发调用)与 退出时写回 SYSMEM_INDEX_SYSMODE(保证下次上电恢复同一模式)。

模式的切换语义

模式切换并非函数调用式跳转,而是"写回存储 + 重启主循环":切换请求被编码为一条系统消息,模式模块处理后更新 work_mode 并写 SYSMEM_INDEX_SYSMODE,随后主循环继续按新 work_mode 分发。clear_all_message() 保证切换瞬间不会有半途消息残留在队列中。这种设计避免了深层次函数调用栈,使每个模式的状态机保持独立、可静态分析,也便于在 Flash 上做 overlay(同一段 Flash 代码区域按模式覆盖加载)。

定时轮询循环

框架维护两个 tick 级循环,均无独立任务、直接在中断/主循环上下文中执行,从而节省任务栈 RAM。

tick_timer_ram_loop() —— LED 扫描

AT(.tick_timer.text.cache.L2)
void tick_timer_ram_loop(void)
{
#if LED_5X7
    LED5X7_scan();
#endif
}

Source: app.c

该函数被显式放入 L2 缓存段(AT(.tick_timer.text.cache.L2)),与链接脚本中 *(.*.text.cache.L2) 收集规则对应——LED 5×7 点阵扫描需要严格周期性,放在缓存段保证其代码在 IRAM/缓存中稳定命中,不受 XIP Flash 总线竞争影响。该函数同时被注册为 VM 擦写期间的 tick 响应回调(见上文启动序列第 2 步)。

app_timer_loop() —— 外设分时轮询

void app_timer_loop(void)
{
    if ((0 == (tick_cnt % 2))) {
        adc_scan();
    }
#if (TCFG_PC_ENABLE || TCFG_UDISK_ENABLE)
#if defined (BLE_EN) && (BLE_EN)
#if (0 == (RF_REMOTECONTROL_MODE_EN & TRANS_DATA_SPPLE_EN))
    if (0 == (tick_cnt % 25)) {
        void usb_hotplug_detect(void *argv);
        usb_hotplug_detect(NULL);
    }
#endif
#endif
#endif
#if SYS_TIMER_EN
    timer_task_scan();
#endif
#if (FLASH_CACHE_ENABLE == 1)
#if TFG_EXT_FLASH_EN
    if (get_flash_cache_timer()) {
        if (0 == (tick_cnt % 50)) {
            bsp_post_event(B_EVENT_100MS);
        }
    }
#endif
#endif
#if TCFG_PC_ENABLE
    static u16 cnt = 0;
    cnt++;
    if (0 == (cnt % 10)) {
        uac_inc_sync();
    }
    if (cnt >= 500) {
        uac_1s_sync();
        cnt = 0;
    }
#endif
#if TFG_SD_EN
    if (0 == (tick_cnt % 100)) {
        extern void sd0_dev_detect(void *p);
        sd0_dev_detect(NULL);
    }
#endif
#if TCFG_CHARGE_ENABLE
    charge_timer_handle();
#endif
}

Source: app.c

这是一个典型的分时复用(time-division multiplexing)轮询器,节拍由 tick_cnt 驱动,各外设按不同周期挂接:

周期(tick)任务使能宏
每 2 tickadc_scan() ADC 扫描(按键/电池检测)恒开
每 25 tickusb_hotplug_detect() USB 热插拔TCFG_PC_ENABLE || TCFG_UDISK_ENABLE 且 BLE 使能且非 RF 遥控/透传组合
每 ticktimer_task_scan() 软件定时器SYS_TIMER_EN
每 50 tick(缓存定时器触发时)bsp_post_event(B_EVENT_100MS) 100ms 事件FLASH_CACHE_ENABLE + TFG_EXT_FLASH_EN
每 10 次调用uac_inc_sync() UAC 增量同步TCFG_PC_ENABLE
每 500 次调用uac_1s_sync() UAC 1 秒同步TCFG_PC_ENABLE
每 100 ticksd0_dev_detect() SD 卡检测TFG_SD_EN
每 tickcharge_timer_handle() 充电状态机TCFG_CHARGE_ENABLE

设计意图:把周期差异大的外设轮询合并进同一个 tick 回调,避免为每个外设创建独立线程。tick_cnt % N 的取模方式天然抗抖(无需额外计时变量),同时保证最紧急的 ADC 采样以最高频率执行。各任务通过编译期宏裁剪,未使能的外设完全不产生代码与执行开销。

核心流程

启动 → 模式分发 → 外设轮询(端到端时序)

sequenceDiagram
    participant ROM as MaskROM/Boot
    participant LD as 链接脚本 (app_ld.c)
    participant APP as app.c
    participant SYS as 系统服务 (sysmem/vm/wdt)
    participant MOD as 模式模块 (music/record/...)
    participant TICK as tick 中断层

    ROM->>LD: 跳转 _start (ENTRY)
    LD->>APP: 初始化 .data/.bss,进入 app_system_init()
    APP->>APP: app_system_init() 系统基础初始化
    APP->>APP: mbox_flash_main() 入口
    APP->>SYS: get_up_suc_flag() 查询升级标志
    alt 升级刚完成
        APP->>SYS: wdt_close() + while(1) 等待断电
    else 正常启动
        APP->>SYS: vm_isr_response_index_register(IRQ_TICKTMR_IDX)
        APP->>SYS: delay_10ms(50) 等待稳定
        APP->>APP: pa_mute(0) 静音防爆音
        APP->>SYS: sysmem_read_api(SYSMEM_INDEX_VOL) 恢复音量
        APP->>SYS: sysmem_read_api(SYSMEM_INDEX_SYSMODE) 读取 work_mode
        loop 主循环 while(1)
            APP->>APP: clear_all_message() 清空消息
            APP->>SYS: sysmem_pre_erase_api() 模式切换预擦除
            APP->>MOD: 按 work_mode 分发事件
            MOD-->>APP: 模式消息处理/状态机推进
            TICK->>APP: tick_timer_ram_loop() LED5X7 扫描 (L2 缓存段)
            TICK->>APP: app_timer_loop() ADC/USB/SD/充电/UAC 分时轮询
        end
    end

时序要点:整个应用生命周期只有一个主循环 + 一个 tick 中断回调集合。模式模块不是并行任务,而是被主循环"轮流调用"的协程式状态机;外设轮询则被压缩进 tick 回调,保证 RAM 占用可预测。升级完成场景被单独优先处理,是框架对"烧录后首次上电"这一高风险窗口的显式保护。

模式切换时序

sequenceDiagram
    participant KEY as 按键/遥控事件
    participant MOD as 当前模式模块
    participant APP as mbox_flash 主循环
    participant SYS as sysmem (VM 存储)

    KEY->>MOD: 模式切换按键事件
    MOD->>SYS: 写 SYSMEM_INDEX_SYSMODE = 新模式
    MOD-->>APP: 返回/请求退出
    APP->>APP: clear_all_message()
    APP->>SYS: sysmem_pre_erase_api() 预擦除
    APP->>APP: work_mode 已更新,重新分发
    APP->>MOD: 进入新模式事件循环

链接脚本与内存布局

app_ld.c 的存储区定义

post_build/bd49/mbox_flash/app_ld.c 定义了 mbox_flash 方案的完整内存地图:

#define _ADDR_RAM0_END    0x3f10000
#define _ADDR_RAM0_START  (0x3f00000 + MAX_IRQ_ENTRY_NUM * 4 + 0x200)
#define _ADDR_CACHE_RAM_START 0x3f30000

UPDATA_SIZE     = 0x80;
UPDATA_BEG      = _ADDR_RAM0_END - UPDATA_SIZE;
ICACHE_RAM_SIZE = 0x4000;

MEMORY
{
#if ICACHE_RAM_TO_RAM_ENABLE
	cache_ram			: ORIGIN =  _ADDR_CACHE_RAM_START + ICACHE_RAM_SIZE - ICACHE_RAM_TO_RAM,   LENGTH = ICACHE_RAM_TO_RAM
#endif
    app_code(rx)        : ORIGIN = _SFC_MEMORY_START_ADDR + 0x100,  LENGTH = 32M-0x100
    irq_vec(rx)         : ORIGIN = _IRQ_MEM_ADDR,                   LENGTH = MAX_IRQ_ENTRY_NUM * 4
    ram0(rw)            : ORIGIN = _ADDR_RAM0_START,                LENGTH = _ADDR_RAM0_END - _ADDR_RAM0_START - 0x24
    boot_ram(rw)        : ORIGIN = _ADDR_RAM0_END - 0x24,           LENGTH = 0x24
}
ENTRY(_start)

Source: app_ld.c

各存储区的作用与设计意图:

存储区属性位置/大小用途
cache_ramrw0x3f30000 起,长度 ICACHE_RAM_TO_RAM(受 ICACHE_RAM_TO_RAM_ENABLE 控制)用户数据镜像到缓存 RAM,存放 .usr_data
app_coderx_SFC_MEMORY_START_ADDR + 0x100,长度 32M-0x100XIP 应用代码与只读常量;+0x100 偏移避开头部信息,超过 32M 需长跳转(注释注明)
irq_vecrx_IRQ_MEM_ADDR,长度 MAX_IRQ_ENTRY_NUM * 4中断向量表
ram0rw0x3f00000 + MAX_IRQ_ENTRY_NUM*4 + 0x200 至 0x3f10000-0x24L1 SRAM:数据、bss、栈、蓝牙协议栈数据
boot_ramrw0x3f10000 - 0x24,长度 0x24(36 字节)启动信息区 .boot_info,与 Boot 交换参数

app_code 长达 32M 的设计说明 SDK 面向大容量 SPI Flash;+0x100 保留给 Flash 头部(升级信息/密钥等)。UPDATA_BEG = _ADDR_RAM0_END - 0x80 在 RAM 顶部预留 128 字节更新参数区,供升级流程在 RAM 中传递数据。

关键 Section 布局

  • .data(ram0):data_buf_start 起,依次收集 .data_magic、.data、.common、.ble_app_data,中间内联 cache_Lx_code_text_begin/end 区(收集 *.text.cache.L1/L2/L3 与 audio_isr_text、log_ut_text),随后 include 蓝牙协议栈数据段(btstack_lib_data.ld、btctler_lib_data.ld)。
  • .ans_data_sec(ram0):集中放置 AEC/NS/NLP/FFT/noisegate 音频算法数据,便于统一管理算法内存。
  • .bss(NOLOAD):cpu0_sstack_begin/end 界定 CPU0 栈区(DONGLE 变体额外加 0xc00 栈深度),栈区前后布置 .stack_magic/.stack_magic0 哨兵用于栈溢出检测;之后是普通 bss 与蓝牙协议栈 bss。

Overlay 条件编译

app_ld.c 按应用变体动态选择 overlay 链接文件:

#if FULL_DUPLEX_RADIO
    #include "../post_build/bd49/mbox_flash/app_ld_overlay_fullduplex_radio.c"
#elif RUN_APP_RC
    #include "../post_build/bd49/mbox_flash/app_ld_overlay_rc.c"
#elif RUN_APP_DONGLE
    #include "../post_build/bd49/mbox_flash/app_ld_overlay_dongle.c"
#else
    #include "../post_build/bd49/mbox_flash/app_ld_overlay_custom.c"
#endif

Source: app_ld.c

设计意图:同一套应用代码,通过 overlay 覆盖 Flash 中同一段地址区域来切换不同产品形态(全双工收音、遥控器、Dongle、自定义)。overlay 机制让"一份框架、多种变体"成为可能,变体间共享的代码只保留一份,节省 Flash 空间。

Usage Examples

示例 1:向框架中接入一个新的工作模式

mbox_flash 框架通过 app_modules.h 统一接入模式模块。在 mbox_flash_main() 的模式装载阶段(app.c 第 166 行),框架只读取 SYSMEM_INDEX_SYSMODE 得到 work_mode;模式的具体分发发生在主循环体内。接入新模式需要:在 app_modules.h 声明模块入口、在 app.c 顶部 include 模块头文件(如 #include "loudspk_mode.h")、在模式枚举中定义新模式宏。框架侧无需修改启动序列——这正是"模式即模块"的解耦设计。

示例 2:调试时强制进入指定模式

    sysmem_read_api(SYSMEM_INDEX_SYSMODE, &work_mode, sizeof(work_mode));
    /* work_mode = MUSIC_MODE; */
    /* work_mode = RECORD_MODE; */
    /* work_mode = AUX_MODE; */
    /* work_mode = MIDI_DEC_MODE; */
    /* work_mode = MIDI_KEYBOARD_MODE; */
    /* work_mode = SIMPLE_DEC_MODE; */
    /* work_mode = REMOTECONTROL_MODE; */
    /* work_mode = LOUDSPEAKER_MODE; */
    /* work_mode = RTC_MODE; */
    /* work_mode = RF_RADIO_MODE; */

Source: app.c

这些注释是框架作者留给调试者的"后门":取消某一行注释即可绕过存储中的模式记忆、强制进入目标模式,用于快速验证单一模式的完整链路(例如验证 MIDI 键盘模式时固定 MIDI_KEYBOARD_MODE),无需反复修改存储或按键操作。

示例 3:在 tick 循环中挂接新的周期任务

#if TFG_SD_EN
    if (0 == (tick_cnt % 100)) {
        extern void sd0_dev_detect(void *p);
        sd0_dev_detect(NULL);
    }
#endif

Source: app.c

这是框架外设轮询的标准挂接模板:外部模块以 extern 声明暴露函数,框架在 app_timer_loop() 中以 tick_cnt % N 控制周期。新增外设时照此模式添加一个 #if 使能宏 + 取模判断即可,代价仅是一两条比较指令。

示例 4:将热路径代码放入缓存段

AT(.tick_timer.text.cache.L2)
void tick_timer_ram_loop(void)
{
#if LED_5X7
    LED5X7_scan();
#endif
}

Source: app.c

AT(...) 属性把需要确定性执行周期的函数放进 L2 缓存区,配合链接脚本 *(.*.text.cache.L2) 收集规则(app_ld.c),保证 LED 扫描等中断级任务不因 XIP 总线忙而抖动。这是 XIP 方案下"以缓存换实时性"的标准做法。

Configuration Options

框架行为由编译期宏控制,以下为主要配置项(定义于 app_config.h 及模块头文件):

宏类型默认/取值说明
work_modeu8运行时从 SYSMEM_INDEX_SYSMODE 读取当前工作模式:MUSIC_MODE/RECORD_MODE/AUX_MODE/MIDI_DEC_MODE/MIDI_KEYBOARD_MODE/SIMPLE_DEC_MODE/REMOTECONTROL_MODE/LOUDSPEAKER_MODE/RTC_MODE/RF_RADIO_MODE
LED_5X7bool0使能 5×7 LED 点阵扫描(tick_timer_ram_loop)
RF_RADIO_ENbool0使能 RF 收音模块 rf_radio_app
BLE_ENbool1使能 BLE 协议栈(影响 USB 热插拔检测周期分支)
TCFG_PC_ENABLEbool0使能 PC(UAC)模式,开启 uac_inc_sync/uac_1s_sync 周期同步
TCFG_UDISK_ENABLEbool0使能 U 盘模式,加入 USB 热插拔检测
TFG_SD_ENbool0使能 SD 卡检测(每 100 tick)
TFG_EXT_FLASH_ENbool0使能外部 Flash 缓存定时器(每 50 tick 发 B_EVENT_100MS)
FLASH_CACHE_ENABLEbool1使能 Flash 缓存机制
SYS_TIMER_ENbool0使能软件定时器 timer_task_scan
TCFG_CHARGE_ENABLEbool0使能充电管理 charge_timer_handle
KEY_IR_ENbool0使能红外按键(定义 Sys_IRInput/Input_Number)
FULL_DUPLEX_RADIObool0链接 overlay:全双工收音变体
RUN_APP_RCbool0链接 overlay:遥控器变体
RUN_APP_DONGLEbool0链接 overlay:Dongle 变体(栈区 +0xc00)
ICACHE_RAM_TO_RAM_ENABLEbool0使能 cache_ram 区(.usr_data 镜像)
MAX_IRQ_ENTRY_NUMint平台默认中断向量条目数,影响 ram0 起始地址

注意:work_mode 是持久化配置(存储于 VM 的 SYSMEM_INDEX_SYSMODE),其余均为编译期宏。宏之间有关联约束,例如 USB 热插拔检测仅在 (TCFG_PC_ENABLE \|\| TCFG_UDISK_ENABLE) 且 BLE 使能且 (RF_REMOTECONTROL_MODE_EN & TRANS_DATA_SPPLE_EN) == 0 时生效,配置组合需与链接 overlay 变体一致。

API Reference

void mbox_flash_main(void)

应用主入口。在 app_system_init() 之后被调用(app.c 第 278 行),完成升级守卫、VM 中断注册、音量/模式恢复后进入主循环。

  • 参数:无
  • 返回:无(正常路径永不返回,进入 while(1))
  • 行为:升级完成标志置位时 wdt_close() 并死循环;否则恢复音量与 work_mode,在主循环中分发模式事件并驱动外设轮询。

void app_timer_loop(void)

tick 级外设分时轮询回调。以 tick_cnt 为节拍,按周期分派 ADC 扫描、USB 热插拔、软件定时器、Flash 缓存事件、UAC 同步、SD 检测与充电处理。

  • 参数:无
  • 返回:无
  • 调用上下文:tick 中断或主循环低优先级位置;内部使用 static 计数变量(UAC),可重入性由调用方保证。

void tick_timer_ram_loop(void)

LED 5×7 点阵扫描回调,被 AT(.tick_timer.text.cache.L2) 放入 L2 缓存段;同时注册为 VM 擦写 Flash 期间的 tick 响应函数(vm_isr_response_index_register(IRQ_TICKTMR_IDX))。

  • 参数:无
  • 返回:无

系统服务 API(框架调用,非框架定义)

函数用途
sysmem_read_api(SYSMEM_INDEX_VOL, &vol, sizeof(vol))从 VM 系统存储读取音量(0~31 有效)
sysmem_read_api(SYSMEM_INDEX_SYSMODE, &work_mode, sizeof(work_mode))读取持久化工作模式
sysmem_pre_erase_api()模式切换前的 VM 预擦除,缩短切换阻塞时间
vm_isr_response_index_register(IRQ_TICKTMR_IDX)注册 VM 擦写期间响应的中断索引
get_up_suc_flag()查询设备升级完成标志
dac_vol(0, vol)设置 DAC 音量
pa_mute(0)功放静音控制
bsp_post_event(B_EVENT_100MS)投递 100ms 周期事件

这些 API 的具体实现在系统库/其他模块中,mbox_flash 框架仅以声明方式调用,保持应用层与系统层的接口边界清晰。

故障模式、边界情况与并发

升级完成后的死循环守卫

get_up_suc_flag() 为真时框架进入 wdt_close(); while(1);。这是有意的"停机"而非缺陷:固件升级完成后,Flash 内容刚被改写,立即执行业务代码可能读到不一致的代码/数据;同时升级工具可能仍占用 Flash 总线。该守卫确保升级流程以干净状态结束,用户重新上电后(标志位已被升级流程清除)才正常启动。风险点:若升级标志未正确清除,设备将表现为"上电后无反应"——排查时应优先检查升级流程对标志位的清理逻辑。

VM 擦写与 XIP 的 Flash 总线竞争

mbox_flash 方案中,应用代码与 VM 数据同处 SPI Flash。VM 擦写期间 SFC 总线被独占,XIP 取指会被阻塞。框架的应对有三层:

  1. vm_isr_response_index_register(IRQ_TICKTMR_IDX)——擦写期间仍响应 tick 中断,执行 tick_timer_ram_loop()(LED 扫描)这类短任务;
  2. sysmem_pre_erase_api()——在模式切换前预擦除,把擦除时间移出对实时性敏感的阶段;
  3. 热路径代码(AT(.tick_timer.text.cache.L2))放入缓存段,即使 Flash 忙也能从缓存取指。

若产品在 VM 擦写期间出现 LED 闪烁异常或音频卡顿,应检查以上三层的配置是否完整。

并发模型:单循环 + tick 回调

框架没有多线程,并发维度只有两个:主循环上下文与 tick 中断上下文。共享状态(work_mode、tick_cnt、Sys_IRInput/Input_Number)的读写需要遵循"主循环写、tick 读"或反之的单向约定,避免中断与主循环同时修改同一变量。tick_cnt % N 的轮询判断天然原子(单字节读),但若在轮询回调中访问多字节共享结构,仍需自行保证原子性。UAC 同步使用函数内 static 计数器,说明框架允许轮询回调持有自己的私有状态——这是"无任务"模型的 RAM 节省代价:每个回调都要自行管理状态生命周期。

边界条件

  • 音量越界:sysmem_read_api 读回的音量只有在 vol <= 31 && res == sizeof(vol) 时才应用,损坏/越界的存储值被安全忽略(不写入 DAC),防止音量寄存器越界。
  • 栈溢出哨兵:链接脚本在 CPU0 栈区前后布置 .stack_magic/.stack_magic0(app_ld.c),栈越界会先破坏哨兵,便于运行时检测。Dongle 变体额外 +0xc00 栈深度,说明不同变体对栈的需求差异由链接脚本显式管理。
  • 代码超过 32M:链接脚本注释明确"超过 32M 运行代码需要使用长跳转",app_code 容量上限 32M-0x100 是 XIP 线性寻址边界。

性能与运维

性能特征

  • XIP 取指 vs IRAM:绝大多数代码从 SPI Flash 取指,速度低于 IRAM;框架用两级手段缓解——L1/L2/L3 缓存段收集热路径函数(.text.cache.L1/L2/L3、audio_isr_text),以及将音频算法数据集中到 .ans_data_sec 便于 cache 友好访问。
  • 轮询开销:app_timer_loop() 每个 tick 的固定开销为几次取模比较(<1μs 级),即使所有外设使能也可忽略;未使能的模块完全不参与编译(#if 裁剪)。
  • 启动时间:delay_10ms(50) 固定 500ms 稳定等待,属于有意取舍(防止上电抖动)而非可优化项;模式切换的耗时大头在 VM 预擦除,已通过 sysmem_pre_erase_api() 前置化。

运维与调试

  • 强制模式:临时取消 work_mode 赋值注释(app.c)可固定进入目标模式,是定位"某模式下才出现"问题的首选手段。
  • 日志:LOG_TAG "[mbox_app]"(app.c)标识框架日志;启动关键路径有 Mbox-Flash App、----device update end----、powerup set vol : %d 三条日志,可用于判断启动阶段。
  • 构建/烧录:工程文件 sdk/AW30N_mbox_flash.cbp(Code::Blocks 工程)与烧录脚本 post_build/bd49/mbox_flash/download_bat.c(输出 "BD49 mbox_flash" 烧录信息)构成该方案的构建-下载链路。

Extension Points

  1. 新增工作模式:在模式枚举中定义新模式宏 → 在 app_modules.h 声明模块接口 → 在 app.c include 模块头文件 → 在主循环分发处添加分支。框架对模式的唯一契约是"事件循环 + 退出时写回 SYSMEM_INDEX_SYSMODE"。
  2. 新增周期外设:照 TFG_SD_EN 模板在 app_timer_loop() 中增加 #if 宏 + tick_cnt % N 分支即可;周期选择需考虑与现有任务错峰(如 100ms 事件与 SD 检测的相位)。
  3. 新增产品变体:通过 app_ld_overlay_*.c 链接 overlay 机制(app_ld.c),在 app_ld.c 中增加 #elif 分支并编写对应 overlay 段文件,即可复用同一应用代码生成不同 Flash 布局的固件。
  4. 热路径优化:对需要确定执行周期的新中断回调,使用 AT(.xxx.text.cache.L2) 属性放入缓存段,并同步在 app_ld.c 的 cache 收集区添加对应通配符。

Tests

本次读取的源文件(app.c、app_ld.c)未包含单元测试代码;mbox_flash 框架的验证主要依赖硬件在环测试:模式切换回归(各 work_mode 装载/退出)、升级流程(烧录后首启守卫)、VM 擦写期间的 tick 响应、以及各外设使能组合下的轮询稳定性。测试用例一般通过"强制模式 + 日志断言"(Mbox-Flash App/powerup set vol)在真机上进行。SDK 根目录 补丁包/ 目录下的裁剪/升级补丁包(如 AW30N_v1.2.0_SDK裁剪到256KB以内的补丁包)可视为对该框架的配置级回归验证。

Related Links

  • app.c(mbox_flash 应用入口) —— 本文档核心源文件:主入口、模式装载、tick 轮询
  • app_ld.c(mbox_flash 链接脚本) —— 内存布局、overlay 变体选择
  • AW30N_mbox_flash.cbp(Code::Blocks 工程) —— 该方案的构建工程
  • download_bat.c(烧录脚本) —— "BD49 mbox_flash" 烧录流程
  • 相关系统能力(独立页面):VM 存储与 vm_sfc、sysmem 系统存储、BLE 协议栈(bt_ble)、USB 协议栈、音乐播放(music_play)、录音(record_mode)
Next
板级支持包 (BSP)