杰理 SDK 文档中心
首页
首页
  • 入门指南

    • SDK 概述与芯片平台
    • 环境搭建与开发工具链
    • 编译、烧录与快速开始
  • 应用层开发

    • 语音玩具应用 voice_toy
    • 扩音器应用 voice_enhanced
    • 语音功能状态机 voice_func
    • 应用公共框架与配置
  • 音频子系统

    • 音频解码器与 MIDI 播放
    • 音频编码与录音
    • 音效算法(ANS、变调、变声、混响)
    • 音频输出、功放与硬件重采样
  • 存储与文件系统

    • 文件系统层(FAT、NOR_FS、SYDF 等)
    • 存储设备与设备管理
    • 参数存储 VM 与保留区
  • 系统机制

    • 消息与事件机制
    • 电源管理与低功耗
    • 固件升级机制
    • 外设驱动(按键、红外、SPI、USB)
    • 实时时钟与定时器
  • 构建系统与工具

    • 构建系统(Makefile 与 Code::Blocks)
    • 编译后处理与语音资源打包
  • 硬件平台与文档

    • 芯片平台与启动流程
    • 硬件文档、规格书与原理图

固件升级机制

AD24N SDK 的固件升级(Firmware Update)子系统:定义统一的升级通道枚举(USB/SD/UART/BT/BLE/双 Bank 等)、跨阶段升级参数结构 UPDATA_PARM、升级目标注册机制与升级结果校验流程,负责将 .ufw 升级文件安全地写入 Flash 并引导加载器(loader/uboot)完成刷写。

Purpose and Scope

本文档介绍 AD24N 固件升级机制的完整实现,覆盖:

  • 升级通道类型枚举(UPDATA_TYPE)与升级触发方式(UPGRADE_TYPE)
  • 应用层与 loader 之间传递的升级参数结构 UPDATA_PARM 及其扩展参数
  • 升级状态机(UPDATE_STATE_T)、升级目标注册宏(REGISTER_UPDATE_TARGET)
  • 升级结果校验(UPDATA_RESULT)、开机恢复与结果上报流程
  • 相关 API(update.h、dev_update.h、uart_update.h)与配置开关

以下主题属于相邻页面,不在本文展开:USB MSD 升级的具体实现(见 sdk/app/bsp/common/usb/device/msd_upgrade.c)、UART 升级协议细节(见 sdk/app/bsp/common/uart_update/uart_update.c)、玩具类应用升级(sdk/app/src/voice_func/toy_update/toy_update.c)以及设备端升级执行器 sdk/app/bsp/common/update/dev_update.c。

概述

AD24N 的固件升级采用「应用层下载 + loader 刷写」的两段式架构:

  1. 应用层(SDK) 负责通过不同通道(USB、SD 卡、UART、BLE/SPP、双 Bank 等)获取升级文件(默认 *.ufw),并做初步校验。
  2. 应用层把升级参数(文件路径、目标地址、结果标志等)打包成 UPDATA_PARM 传给底层 loader(uboot),随后跳转复位进入 loader。
  3. loader 根据参数完成 Flash 写入,并把结果写回 UPDATA_PARM.parm_result;下次启动时 SDK 通过 update_result_deal() 读取结果,决定是否恢复用户区(vm_need_recover)或上报成功信息(succ_report_t)。

核心设计意图:

  • 参数段(UPDATA_PARM)固定在 Flash 指定地址(UPDATA_FLAG_ADDR),使 SDK 与 loader 之间无需函数调用即可通过内存映射交换数据,天然支持跨镜像(应用 → loader)传递。
  • 魔法数 UPDATA_MAGIC (0x5A00) 防止 CRC 为 0 的误判;升级结果枚举的起始值即该魔法数。
  • 升级目标(update target)通过段属性注册(sec(.update_target)),将「通道驱动关闭」这类升级前置动作与主流程解耦,新通道只需注册自己的 update_target 即可被 list_for_each_update_target 自动遍历执行。

架构

flowchart TD
    subgraph sg_Channels["升级通道 (Upgrade Channels)"]
        USB["USB 升级<br/>msd_upgrade.c / USB_HID"]
        UART["UART 升级<br/>uart_update.c"]
        SD["SD 卡升级<br/>UPDATA_SD"]
        BT["BT/BLE/SPP 升级<br/>toy_update.c / OTA"]
        DUAL["双 Bank 升级<br/>dual_bank_updata_api"]
        TESTBOX["TestBox 升级<br/>testbox_uart_update"]
    end

    subgraph sg_App["应用层 (SDK App)"]
        INIT["app_update_init()"]
        HANDLE["app_update_handle(msg)"]
        UPDATE_C["update.c<br/>update_mode_api_v2()"]
        DEVC["dev_update.c<br/>dev_update_by_change_stack()"]
        RESULT["update_result_deal()"]
    end

    subgraph sg_Loader["底层引导 (Loader/Uboot)"]
        PARM["UPDATA_PARM<br/>(UPDATA_FLAG_ADDR 固定地址)"]
        FLASH["Flash 写入"]
        BOOT["开机启动检查<br/>update_success_boot_check()"]
    end

    USB --> INIT
    UART --> INIT
    SD --> INIT
    BT --> INIT
    DUAL --> INIT
    TESTBOX --> INIT

    INIT --> HANDLE
    HANDLE --> UPDATE_C
    UPDATE_C --> DEVC
    DEVC -->|"填写参数并跳转"| PARM
    PARM --> FLASH
    FLASH -->|"结果写回 parm_result"| BOOT
    BOOT -->|"读取结果"| RESULT
    RESULT -->|"UPDATA_SUCC 则恢复 VM"| INIT

图中各环节的职责:

  • 升级通道:各种获取升级文件的方式,统一由 app_update_init() 初始化、app_update_handle(int msg) 分发消息驱动。
  • update.c:升级主控,维护 g_updata_flag、ota_status、succ_report 等全局状态;update_mode_api_v2() 是核心入口,接收 UPDATA_TYPE、私有参数填充回调与跳转前处理回调。
  • dev_update.c:设备级升级执行,dev_update_check() 校验升级文件,dev_update_by_change_stack() 切换栈执行升级写入。
  • UPDATA_PARM:SDK 与 loader 之间的唯一契约,256 字节,存放于固定 Flash 地址,含文件路径、ota_addr、扩展参数(ext_arg)等。
  • 结果回读:loader 把结果写入 parm_result,SDK 启动后通过 update_result_deal() 消费,用于判断是否执行 vm_need_recover() 恢复用户配置。

升级通道类型:UPDATA_TYPE

update.h 用枚举集中定义了 SDK 支持的全部升级通道,这是升级机制的核心抽象——任何升级入口最终都归结为一个 UPDATA_TYPE 值,并伴随一个 UPDATA_PARM 传给 loader:

typedef enum {
    USB_UPDATA = UPDATA_MAGIC,      //0x5A00
    SD0_UPDATA,                     //0x5A01
    SD1_UPDATA,
    PC_UPDATA,
    UART_UPDATA,
    BT_UPDATA,
    BLE_APP_UPDATA,
    SPP_APP_UPDATA,
    DUAL_BANK_UPDATA,
    BLE_TEST_UPDATA,
    NORFLASH_UPDATA,
    TESTBOX_UART_UPDATA,
    TESTBOX_UART_UPDATA_2,
    //NOTE:以上的定义不要调整,新升级方式在此添加,注意加在USER_NORFLASH_UFW_UPDATA之前;
    USER_NORFLASH_UFW_UPDATA,
    USER_LC_FLASH_UFW_UPDATA,
    USB_HID_UPDATA,

    NON_DEV = 0xFFFF,
} UPDATA_TYPE;

Source: update.h

设计要点:

  • 枚举值从 UPDATA_MAGIC(0x5A00)开始连续编号,因此「类型」与「结果」共用同一套编号空间起始值,且类型值天然携带魔法数校验语义。
  • 注释明确约定:已定义的顺序不可调整,因为类型值会持久化到 Flash 参数段,老固件/loader 依赖这些数值;新通道必须追加在 USER_NORFLASH_UFW_UPDATA 之前。
  • NON_DEV = 0xFFFF 表示无有效设备,作为非法值哨兵。
  • 触发方式与通道类型解耦:UPGRADE_TYPE 枚举描述「用户如何进入升级」(如 UPGRADE_USB_HARD_KEY 硬按键、UPGRADE_USB_SOFTKEY 软件命令、UPGRADE_UART_SOFT_KEY、UPGRADE_UART_ONE_WIRE_HARD_KEY 单线硬按键),而 UPDATA_TYPE 描述「升级内容从哪个设备来」。
typedef enum {
    UPGRADE_NULL = 0,
    UPGRADE_USB_HARD_KEY,
    UPGRADE_USB_SOFTKEY,
    UPGRADE_UART_SOFT_KEY,
    UPGRADE_UART_ONE_WIRE_HARD_KEY,
} UPGRADE_TYPE;

Source: update.h

跨阶段参数契约:UPDATA_PARM

SDK 与 loader 之间不通过函数调用通信,而是通过一块固定地址的共享内存交换升级参数。update.h 中定义了参数段的布局:

#define UPDATA_FLAG_ADDR		((void *)((u32)&UPDATA_BEG + 0x08))
#define BOOT_STATUS_ADDR		((void *)((u32)&UPDATA_BEG)) //预留8个bytes

#define UPDATA_MAGIC            (0x5A00)        //防止CRC == 0 的情况
#define UPDATA_KEEP_IO_ENABLE   0               //升级时保持住IO功能

typedef struct _UPDATA_PARM {
    u16 parm_crc;
    u16 parm_type;              //UPDATA_TYPE:sdk pass parm to uboot
    u16 parm_result;            //UPDATA_TYPE:uboot return result to sdk
    u16 magic;					//0x5441
    //8byte
    union {
        struct { u8  file_path[32]; };          //updata file path
        struct { u8  file_patch[32]; };         //updata file path
    };
    u8  parm_priv[32];          //sd updata
    //64byte
    u32 ota_addr;
    u16 ext_arg_len;
    u16 ext_arg_crc;
    //8 byte
} UPDATA_PARM;

Source: update.h 与 update.h

字段语义:

字段方向说明
parm_crcSDK→loader参数段 CRC 校验,防止参数被破坏
parm_typeSDK→loaderUPDATA_TYPE 值,告知 loader 升级来源
parm_resultloader→SDKUPDATA_RESULT 值,loader 回写升级结果
magic双向0x5441(UPDATE_PARAM_MAGIC),标识参数段有效
file_path[32]SDK→loader升级文件路径,如 /*.ufw
parm_priv[32]SDK→loader私有参数(如 SD 升级的 IO 配置)
ota_addrSDK→loaderOTA 目标地址
ext_arg_len/ext_arg_crcSDK→loader扩展参数块长度与 CRC

magic 使用 0x5441 而非 UPDATA_MAGIC,是为了在结果枚举(从 0x5A00 起)与参数段魔法数之间保持独立,避免误判。

扩展参数通过 ext_arg_t 以 type/len/data 三元组传递,EXT_ARG_TYPE 枚举定义了可携带的附加信息,覆盖从 RF 校准到升级行为的方方面面:

enum EXT_ARG_TYPE {
    EXT_LDO_TRIM_RES = 0,
    EXT_JUMP_FLAG,
    EXT_BT_MAC_ADDR,
    EXT_RF_PA_INFO,
    EXT_RESERVED_UPDATE,        // 用于sdk传入参数控制是否升级预留区域
    EXT_RESET_TIME_WITHOUT_CONN,// 用于sdk传入未连接状态保持多久就重启的超时时间
    EXT_SD_IO_INFO,
    EXT_BT_WLA_INFO,
    EXT_MUTIL_UPDATE_NAME = 0x8,
    EXT_USER_API_BIN_INFO,
    EXT_NEW_SDK_UPD_AGAIN,
    EXT_NEW_FILENAME,
    EXT_KEEP_ROMIO_INFO = 0x10,
    EXT_TYPE_MAX = 0xff,
};

Source: update.h

例如 EXT_BT_MAC_ADDR 用于在升级过程中保留蓝牙 MAC,EXT_KEEP_ROMIO_INFO 用于保持 ROM IO 配置,EXT_RESET_TIME_WITHOUT_CONN 让 loader 在未连接状态下等待指定时间后自动重启——这些都是「升级不丢配置、不卡死」的关键设计。

升级状态机与结果枚举

UPDATE_STATE_T 定义了应用层升级任务的生命周期:

typedef enum _UPDATE_STATE_T {
    UPDATE_TASK_INIT,
    UPDATE_CH_INIT,
    UPDATE_CH_SUCESS_REPORT,
    UPDATE_CH_EXIT,
} UPDATE_STATE_T;

Source: update.h

升级结果枚举与 UPDATA_MAGIC 对齐,使「未升级」状态携带魔法数语义:

typedef enum {
    UPDATA_NON = UPDATA_MAGIC,   //0x5A00 初始/无升级
    UPDATA_READY,                //已就绪
    UPDATA_SUCC,                 //升级成功
    UPDATA_PARM_ERR,             //参数错误
    UPDATA_DEV_ERR,              //设备错误
    UPDATA_KEY_ERR,              //密钥/校验错误
} UPDATA_RESULT;

Source: update.h

update.c 用两个位标志进一步细化状态:DEVICE_UPDATE_KEY_ERR BIT(30) 标记升级密钥错误、DEVICE_FIRST_START BIT(31) 标记设备首次启动。update_result_set()/update_result_get() 封装对 UPDATA_FLAG_ADDR 的读写,vm_need_recover() 则根据 g_updata_flag 的低 16 位是否等于 UPDATA_SUCC 决定是否恢复用户 VM 区。

升级目标注册机制

为了让每个升级通道在跳转 loader 前执行自己的清理动作(如关闭蓝牙、关闭外设驱动),SDK 提供了基于链接器段的注册机制:

struct update_target {
    char *name;
    update_handler_t driver_close;
};

#define REGISTER_UPDATE_TARGET(target) \
        const struct update_target target sec(.update_target)

extern const struct update_target update_target_begin[];
extern const struct update_target update_target_end[];

#define list_for_each_update_target(p) \
    for (p = update_target_begin; p < update_target_end; p++)

Source: update.h

使用方式:任意模块用 REGISTER_UPDATE_TARGET(my_target) 声明一个 const struct update_target,链接器会把它放入 .update_target 段;升级流程通过 list_for_each_update_target(p) 遍历 update_target_begin 到 update_target_end,逐一调用 driver_close,从而在跳转前统一关闭各外设驱动(如 hci_controller_destory()、ll_hci_destory()、wifi_det_close())。这是典型的编译期注册 + 段遍历模式(类似 Linux 的 __initcall),新增通道无需改动主流程代码。

核心流程

一次完整升级的执行时序

sequenceDiagram
    participant APP as 应用任务 (app)
    participant UPD as update.c
    participant TGT as update_target 段
    participant DEV as dev_update.c
    participant LOADER as Loader/Uboot
    participant FLASH as Flash

    APP->>UPD: app_update_init() 注册回调
    APP->>UPD: app_update_handle(msg) 收到升级消息
    UPD->>UPD: 设置 g_updata_flag / ota_status
    UPD->>TGT: list_for_each_update_target 遍历
    TGT-->>UPD: 依次调用 driver_close() 关闭外设驱动
    UPD->>DEV: update_mode_api_v2(type, priv_fill, jump_hdl)
    DEV->>DEV: dev_update_check(logo, check_flag) 校验升级文件
    DEV->>UPD: update_param_priv_fill(p, priv, len) 填充私有参数
    UPD->>LOADER: 填写 UPDATA_PARM(parm_type, file_path, ota_addr)
    UPD->>LOADER: 跳转复位 (common_update_before_jump_reset_handle)
    LOADER->>FLASH: 按 parm_type 读取文件并写入 Flash
    FLASH-->>LOADER: 写入完成
    LOADER->>UPD: 回写 parm_result (UPDATA_SUCC/ERR)
    Note over UPD: 设备重启
    APP->>UPD: update_result_deal() 读取结果
    UPD->>UPD: update_success_boot_check() 校验成功启动
    UPD->>APP: vm_need_recover() == true 时恢复用户 VM 区

主控实现要点

update.c 是升级主控的落地文件,其关键全局定义如下:

#define LOADER_NAME		"LOADER.BIN"
#define DEVICE_UPDATE_KEY_ERR  BIT(30)
#define DEVICE_FIRST_START     BIT(31)

//升级文件路径必须是短文件名(8+3)结构,仅支持2层目录
const char updata_file_name[] = "/*.ufw";
static u32 g_updata_flag = 0;
static volatile u8 ota_status = 0;
static succ_report_t succ_report;

bool vm_need_recover(void)
{
    log_info(">>>[test]:g_updata_flag = 0x%x\n", g_updata_flag);
    return ((g_updata_flag & 0xffff) == UPDATA_SUCC) ? true : false;
}

u16 *get_updata_flag_addr()
{
    return UPDATA_FLAG_ADDR;
}

char *get_last_update_device(void)
{
    u32 update_res = get_update_result();
    u32 update_type = get_update_type();
    ...
}

Source: update.c

要点解读:

  • updata_file_name[] = "/*.ufw" 是默认升级文件匹配模式;注释强调升级文件路径必须是短文件名(8+3)结构且仅支持两层目录——这是 loader 文件系统(FAT)的硬约束。
  • g_updata_flag 的低 16 位存放 UPDATA_RESULT,vm_need_recover() 以此判断是否需要恢复 VM——升级成功后用户配置区的数据(音量、蓝牙配对等)需要保留,若升级失败则丢弃。
  • ota_status 声明为 volatile u8,可能被中断/协议栈异步修改(如 get_ota_status() 外部查询)。
  • DEVICE_UPDATE_KEY_ERR/DEVICE_FIRST_START 两个高位标志用于区分「密钥错误」与「首次启动」,避免与低 16 位的升级结果混淆。
  • 启动时通过 get_last_update_device() 结合 get_update_result() 与 get_update_type() 还原上一次升级的来源设备与结果,用于语音/灯效提示(UPDATE_VOICE_REMIND/UPDATE_LED_REMIND 编译开关)。

使用示例

1. 发起一次升级(核心 API 组合)

应用通过 update_mode_api_v2() 发起升级,可传入私有参数填充回调与跳转前处理回调:

void update_mode_api_v2(UPDATA_TYPE type, void (*priv_param_fill_hdl)(UPDATA_PARM *p), void (*priv_update_jump_handle)(int type));
void update_param_priv_fill(UPDATA_PARM *p, void *priv, u16 priv_len);
u16 update_result_get(void);
bool device_is_first_start(void);
int update_result_deal(void);
void update_result_set(u16 result);
void update_clear_result(void);
bool update_success_boot_check(void);
void update_enter_cb_register(void *callback);
void app_update_handle(int msg);
int app_update_init(void);
void update_keep_io(UPDATA_PARM *p);

Source: update.h

典型调用链:app_update_init() 完成初始化并注册进入回调 → 收到升级消息后调用 app_update_handle(msg) → 内部按 UPDATA_TYPE 分发到对应通道 → 经 update_mode_api_v2() 组装参数 → update_param_priv_fill() 追加私有数据(如蓝牙 MAC)→ 跳转 loader。

2. 设备级升级校验与执行

dev_update.h 暴露设备侧接口,供 update.c 调用:

void *dev_update_get_parm(int type);
u16  dev_update_check(char *logo, bool check_flag);
u16 dev_update_by_change_stack(update_mode_info_t *p_info);

Source: dev_update.h

  • dev_update_get_parm(type):按类型取设备升级参数(如 UPDATA_SD 的 IO/波特率配置)。
  • dev_update_check(logo, check_flag):校验升级文件头部 logo 与标志,决定是否继续。
  • dev_update_by_change_stack(p_info):切换栈后执行升级——升级过程需要较大的栈空间,直接在主任务栈上运行可能溢出,故先切换到专用升级栈再执行 Flash 写入,完成后恢复。

3. 注册升级目标(自定义通道清理动作)

struct update_target {
    char *name;
    update_handler_t driver_close;
};

#define REGISTER_UPDATE_TARGET(target) \
        const struct update_target target sec(.update_target)

#define list_for_each_update_target(p) \
    for (p = update_target_begin; p < update_target_end; p++)

Source: update.h

新通道只需在任意 .c 文件中写 REGISTER_UPDATE_TARGET(my_target) 并实现 driver_close,升级跳转前框架会自动调用。update.c 中已经用 extern 声明了一批待关闭驱动:ll_hci_destory()、hci_controller_destory()、ram_protect_close()、hwi_all_close()、wifi_det_close()(weak 符号,未实现时打印提示),可见跳转 loader 前会关闭中断、关闭 RAM 保护、销毁蓝牙控制器,确保 loader 环境干净。

配置选项

升级机制的编译期/运行期开关分散在 app_config.h 与 update.h,update.c 顶部同时通过 #undef 覆盖了一批默认值:

配置项类型默认值说明
UPDATA_MAGIC宏0x5A00升级结果/类型枚举的起始魔法数,防止 CRC 为 0 的误判
UPDATA_KEEP_IO_ENABLE宏0升级时是否保持 IO 功能(置 1 则升级期间 IO 不释放)
UPDATE_PARAM_MAGIC宏0x5441UPDATA_PARM.magic 参数段魔法数
UPDATA_PARM_SIZE宏256参数段总大小(字节)
UPDATE_PRIV_PARAM_LEN宏32私有参数区长度
CONFIG_UPDATE_STORAGE_DEV_EN常量见编译配置存储设备(SD/NOR)升级使能
CONFIG_UPDATE_TESTBOX_UART_EN常量见编译配置TestBox UART 升级使能
CONFIG_UPDATE_APP_OTA_EN常量见编译配置应用 OTA 使能(依赖 rcsp_bluetooth.h)
CONFIG_UPDATE_TESTBOX_BLE_EN常量见编译配置TestBox BLE 升级使能
TESTBOX_UART_UPDATE_EN宏编译配置是否包含 testbox_uart_update.h
CONFIG_APP_OTA_EN宏编译配置是否包含 rcsp_bluetooth.h(OTA 依赖)
CONFIG_UPDATE_CHANGE_NAME宏1(update.c 覆盖)升级后是否修改设备名
CONFIG_UPDATE_JUMP_TO_MASK宏0(update.c 覆盖)升级是否跳转到 Mask ROM
TCFG_UI_ENABLE宏0(update.c 覆盖)升级期间 UI 使能
TCFG_BT_BACKGROUND_ENABLE宏0(update.c 覆盖)升级期间蓝牙后台运行
OTA_TWS_SAME_TIME_ENABLE宏0(update.c 覆盖)双耳同时 OTA

Source: update.h 与 update.c

update.c 顶部的 #undef 块是强制覆盖:即使 app_config.h 开了 UI/蓝牙/ANC 等功能,升级流程也会强制关闭这些无关模块,保证升级期间系统资源(CPU、Flash、中断)完全让给升级任务。这是「升级优先」的嵌入式设计惯例——升级失败会导致设备变砖,因此不惜牺牲功能可用性。

API 参考

void update_mode_api_v2(UPDATA_TYPE type, void (*priv_param_fill_hdl)(UPDATA_PARM *p), void (*priv_update_jump_handle)(int type))

升级主入口。按 type 选择升级通道,priv_param_fill_hdl 在参数填充阶段被回调(用于追加私有数据),priv_update_jump_handle 在跳转 loader 前被回调(用于最后的清理动作)。

参数:

  • type(UPDATA_TYPE):升级通道类型,如 USB_UPDATA、UART_UPDATA、DUAL_BANK_UPDATA。
  • priv_param_fill_hdl:私有参数填充回调,可为 NULL。
  • priv_update_jump_handle:跳转前处理回调,可为 NULL。

int app_update_init(void) / void app_update_handle(int msg)

升级模块初始化与消息处理。app_update_init 注册升级进入回调(update_enter_cb_register)并初始化各通道;app_update_handle 处理应用层升级消息(如 OTA 命令、按键触发),驱动状态机从 UPDATE_TASK_INIT 流转。

int update_result_deal(void) / void update_result_set(u16 result) / void update_clear_result(void)

升级结果消费与写入。update_result_deal 在开机早期调用,读取 loader 回写的 parm_result 并执行对应处理(成功恢复 VM、失败清理标志);update_result_set 供上层写入结果;update_clear_result 清空标志。

bool update_success_boot_check(void) / bool device_is_first_start(void)

启动检查。update_success_boot_check 判断本次启动是否为「升级成功后的首次启动」;device_is_first_start 结合 DEVICE_FIRST_START 位判断设备是否首次上电。

bool vm_need_recover(void)

根据 g_updata_flag 低 16 位是否等于 UPDATA_SUCC 判断是否需要恢复用户 VM 区数据,由启动流程调用。

u16 dev_update_check(char *logo, bool check_flag) / u16 dev_update_by_change_stack(update_mode_info_t *p_info)

设备级接口。dev_update_check 校验升级文件 logo;dev_update_by_change_stack 切换到专用栈执行升级写入。

返回: UPDATA_RESULT 值,UPDATA_SUCC 表示成功。

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

升级结果错误码

loader 通过 parm_result 回写 UPDATA_RESULT:

结果含义处理
UPDATA_NON (0x5A00)未发生升级正常启动路径
UPDATA_READY升级已就绪等待跳转
UPDATA_SUCC升级成功vm_need_recover() 恢复用户配置并上报成功
UPDATA_PARM_ERR参数错误(CRC/magic 校验失败)清除标志,不恢复 VM
UPDATA_DEV_ERR设备错误(Flash 写入失败)清除标志,保留现场以便重试
UPDATA_KEY_ERR密钥/签名校验失败与 DEVICE_UPDATE_KEY_ERR 位联动标记

关键防御机制

  • CRC 双重保护:parm_crc 保护整个参数段;ext_arg_crc 单独保护扩展参数块。任何一处在 loader 侧校验失败都会以 UPDATA_PARM_ERR 拒绝升级,避免把损坏参数写入 Flash。
  • 魔法数防误判:UPDATA_MAGIC = 0x5A00 刻意选择非零值(注释明言「防止 CRC == 0 的情况」);参数段另有 0x5441 魔法数,双魔法数互为备份。
  • 短文件名约束:升级文件必须是 8+3 短文件名、最多两层目录,违反则 loader 找不到文件,表现为 UPDATA_DEV_ERR。
  • 首次启动位:DEVICE_FIRST_START 位用于区分「正常首次开机」与「升级后重启」,避免首次开机误触发 VM 恢复逻辑。

并发与中断考量

  • ota_status 声明为 volatile u8,会被协议栈(BLE/SPP OTA 回调)异步修改;主升级流程读取时须容忍中间态。
  • 跳转 loader 前通过 update_target 的 driver_close 依次销毁蓝牙控制器(hci_controller_destory)、关闭 HCI(ll_hci_destory)、关闭全部中断(hwi_all_close)、解除 RAM 保护(ram_protect_close)——顺序很重要:先停协议栈再关中断,避免在中断仍开启时协议栈还在访问将被 loader 覆盖的内存。
  • dev_update_by_change_stack 切换栈执行,规避主任务栈深度不足导致升级写 Flash 时栈溢出。

升级中断/掉电

升级期间掉电的恢复策略依赖两段式设计:UPDATA_PARM 常驻固定 Flash 地址,loader 侧有独立的写入进度保护;SDK 启动时依据 parm_result 与 update_success_boot_check() 判断是「升级成功重启」还是「升级中断重启」,后者不再触发 VM 恢复,保证用户区不被半成品数据污染。

扩展点与自定义通道开发

新增一种升级来源(如自定义私有协议)的标准步骤:

  1. 追加枚举值:在 UPDATA_TYPE 的 USER_NORFLASH_UFW_UPDATA 之前插入新值(顺序不可打乱,兼容老 loader)。
  2. 注册升级目标:实现 struct update_target(name + driver_close),用 REGISTER_UPDATE_TARGET 放入 .update_target 段;若无需清理驱动,driver_close 可为空实现。
  3. 实现文件获取逻辑:参考现有通道(uart_update.c、msd_upgrade.c、toy_update.c),在 app_update_handle 的消息分发中接入;升级文件路径遵循短文件名约束。
  4. 填充参数:通过 update_param_priv_fill() 写入私有数据(如通道 IO、波特率),需要跨阶段携带的附加信息走 ext_arg_t 扩展参数(新增 EXT_ARG_TYPE 枚举值)。
  5. 调用 update_mode_api_v2(new_type, fill_hdl, jump_hdl) 完成参数组装与跳转。

扩展参数机制(EXT_ARG_TYPE)是官方预留的扩展点:EXT_MUTIL_UPDATE_NAME(多升级文件名)、EXT_USER_API_BIN_INFO(用户 API 固件信息)、EXT_NEW_SDK_UPD_AGAIN(新 SDK 重复升级)、EXT_NEW_FILENAME(新文件名)等枚举注释表明 SDK 持续在通过该通道演进升级能力,无需改动 UPDATA_PARM 主结构。

测试与相关实现

  • sdk/app/bsp/common/update/dev_update.c:设备级升级执行器,dev_update_check 文件校验 + dev_update_by_change_stack 栈切换写入,是 update.c 的下游核心。
  • sdk/app/bsp/common/uart_update/uart_update.c/.h:UART 升级通道实现(含 UPDATA_UART 参数结构:tx/rx IO、波特率、超时)。
  • sdk/app/bsp/common/usb/device/msd_upgrade.c:USB 大容量存储升级通道。
  • sdk/app/src/voice_func/toy_update/toy_update.c/.h:玩具类应用的升级逻辑(BLE/SPP 通道的典型实现)。
  • sdk/include_lib/update/code_v2/update_loader_download.h:loader 下载相关接口声明。
  • 编译产物 sdk/include_lib/liba/sh58/voice_enhanced/update_lib.a、voice_toy/update_lib.a:预编译升级库,说明该机制以静态库形式发布给上层应用。

升级机制的核心正确性依赖「参数段地址固定」这一硬件布局约定:UPDATA_BEG/UPDATA_SIZE 由链接脚本定义,UPDATA_FLAG_ADDR 位于其偏移 0x08 处(前 8 字节为 BOOT_STATUS_ADDR 预留)。任何修改链接脚本或 Flash 分区布局的行为都可能破坏 SDK 与 loader 的参数契约,属于高风险改动,需同步修改 loader 侧代码。

相关链接

  • update.h(升级机制头文件)
  • update.c(升级主控实现)
  • dev_update.h(设备升级接口)
  • dev_update.c(设备升级执行)
  • uart_update.c(UART 升级通道)
  • msd_upgrade.c(USB 升级通道)
  • toy_update.c(玩具应用升级)
  • update_loader_download.h(loader 下载接口)
  • 相关相邻页面:Boot/Loader 启动流程、UART 升级协议、USB 升级协议
Prev
电源管理与低功耗
Next
外设驱动(按键、红外、SPI、USB)