杰理 SDK 文档中心
首页
首页
  • 概述

    • SDK 概览与产品定位
    • 支持芯片平台与蓝牙认证
    • SDK 架构与目录分层
  • 快速开始

    • 环境搭建与编译工具链
    • 编译构建系统
    • 板级工程与配置
    • 烧录与固件升级工具
  • 应用工程

    • 应用选择与工程总览
    • SPP + BLE 数传应用框架
    • 透传与 AT 指令示例
    • BLE 广播/中心与定位示例
    • 2.4G 私有协议与 Dongle 示例
    • 云平台接入示例
    • HID 人机交互应用框架
    • HID 示例工程(键盘/鼠标/遥控器/手柄)
    • Bluetooth Mesh 应用框架
    • Mesh 模型与 Mesh DFU 固件升级
    • Mesh 音频编解码演示
  • 芯片平台与硬件抽象

    • 芯片平台总览与差异
    • 音频编解码与时钟管理
    • 外设驱动接口(ADC/IIC/SPI/PWM/LED/充电)
    • 芯片配置工具与下载支持
  • 蓝牙协议栈

    • 蓝牙控制器层(btctrler)
    • 蓝牙协议栈与 Profile(btstack)
    • 蓝牙模块选择与配置
  • 媒体与音频框架

    • 音频流框架
    • 音频编解码与 A2DP 媒体
    • 音频效果处理(EQ/频谱/变调/环绕/超低音)
    • 本地 TWS 与音频同步
  • 系统服务与运行时

    • 实时操作系统与任务调度
    • 消息事件机制
    • 电源管理与低功耗
    • 存储与配置系统
    • 设备驱动框架(USB/RTC)
  • 应用公共组件

    • 音频应用组件
    • 设备外设抽象(按键/触摸/传感器/存储)
    • 蓝牙公共模块与消息联动
    • 调试与配置组件
    • 杰理关键词唤醒(jl_kws)
  • 第三方协议与云平台接入

    • 杰理 RCSP 私有协议
    • 低功耗蓝牙 Mesh 方案(llsync_mesh)
    • Sig Mesh 方案
    • 涂鸦协议接入
    • 腾讯连连接入
    • 华为 HiLink 接入
  • 固件升级与维护

    • OTA 升级机制
    • 升级补丁与版本维护
    • 升级工具链(BLE OTA / USB Dongle OTA)
  • 文档与开发资源

    • 数据手册与架构文档
    • 协议与云平台开发文档
    • 常见问题与技术支持

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 将升级机制设计为可裁剪、多通道、状态可恢复的公共框架:

  1. 可裁剪:通过 UPDATE_MODULE_IS_SUPPORT(...)、support_norflash_update_en、support_dual_bank_update_en 等编译期/运行期开关控制参与编译与运行的升级模块;
  2. 多通道:同一套结果管理与启动检查逻辑可服务于 UART、SD/TF 卡、USB、私有协议等多种升级来源;
  3. 状态可恢复:升级结果通过带 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

流程说明:

  1. 数据接收阶段:升级主机通过所选通道(UART、测试盒、私有协议)与设备建立连接;UART 通道由 uart_data_decode 回调持续解析串口数据帧。
  2. 搬运阶段:数据就绪后创建 update_loader_download_task 任务,将固件写入 flash 升级分区(loader 场景则写 LOADER.BIN,路径为 mnt/norflash/C/LOADER.BIN,升级文件匹配 /*.UFW)。
  3. 结果落盘阶段:update_result_set() 将结果码写入 UPDATA_FLAG_ADDR 并附加 CRC16,随后复位。
  4. 重启验证阶段:新固件启动后 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_enextern int链接期常量是否启用双 bank(A/B 分区)升级
support_norflash_update_enextern int链接期常量是否启用 norflash 升级
support_vm_data_keepextern 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 框架为新增升级来源预留了清晰的接入路径:

  1. 新增消息入口:在 testbox_update.c 的消息分发处仿照 MSG_BT_UPDATE_LOADER_DOWNLOAD_START / MSG_BLE_TEST_OTA_LOADER_DOWNLOAD_START 增加消息分支,并用 UPDATE_MODULE_IS_SUPPORT(...) 宏控制裁剪;
  2. 复用 loader 下载:任何通道在数据就绪后均可创建 update_loader_download_task 或调用 app_update_loader_downloader_init(...) 复用统一的固件搬运逻辑;
  3. 对接私有协议:RCSP_BTMATE_EN / RCSP_ADV_EN 二选一包含对应 rcsp_*_user_update.h,新协议版本只需提供同名的用户升级接口即可挂接;
  4. 覆盖弱符号:wifi_det_close()、breakpoint_uninit() 为 __attribute__((weak)) 实现,平台相关收尾逻辑可通过强符号覆盖注入;
  5. 结果码扩展: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
Next
升级补丁与版本维护