OTA 升级机制
本文档介绍 fw-AC63_BT_SDK 中 OTA(Over-The-Air)升级机制的完整实现,涵盖升级框架分层、升级结果管理与 CRC 校验、loader 下载流程、UART/双 bank 等升级通道,以及第三方协议(Hilink、腾讯连连、涂鸦)与 Mesh DFU 的接入方式。
Purpose and Scope
本页面聚焦于 SDK 中与「设备固件升级」相关的公共升级框架(apps/common/update/),包括:
- 升级状态机与升级结果持久化(
UPDATA_PARM结构、CRC16 校验、g_updata_flag/ota_status); - 升级入口与触发通道:UART 升级、loader 下载、testbox(测试盒)升级、双 bank 升级;
- 与杰理私有 RCSP 协议(
rcsp_user_update.h/rcsp_adv_user_update.h)的对接点; - 升级完成后的启动检查(首次启动判定、VM 数据恢复)。
以下主题属于独立页面,不在本文展开:apps/mesh/mesh_dfu/(Mesh DFU 组网升级)、apps/common/third_party_profile/ 下的各云平台 OTA 协议实现(Hilink / 腾讯连连 / 涂鸦)、apps/spp_and_le/examples/ 中的具体示例工程(dongle / findmy OTA)。本页仅描述这些模块共同依赖的公共升级机制。
Overview
在蓝牙 SoC(如 AC63 系列)应用中,OTA 升级是指设备在运行时通过某种传输通道(UART、SD 卡、USB、BLE/私有 RF 协议等)接收新的固件(.UFW 文件)或引导程序(LOADER.BIN),将其写入 flash 的升级分区,并在复位后完成固件切换的过程。
SDK 将升级机制设计为可裁剪、多通道、状态可恢复的公共框架:
- 可裁剪:通过
UPDATE_MODULE_IS_SUPPORT(...)、support_norflash_update_en、support_dual_bank_update_en等编译期/运行期开关控制参与编译与运行的升级模块; - 多通道:同一套结果管理与启动检查逻辑可服务于 UART、SD/TF 卡、USB、私有协议等多种升级来源;
- 状态可恢复:升级结果通过带 CRC16 校验的
UPDATA_PARM结构写入 flash 固定地址(UPDATA_FLAG_ADDR),复位后由update_result_get()读取并驱动后续的启动行为(如恢复 VM 数据、判定首次启动)。
核心实现位于 apps/common/update/update.c,其顶部定义了升级框架的关键常量与全局状态:
#define LOADER_NAME "LOADER.BIN"
#define DEVICE_UPDATE_KEY_ERR BIT(30)
#define DEVICE_FIRST_START BIT(31)
extern void update_module_init(void (*cbk)(update_mode_info_t *, u32, void *));
extern void testbox_update_init(void);
...
const u8 loader_file_path[] = "mnt/norflash/C/"LOADER_NAME"";
//升级文件路径必须是短文件名(8+3)结构,仅支持2层目录
/* const char updata_file_name[] = "/UPDATA/JL_692X.BFU"; */
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
设计要点:升级文件路径强制使用 8+3 短文件名且仅支持两级目录(mnt/norflash/C/),这是为了在升级场景(通常运行在精简的 loader 或最小化系统中)下避免长文件名解析带来的复杂性与内存开销。g_updata_flag 的低 16 位保存升级结果,高 16 位保存升级类型(magic),ota_status 为 volatile 状态量,供中断/任务间共享。
Architecture
下图展示了 SDK 中 OTA 升级机制的总体架构与各模块间的依赖关系:
flowchart TD
subgraph sg_Channels["升级通道层"]
UART["uart_update.c / uart_update_master.c<br/>UART 主机/从机升级"]
LOADER["update_loader_download.c<br/>Loader 下载"]
TESTBOX["testbox_update.c<br/>测试盒升级"]
DUAL["dual_bank_updata_api.h<br/>双 bank 升级"]
end
subgraph sg_Core["公共升级框架 (apps/common/update)"]
CORE["update.c<br/>结果管理 / 启动检查"]
INIT["update_module_init()<br/>模块注册与回调"]
RCSP["rcsp_user_update.h / rcsp_adv_user_update.h<br/>私有协议升级"]
end
subgraph sg_Proto["第三方协议(独立页面)"]
HILINK["hilink_ota.c"]
TENCENT["ble_qiot_llsync_ota.c"]
TUYA["tuya_ota.c"]
end
subgraph sg_HW["硬件与系统服务"]
FLASH[("Flash 分区<br/>UPDATA_FLAG_ADDR / 升级区")]
CRC["asm/crc16.h CRC16"]
WDT["asm/wdt.h 看门狗"]
OS["os/os_api.h 任务与消息"]
end
UART --> LOADER
UART --> DUAL
TESTBOX --> LOADER
LOADER --> CORE
DUAL --> CORE
RCSP --> CORE
HILINK --> CORE
TENCENT --> CORE
TUYA --> CORE
CORE --> CRC
CORE --> FLASH
CORE --> WDT
CORE --> OS
INIT --> CORE
架构说明:
- **公共升级框架(update.c)**是唯一负责「升级结果写/读」「CRC 校验」「启动行为决策」的模块。所有升级通道(UART、loader、testbox、双 bank、RCSP、第三方协议)最终都汇聚到这一层,避免各通道各自维护一套结果逻辑。
- 升级通道层负责具体的数据搬运:
uart_update.c通过串口接收固件数据流,update_loader_download.c负责将LOADER.BIN下载到 flash 供引导程序升级,dual_bank_updata_api.h提供 A/B 双 bank 切换能力以降低升级失败风险。 - 第三方协议层(Hilink / 腾讯 / 涂鸦)各自实现云平台的 OTA 数据包协议,但最终复用公共框架的结果记录与启动检查,保证升级结果语义一致。
- 硬件服务:CRC16(
asm/crc16.h)用于校验UPDATA_PARM结构完整性;看门狗(asm/wdt.h)在升级过程中防止异常卡死;os/os_api.h提供任务创建(如update_loader_download_task)与消息传递(如MSG_BT_UPDATE_LOADER_DOWNLOAD_START)。
依赖关系依据 update.c 的 include 列表与各通道文件中
update_loader_download.h、dual_bank_updata_api.h、app_active_update_task_init()、task_create(update_loader_download_task, ...)等真实引用确认。
升级状态与结果管理
UPDATA_PARM 持久化结构
升级结果并非保存在内存中,而是写入 flash 固定地址 UPDATA_FLAG_ADDR 处的 UPDATA_PARM 结构(定义于 update.h,由 UPDATE_SUPPORT_DEV_IS_NULL() 宏判断该地址是否可用)。结构布局为:前 2 字节是 parm_crc(对结构体其余部分做 CRC16),随后是 parm_result(升级结果)与 magic(升级类型标识)。
结果写入:update_result_set()
void update_result_set(u16 result)
{
if (!UPDATE_SUPPORT_DEV_IS_NULL()) {
UPDATA_PARM *p = UPDATA_FLAG_ADDR;
memset(p, 0x00, sizeof(UPDATA_PARM));
p->parm_result = result;
p->parm_crc = CRC16(((u8 *)p) + 2, sizeof(UPDATA_PARM) - 2);
}
#if (RCSP_UPDATE_EN && SMART_BOX_EN && JL_SMART_BOX_EXTRA_FLASH_OPT)
if (UPDATA_SUCC == result) {
smartbox_eflash_update_flag_set(0);
smartbox_eflash_flag_set(0);
extern void set_update_ex_flash_flag(u8 update_flag);
set_update_ex_flash_flag(0);
}
#endif
}
Source: update.c
设计意图:写入前先 memset 清零再填充,确保 CRC 计算基于确定的初始状态;CRC16 跳过前 2 字节(parm_crc 自身),保证校验域不包含被校验数据自身。当智能音箱扩展 flash 优化开启(RCSP_UPDATE_EN && SMART_BOX_EN && JL_SMART_BOX_EXTRA_FLASH_OPT)且升级成功时,同步清理外部 flash 的升级标志,保证内外 flash 状态一致。
结果读取:update_result_get()
u16 update_result_get(void)
{
u16 ret = UPDATA_NON;
if (!UPDATE_SUPPORT_DEV_IS_NULL()) {
UPDATA_PARM *p = UPDATA_FLAG_ADDR;
u16 crc_cal;
crc_cal = CRC16(((u8 *)p) + 2, sizeof(UPDATA_PARM) - 2); //2 : crc_val
if (crc_cal && crc_cal == p->parm_crc) {
ret = p->parm_result;
}
g_updata_flag = ret;
g_updata_flag |= ((u32)(p->magic)) << 16;
memset(p, 0x00, sizeof(UPDATA_PARM));
}
return ret;
}
Source: update.c
关键行为:读取即清零。update_result_get() 在读取结果后立即 memset 清除 flash 中的记录,避免重复启动时误判。CRC 校验失败(或 CRC 为 0)时返回 UPDATA_NON(无升级事件),保证 flash 数据损坏时系统按「无升级」处理。读取的结果被组装进全局 g_updata_flag:低 16 位为 parm_result,高 16 位为 magic(升级类型),供后续 update_success_boot_check()、device_is_first_start()、vm_need_recover() 使用。
启动行为与升级结果消费
g_updata_flag 组装完成后,框架在启动阶段提供三个查询接口,驱动上层(app 主循环 / 业务代码)做出相应动作:
| 接口 | 判定逻辑 | 用途 |
|---|---|---|
vm_need_recover() | (g_updata_flag & 0xffff) == UPDATA_SUCC | 升级成功后需要恢复 VM(虚拟管理)数据 |
update_success_boot_check() | 结果为 UPDATA_SUCC 且升级类型为 SD0_UPDATA / SD1_UPDATA / USB_UPDATA | 判断本次启动是否为「存储介质升级」后的成功启动 |
device_is_first_start() | DEVICE_FIRST_START、DEVICE_UPDATE_KEY_ERR 或 UPDATA_SUCC 任一命中 | 判断是否首次启动(用于恢复出厂/初始化流程) |
bool vm_need_recover(void)
{
return ((g_updata_flag & 0xffff) == UPDATA_SUCC) ? true : false;
}
bool update_success_boot_check(void)
{
if (!UPDATE_SUPPORT_DEV_IS_NULL()) {
u16 result = g_updata_flag & 0xffff;
u16 up_tpye = g_updata_flag >> 16;
if ((UPDATA_SUCC == result) && ((SD0_UPDATA == up_tpye) || (SD1_UPDATA == up_tpye) || (USB_UPDATA == up_tpye))) {
return true;
}
}
return false;
}
bool device_is_first_start()
{
log_info("g_updata_flag=0x%x\n", g_updata_flag);
if ((g_updata_flag & DEVICE_FIRST_START) || (g_updata_flag & DEVICE_UPDATE_KEY_ERR) || (g_updata_flag == UPDATA_SUCC)) {
puts("\n=================device_is_first_start=========================\n");
return true;
}
return false;
}
Source: update.c
设计意图:DEVICE_FIRST_START(BIT(31))与 DEVICE_UPDATE_KEY_ERR(BIT(30))被放在 g_updata_flag 的高位,与低 16 位的升级结果类型区分开——它们是「标志位」而非「结果码」。update_success_boot_check() 额外限定升级来源为 SD0/SD1/USB,是因为这些介质升级在写入固件后可能由引导程序或 loader 完成最后的校验,应用层需要据此决定是否执行「升级后首次进入」的收尾流程(如清理缓存、恢复 VM)。
升级通道与触发方式
UART 升级(uart_update.c / uart_update_master.c)
UART 通道负责在设备端接收上位机(或主设备)通过串口下发的升级数据。数据到达后由 uart_data_decode 回调解析,uart_hw_init() 完成串口硬件初始化。升级数据准备完毕后,通过 task_create(update_loader_download_task, NULL, THIS_TASK_NAME) 创建独立的 loader 下载任务执行固件搬运,避免阻塞业务任务:
static void update_loader_download_task(void *p)
{
...
}
...
memcpy(&update_cfg, cfg, sizeof(uart_update_cfg));
task_create(update_loader_download_task, NULL, THIS_TASK_NAME);
uart_hw_init(&update_cfg, uart_data_decode);
Source: uart_update_master.c
uart_update.c 中同样存在该任务创建逻辑,并保留了对 update_mode_api(type, update_baudrate, tx, rx)(设置升级模式与波特率)以及 app_active_update_task_init(&info)(激活升级任务)的调用路径,其中 UPDATE_LOADER_OK 命令用于确认 loader 升级握手成功。
Loader 下载(update_loader_download.c / testbox_update.c)
update_loader_download.h 是多个通道的公共依赖。测试盒(testbox)升级通过消息驱动:
case MSG_BT_UPDATE_LOADER_DOWNLOAD_START:
if (UPDATE_MODULE_IS_SUPPORT(UPDATE_BT_LMP_EN)) {
...
}
case MSG_BLE_TEST_OTA_LOADER_DOWNLOAD_START:
if (UPDATE_MODULE_IS_SUPPORT(UPDATE_BLE_TEST_EN)) {
...
}
Source: testbox_update.c
要点:不同升级来源(BT LMP 升级、BLE 测试盒 OTA)由 UPDATE_MODULE_IS_SUPPORT(...) 宏按编译配置裁剪,MSG_BT_UPDATE_LOADER_DOWNLOAD_START 与 MSG_BLE_TEST_OTA_LOADER_DOWNLOAD_START 是进入 loader 下载的入口消息。这种「消息 → 模块开关 → 下载任务」的链路使新增升级来源只需在消息分发处增加分支。
双 bank 升级(dual_bank_updata_api.h)
update.c 直接引用 dual_bank_updata_api.h。双 bank(A/B 分区)升级将固件写入非当前运行分区,校验成功后切换启动分区;若校验失败则回退到原分区,配合 support_dual_bank_update_en 运行期开关控制。该机制与 UPDATA_PARM 结果记录配合:只有 update_result_set(UPDATA_SUCC) 写入成功,新 bank 才会在下次启动时被采纳。
私有协议升级(RCSP)
根据编译配置,框架链接不同的私有协议升级实现:
#if RCSP_BTMATE_EN
#include "rcsp_user_update.h"
#elif RCSP_ADV_EN
#include "rcsp_adv_user_update.h"
#endif
Source: update.c
RCSP_BTMATE_EN(杰理 BTMATE App)与 RCSP_ADV_EN(杰理增强协议)二选一,保证同一份 update.c 能适配两代私有协议而无需复制框架代码。
Core Flow
下图描述了从「升级数据就绪」到「复位后启动处理」的完整时序:
sequenceDiagram
participant Host as 升级主机/上位机
participant Chan as 升级通道 (UART/testbox/RCSP)
participant Task as update_loader_download_task
participant Core as update.c 框架
participant Flash as Flash (UPDATA_FLAG_ADDR / 升级区)
Host->>Chan: 下发升级数据 / 握手 (UPDATE_LOADER_OK)
Chan->>Chan: uart_data_decode 解析数据流
Chan->>Task: task_create(update_loader_download_task)
Task->>Flash: 写入固件/LOADER.BIN 到升级分区
Task->>Core: 升级完成回调
Core->>Core: 校验升级结果
Core->>Flash: update_result_set(result) 写入 UPDATA_PARM + CRC16
Core->>System: 复位/重启
System->>System: 引导程序切换固件
System->>Core: update_result_get() 读取并清零结果
Core->>Core: 组装 g_updata_flag (result + magic)
Core-->>App: vm_need_recover / update_success_boot_check / device_is_first_start
流程说明:
- 数据接收阶段:升级主机通过所选通道(UART、测试盒、私有协议)与设备建立连接;UART 通道由
uart_data_decode回调持续解析串口数据帧。 - 搬运阶段:数据就绪后创建
update_loader_download_task任务,将固件写入 flash 升级分区(loader 场景则写LOADER.BIN,路径为mnt/norflash/C/LOADER.BIN,升级文件匹配/*.UFW)。 - 结果落盘阶段:
update_result_set()将结果码写入UPDATA_FLAG_ADDR并附加 CRC16,随后复位。 - 重启验证阶段:新固件启动后
update_result_get()读取并立即清零结果,CRC 校验通过则返回结果码;业务层依据g_updata_flag决定是否恢复 VM、执行首次启动初始化等。
配置选项
OTA 机制的裁剪与行为主要通过编译宏、链接常量与消息开关控制:
| 配置项 | 类型 | 默认/取值 | 作用 |
|---|---|---|---|
UPDATE_MODULE_IS_SUPPORT(mod) | 宏 | 编译期 | 判定某升级模块是否使能,如 UPDATE_BT_LMP_EN(BT LMP 升级)、UPDATE_BLE_TEST_EN(BLE 测试盒 OTA) |
support_dual_bank_update_en | extern int | 链接期常量 | 是否启用双 bank(A/B 分区)升级 |
support_norflash_update_en | extern int | 链接期常量 | 是否启用 norflash 升级 |
support_vm_data_keep | extern int | 链接期常量 | 升级时是否保留 VM 数据 |
RCSP_BTMATE_EN / RCSP_ADV_EN | 宏 | 二选一 | 选择 BTMATE 或增强版私有协议升级实现 |
RCSP_UPDATE_EN && SMART_BOX_EN && JL_SMART_BOX_EXTRA_FLASH_OPT | 宏 | 条件组合 | 智能音箱扩展 flash 场景下,升级成功后同步清理外部 flash 升级标志 |
UPDATE_VOICE_REMIND | 宏 | 可选 | 升级过程语音提示(引入 tone_player.h) |
UPDATE_LED_REMIND | 宏 | 可选 | 升级过程 LED 提示(led_update_start() / led_update_finish() 调用 pwm_led_mode_set()) |
TCFG_UI_EN | 宏 | 可选 | 升级进度 UI 显示 |
DEV_UPDATE_SUPPORT_JUMP | 宏 | 可选 | 升级前跳转 MaskROM(__JUMP_TO_MASKROM()),并预先关闭 SPI 端口、卸载 SD1、关闭 USB SIE |
LOADER_NAME | 宏 | "LOADER.BIN" | loader 文件名,存放路径 mnt/norflash/C/ |
updata_file_name | 常量 | "/*.UFW" | 升级固件文件名匹配模式(8+3 短文件名,两级目录) |
DEVICE_FIRST_START / DEVICE_UPDATE_KEY_ERR | 宏 | BIT(31) / BIT(30) | g_updata_flag 高位标志位 |
运行期可调项还包括升级串口的波特率与引脚(通过 uart_update_cfg 与 update_mode_api(type, update_baudrate, tx, rx) 传入),以及 update_result_set() 写入的结果码(UPDATA_NON、UPDATA_SUCC 等定义于 update.h)。
API Reference
以下为 apps/common/update/update.c 暴露的核心接口(均在 update.h 中声明):
u16 update_result_get(void)
读取并清零上次升级结果。从 UPDATA_FLAG_ADDR 读取 UPDATA_PARM,用 CRC16(跳过自身 2 字节)校验;通过则返回 parm_result 并组装全局 g_updata_flag,随后清空结构。
- 返回:
u16结果码(UPDATA_NON表示无有效记录或 CRC 失败)。
void update_result_set(u16 result)
写入升级结果。清零 UPDATA_PARM 后写入 parm_result 并计算 parm_crc;智能音箱扩展 flash 场景下,成功(UPDATA_SUCC)时同步清除外部 flash 升级标志。
- 参数:
result— 结果码,如UPDATA_SUCC、UPDATA_NON。
bool vm_need_recover(void)
- 返回:
g_updata_flag低 16 位为UPDATA_SUCC时为true,表示升级成功后需要恢复 VM 数据。
void update_clear_result(void)
清除全局升级标志(g_updata_flag = 0)。
bool update_success_boot_check(void)
- 返回:结果为
UPDATA_SUCC且升级类型为SD0_UPDATA/SD1_UPDATA/USB_UPDATA时为true,用于判定「介质升级成功后的首次启动」。
bool device_is_first_start(void)
- 返回:命中
DEVICE_FIRST_START、DEVICE_UPDATE_KEY_ERR或UPDATA_SUCC任一标志时为true(首次启动,需走初始化/恢复流程)。
void update_module_init(void (*cbk)(update_mode_info_t *, u32, void *))
升级模块初始化入口(外部声明,实现在通道层),注册升级模式回调;配套的 testbox_update_init() 初始化测试盒升级。update.c 通过弱符号机制(__attribute__((weak)))提供 wifi_det_close()、breakpoint_uninit() 的兜底空实现,允许上层按需强符号覆盖。
辅助外部服务(由 update.c 调用)
| 函数 | 作用 |
|---|---|
get_ota_status() | 获取当前 OTA 状态(对应 volatile ota_status) |
dev_update_get_parm(type) | 获取设备升级参数 |
get_nor_update_param(buf) | 获取 norflash 升级参数 |
ll_hci_destory() / hci_controller_destory() | 升级前销毁 HCI/控制器资源 |
ram_protect_close() / hwi_all_close() | 关闭 RAM 保护与所有硬件中断(升级写 flash 前调用) |
tws_sniff_controle_check_disable() / tws_tx_unsniff_req() / sys_auto_sniff_controle() | TWS 升级前关闭嗅探,避免连接干扰 |
app_audio_set_wt_volume(vol) / get_max_sys_vol() / get_ldo_trim_res(res) | 升级前的音频/电压校准收尾 |
失败模式、边界情况与并发
结果记录损坏
UPDATA_PARM 的 CRC16 校验域覆盖除 parm_crc 自身外的全部字段。若 flash 写入被中断、掉电或位翻转导致数据损坏,update_result_get() 中 crc_cal != p->parm_crc 分支生效,返回 UPDATA_NON。设计意图:把「无法确认的升级」当作「未发生升级」,避免在数据不确定时执行 VM 恢复、首次启动等破坏性初始化。同时 update_result_get() 无论校验是否通过都会 memset 清零,保证损坏记录只影响一次启动判定。
CRC 为 0 的边界
crc_cal && crc_cal == p->parm_crc 显式要求计算出的 CRC 非零。若 flash 全部为 0xFF 擦除态且结构未初始化,CRC 恰好为 0 时同样按无升级处理,防止未初始化区域被误判。
升级中断电 / 看门狗
升级写 flash 期间若发生掉电,固件可能不完整。框架侧对策分两层:
- loader 机制:
LOADER.BIN与升级文件分离——引导程序先于应用启动,若检测到升级未完成(结果记录缺失或类型不符),可停留在 loader 等待重新下载,避免进入损坏的应用; - 看门狗:
asm/wdt.h被引入,升级任务异常卡死时触发复位,复位后由update_result_get()依据 CRC 状态决定恢复路径。
多任务与并发访问
ota_status声明为volatile u8,供中断上下文与任务上下文共享,避免编译器优化导致的状态不一致;update_loader_download_task通过task_create独立运行,与业务任务隔离,升级数据解析(uart_data_decode)与 flash 写入解耦;- 升级前框架调用
ram_protect_close()、hwi_all_close()、ll_hci_destory()等关闭可能干扰 flash 操作的资源(RAM 保护、全部硬件中断、HCI/控制器),TWS 场景额外禁用嗅探(tws_sniff_controle_check_disable()/tws_tx_unsniff_req()),保证写 flash 期间系统静默。
首次启动与升级成功标志的重叠
device_is_first_start() 将 UPDATA_SUCC 与 DEVICE_FIRST_START 并列判断,意味着「升级成功后的首次启动」与「出厂首次启动」走同一初始化路径(如恢复默认配置)。这是刻意设计:升级成功往往伴随配置格式变更,复用首次启动流程可以强制应用新固件的默认配置基线。
性能与运维注意事项
- 升级文件命名约束:固件必须为 8+3 短文件名且位于两级目录内(
mnt/norflash/C/),长文件名或深层目录会导致匹配失败——这是运维侧最常见的升级失败原因之一; - 写 flash 前的资源收敛:
ram_protect_close()、hwi_all_close()、wifi_det_close()等调用顺序敏感,扩展升级流程时应保持「先收敛资源、再写 flash、最后记录结果」的次序; - 升级结果的单次消费语义:
update_result_get()读取即清零,业务代码不得多次调用依赖同一结果——如需缓存判定,应读取后基于g_updata_flag或返回值自行保存; - 波特率与通道参数:UART 升级性能取决于
update_baudrate与收发引脚配置(uart_update_cfg),高波特率可显著缩短升级窗口,降低掉电风险暴露时间。
扩展点
OTA 框架为新增升级来源预留了清晰的接入路径:
- 新增消息入口:在
testbox_update.c的消息分发处仿照MSG_BT_UPDATE_LOADER_DOWNLOAD_START/MSG_BLE_TEST_OTA_LOADER_DOWNLOAD_START增加消息分支,并用UPDATE_MODULE_IS_SUPPORT(...)宏控制裁剪; - 复用 loader 下载:任何通道在数据就绪后均可创建
update_loader_download_task或调用app_update_loader_downloader_init(...)复用统一的固件搬运逻辑; - 对接私有协议:
RCSP_BTMATE_EN/RCSP_ADV_EN二选一包含对应rcsp_*_user_update.h,新协议版本只需提供同名的用户升级接口即可挂接; - 覆盖弱符号:
wifi_det_close()、breakpoint_uninit()为__attribute__((weak))实现,平台相关收尾逻辑可通过强符号覆盖注入; - 结果码扩展:
update_result_set()的结果码在update.h中定义,新增升级失败类型只需新增枚举并保持update_result_get()的消费语义不变。
第三方协议与 Mesh 升级(跨页参考)
公共框架之上,仓库还包含独立的协议层实现,各自页面另行详述:
- Hilink:hilink_ota.c — 华为智能家居 OTA;
- 腾讯连连:ble_qiot_llsync_ota.c — 腾讯 IoT 平台 OTA;
- 涂鸦:tuya_ota.c — 涂鸦智能 OTA;
- Mesh DFU:mesh_target_node_ota.c — Mesh 目标节点固件分发升级;
- 示例工程:ota_dg_central.c(Dongle OTA 主机)、ble_fmy_ota.c(FindMy OTA)。
这些模块与公共框架的分工是:协议层解决「怎么传」(数据包封装、握手、断点续传),公共框架解决「怎么落」(结果持久化、CRC 校验、启动行为)。
测试与验证
SDK 未提供独立的 OTA 单元测试工程,升级机制的验证依赖以下手段(依据源码中的自检路径归纳):
- testbox 升级链路:
testbox_update.c通过MSG_BT_UPDATE_LOADER_DOWNLOAD_START/MSG_BLE_TEST_OTA_LOADER_DOWNLOAD_START消息触发 loader 下载,是产线自检与功能验证的标准入口;UPDATE_BT_LMP_EN、UPDATE_BLE_TEST_EN模块开关决定哪些链路参与测试; - 升级结果自检:
update_result_get()的 CRC16 校验逻辑(crc_cal && crc_cal == p->parm_crc)覆盖了「写后读回」的完整性验证,测试时可人为篡改UPDATA_FLAG_ADDR数据验证损坏分支返回UPDATA_NON; - 启动行为验证:
device_is_first_start()的日志输出(log_info("g_updata_flag=0x%x\n", ...)与puts(...))用于确认升级成功后的首次启动判定;通过对比正常启动与升级后启动的日志可验证vm_need_recover()/update_success_boot_check()分支; - UART 抓包/回环:
uart_update_master.c的uart_data_decode回调可在 PC 端模拟主设备数据流,验证数据解码与update_loader_download_task的搬运时序。
相关链接
- 核心框架源码:apps/common/update/update.c
- 升级头文件与结果定义:apps/common/update/update.h
- Loader 下载:apps/common/update/update_loader_download.c
- UART 从机升级:apps/common/update/uart_update.c
- UART 主机升级:apps/common/update/uart_update_master.c
- 测试盒升级:apps/common/update/testbox_update.c
- 双 bank 升级 API:apps/common/update/dual_bank_updata_api.h
- Mesh DFU 升级(独立页面):apps/mesh/mesh_dfu/mesh_target_node_ota.c
- 云平台 OTA 协议(独立页面):hilink_ota.c、ble_qiot_llsync_ota.c、tuya_ota.c
- 示例工程:ota_dg_central.c、ble_fmy_ota.c