杰理 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)
    • 问题修复补丁
    • 固件裁剪与资源优化
  • 开发工具与支持

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

双 Bank 升级机制

双 Bank(A/B)升级机制是 AW30N BLE SDK 提供的固件 OTA 安全升级方案:Flash 中保存两份应用固件(Bank0/Bank1),升级时只写入非运行 Bank,校验通过后再切换 boot info 指向新固件,从而在升级失败、掉电等异常情况下仍能回退到旧固件运行。

Purpose and Scope

本文档系统性地介绍 AW30N 双 Bank 升级机制的完整实现,包括:

  • 双 Bank 布局与 boot info 切换原理;
  • 被动升级(passive update)的完整 API 与调用时序;
  • 升级任务的状态机与内部校验流程(目标 Bank 选择、app code head 比对、CRC 校验、boot info 写入);
  • 与 uboot 之间的参数传递(UPDATA_PARM / DUAL_BANK_UPDATA);
  • 错误码、失败模式、扩展点与集成方式。

本页聚焦"双 Bank"相关的升级通路。以下主题属于兄弟页面,不在本文展开:单 Bank 升级(single_bank_update_loop)、UART/USB/SD 等传统升级通道、loader 下载(update_loader_download.h)、TWS 同步升级,以及 uboot 本身的引导实现。

概述

为什么需要双 Bank 升级

普通单 Bank 升级需要先擦除正在运行的固件区再写入新固件,一旦中途掉电或写入损坏,设备将无法启动(变砖)。双 Bank 机制的核心设计意图是通过冗余与原子切换消除这一风险:

  1. 冗余:Flash 中同时存在两份应用代码(app_code0 / app_code1),运行 Bank 与升级 Bank 分离;
  2. 原子切换:新固件完整写入并校验通过后,才通过"烧录 boot info"这一最后动作切换启动目标;boot info 写入前系统复位,uboot 仍会启动旧 Bank;
  3. 可回退:flash_update_clr_boot_info(CLEAR_APP_RUNNING_BANK) 可擦除运行 Bank 的 boot info 并复位,强制系统切换到另一 Bank,用于新固件运行异常时的应急回退。

实现形态

本 SDK 将升级实现以预编译静态库形式交付:update_lib.a(符号信息显示其编译自 apps/libs/update/code_v2/update_main.c、download_loop.c 等源文件,但仓库内不包含这些 .c 源文件)。对外暴露的头文件位于 sdk/apps/include_lib/update/code_v2/,其中 dual_bank_updata_api.h 即双 Bank 被动升级的公开 API;update.h 定义了升级模式枚举、uboot 参数结构等公共契约。应用层通过 sdk/apps/app/bsp/common/update/update.c 引入该 API。

关键术语

术语含义
BankFlash 中一份完整的应用固件区,本平台为 Bank0/Bank1(app_code0/app_code1)
Running Bank当前正在执行的固件所在 Bank
Update Bank / Target Bank本次升级写入的目标 Bank(即非运行 Bank)
Boot Info记录各 Bank 固件地址、CRC 等信息的头部区域,uboot 据此选择启动 Bank
Passive Update被动升级:主机(手机 APP/测试盒)分包下发固件,设备端逐包写入并最终校验切换
UFW升级固件文件格式(含 app code head、boot head 等)

架构

flowchart TD
    subgraph sg_Host["升级发起端"]
        APP["手机 APP / 测试盒 / 上位机"]
    end

    subgraph sg_Channel["升级通道"]
        BLE["BLE / SPP"]
        UART["UART / USB"]
    end

    subgraph sg_App["应用层 (SDK App)"]
        UpdTask["update 任务"]
        UpdateC["update.c 集成层"]
    end

    subgraph sg_Api["双 Bank 被动升级 API (dual_bank_updata_api.h)"]
        Init["dual_bank_passive_update_init"]
        Write["dual_bank_update_write"]
        Verify["dual_bank_update_verify"]
        Burn["dual_bank_update_burn_boot_info"]
    end

    subgraph sg_Lib["升级库 update_lib.a (内部实现)"]
        Loop["dual_bank_update_loop"]
        HeadCmp["app code head 比对<br/>remote vs local"]
        SetBank["update_set_target_bank_addr_n_size"]
        CRC["CRC16-CCITT 校验"]
        BootWrite["flash_boot_info_write"]
    end

    subgraph sg_Flash["Flash 布局"]
        Bank0["Bank0 (app_code0)"]
        Bank1["Bank1 (app_code1)"]
        BootArea["boot info / 升级标志区<br/>UPDATA_BEG"]
    end

    subgraph sg_Uboot["uboot"]
        UBoot["根据 boot info 选择启动 Bank"]
    end

    APP --> BLE
    APP --> UART
    BLE --> UpdTask
    UART --> UpdTask
    UpdTask --> UpdateC
    UpdateC --> Init
    UpdateC --> Write
    UpdateC --> Verify
    UpdateC --> Burn
    Init --> Loop
    Write --> Loop
    Verify --> Loop
    Loop --> HeadCmp
    Loop --> SetBank
    Loop --> CRC
    Loop --> BootWrite
    SetBank --> Bank0
    SetBank --> Bank1
    CRC --> Bank0
    CRC --> Bank1
    BootWrite --> BootArea
    BootArea --> UBoot
    UBoot --> Bank0
    UBoot --> Bank1

架构说明

  • 升级发起端与通道:主机通过 BLE(BLE_APP_UPDATA)、SPP(SPP_APP_UPDATA)或 UART/USB 等通道建立传输;所有通道最终汇入统一的升级任务。
  • 应用层集成:update.c 是应用侧集成点,包含 dual_bank_updata_api.h,负责把通道收到的数据包接入被动升级 API。
  • API 层:dual_bank_updata_api.h 定义 6 个核心阶段接口(init / allow check / write / verify / burn boot info / exit),供任务按序调用;这是 SDK 与应用之间唯一的稳定契约。
  • 库内部:dual_bank_update_loop 是双 Bank 升级主循环(符号可从 update_lib.a 调试信息中确认),内部依次完成远端/本地 app code head 获取与比对、目标 Bank 地址与大小设定、分次写入、CRC 校验、boot info 烧录。
  • Flash 与 uboot:UPDATA_BEG/UPDATA_SIZE 为链接脚本导出的升级区边界;uboot 上电读取 boot info,决定从 Bank0 还是 Bank1 启动,从而实现"写新 bank → 切换 boot info → 复位后运行新固件"的闭环。

核心概念与数据模型

Flash 分区与 Bank 布局

update.h 通过链接脚本符号定义升级区边界与标志地址:

extern u32 UPDATA_BEG;
extern u32 UPDATA_SIZE;

#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功能

Source: update.h

  • BOOT_STATUS_ADDR 位于升级区起始位置,预留 8 字节,用于保存升级/启动状态(UPDATA_READY / UPDATA_SUCC 等);
  • UPDATA_FLAG_ADDR 为升级标志位地址,位于 UPDATA_BEG + 0x08;
  • UPDATA_MAGIC = 0x5A00 作为所有结果/类型枚举的基准值,其设计意图是防止 CRC 恰为 0 时无法区分"未初始化"与"成功"。

双 Bank 的物理布局由链接脚本与分区表决定(Bank0 = app_code0,Bank1 = app_code1),升级库通过 local_flash_op_get_app_start_addr、get_current_run_code_index 等符号确定当前运行 Bank 与两个 Bank 的起始地址(符号见 update_lib.a 调试信息)。

Boot Info 与原子切换

Boot Info 是双 Bank 机制实现"原子切换"的关键数据。其结构体为 stBOOT_INFO_HEAD(符号来自 update_lib.a,字段包括 DataCrc、EncDataCrc、reserved1、szFileName),每次升级的最后一步 dual_bank_update_burn_boot_info() 会把新固件的地址与 CRC 写入该区域,随后系统复位,uboot 依据 boot info 启动新 Bank。

设计意图:boot info 是切换的唯一"开关"。固件写入、校验全部完成后才允许拨动开关;在此之前任何异常(掉电、写坏)都只影响未激活的 Bank,系统仍从旧 Bank 启动。这正是双 Bank 相比单 Bank 的本质优势。

升级结果枚举

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.h 定义统一的升级任务状态,双 Bank 被动升级同样运行在该状态机上:

typedef enum _UPDATE_STATE_T {
    UPDATE_TASK_INIT,          // 任务初始化:解析升级参数、建立通道
    UPDATE_CH_INIT,            // 通道初始化:等待并接收固件数据
    UPDATE_CH_SUCESS_REPORT,   // 通道成功上报:校验、烧录 boot info
    UPDATE_CH_EXIT,            // 任务退出:复位或清理
} UPDATE_STATE_T;

Source: update.h

stateDiagram-v2
    [*] --> UPDATE_TASK_INIT
    UPDATE_TASK_INIT --> UPDATE_CH_INIT
    UPDATE_CH_INIT --> UPDATE_CH_SUCESS_REPORT
    UPDATE_CH_SUCESS_REPORT --> UPDATE_CH_EXIT
    UPDATE_CH_INIT --> UPDATE_CH_EXIT
    UPDATE_CH_EXIT --> [*]

状态流转说明:

  1. UPDATE_TASK_INIT:初始化升级参数(通道类型、固件 CRC/大小),对于双 Bank 升级即调用 dual_bank_passive_update_init();
  2. UPDATE_CH_INIT:进入接收循环,主机分包下发固件,逐包调用 dual_bank_update_write() 写入非运行 Bank;
  3. UPDATE_CH_SUCESS_REPORT:数据接收完毕后执行 dual_bank_update_verify() 校验,通过后 dual_bank_update_burn_boot_info() 烧录 boot info;
  4. UPDATE_CH_EXIT:复位(system_reset)进入新固件,或异常退出。任何阶段失败都会携带错误码进入退出状态,且由于 boot info 未切换,系统仍可启动旧固件。

升级模式枚举与 uboot 参数传递

UPDATA_TYPE 枚举

DUAL_BANK_UPDATA 是升级模式枚举的一员,SDK 通过它告知 uboot 本次升级的类型:

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,               // 双 Bank 升级
    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

头文件注释明确要求枚举顺序不可调整(新升级方式追加在 USER_NORFLASH_UFW_UPDATA 之前),因为该枚举值会作为 UPDATA_PARM.parm_type 传递到 uboot,属于跨镜像(SDK ↔ uboot)的稳定 ABI。

UPDATA_PARM:SDK 与 uboot 的通信契约

#define UPDATE_PARAM_MAGIC		0x5441
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
    union {
        struct { u8  file_path[32]; };
        struct { u8  file_patch[32]; };
    };
    u8  parm_priv[32];          //sd updata
    u32 ota_addr;
    u16 ext_arg_len;
    u16 ext_arg_crc;
} UPDATA_PARM;

Source: update.h

  • parm_type 填入 DUAL_BANK_UPDATA,SDK 复位前把该参数结构写入约定内存区(UPDATE_PRIV_PARAM_LEN/UPDATA_PARM_SIZE 定义了参数区大小,update_mode_api_v2() 负责注册模式与参数填充回调);
  • parm_result 由 uboot 回填升级结果,SDK 侧通过 update_result_get() / update_result_deal() 读取并处理;
  • 设计意图:uboot 与 SDK 是两套独立固件,通过固定地址的固定结构交换信息,避免两者直接耦合;parm_crc/magic 用于校验参数有效性。

双 Bank 升级主流程

被动升级时序

sequenceDiagram
    participant H as Host (手机APP/测试盒)
    participant T as 升级任务 (update task)
    participant A as dual_bank API
    participant F as Flash
    participant U as uboot

    H->>T: 建立传输通道 (BLE/UART),开始 OTA
    T->>A: dual_bank_passive_update_init(fw_crc, fw_size, max_pkt_len)
    A->>A: 解析远端 UFW app head
    A->>A: 读取本地两个 Bank 的 app head 并比对
    A->>A: 选择目标 Bank (非运行 Bank),设定地址与大小
    A-->>T: 返回目标更新地址
    Note over T,A: dual_bank_passive_update_get_target_update_addr()
    loop 分包下发
        H->>T: 固件数据包
        T->>A: dual_bank_update_write(data, len, write_complete_cb)
        A->>F: 擦除 + 编程目标 Bank (按 max_pkt_len 分次)
        F-->>A: 写入完成
        A-->>T: write_complete_cb 回调
    end
    H->>T: 数据发送完毕
    T->>A: dual_bank_update_verify(crc_init, crc_calc, verify_result)
    A->>F: 回读校验 CRC16-CCITT
    A-->>T: verify_result(1=通过 / 0=失败)
    T->>A: dual_bank_update_burn_boot_info(result_hdl)
    A->>F: 写入新 boot info (指向新 Bank)
    A-->>T: 烧录结果回调 (err==0 成功)
    T->>U: 复位并传递 UPDATA_PARM(DUAL_BANK_UPDATA)
    U->>F: 读取 boot info
    U->>F: 启动新 Bank (升级完成)

库内部主循环(dual_bank_update_loop)

从 update_lib.a 的调试符号可以还原 dual_bank_update_loop 的执行骨架。该函数位于下载循环(download_loop.c)之上,是双 Bank 升级的核心决策链:

flowchart TD
    Start([进入 dual_bank_update_loop]) --> GetRemote["获取远端 UFW app code head<br/>(remote_file_app_code_headers_get)"]
    GetRemote --> Err1{"获取失败?"}
    Err1 -->|"是"| Fail1["UPDATE_RESULT_DUALBANK_GET_UFW_APP_HEAD_ERR"]
    Err1 -->|"否"| GetLocal["获取本地两 Bank app code head<br/>(local_flash_app_code_headers_get)"]
    GetLocal --> Err2{"获取失败?"}
    Err2 -->|"是"| Fail2["UPDATE_RESULT_DUALBANK_GET_LOCAL_APP_HEAD_ERR"]
    Err2 -->|"否"| Judge["判断本地与远端地址是否匹配<br/>(update_judge_local_app_and_remote_addr_is_match)"]
    Judge --> Match{"地址匹配?"}
    Match -->|"否"| Fail3["UPDATE_RESULT_DUALBANK_APP_HEAD_NOT_MATCH"]
    Match -->|"是"| SetBank["设定目标 Bank 地址与大小<br/>(update_set_target_bank_addr_n_size)"]
    SetBank --> ReadBoot["读取远端 boot info<br/>(remote_read_boot_info)"]
    ReadBoot --> KeyVerify["固件密钥校验<br/>(remote_file_app_key_verify)"]
    KeyVerify --> Erase["擦除目标 Bank 区域"]
    Erase --> WriteLoop["写入固件 (按 max_pkt_len 分次)"]
    WriteLoop --> Verify["回读校验 (update_verify_process)"]
    Verify --> Ok{"CRC 通过?"}
    Ok -->|"否"| Fail4["UPDATE_ERR_CODE_VERIFY_ERR"]
    Ok -->|"是"| Boot["写入新 boot info (flash_boot_info_write)"]
    Boot --> TWS["TWS 同步从机 boot info<br/>(tws_sync_update_slave_boot_info)"]
    TWS --> Reset["复位,进入新 Bank"]

流程要点:

  1. 远端 head 获取:从 UFW 升级文件中解析 app_code0/app_code1 对应的 app code head(p_ufw_app_code0_head/p_ufw_app_code1_head),失败返回 UPDATE_RESULT_DUALBANK_GET_UFW_APP_HEAD_ERR;
  2. 本地 head 获取:从 Flash 读取两个 Bank 的当前 app code head(p_lc_app_code0_head/p_lc_app_code1_head),失败返回 UPDATE_RESULT_DUALBANK_GET_LOCAL_APP_HEAD_ERR;
  3. 地址匹配判断:update_judge_local_app_and_remote_addr_is_match 比对远端与本地固件的运行地址是否一致——双 Bank 升级要求新旧固件链接地址相同,否则返回 UPDATE_RESULT_DUALBANK_APP_HEAD_NOT_MATCH;
  4. 目标 Bank 设定:update_set_target_bank_addr_n_size 依据当前运行 Bank(get_current_run_code_index)选定非运行 Bank 作为 target_bank_start/target_bank_size(符号见 update_ctrl_t 结构);
  5. 密钥与 CRC 校验:写入前做固件密钥校验(remote_file_app_key_verify),写入后 update_verify_process 回读计算 CRC(CRC16-CCITT),失败返回 UPDATE_ERR_CODE_VERIFY_ERR;
  6. boot info 写入:flash_boot_info_write 写入 new_boot_info_head(含 EncdataCrc/OridataCrc),成功后复位。

双 Bank 与单 Bank 的关系

update_lib.a 同时导出 single_bank_update_loop 与 dual_bank_update_loop,两者由升级模式(DUAL_BANK_UPDATA 或传统模式)决定走哪条路径。双 Bank 路径额外增加了:本地/远端 app head 比对、目标 Bank 选择、boot info 切换三步;单 Bank 路径则直接擦写当前固件区。产品是否需要双 Bank 由 Flash 容量(UPDATA_SIZE 是否可容纳两份固件)与分区表决定。

被动升级 API 使用示例

以下代码均摘自 SDK 提供的公开头文件,展示了双 Bank 被动升级的标准调用序列。实际调用方(如 update.c 集成的 BLE/测试盒通道)按"init → write×N → verify → burn boot info"顺序执行。

初始化升级任务

/* @brief:Initializes the update task,and setting the crc value and file size of new fw;
 * @param fw_crc:crc value of new fw file
 * @param fw_size:total size of new fw file
 * @param priv:reserved
 * @param max_ptk_len: Supported maxium length of every programming,it decides the max size of programming every time
 */
u32 dual_bank_passive_update_init(u32 fw_crc, u32 fw_size, u16 max_pkt_len, void *priv);

Source: dual_bank_updata_api.h

fw_crc/fw_size 由升级文件头解析得到;max_pkt_len 决定每次编程的最大长度,直接影响写入缓冲 get_dual_bank_passive_update_max_buf() 的分配大小与传输效率。

写入固件数据(分包)

/* @brief:copy the data to temporary buffer and notify task to write non-volatile storage
 * @param data:the pointer to download data
 * @param len:the length to download data
 * @param write_complete_cb:callback for programming done,return 0 if no err occurred
*/
u32 dual_bank_update_write(void *data, u16 len, int (*write_complete_cb)(void *priv));

Source: dual_bank_updata_api.h

该 API 先把数据拷贝到临时缓冲(g_update_tmp_buf/g_update_buf),再通知升级任务执行 Flash 擦写;write_complete_cb 在非易失存储编程完成后回调(返回 0 表示无错误),主机可据此流控,避免缓冲溢出。

校验与烧录 boot info

/* @brief: caculate all the data had flashed,and compare with the cre value intializeed when update init;
 * @crc_init_hdl:if it equals NULL,use internal implementation(CRC16-CCITT Standard);otherwise,use user's customization;
 * @crc_calc_hdl:if it equals NULL,use internal implementation(CRC16-CCITT Standard);otherwise,use user's customization;
 * @verify_result_hdl:when the verification completed,this callback for result notification;
 *                    if crc_res equals 1,crc verification passed,if 0,the verification failed.
*/
u32 dual_bank_update_verify(void (*crc_init_hdl)(void), u32(*crc_calc_hdl)(u32 init_crc, const void *data, u32 len), int (*verify_result_hdl)(int calc_crc));

/* @brief:After the new fw verification succeed,call this api to program the new boot info for new fw
 * @param burn_boot_info_result_hdl:this callback for error notification
 *                                  if err equals 0,the operate to burn boot info succeed,other value means to fail.
 */
u32 dual_bank_update_burn_boot_info(int (*burn_boot_info_result_hdl)(int err));

Source: dual_bank_updata_api.h

dual_bank_update_verify 是"原子切换"的守门员:CRC 校验不通过时绝不进入 dual_bank_update_burn_boot_info。校验通过后烧录 boot info(err == 0 表示成功),随后任务复位进入新固件。

回退与辅助接口

enum {
    CLEAR_APP_RUNNING_BANK = 0,   // 擦除运行 Bank 的 boot info
    CLEAR_APP_UPDATE_BANK,        // 擦除升级 Bank 的 boot info
};

/* @brief:this api for erasing the boot info of specific bank,it should be called much carefully
 * @param type:it decides which bank's boot info would be erased;
 *             clean the boot info of running bank and call system_reset,system will run the other bank if available;
 */
int flash_update_clr_boot_info(u8 type);

/* @brief:this api for user read flash data to calculate crc
 * @param offset: the offset relative to update area
          read_buf: user data buffer
          read_len: read length
   @returns: Actual read length
 */
u32 dual_bank_update_read_data(u32 offset, u8 *read_buf, u32 read_len);

Source: dual_bank_updata_api.h

  • flash_update_clr_boot_info(CLEAR_APP_RUNNING_BANK):擦除运行 Bank 的 boot info 并复位,系统将尝试启动另一 Bank——这是新固件运行异常时的应急回退入口,头文件特别标注"should be called much carefully";
  • dual_bank_update_read_data:按相对升级区的偏移回读 Flash 数据,供上层自定义 CRC 计算使用。

API 参考

以下为 dual_bank_updata_api.h(头文件)声明的全部公开接口。

u32 get_dual_bank_passive_update_max_buf(void)

获取被动升级用于临时存储的缓冲大小。调用方据此分配/确认接收缓冲,避免 dual_bank_update_write 写入超限。

返回:缓冲区字节数。

u32 dual_bank_passive_update_init(u32 fw_crc, u32 fw_size, u16 max_pkt_len, void *priv)

初始化双 Bank 被动升级任务,并设置新固件的 CRC 与大小。

参数:

  • fw_crc (u32):新固件文件的 CRC 值,校验阶段比对基准;
  • fw_size (u32):新固件文件总大小;
  • max_pkt_len (u16):单次编程支持的最大长度,决定每次编程的数据量上限;
  • priv (void *):保留参数。

返回:u32,0 表示成功,非 0 为错误码。

u32 dual_bank_passive_update_exit(void *priv)

退出升级任务,释放升级过程占用的资源。

参数: priv (void *):保留参数。 返回:u32 结果码。

u32 dual_bank_update_allow_check(u32 fw_size)

判断是否有足够空间存放新固件。

注意:必须在 dual_bank_passive_update_init(...) 之后调用。 参数: fw_size (u32):新固件大小。 返回:u32,0 表示空间充足。

u32 dual_bank_update_write(void *data, u16 len, int (*write_complete_cb)(void *priv))

把下载数据拷贝到临时缓冲,并通知升级任务写入非易失存储(Flash)。

参数:

  • data (void *):待写入数据指针;
  • len (u16):本次数据长度;
  • write_complete_cb (int (*)(void *priv)):编程完成回调,返回 0 表示无错误。

返回:u32 结果码。

u32 dual_bank_update_verify(void (*crc_init_hdl)(void), u32(*crc_calc_hdl)(u32 init_crc, const void *data, u32 len), int (*verify_result_hdl)(int calc_crc))

计算所有已写入数据的总 CRC,并与 init 时设置的 CRC 比对。

参数:

  • crc_init_hdl (void (*)(void)):CRC 初始化钩子,传 NULL 使用内部 CRC16-CCITT 标准实现;
  • crc_calc_hdl (u32 (*)(u32 init_crc, const void *data, u32 len)):CRC 计算钩子,传 NULL 使用内部实现;
  • verify_result_hdl (int (*)(int calc_crc)):校验完成结果回调;calc_crc == 1 表示通过,0 表示失败。

返回:u32 结果码。

u32 dual_bank_update_burn_boot_info(int (*burn_boot_info_result_hdl)(int err))

新固件校验成功后,编程新的 boot info(切换开关)。

参数: burn_boot_info_result_hdl (int (*)(int err)):烧录结果回调;err == 0 表示烧录成功,其他值表示失败。 返回:u32 结果码。

int flash_update_clr_boot_info(u8 type)

擦除指定 Bank 的 boot info。

参数: type (u8):CLEAR_APP_RUNNING_BANK(0) 或 CLEAR_APP_UPDATE_BANK(1)。 返回:int 结果码。擦除运行 Bank 的 boot info 后需调用 system_reset,系统将在可用时启动另一 Bank。

u32 dual_bank_update_read_data(u32 offset, u8 *read_buf, u32 read_len)

按相对升级区的偏移回读 Flash 数据。

参数:

  • offset (u32):相对升级区的偏移;
  • read_buf (u8 *):数据缓冲;
  • read_len (u32):读取长度。

返回:实际读取长度。

u8 dual_bank_update_verify_without_crc_new(int (*verify_result_hdl)(int calc_crc))

不携带 init CRC 参数的校验变体(用于已通过其他途径确认 CRC 的场景)。

参数: verify_result_hdl (int (*)(int calc_crc)):校验结果回调。 返回:u8 状态码。

u32 dual_bank_passive_update_get_target_update_addr(void)

获取待升级目标地址,在 dual_bank_passive_update_init 之后使用有效。

返回:目标 Bank 的起始地址(target_update_addr)。

配置选项

选项类型默认值说明
UPDATA_MAGIC宏0x5A00升级结果/类型枚举基准值,防止 CRC 为 0 时误判
UPDATA_KEEP_IO_ENABLE宏0升级时是否保持 IO 功能(1 保持)
BOOT_STATUS_ADDR宏UPDATA_BEGboot 状态区地址,预留 8 字节
UPDATA_FLAG_ADDR宏UPDATA_BEG + 0x08升级标志位地址
UPDATE_PARAM_MAGIC宏0x5441UPDATA_PARM 结构有效性校验魔数
UPDATE_PRIV_PARAM_LEN宏32私有参数长度
UPDATA_PARM_SIZE宏256SDK↔uboot 参数区总大小
CLEAR_APP_RUNNING_BANK枚举0擦除运行 Bank boot info(回退)
CLEAR_APP_UPDATE_BANK枚举1擦除升级 Bank boot info
UPDATA_BEG / UPDATA_SIZE链接脚本符号分区表决定升级区起始地址与大小(是否容纳双 Bank 由 Flash 容量决定)
max_pkt_len(运行时)u16调用方指定单次编程最大长度,决定写入缓冲与编程分片

错误码

双 Bank 升级的错误码在升级库中定义(UPDATE_RESULT_* 与 UPDATE_ERR_* 两套命名,前者为结果码、后者为内部错误码)。以下为与双 Bank 路径直接相关的错误码(依据 update_lib.a 调试符号确认):

错误码含义
UPDATE_RESULT_DUALBANK_GET_UFW_APP_HEAD_ERR从 UFW 升级文件解析 app code head 失败
UPDATE_RESULT_DUALBANK_GET_LOCAL_APP_HEAD_ERR读取本地 Flash 中 Bank 的 app code head 失败
UPDATE_RESULT_DUALBANK_APP_HEAD_NOT_MATCH远端固件与本地固件的运行地址不匹配
UPDATE_RESULT_UFW_FLASH_HEAD_CRC_ERRUFW flash 头 CRC 错误
UPDATE_RESULT_UFW_CODE_HEAD_CRC_ERRUFW code head CRC 错误
UPDATE_RESULT_LOADER_HEAD_CRC_ERR / UPDATE_RESULT_LOADER_WRITE_ERRloader 头 CRC / 写入错误
UPDATE_RESULT_FLASH_ERASE_ERRFlash 擦除失败
UPDATE_RESULT_FLASH_DATA_VERIFY_ERR写入后 Flash 数据回读校验失败
UPDATE_ERR_CODE_VERIFY_ERR固件代码 CRC 校验失败
UPDATE_RESULT_UBOOT_NOT_MATCHSDK 与 uboot 版本不匹配
UPDATE_RESULT_PRODUCT_INFO_NOT_MATCH产品信息不匹配(固件与设备型号不符)
UPDATE_RESULT_LOCAL_VM_NOT_ENOUGH_FOR_LOADER_SIZE / REMOTE_VM_NOT_ENOUGH...VM 空间不足
UPDATE_RESULT_OTA_APP_EXITOTA 过程中应用主动退出

说明:升级实现以预编译库 update_lib.a 交付(如 补丁包/AW30N_v1.2.0_SDK裁剪到256KB以内的补丁包_20240815/include_lib/liba/update_lib.a),上述符号与错误码名称来自该库的调试字符串信息;仓库内未包含 update_main.c/download_loop.c 源文件,具体取值以库头文件与厂商文档为准。

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

掉电/中断安全性

双 Bank 机制的核心可靠性保证:任何时刻系统复位,都能从两个 Bank 中选一个启动。

  • 写入过程中掉电:只损坏未激活的升级 Bank,BOOT_STATUS_ADDR 状态与 boot info 均未变化,uboot 仍启动旧 Bank,可重新发起升级;
  • boot info 烧录过程中掉电:boot info 区写入是最后一步且紧邻复位动作。若写入未完成,uboot 读到旧 boot info 或无效 boot info,仍回退旧 Bank;
  • 新固件启动后异常:应用可调用 flash_update_clr_boot_info(CLEAR_APP_RUNNING_BANK) 擦除运行 Bank 的 boot info 并复位,强制回退另一 Bank。

校验失败

  • 固件 CRC 校验失败(UPDATE_ERR_CODE_VERIFY_ERR)时流程停留在"写新 Bank"阶段,不烧录 boot info,系统不变砖;
  • app code head 获取失败(DUALBANK_GET_UFW_APP_HEAD_ERR / DUALBANK_GET_LOCAL_APP_HEAD_ERR)与地址不匹配(DUALBANK_APP_HEAD_NOT_MATCH)均属于前置校验,在设计上越早失败越好——在擦写任何 Flash 之前就中止,避免无谓的擦写损耗与风险。

边界情况

  • 地址匹配约束:双 Bank 升级要求新旧固件链接地址一致(update_judge_local_app_and_remote_addr_is_match),否则升级被拒绝。设计意图:两份固件必须能在同一地址运行,切换 Bank 才无感知;
  • 空间不足:dual_bank_update_allow_check(fw_size) 在 init 后立即检查目标 Bank 容量,防止写入越界破坏其他分区;
  • max_pkt_len 越界:dual_bank_update_write 的 len 超过 get_dual_bank_passive_update_max_buf() 时应被调用方拒绝,否则可能覆写临时缓冲;
  • 重复升级/升级中断恢复:升级标志(UPDATA_FLAG_ADDR)与 boot 状态(BOOT_STATUS_ADDR)用于区分"首次启动/升级后启动/升级失败"(device_is_first_start()、update_success_boot_check() 等接口处理)。

并发与中断上下文

  • 升级任务运行在独立任务上下文,Flash 擦写为阻塞操作,写入回调(write_complete_cb)在编程完成后触发,用于主机侧流控;
  • 升级期间 BLE 协议栈仍运行(UPDATA_KEEP_IO_ENABLE 可配置是否保持 IO),因此 Flash 擦写期间的长阻塞可能影响连接保活,需要通过分片大小(max_pkt_len)与回调节奏平衡吞吐与连接稳定性;
  • boot info 擦除/烧录是单点写操作,代码设计上不允许与常规 Flash 读写并发(依赖升级任务独占升级区)。

性能与运维注意事项

  • 吞吐瓶颈:单次编程长度由 max_pkt_len 决定,越大擦写效率越高,但临时缓冲(g_update_buf/get_dual_bank_passive_update_max_buf())占用 RAM 也越大,需要按芯片 RAM 预算权衡;
  • 擦写磨损:双 Bank 升级每次只写一个 Bank,且仅在升级时擦写,相比频繁整区擦写的方案寿命更优;但反复回退(flash_update_clr_boot_info + 复位)会重复擦写 boot info 区,运维上应限制异常回退次数;
  • 升级进度:升级库提供 register_update_percent_info_callback_handle / update_percent_info_query 等符号用于上报百分比,主机可据此展示进度;update_get_err_code() 可查询最近一次错误码,便于产线与售后定位;
  • 产线烧录:出厂双 Bank 均需有效固件与 boot info,否则首启无法完成 Bank 选择;分区表与链接脚本(UPDATA_BEG/UPDATA_SIZE)必须与库内 local_flash_op_get_app_start_addr 的计算一致。

扩展点

扩展点接口用途
自定义 CRCcrc_init_hdl / crc_calc_hdl(dual_bank_update_verify)替换内部 CRC16-CCITT,适配产线/第三方校验算法
校验结果回调verify_result_hdl校验完成通知,可在此记录日志或上报主机
写完成回调write_complete_cb(dual_bank_update_write)编程完成流控,实现"边收边写"
boot info 烧录回调burn_boot_info_result_hdl切换开关动作的结果上报
回退控制flash_update_clr_boot_info新固件异常时主动回退
升级模式注册update_mode_api_v2(type, priv_param_fill_hdl, priv_update_jump_handle)注册新的升级通道模式并填充 uboot 参数
升级通道目标REGISTER_UPDATE_TARGET(target) / struct update_target在链接段注册通道驱动的 driver_close 回调

集成与测试

  • 集成点:应用层在 sdk/apps/app/bsp/common/update/update.c 引入 code_v2/dual_bank_updata_api.h(update.c L13),BLE/测试盒等通道的数据最终汇聚到被动升级 API;app_update_init() / app_update_handle(int msg) 为任务初始化与消息入口;
  • 测试建议(基于库符号与 API 语义):升级成功路径(新旧版本往返升级)、写一半掉电复位、boot info 烧录前复位、新固件启动后回退、空间不足拒绝、错误固件(CRC/head 不匹配)拒绝、max_pkt_len 边界值、双 Bank 交替升级多次(验证 bank 轮换逻辑)与产线首启行为;
  • 说明:仓库内未包含双 Bank 升级库的单元测试源码(实现以 update_lib.a 预编译库交付),测试需基于公开 API 在目标板(BD49 平台)上进行。

相关链接

  • dual_bank_updata_api.h — 双 Bank 被动升级公开 API
  • update.h — 升级模式枚举、UPDATA_PARM、状态机与结果码
  • update.c — 应用层升级集成入口
  • dev_update.h — 设备升级相关接口
  • update_loader_download.h — loader 下载接口(单 Bank 通道共用)
  • uart_update.h — UART 升级通道
  • 预编译升级库(含 dual_bank_update_loop 等符号):update_lib.a
Prev
升级框架总览 (code_v1 / code_v2)
Next
升级通道:UART / 测试盒 / BLE OTA / USB / SD