杰理 SDK 文档中心
首页
首页
  • fw-Bootloader:JL 系列定制 Bootloader

    • Bootloader 架构与芯片适配
    • uboot 升级协议与流程
    • 上位机升级工具
    • 编译环境与快速开始
  • ac792n-ota-loader:AC792N 系列 OTA Loader

    • 工程结构与公共运行时框架
    • SD 卡与 USB 基础升级通道
    • 安全升级通道(SD/USB)
    • 用户自定义升级通道(UART/USB HID)
    • LVGL 图形化升级界面与模拟器
  • ac791n-ota-loader:AC791N 系列 OTA Loader

    • uboot 应用框架与 WiFi 示例
    • 升级通道变体(AP/STA/USB HID)
    • 网络与系统库依赖

SD 卡与 USB 基础升级通道

本文档介绍 Jieli(杰理)AC79 系列芯片定制 OTA loader(ac791n-ota-loader / ac792n-ota-loader)中,通过 SD 卡与 USB 两种基础介质完成固件升级的完整机制,包括启动检测流程、通道选择、版本标识、升级结果处理与失败恢复。

Purpose and Scope

本页聚焦 OTA loader 中 SD 卡升级(SD_MODULE_CONTROL / sd_update2) 与 USB 升级(USB_HID_MODULE_CONTROL / usb_hid_ota) 两条基础升级通道:它们共用同一套启动检测入口(updata_check())、Flash 分区写入(flash_update_reserve_area())与升级状态上报(dev_update_status_req_handle())机制。

以下内容属于其他页面,不在本页展开:

  • 蓝牙(BLE/RCSP)升级通道:ble_rcsp_server.c、BLE_UPDATA_SUPPORT_CONNECT 宏控制,以及 wl82_AP_ota_loader.cbp 中的 OTA_LOADER_TYPE=net_ota 网络升级工程。
  • UART 升级通道:UART_UPDATA_MODULE_CONTROL / UART_UPDATA_USER_MODULE_CONTROL 宏分支(见 main.c 的 uart_update_set_gpio 等调用)。
  • NOR Flash 升级、Efuse 升级等其它 UPDATA_MODULE_CONTROL 变体。

Overview

OTA loader(ota_loader)是独立于主应用程序(AP)运行的引导升级程序。它在上电复位后抢先运行,通过检测外部介质(SD 卡)或 USB 枚举状态,决定是否进入升级模式:若检测到升级请求(如 SD 卡中存在升级文件、USB 被主机枚举并下发升级命令),则执行固件搬运与 Flash 写入;否则跳过升级流程,跳转启动主应用。

其核心设计意图是 "升级优先、失败可恢复":

  • 升级判断发生在主应用运行之前,保证主应用崩溃或升级中断后仍能再次进入升级流程(update_loader_ram_record_check() 恢复 RAM 中的升级记录)。
  • SD 与 USB 通道共用 dev_update(fs_v2_update、ex_api_code)这套统一的 Flash 文件系统升级抽象,介质差异被封装在模块内部。
  • 升级失败(如固件与产品 ID 不匹配)会清理升级记录(clr_update_loader_record()),避免反复进入升级流程形成死循环。
flowchart TD
    subgraph sg_Loader["ota_loader 启动域"]
        MAIN["main.c<br/>系统初始化"]
        RAMCHECK["update_loader_ram_record_check()<br/>恢复升级记录"]
        CHECK["updata_check()<br/>升级请求检测"]
    end

    subgraph sg_SD["SD 卡通道 (sd_update2)"]
        SD["SD_MODULE_CONTROL 宏开关"]
        FS2["dev_update/fs_v2_update<br/>文件系统升级"]
    end

    subgraph sg_USB["USB 通道 (usb_hid_ota)"]
        USB["USB_HID_MODULE_CONTROL 宏开关"]
        HID["usb_hid_ota 静态库<br/>logic_lib / cpu_lib"]
    end

    subgraph sg_Core["升级核心 (dev_update)"]
        STATUS["dev_update_status_req_handle()<br/>状态请求处理"]
        FLASH["flash_update_reserve_area()<br/>Flash 分区写入"]
        CLR["clr_update_loader_record()<br/>清理升级记录"]
        RESULT["updata_res.updata_result<br/>升级结果"]
    end

    MAIN --> RAMCHECK --> CHECK
    CHECK -->|"SD 升级请求"| SD --> FS2 --> FLASH
    CHECK -->|"USB 升级请求"| USB --> HID --> FLASH
    FLASH --> STATUS
    STATUS --> CLR
    FLASH --> RESULT
    RESULT -->|"失败(如产品ID不匹配)"| CLR

启动入口与升级检测

OTA loader 的升级检测入口位于 main.c。系统完成基本初始化后,根据编译期宏开关决定是否执行升级检测:

#if (SD_MODULE_CONTROL || USB_HID_MODULE_CONTROL || BLE_UPDATA_SUPPORT_CONNECT || USER_NORFLASH_UPDATA_MODULE_CONTROL || USER_LC_FLASH_UPDATA_MODULE_CONTROL || DEV_NORFLASH_UPDATA_MODULE_CONTROL || USB_HOST_MODULE_CONTROL )
    update_loader_ram_record_check();
    check_res = updata_check();
#elif (BT_UPDATA_MODULE_CONTROL && (defined(CONFIG_CPU_BR23) || defined(CONFIG_CPU_BR25))) //这里23\25需要读ldo trim和share_en
    update_loader_ram_record_check();
    updata_check(); //不修改check_res返回值,不影响bt_reinit流程;
#elif (UART_UPDATA_USER_MODULE_CONTROL)
    if (!check_res) {
        update_loader_ram_record_check();
    }
#endif

Source: main.c

这段代码的语义可以拆解为三个层次:

  1. 记录恢复先行:update_loader_ram_record_check() 在任何介质检测之前执行。它把上次升级中断时保存在 RAM/保留区中的升级进度记录恢复出来,使升级可以"断点续传"或至少能识别上次未完成的升级,这是升级可靠性的第一道保障。
  2. 统一检测入口:updata_check() 是升级请求检测的总入口,其内部根据 updata_mode.h(dev_update/updata_mode.h)定义的升级类型枚举去轮询各通道。main.c 中仅通过宏组合决定是否调用,以及返回值 check_res 是否影响后续主应用启动流程。
  3. 平台差异:BR23/BR25 平台(CONFIG_CPU_BR23/CONFIG_CPU_BR25)的蓝牙升级分支不修改 check_res,注释明确说明"不修改 check_res 返回值,不影响 bt_reinit 流程",即蓝牙升级不阻塞蓝牙重初始化;而 UART 用户升级分支则只有在 check_res 为假时才恢复记录,说明 UART 升级与其他通道互斥。

main.c 中还保留了用于调试的通道强制选择接口,可直接指定升级类型绕过介质探测:

    /* extern void bt_updata_reinit(void); */
    /* bt_updata_reinit(); */
    /* set_updata_type(USB_UPDATA); */
    /* set_updata_type(SD1_UPDATA); */

Source: main.c

set_updata_type(USB_UPDATA) 与 set_updata_type(SD1_UPDATA) 表明升级类型以枚举形式集中管理(USB_UPDATA、SD1_UPDATA 等),调试时可以直接"注入"升级类型,验证某条通道而不依赖实际介质。这也解释了为什么 updata_check() 是"可重入"的:升级类型是一个可被外部设置的状态变量。

SD 卡升级通道(sd_update2)

SD 卡通道由 SD_MODULE_CONTROL 宏控制,版本标识为 sd_update2(BR25 平台为 sd_update2/sd_sec_ota,即安全 OTA 变体):

static const char version_type_tag[] = "sd_update2/sd_sec_ota";
#else
static const char version_type_tag[] = "sd_update2";
#endif

Source: version.c

SD 卡升级的工作方式(依据 update_main.c 的引用与调用关系推断):

  • update_main.c 引入了 dev_update/fs_v2_update.h 与 dev_update/ex_api_code.h。fs_v2_update 是"文件系统 v2 升级"抽象:它把升级包当作 SD 卡文件系统中的一个文件(如 update.iso / 固件 bin)来读取,而不是裸地址读取。这样做的好处是升级包可以通过普通文件拷贝方式放到 SD 卡上,产线不需要专用烧录工具。
  • ex_api_code.h 提供外部 API 代码加载,用于在升级环境中执行受保护/加密的代码段。
  • 升级文件最终通过 flash_update_reserve_area() 写入 Flash 的保留区域(reserve area),实现"双区/保留区"升级布局:新固件先写入保留区,校验通过后再切换启动区域,降低升级失败导致变砖的风险。

USB 升级通道(usb_hid_ota)

USB 通道由 USB_HID_MODULE_CONTROL 宏控制。wl82_ota_loader.cbp 工程文件揭示了该通道的编译配置:

<Add option="-DBT_LOCAL_NAME=&quot;AC791N_update&quot;" />
<Add option="-DUSB_HID_MODULE_CONTROL=1" />
...
<Add option="uboot/include_lib/liba/wl82/usb_hid_ota/logic_lib.a" />
<Add option="uboot/include_lib/liba/wl82/usb_hid_ota/cpu_lib.a" />

Source: wl82_ota_loader.cbp

关键信息:

  • USB 通道基于 HID 类设备(usb_hid_ota),设备以 HID 设备身份被主机枚举,无需安装专用驱动即可被 JL 升级工具识别——这是量产与售后场景选择 HID 而非 MSC/CDC 的主要原因。
  • 该通道以预编译静态库形式提供(logic_lib.a、cpu_lib.a、btctrler_lib.a、bt_protocol_lib.a),主程序只需通过 -DUSB_HID_MODULE_CONTROL=1 开启模块并链接库,具体 HID 协议、分包、校验逻辑封装在库内。
  • 工程同时链接了蓝牙协议库(btctrler_lib.a、bt_protocol_lib.a),表明该 ota_loader 是"USB + 蓝牙"合一的升级工程:USB HID 通道与蓝牙通道可以共存于同一镜像,由 updata_check() 按优先级/状态分派。
  • 对应地,wl82_AP_ota_loader.cbp 是纯网络升级工程(-DOTA_LOADER_TYPE=net_ota,BT_LOCAL_NAME="AC791N_update",CONFIG_NO_SDRAM_ENABLE),与基础介质通道形成"本地基础通道 vs 网络通道"的镜像分工。

main.c 的 updata_check() 调用条件中同时包含了 USB_HOST_MODULE_CONTROL,说明 USB 方向还有"loader 作为主机读取 USB 存储"的变体;而 USB_HID_MODULE_CONTROL 对应"loader 作为 HID 从设备接受上位机升级"。两条 USB 路径共用 update_loader_ram_record_check() + updata_check() 入口。

核心流程:从复位到升级完成

SD/USB 基础升级通道共享同一条主流程,差异仅在介质探测阶段。以 SD 卡升级为例:

sequenceDiagram
    participant H as 硬件复位
    participant M as main.c
    participant R as update_loader_ram_record_check
    participant U as updata_check()
    participant F as fs_v2_update / usb_hid_ota
    participant S as dev_update_status_req_handle
    participant L as flash_update_reserve_area

    H->>M: 上电/复位
    M->>R: 恢复上次升级记录
    R-->>M: 记录状态
    M->>U: 检测升级请求 (SD/USB 宏已编译)
    U->>U: 枚举升级类型 (SD1_UPDATA / USB_UPDATA)
    U->>F: 读取升级包 / 接收 HID 数据
    F-->>U: 升级包就绪
    U->>S: 查询升级状态
    S->>L: 写入 Flash 保留区
    L-->>S: 写入结果
    S-->>U: 状态/错误码
    U-->>M: check_res = 结果
    M->>M: 成功则跳转主应用 / 失败清理记录

流程分步说明:

  1. 复位进入 loader:系统上电或复位后,main.c 执行初始化(时钟、GPIO、电源引脚等),随后进入升级检测区。
  2. 恢复记录:update_loader_ram_record_check() 先执行,将上次未完成的升级进度恢复到 RAM,使 updata_check() 能判断是否需要继续升级(对应 main.c 中宏分支的第一条语句)。
  3. 通道检测:updata_check() 依据编译期启用的模块宏(SD_MODULE_CONTROL / USB_HID_MODULE_CONTROL)去探测介质——SD 通道扫描文件系统升级包,USB 通道等待主机枚举并下发 HID 升级命令。
  4. 数据搬运:fs_v2_update(SD)或 usb_hid_ota 静态库(USB)负责把升级数据分段送入升级核心。
  5. 写入与状态上报:dev_update_status_req_handle() 处理状态请求(update_main.c 第 411 行),驱动 flash_update_reserve_area() 完成 Flash 保留区写入;写完后由 updata_check() 返回 check_res。
  6. 收尾:升级成功则跳转新固件;失败则 clr_update_loader_record() 清理记录,避免下次复位再次进入同一失败升级。

升级结果处理与错误恢复

update_main.c 中升级结果与错误码的处理揭示了失败恢复策略:

    clr_update_loader_record();
    res = flash_update_reserve_area();
    ...
    if ((UPDATE_ERR_PRODUCT_ID_NOT_MATCH == res)) {
        clr_update_loader_record();
        ret = DEV_UPDATA_KEY_ERR;
    }

Source: update_main.c

设计要点:

  • 写入前先清记录:flash_update_reserve_area() 之前先 clr_update_loader_record(),保证这次写入是"全新开始",避免旧记录污染新升级的状态机。
  • 产品 ID 校验:UPDATE_ERR_PRODUCT_ID_NOT_MATCH 说明升级包内嵌产品 ID 信息,Flash 写入前会校验升级包产品 ID 与当前硬件是否匹配,防止"刷错固件"。该错误被映射为 DEV_UPDATA_KEY_ERR(密钥/设备错误),上位机据此提示用户固件不匹配。
  • 失败幂等:无论写入前还是校验失败后,都调用 clr_update_loader_record(),保证失败路径不会残留"未完成升级"标记——否则下次开机 update_loader_ram_record_check() 会误认为有未完成升级而反复尝试。
flowchart TD
    Start([进入升级写入]) --> CLR1["clr_update_loader_record()"]
    CLR1 --> WRITE["flash_update_reserve_area()"]
    WRITE --> RES{"写入结果?"}
    RES -->|"成功"| OK["跳转新固件 / 上报成功"]
    RES -->|"UPDATE_ERR_PRODUCT_ID_NOT_MATCH"| CLR2["clr_update_loader_record()"]
    CLR2 --> ERR["返回 DEV_UPDATA_KEY_ERR"]
    RES -->|"其他错误"| CLR3["clr_update_loader_record()"]
    CLR3 --> ERR2["上报对应错误码"]

升级等待超时由 UPDATE_CMD_WAIT_TIMEOUT 控制(2000UL,单位 2ms,即约 4 秒),用于 USB 升级命令等待等场景,防止 loader 无限阻塞:

#define UPDATE_CMD_WAIT_TIMEOUT	(2000UL) //unit:2ms

Source: update_main.c

使用示例

以下示例均直接摘自仓库源码,展示如何基于现有框架启用并验证 SD/USB 基础升级通道。

示例一:编译期启用 SD + USB 升级通道

基础升级通道通过工程文件的编译宏开启。以 USB HID 通道为例,wl82_ota_loader.cbp 中同时开启蓝牙名称与 USB HID 模块,并链接 usb_hid_ota 预编译库:

<Add option="-DBT_LOCAL_NAME=&quot;AC791N_update&quot;" />
<Add option="-DUSB_HID_MODULE_CONTROL=1" />
...
<Add option="uboot/include_lib/liba/wl82/usb_hid_ota/logic_lib.a" />
<Add option="uboot/include_lib/liba/wl82/usb_hid_ota/cpu_lib.a" />

Source: wl82_ota_loader.cbp

启用 SD_MODULE_CONTROL 或 USB_HID_MODULE_CONTROL 后,main.c 的升级检测分支即被编译进入,启动时自动执行 update_loader_ram_record_check() 与 updata_check()(无需额外代码)。

示例二:调试期强制指定升级通道

在产线调试或单通道验证时,可临时取消注释 main.c 中的调试调用,强制以指定升级类型进入升级流程,绕过介质探测:

    /* extern void bt_updata_debug(void); */
    /* bt_updata_debug(); */

    /* extern void bt_updata_reinit(void); */
    /* bt_updata_reinit(); */
    /* set_updata_type(USB_UPDATA); */
    /* set_updata_type(SD1_UPDATA); */

Source: main.c

set_updata_type(USB_UPDATA) 强制走 USB 通道,set_updata_type(SD1_UPDATA) 强制走 SD 通道。此接口是调试扩展点,正式发布时保持注释状态即可。

示例三:版本标识随平台切换

升级包/日志中携带的版本标签由 version.c 按 CPU 平台区分。BR25 平台使用安全 OTA 标签,其余平台使用基础标签:

#if defined(CONFIG_CPU_BR25)
static const char version_type_tag[] = "sd_update2/sd_sec_ota";
#else
static const char version_type_tag[] = "sd_update2";
#endif

Source: version.c

该标签直接关系到升级包的解析方式:sd_sec_ota 变体需要对升级包做安全校验/解密,普通 sd_update2 则直接解析。平台差异被收敛在版本标签一处,升级核心无需感知平台差异。

配置选项

选项(编译宏)类型默认值说明
SD_MODULE_CONTROL宏开关0(工程决定)启用 SD 卡升级通道,使 updata_check() 扫描 SD 文件系统升级包
USB_HID_MODULE_CONTROL宏开关0启用 USB HID 升级通道,链接 usb_hid_ota 静态库,loader 作为 HID 从设备
USB_HOST_MODULE_CONTROL宏开关0启用 USB Host 方向(loader 作为主机读取 USB 存储介质升级)
BLE_UPDATA_SUPPORT_CONNECT宏开关0启用蓝牙 BLE 升级连接(与 SD/USB 共用检测入口)
UART_UPDATA_MODULE_CONTROL宏开关0启用 UART 升级通道(与基础通道互斥分支)
UART_UPDATE_ONLY_TEST_MODE宏开关0UART 仅测试模式,为 0 时执行复位引脚初始化
CONFIG_CPU_BR25平台宏由芯片型号决定切换版本标签为 sd_update2/sd_sec_ota(安全 OTA)
OTA_LOADER_TYPE编译宏平台决定工程类型选择,如 net_ota 表示网络升级工程(wl82_AP_ota_loader.cbp)
UPDATE_CMD_WAIT_TIMEOUT常量2000UL(≈4s)升级命令等待超时,单位 2ms,见 update_main.c 第 55 行
BT_LOCAL_NAME编译宏字符串"AC791N_update"升级状态下本地蓝牙名称,便于识别升级设备

各模块宏在 main.c 的 updata_check() 调用条件中以"或"关系组合,即任一通道启用即进入升级检测流程;具体某次升级走哪条通道由 updata_check() 内部按介质探测结果与 set_updata_type() 设置的状态决定。

API 参考

以下接口均来自 main.c / update_main.c 的实际调用与声明(部分实现位于 dev_update 预编译库中,此处以调用侧签名为准)。

void update_loader_ram_record_check(void)

恢复上次升级在 RAM 中的记录。在 updata_check() 之前调用,保证升级状态可跨复位恢复(断点续传/失败识别)。

调用时机:任何升级通道启用时,启动流程最先执行(main.c)。

u8 updata_check(void)

升级请求检测总入口。内部轮询各启用通道(SD 文件系统升级包、USB HID 命令等),执行升级写入并返回结果。

参数:无 返回:check_res(非零表示有升级动作/结果,影响主应用启动流程;BR23/BR25 蓝牙分支忽略返回值) 调用时机:update_loader_ram_record_check() 之后(main.c)

void set_updata_type(enum 类型)

强制设置升级类型,如 USB_UPDATA、SD1_UPDATA。调试接口,可绕过介质探测直接指定通道(main.c)。

u8 dev_update_status_req_handle(void)

处理升级状态请求:向调用方(上位机/状态机)上报当前升级进度与结果,并驱动 flash_update_reserve_area() 写入(update_main.c)。

void clr_update_loader_record(void)

清除升级记录。在写入 Flash 前调用以保证全新写入;在失败路径(如产品 ID 不匹配)调用以保证失败幂等(update_main.c)。

u8 flash_update_reserve_area(void)

将升级数据写入 Flash 保留区。返回值为结果码,常见失败码包括 UPDATE_ERR_PRODUCT_ID_NOT_MATCH(产品 ID 不匹配,映射为 DEV_UPDATA_KEY_ERR)。

相关错误码:

  • UPDATE_ERR_PRODUCT_ID_NOT_MATCH:升级包产品 ID 与硬件不匹配,拒绝写入
  • DEV_UPDATA_KEY_ERR:对外上报的设备/密钥错误(由产品 ID 不匹配转换而来)

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

  • 升级中断(断电/拔卡):update_loader_ram_record_check() 在每次启动时恢复 RAM 记录,配合"先写保留区、后切换"的布局,中断后可重新进入升级而非变砖。这是该 loader 最核心的可靠性设计。
  • 固件与硬件不匹配:flash_update_reserve_area() 返回 UPDATE_ERR_PRODUCT_ID_NOT_MATCH 时,clr_update_loader_record() 被再次调用并返回 DEV_UPDATA_KEY_ERR,避免同一失败升级被反复重试。
  • 升级等待超时:USB 等命令型通道依赖 UPDATE_CMD_WAIT_TIMEOUT(2000 × 2ms ≈ 4s)防止 loader 在无主机的场景下无限阻塞;超时后应退出升级流程跳转主应用。
  • 通道互斥:main.c 中 UART_UPDATA_USER_MODULE_CONTROL 分支仅在 !check_res 时恢复记录,与 SD/USB 通道互斥;BR23/BR25 蓝牙分支忽略返回值,避免干扰 bt_reinit。多通道同时启用时,通道优先级由 updata_check() 内部实现决定(位于 dev_update 库中,源码未展开)。
  • 并发模型:loader 阶段为单线程顺序执行(while(1) 主循环 + WDT 喂狗),升级写入期间无多任务竞争;蓝牙相关初始化(btctler_init 等)仅在有蓝牙需求时发生,与 SD/USB 写入流程串行。

性能与运维注意

  • 预编译库依赖:SD/USB 通道的核心逻辑(fs_v2_update、usb_hid_ota)以 .a 静态库形式提供,升级协议与校验细节不可从本仓库源码直接阅读;涉及协议定制时需与杰理官方库版本对齐(见 wl82_ota_loader.cbp 中 include_lib/liba/wl82/usb_hid_ota/ 路径)。
  • 构建工程选择:wl82_ota_loader.cbp(SD/USB/蓝牙合一)与 wl82_AP_ota_loader.cbp(OTA_LOADER_TYPE=net_ota 网络升级)是两个独立镜像,产线可根据目标场景选型;基础升级通道镜像体积更小,且 CONFIG_NO_SDRAM_ENABLE 仅出现在网络工程中。
  • 升级包格式:SD 通道升级文件需符合 fs_v2_update 的文件系统布局并写入 SD 卡根目录约定位置;版本标签 sd_update2 / sd_sec_ota 决定解析是否走安全校验分支,升级包制作工具需与标签匹配。

扩展点

  • 新增升级类型:在 updata_mode.h(dev_update/updata_mode.h)定义的升级类型枚举中扩展,并通过 set_updata_type() 注入;updata_check() 统一分派,无需改动 main.c 主流程。
  • 通道开关:新增介质通道只需定义新的 *_UPDATA_MODULE_CONTROL 宏并加入 main.c 升级检测的宏组合条件,即自动获得"启动检测 + 记录恢复"能力。
  • 平台差异:版本标签(version.c)按 CONFIG_CPU_BRxx 区分,是接入新平台的唯一改动点之一;升级核心保持平台无关。

相关链接

  • main.c — 启动升级检测入口
  • update_main.c — 升级状态处理与 Flash 写入
  • version.c — 升级版本标签
  • wl82_ota_loader.cbp — SD/USB/蓝牙升级工程配置
  • wl82_AP_ota_loader.cbp — 网络升级工程配置
  • 仓库根 README — 各 loader 工程总览

注:升级核心协议(fs_v2_update、usb_hid_ota)以预编译库形式提供,本文档中涉及库内部行为的部分基于调用侧源码(main.c、update_main.c、version.c、.cbp 工程配置)的交叉验证;库内实现细节如未在源码中出现,均已标注,未做推测性描述。

Prev
工程结构与公共运行时框架
Next
安全升级通道(SD/USB)