双 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 机制的核心设计意图是通过冗余与原子切换消除这一风险:
- 冗余:Flash 中同时存在两份应用代码(
app_code0/app_code1),运行 Bank 与升级 Bank 分离; - 原子切换:新固件完整写入并校验通过后,才通过"烧录 boot info"这一最后动作切换启动目标;boot info 写入前系统复位,uboot 仍会启动旧 Bank;
- 可回退:
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。
关键术语
| 术语 | 含义 |
|---|---|
| Bank | Flash 中一份完整的应用固件区,本平台为 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 --> [*]
状态流转说明:
UPDATE_TASK_INIT:初始化升级参数(通道类型、固件 CRC/大小),对于双 Bank 升级即调用dual_bank_passive_update_init();UPDATE_CH_INIT:进入接收循环,主机分包下发固件,逐包调用dual_bank_update_write()写入非运行 Bank;UPDATE_CH_SUCESS_REPORT:数据接收完毕后执行dual_bank_update_verify()校验,通过后dual_bank_update_burn_boot_info()烧录 boot info;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"]
流程要点:
- 远端 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; - 本地 head 获取:从 Flash 读取两个 Bank 的当前 app code head(
p_lc_app_code0_head/p_lc_app_code1_head),失败返回UPDATE_RESULT_DUALBANK_GET_LOCAL_APP_HEAD_ERR; - 地址匹配判断:
update_judge_local_app_and_remote_addr_is_match比对远端与本地固件的运行地址是否一致——双 Bank 升级要求新旧固件链接地址相同,否则返回UPDATE_RESULT_DUALBANK_APP_HEAD_NOT_MATCH; - 目标 Bank 设定:
update_set_target_bank_addr_n_size依据当前运行 Bank(get_current_run_code_index)选定非运行 Bank 作为target_bank_start/target_bank_size(符号见update_ctrl_t结构); - 密钥与 CRC 校验:写入前做固件密钥校验(
remote_file_app_key_verify),写入后update_verify_process回读计算 CRC(CRC16-CCITT),失败返回UPDATE_ERR_CODE_VERIFY_ERR; - 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_BEG | boot 状态区地址,预留 8 字节 |
UPDATA_FLAG_ADDR | 宏 | UPDATA_BEG + 0x08 | 升级标志位地址 |
UPDATE_PARAM_MAGIC | 宏 | 0x5441 | UPDATA_PARM 结构有效性校验魔数 |
UPDATE_PRIV_PARAM_LEN | 宏 | 32 | 私有参数长度 |
UPDATA_PARM_SIZE | 宏 | 256 | SDK↔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_ERR | UFW flash 头 CRC 错误 |
UPDATE_RESULT_UFW_CODE_HEAD_CRC_ERR | UFW code head CRC 错误 |
UPDATE_RESULT_LOADER_HEAD_CRC_ERR / UPDATE_RESULT_LOADER_WRITE_ERR | loader 头 CRC / 写入错误 |
UPDATE_RESULT_FLASH_ERASE_ERR | Flash 擦除失败 |
UPDATE_RESULT_FLASH_DATA_VERIFY_ERR | 写入后 Flash 数据回读校验失败 |
UPDATE_ERR_CODE_VERIFY_ERR | 固件代码 CRC 校验失败 |
UPDATE_RESULT_UBOOT_NOT_MATCH | SDK 与 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_EXIT | OTA 过程中应用主动退出 |
说明:升级实现以预编译库
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的计算一致。
扩展点
| 扩展点 | 接口 | 用途 |
|---|---|---|
| 自定义 CRC | crc_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 平台)上进行。