固件升级机制
AD23N MCU SDK 的固件升级(Update)子系统,负责设备端接收并应用 .ufw 升级固件,覆盖 USB、UART、SD 卡、蓝牙 BLE/SPP、双 bank、norflash、测试盒等多种升级通道,并管理升级标志、结果回写、跳转 Loader 与 VM 数据恢复等完整生命周期。
Purpose and Scope
本文档介绍 AD23N SDK 中固件升级机制的整体架构与实现细节,包括:
- 升级类型体系(
UPDATA_TYPE/UPGRADE_TYPE)与各升级通道的职责划分 - 升级状态机与标志位(
UPDATA_RESULT、UPDATA_FLAG_ADDR、BOOT_STATUS_ADDR) - 核心升级流程(
update.c):升级参数构造、跳转 Loader 前处理、升级结果处理与启动校验 - UART 升级(
uart_update.c)与设备升级(dev_update.c)的实现要点 - 升级配置文件(
app_config.c)中的全部开关项及含义 - 失败模式、边界条件与并发/一致性注意事项
本文档不覆盖以下内容(由其他目录页负责):蓝牙协议栈(BT/BLE)具体链路、VM 存储子系统内部实现、Flash 驱动(SFC)细节、量产测试盒(TestBox)协议细节。
Overview
在嵌入式音频 SoC(如 AD23N)上,固件升级是产品出厂烧录和售后维护的关键能力。SDK 将升级能力抽象为统一的升级入口 + 多种传输通道:
- 无论固件来自 USB 优盘、SD 卡、UART 串口、BLE/SPP 蓝牙还是 norflash 备份区,最终都要写入片内/片外 Flash 的代码区,并保证掉电安全(通过 CRC 校验与升级标志)。
- 升级文件为
.ufw格式,设备按短文件名(8+3)规则在挂载的文件系统中查找(默认匹配/*.ufw),文件路径仅支持两层目录。 - 升级完成后,Loader 引导阶段根据
UPDATA_FLAG_ADDR处的标志判断升级结果,决定是否回写 VM 数据或进入正常启动。
设计上,升级状态被持久化到 Flash 的固定地址(UPDATA_BEG 区域),因此意外掉电后设备仍能通过标志恢复正确状态——这是整个机制可靠性的核心。
Architecture
下图展示了固件升级子系统的总体架构:上层应用通过多种方式触发升级,统一进入 update.c 的升级流程,最终跳转到 Loader(LOADER.BIN)完成 Flash 写入。
flowchart TD
subgraph sg_Trigger["升级触发层"]
USB["USB 优盘/PC<br/>(USB_UPDATA)"]
SD["SD 卡<br/>(SD0/SD1_UPDATA)"]
UART["UART 串口<br/>(UART_UPDATA)"]
BT["蓝牙 BLE/SPP<br/>(BLE_APP/SPP_APP_UPDATA)"]
DUAL["双 Bank<br/>(DUAL_BANK_UPDATA)"]
NOR["Norflash<br/>(NORFLASH_UPDATA)"]
TB["测试盒<br/>(TESTBOX_UART_UPDATA)"]
end
subgraph sg_UpdateCore["升级核心 update.c"]
ENTRY["升级入口<br/>update_module_init / 各通道初始化"]
PARAM["参数构造<br/>update_param_priv_fill / ext_fill"]
RESULT["结果处理<br/>update_result_set / update_result_deal"]
JUMP["跳转前处理<br/>update_before_jump_common_handle"]
end
subgraph sg_Boot["启动/引导层"]
BOOT_CHECK["启动校验<br/>update_success_boot_check"]
LOADER["Loader<br/>(LOADER.BIN)"]
VM["VM 数据恢复<br/>vm_need_recover"]
end
subgraph sg_Storage["存储层"]
FLAG["升级标志区<br/>UPDATA_FLAG_ADDR / BOOT_STATUS_ADDR"]
UFW["升级文件<br/>/*.ufw"]
end
USB --> ENTRY
SD --> ENTRY
UART --> ENTRY
BT --> ENTRY
DUAL --> ENTRY
NOR --> ENTRY
TB --> ENTRY
ENTRY --> PARAM
PARAM --> JUMP
JUMP --> LOADER
LOADER --> FLAG
BOOT_CHECK --> FLAG
BOOT_CHECK --> VM
UFW --> LOADER
架构要点说明:
- 触发层:每种传输通道对应一个
UPDATA_TYPE枚举值(从USB_UPDATA = 0x5A00起编号),升级时该值作为参数传入核心流程,用于区分升级来源并在跳转前做对应的硬件/协议清理(例如关闭蓝牙、关闭 WiFi 检测、保持 IO 状态等)。 - 核心层:
update.c是唯一入口,负责构造UPDATA_PARM参数、填充私有数据(update_param_priv_fill)与扩展数据(update_param_ext_fill),最终调用update_before_jump_common_handle完成跳转 Loader 前的公共处理。 - 引导层:Loader 负责实际擦写 Flash;上电后
update_success_boot_check读取标志区判断上次升级是否成功,若成功则通过vm_need_recover触发 VM 数据恢复,避免升级过程中因 flash 布局变化导致用户数据丢失。 - 存储层:升级标志区位于
UPDATA_BEG起始地址偏移 0x08 处(UPDATA_FLAG_ADDR),UPDATA_MAGIC = 0x5A00用于区分"未升级"与"CRC 恰好为 0"的边界情况。
升级类型体系
触发方式(UPGRADE_TYPE)
UPGRADE_TYPE 枚举定义在 update.h,描述了用户可感知的升级触发方式:
| 枚举值 | 含义 |
|---|---|
UPGRADE_USB_HARD_KEY | USB 硬件按键强制升级(进入升级模式不依赖应用状态) |
UPGRADE_USB_SOFTKEY | USB 软件按键升级(由应用检测到升级请求后触发) |
UPGRADE_UART_SOFT_KEY | UART 软件按键升级 |
UPGRADE_UART_ONE_WIRE_HARD_KEY | UART 单线硬件按键升级 |
升级通道(UPDATA_TYPE)
UPDATA_TYPE 枚举定义在 update.h,是升级流程内部使用的通道标识,起始值故意与 UPDATA_MAGIC (0x5A00) 一致:
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 是为了让"升级通道类型"与"升级结果标志"共享同一套取值空间,便于用 g_updata_flag 同时记录"从哪个通道升级"与"升级是否成功"。NON_DEV = 0xFFFF 表示无效设备。代码注释明确要求:已有定义顺序不可调整,新增通道必须加在 USER_NORFLASH_UFW_UPDATA 之前,以保持与 Boot/Loader 侧的兼容。
升级结果(UPDATA_RESULT)
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
升级结果被写入 UPDATA_FLAG_ADDR,Boot/Loader 上电后据此判断本次升级是否成功。UPDATA_NON 与 UPDATA_MAGIC 相同,专门处理"CRC 校验值恰好为 0"的退化场景——用魔法数替代 CRC==0 作为初始态标记。
通道参数结构
UPDATA_UART(12 字节):control_io_tx、control_io_rx、control_baud、control_timeout(超时单位 10ms),见 update.h。UPDATA_SD:SD 控制器(SD_CONTROLLER_0/1)与 IO 组合选择(SD0_IO_A~SD0_IO_F、SD1_IO_A/B),以及检测函数、在线检测方式、最大波特率等,见 update.h。UPDATE_UDISK:U 盘参数,内含rx_ldo_trim/tx_ldo_trim的联合体,用于调试 LDO 微调,见 update.h。
核心流程实现分析(update.c)
升级文件与关键宏
#define LOADER_NAME "LOADER.BIN"
#define DEVICE_UPDATE_KEY_ERR BIT(30)
#define DEVICE_FIRST_START BIT(31)
...
const char updata_file_name[] = "/*.ufw";
static u32 g_updata_flag = 0;
static volatile u8 ota_status = 0;
static succ_report_t succ_report;
Source: update.c
LOADER.BIN是升级引导程序,应用代码在升级前跳转到 Loader 执行实际擦写。DEVICE_UPDATE_KEY_ERR(bit30)与DEVICE_FIRST_START(bit31)是g_updata_flag高位标志,分别表示"设备升级密钥错误"与"设备首次启动"。updata_file_name = "/*.ufw"表明升级文件必须放在根目录(通配符匹配),且为短文件名 8+3 格式、仅支持两层目录。
升级标志与 VM 恢复
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;
}
Source: update.c
vm_need_recover 检查 g_updata_flag 低 16 位是否等于 UPDATA_SUCC——当升级成功标志被置位时,说明升级过程中可能发生了 Flash 布局/VM 偏移变化,需要恢复 VM 数据。这是升级与用户数据保持(support_vm_data_keep 配置)协作的关键接口。
升级结果设置与启动校验
void update_result_set(u16 result)
void update_clear_result()
bool update_success_boot_check(void)
Source: update.c
update_result_set(u16 result):将升级结果写入标志区,供 Boot/Loader 读取。update_clear_result():清除升级结果,进入正常启动态。update_success_boot_check():上电时调用,读取标志区判断上次升级是否成功,成功则执行后续的 VM 恢复与成功上报(succ_report)。
结果处理与硬件清理
int update_result_deal();
void update_close_hw(void *filter_name);
static void update_before_jump_common_handle(UPDATA_TYPE up_type)
Source: update.c
update_result_deal():升级完成后的收尾处理,根据结果执行成功/失败分支(例如成功时关闭 UI、失败时回滚标志)。update_close_hw(void *filter_name):按名称过滤关闭外设硬件,保证跳转 Loader 时外设处于安全状态。update_before_jump_common_handle(UPDATA_TYPE up_type):跳转 Loader 前的公共处理——关闭蓝牙控制器(ll_hci_destory/hci_controller_destory)、关闭 RAM 保护(ram_protect_close)、关闭全部中断(hwi_all_close)、关闭 WiFi 检测(wifi_det_close,弱符号实现)、最后chip_reset()复位进入 Loader。该函数接收up_type,以便按通道做差异化处理(例如 UART 单线升级需要保持 IO 状态)。
升级参数填充
void update_param_priv_fill(UPDATA_PARM *p, void *priv, u16 priv_len);
void update_param_ext_fill(UPDATA_PARM *p, u8 ext_type, u8 *ext_data, u8 ext_len);
Source: update.c
update_param_priv_fill:将通道私有参数(如 UART 波特率、SD IO 配置)填入UPDATA_PARM。注释特别说明:ota.bin 放 exflash 升级时,parm_priv 存放 norflash 参数,对应实际升级方式的参数必须放在 norflash 参数之后——这反映了 norflash 升级与其他通道参数在内存布局上的约束。update_param_ext_fill:填充扩展类型数据,为不同升级方式传递附加信息。
Core Flow — 升级全流程
下图展示一次典型升级(以 UART 软件按键为例)从触发到完成的完整时序:
sequenceDiagram
participant App as 应用层
participant Uart as uart_update.c
participant Core as update.c 核心
participant Loader as Loader (LOADER.BIN)
participant Flash as Flash 标志区/代码区
participant VM as VM 数据
App->>Uart: 检测到升级请求 (UPGRADE_UART_SOFT_KEY)
Uart->>Core: 构造 UPDATA_PARM (UART_UPDATA, 波特率/IO/超时)
Core->>Core: update_param_priv_fill / update_param_ext_fill
Core->>Core: update_before_jump_common_handle(up_type)
Core->>Loader: 关闭外设/中断 → chip_reset() 跳转
Loader->>Flash: 校验 .ufw (CRC/VID) 并擦写代码区
Loader->>Flash: 写入升级结果 (update_result_set: UPDATA_SUCC)
Loader->>Loader: 复位重启
Loader->>Core: 上电 update_success_boot_check() 读标志
Core->>VM: vm_need_recover() == true → 恢复 VM 数据
Core->>App: 正常启动,上报升级成功
流程要点:
- 触发:应用层通过 UART 收到升级命令(软件按键)后,
uart_update.c初始化UPDATA_UART参数(TX/RX IO、波特率、超时)。 - 参数构造:
update.c将通道参数通过update_param_priv_fill填入UPDATA_PARM,必要时追加扩展数据。 - 跳转准备:
update_before_jump_common_handle依次销毁蓝牙协议栈、关闭 RAM 保护、关闭全部中断、关闭 WiFi 检测,然后chip_reset()复位。 - Loader 执行:Loader 读取
/*.ufw文件,校验 CRC 与 VID(VID 策略由ufw_vid_need_to_be_different配置),写入 Flash 代码区,最后把UPDATA_SUCC写入UPDATA_FLAG_ADDR。 - 启动校验:复位后应用调用
update_success_boot_check,若标志为UPDATA_SUCC则触发vm_need_recover恢复 VM 数据,保证用户配置不丢失。
UART 升级实现(uart_update.c)
uart_update.c 位于 sdk/app/bsp/common/uart_update/uart_update.c,实现了 UART 通道的升级收发逻辑。其头文件 uart_update.h 提供了对外接口。
关键职责:
- 维护 UART 接收缓冲区与分包解析,将上位机发送的升级数据流按 Loader 协议分包。
- 对接
UPDATA_UART参数(TX/RX IO、波特率、超时),在升级期间接管串口。 - 支持软件按键(
UPGRADE_UART_SOFT_KEY)与单线硬件按键(UPGRADE_UART_ONE_WIRE_HARD_KEY)两种触发方式。
设备升级实现(dev_update.c)
dev_update.c 位于 sdk/app/bsp/common/update/dev_update.c,头文件为 dev_update.h。它负责设备级升级的统一入口,其配置项由 app_config.c 提供:
dev_update_use_eeprom:升级状态区域选择——0使用 VM 区,1使用 EEPROM 区。dev_update_keep_io_status:升级过程中是否保持 IO 状态(防止升级瞬间外设掉电)。dev_update_power_io:升级时使用的电源引脚,-1表示不控制。
这些配置在 app_config.c 中集中定义,是产品定制升级行为的主要旋钮。
配置选项
升级相关配置集中在 app_config.c 的 "update Configuration" 区块,产品定制时直接修改这些全局常量:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
dev_update_use_eeprom | u8 | 0 | 升级状态区域:0=VM 区,1=EEPROM 区 |
dev_update_keep_io_status | u8 | 0 | 设备升级时是否保持 IO 状态 |
dev_update_power_io | u8 | -1 | 升级时用到的电源引脚(-1 表示不控制) |
ufw_vid_need_to_be_different | u8 | 0 | .ufw 文件 VID 校验策略:0=相同,1=不同,2=文件 VID > 设备 VID,3=文件 VID < 设备 VID |
support_norflash_update_en | int | 0 | 是否支持 norflash(外置 Flash)升级 |
support_ota_tws_same_time_new | int | 0 | 是否支持 TWS 双耳同时 OTA(新方案) |
CONFIG_UPDATE_STORAGE_DEV_EN | int | 1 | 是否使能存储设备(USB/SD)升级通道 |
CONFIG_UPDATE_TESTBOX_UART_EN | int | 0 | 是否使能测试盒 UART 升级 |
CONFIG_UPDATE_APP_OTA_EN | int | 0 | 是否使能 APP OTA(蓝牙)升级 |
CONFIG_UPDATE_TESTBOX_BLE_EN | int | 0 | 是否使能测试盒 BLE 升级 |
support_dual_bank_update_en | int | 0 | 是否支持双 Bank(A/B 分区)升级 |
support_vm_data_keep | int | 0 | 升级时是否保留 VM 数据 |
FLASH_ALIGNED_MODE | int | 16 | Flash 对齐系数,仅填 1 或 16,实际对齐 = align × 256 字节 |
设计意图: 这些开关以编译期常量形式存在,保证升级路径在运行时零配置开销;同时用 CONFIG_UPDATE_*_EN 系列控制哪些升级通道被链接进固件,减小未使用通道的代码体积。FLASH_ALIGNED_MODE 对齐策略直接影响 Loader 擦写时的扇区边界计算,修改后必须与 Flash 型号的 sector size 匹配。
API 参考
以下接口来自 update.c(定义于 update.h):
bool vm_need_recover(void)
判断升级成功后是否需要恢复 VM 数据。返回 true 表示低 16 位升级标志为 UPDATA_SUCC,说明发生过升级且 Flash 布局可能变化。
u16 *get_updata_flag_addr(void)
返回升级标志区地址(UPDATA_FLAG_ADDR,即 UPDATA_BEG + 0x08),供 Boot/Loader 读写升级状态。
void update_result_set(u16 result)
将升级结果(UPDATA_RESULT 枚举值)写入标志区。Loader 在升级完成后调用,写入 UPDATA_SUCC / UPDATA_DEV_ERR / UPDATA_KEY_ERR 等。
void update_clear_result(void)
清除升级结果标志,使设备进入正常启动态。
bool update_success_boot_check(void)
上电启动时调用,读取标志区校验上次升级结果,成功则触发 VM 恢复与成功上报。
int update_result_deal(void)
升级完成后的收尾处理:根据结果执行成功/失败分支、关闭 UI 提示等。
void update_close_hw(void *filter_name)
按名称过滤关闭外设硬件,确保跳转 Loader 前外设安全。filter_name 为需要保留的外设名称列表。
static void update_before_jump_common_handle(UPDATA_TYPE up_type)
跳转 Loader 前的公共处理:销毁蓝牙控制器、关闭 RAM 保护、关闭全部中断、关闭 WiFi 检测,最后复位进入 Loader。
void update_param_priv_fill(UPDATA_PARM *p, void *priv, u16 priv_len)
向 UPDATA_PARM 填充通道私有参数(如 UART/SD/norflash 参数)。注意:norflash 升级时私有参数需排在通道参数之前。
void update_param_ext_fill(UPDATA_PARM *p, u8 ext_type, u8 *ext_data, u8 ext_len)
向 UPDATA_PARM 填充扩展类型数据,用于传递附加升级信息。
失败模式、边界情况与并发
失败模式
| 场景 | 检测方式 | 处理策略 |
|---|---|---|
| 升级文件 CRC 错误 | Loader 校验 CRC | 写入 UPDATA_PARM_ERR,保持旧固件 |
| 升级文件 VID 不匹配 | ufw_vid_need_to_be_different 策略 | 写入 UPDATA_KEY_ERR,拒绝升级 |
| 设备错误(擦写失败/掉盘) | Loader 擦写返回错误 | 写入 UPDATA_DEV_ERR,设备回退旧固件 |
| 升级密钥错误 | DEVICE_UPDATE_KEY_ERR (bit30) | 置位标志位,阻止无效固件写入 |
| 升级中途掉电 | UPDATA_FLAG_ADDR 标志未置 SUCC | 上电后 update_success_boot_check 判定失败,重新进入可升级状态 |
边界情况
- CRC == 0:升级标志初始值使用
UPDATA_MAGIC (0x5A00)而非 0,避免与"CRC 恰好为 0"混淆(见 update.h)。 - 首次启动:
DEVICE_FIRST_START(bit31) 用于区分设备首次上电与升级后重启,避免误触发 VM 恢复。 - 文件系统限制:升级文件必须是短文件名(8+3)且最多两层目录,路径硬编码为
/*.ufw——这是 Loader 端文件系统实现的约束。 - 对齐约束:
FLASH_ALIGNED_MODE只允许 1 或 16,实际按 ×256 字节对齐,违反约定会导致擦写越界。
并发/时序一致性
- 升级是单线程独占流程:进入升级流程后通过
hwi_all_close关闭全部中断、销毁蓝牙协议栈,避免升级期间外设事件干扰擦写时序。 g_updata_flag是全局状态,但只在升级流程(单线程)与 Boot 启动早期读取,不存在多任务竞争;ota_status声明为volatile供中断/任务间传递升级进度。wifi_det_close为弱符号(__attribute__((weak)))实现,未启用 WiFi 的工程可被链接器自动替换为空操作,保证升级核心不依赖特定外设。
扩展点
- 新增升级通道:在
UPDATA_TYPE枚举的USER_NORFLASH_UFW_UPDATA之前追加新值,并在update_before_jump_common_handle中按up_type增加差异化处理(如新增硬件外设的关闭逻辑)。 - 自定义升级文件路径:修改
updata_file_name(当前为/*.ufw)可调整升级文件查找规则,但必须满足短文件名与两层目录约束。 - 升级结果回调:
succ_report_t succ_report结构用于升级成功上报,可在update_result_deal成功后扩展上报内容(如版本号、升级耗时)。 - 产品定制开关:通过
app_config.c的升级配置项即可启用/禁用各通道(存储设备、测试盒 UART/BLE、APP OTA、双 Bank、norflash),无需修改核心代码。