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
这段代码的语义可以拆解为三个层次:
- 记录恢复先行:
update_loader_ram_record_check()在任何介质检测之前执行。它把上次升级中断时保存在 RAM/保留区中的升级进度记录恢复出来,使升级可以"断点续传"或至少能识别上次未完成的升级,这是升级可靠性的第一道保障。 - 统一检测入口:
updata_check()是升级请求检测的总入口,其内部根据updata_mode.h(dev_update/updata_mode.h)定义的升级类型枚举去轮询各通道。main.c中仅通过宏组合决定是否调用,以及返回值check_res是否影响后续主应用启动流程。 - 平台差异: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="AC791N_update"" />
<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: 成功则跳转主应用 / 失败清理记录
流程分步说明:
- 复位进入 loader:系统上电或复位后,
main.c执行初始化(时钟、GPIO、电源引脚等),随后进入升级检测区。 - 恢复记录:
update_loader_ram_record_check()先执行,将上次未完成的升级进度恢复到 RAM,使updata_check()能判断是否需要继续升级(对应main.c中宏分支的第一条语句)。 - 通道检测:
updata_check()依据编译期启用的模块宏(SD_MODULE_CONTROL/USB_HID_MODULE_CONTROL)去探测介质——SD 通道扫描文件系统升级包,USB 通道等待主机枚举并下发 HID 升级命令。 - 数据搬运:
fs_v2_update(SD)或usb_hid_ota静态库(USB)负责把升级数据分段送入升级核心。 - 写入与状态上报:
dev_update_status_req_handle()处理状态请求(update_main.c第 411 行),驱动flash_update_reserve_area()完成 Flash 保留区写入;写完后由updata_check()返回check_res。 - 收尾:升级成功则跳转新固件;失败则
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="AC791N_update"" />
<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 | 宏开关 | 0 | UART 仅测试模式,为 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工程配置)的交叉验证;库内实现细节如未在源码中出现,均已标注,未做推测性描述。