Flipper Zero Unleashed 固件 OTA 更新机制与更新包构建实战指南
【免费下载链接】unleashed-firmwareFlipper Zero Unleashed Firmware项目地址: https://gitcode.com/GitHub_Trending/un/unleashed-firmware
本指南以 documentation/OTA.md 为核心,深入剖析 Flipper Zero(Unleashed Firmware)的 OTA(Over-The-Air)固件更新原理:从"从 RAM 执行代码"的特殊引导模式,到备份/int内部存储、烧写主固件与 Core2 无线协议栈、恢复配置的三阶段流程,再到 Update manifest 清单格式、错误码定位与更新包构建命令。读完本文,你将能够理解 OTA 更新的全部内部机制,掌握使用fbt与scripts/update.py构建完整版、精简版、自定义无线协议栈版及部分更新包的方法,并学会根据[XX-YY]格式错误码快速定位更新失败原因。
为什么 OTA 需要"从 RAM 执行代码"的引导模式
Flipper 固件中存在一个特殊的引导模式:系统会将一个精心构造的系统镜像加载到 RAM 中,并把控制权移交给它。从 RAM 中执行的系统镜像对 Flipper 整个 Flash 存储拥有完整的写入权限——这是主固件从同一片 Flash 上运行时不可能做到的(代码正在执行的存储介质无法被同时安全改写)。
OTA 固件更新正是利用了这一引导模式,它还同时承担了对运行在第二颗 MCU 核心(Core2)上的无线电协议栈(Radio Stack)执行安装、卸载等操作的任务。这意味着 OTA 不仅要更新主固件,还要维护双核架构下 BLE 协议栈的版本一致性与安装地址正确性,这是 Flipper OTA 区别于普通单片机固件升级的核心特征。
OTA 更新三阶段流程
一次完整的 OTA 更新安装分为 3 个阶段:
阶段 1:备份内部存储(/int)
/int是 Flipper Flash 上的一块专用分区,占据固件代码未使用的全部剩余空间。由于新版本固件的体积可能不同,直接安装新固件会导致 Flash 重新分区,从而造成数据丢失。因此,在动固件之前,系统会先把当前/int中的配置备份成一个普通的 tar 归档文件保存到 SD 卡上。
在源码中,这一阶段由 applications/system/updater/util/update_task_worker_backup.c 实现,对应 update_task.h 中的UpdateTaskStageIntBackup阶段。备份前,更新任务还会检查内部存储的剩余空间:update_operation.c 中定义了UPDATE_MIN_INT_FREE_SPACE (2 * 4 * 1024),要求至少预留 2 个 LFS 页面(共 8KB)的空闲空间,否则更新会被拒绝(UpdatePrepareResultIntFull)。
阶段 2:执行设备更新
主固件将 updater 镜像——一种主固件的定制构建版本——加载到 RAM 并运行。Updater 按照 Update manifest 文件的描述,对系统 Flash 执行一系列操作:
- 无线协议栈(Radio Stack)处理:如果更新包捆绑了 Radio Stack 镜像,updater 会先将其版本与当前已安装的版本进行比较。版本不匹配时,updater 先执行协议栈卸载,再写入并安装新协议栈。安装动作本身由运行在 Core2 上的专有软件 FUS(STM32 无线固件升级服务)执行,并会引发一系列系统重启。
- Option Bytes 校验与修正:Option Bytes 是存放 Flipper MCU 底层配置的特殊内存区域,updater 会对照 manifest 中的参考值对其进行校验与纠正。
- 固件烧写:updater 加载要烧写的
.dfu固件文件,先用 CRC32 校验其完整性,写入系统 Flash 后再次校验写入的数据。
阶段 3:恢复内部存储并更新资源
对 Flash 的操作完成后,系统重启进入新固件,并执行先前备份的/int内容恢复。如果更新包还包含额外的资源归档(Resources字段指定的 tar 包),则会将其解压到 SD 卡上。此外,如果 manifest 中带有Splashscreen字段,更新后还会安装更新完成后的开机动画(见 update_task.h 中的UpdateTaskStageSplashscreenInstall等阶段)。
Update manifest:更新包的"内容清单"
更新包自带一份描述其内容的 manifest 清单文件(默认名为update.fuf)。manifest 使用 Flipper File Format——一种由键值对组成的简单文本文件。设备端由 update_manifest.c 负责解析,其中定义了所有字段的键名常量(见该文件 第 7-20 行)。
必填字段(必须按以下顺序出现)
| 字段 | 说明 |
|---|---|
| Filetype | 常量字符串,必须为Flipper firmware upgrade configuration。解析时通过 update_manifest.c 第 58-60 行 与flipper_format_read_header读取并严格比较,不匹配则 manifest 无效 |
| Version | manifest 版本号,当前值为 2。设备端要求manifest_version >= UPDATE_OPERATION_MIN_MANIFEST_VERSION(2),更老的包会被判定为OutdatedManifestVersion(见 update_operation.h) |
| Info | 任意字符串,描述包内容,例如版本号r13.3_full |
| Target | 包所面向的硬件版本。生成时 update.py 会把-t f7这类参数剥掉首字母f后写入 |
| Loader | 从 RAM 执行的 Stage 2 loader 的文件名,固定为updater.bin(见 update.py 第 92 行) |
| Loader CRC | Loader 文件的 CRC32,注意以小端十六进制表示。生成逻辑见 update.py 第 180-181 行 的int2ffhex(逐字节倒序排列),设备端用flipper_format_read_hex读回并比较 |
可选字段
其余字段允许为空值;为空时 updater 跳过与之相关的全部操作。设备端解析这些可选字段的代码见 update_manifest.c 第 73-114 行。
| 字段 | 说明 |
|---|---|
| Radio | STM 提供的无线协议栈镜像文件名,生成时固定为radio.bin |
| Radio address | 协议栈的安装地址,由 STM 在 Release Notes 中给出。不指定时 update.py 第 126-131 行 会从镜像签名中读取默认 Flash 加载地址(get_flash_load_addr())并打印提示要求与 Release_Notes 核对 |
| Radio version | 协议栈主版本、次版本、子版本,以及分支(branch)、发布(release)、栈类型(stack type),打包成 6 个十六进制字节。打包逻辑见 update.py 的copro_version_as_int:major \| minor<<8 \| sub<<16 \| branch<<24 \| release<<32 \| stype<<40;设备端用 6 字节联合体UpdateManifestRadioVersion解析(见 update_manifest.h) |
| Radio CRC | 协议栈镜像的 CRC32 |
| Resources | 要解压到 SD 卡的资源 tar 归档文件名,固定为resources.ths(Tar.Heatshrink 压缩格式,见 update.py 第 24-25 行) |
| OB reference / OB mask / OB write mask | 用于校验和纠正 Option Bytes 的参考值。生成时 update.py 第 191-200 行 从scripts目录下的 Option Bytes 数据文件(如scripts/ob.data、scripts/ob_custradio.data)计算得到;文件注释明确警告:"NEVER EVER MESS WITH THESE VALUES, YOU WILL BRICK YOUR DEVICE"。设备端在 update_manifest.c 第 145-157 行 的update_manifest_has_obdata中对 mask 的一致性(值与其反码相等)以及参考值是否完全落在 compare mask 掩码位内做完整性检查 |
除上述字段外,源码中还存在Firmware(.dfu固件文件名,固定为firmware.dfu)与Splashscreen(更新后开机动画splash.bin)两个键(见 update_manifest.c 第 11、20 行),后者在文档中被列为可选字段。
更新包的预检与"武装"机制
值得补充的是,manifest 并不是在重启后被立刻执行的。在正式重启进入 updater 之前,update_operation.c 的update_operation_prepare会做一整套预检:检查/int空闲空间、确认 manifest 文件存在且有效、核对 manifest 版本与硬件 Target(仅当设备硬件版本号非 0 时比较,预量产设备接受任意固件)、验证 Stage2 loader 存在且 CRC32 匹配,然后将 manifest 路径写入 SD 卡根目录的.fupdate指针文件(UPDATE_MANIFEST_POINTER_FILE_NAME)并设置 RTC 引导模式为FuriHalRtcBootModePreUpdate。重启后update_operation_is_armed(第 221-232 行)据此判断是否存在待执行的更新,失败时可用update_operation_disarm取消。
OTA 更新错误码定位表
OTA 更新流程被设计得尽可能防错:在任何有风险的操作开始前,都会先校验所有相关数据,避免设备停留在"部分更新"或变砖的状态。即使出错,updater 也允许重试失败的操作,并通过错误码报告当前状态。
错误码采用[XX-YY]格式:XX编码失败的操作阶段,YY携带该阶段中出错位置/进度的额外细节。以下为完整错误码表(与 documentation/OTA.md 及 update_task.h 的阶段枚举 一一对应):
| 阶段描述 | 代码 | 进度 | 说明 |
|---|---|---|---|
| 加载更新 manifest | 1 | 13 | Updater 报告硬件版本不匹配 |
| 20 | 无法获取已保存的 manifest 路径 | ||
| 30 | 加载 manifest 失败 | ||
| 40 | 不支持的更新包版本 | ||
| 50 | 包与硬件目标不匹配 | ||
| 60 | 缺少 DFU 文件 | ||
| 80 | 缺少无线协议栈固件文件 | ||
| 备份配置 | 2 | 0-100 | 文件系统读/写错误 |
| 检查无线协议栈固件 | 3 | 0-99 | 读取协议栈固件文件错误 |
| 100 | CRC 不匹配 | ||
| 卸载无线协议栈固件 | 4 | 0 | SHCI Delete 命令错误 |
| 80 | 等待命令状态出错 | ||
| 写入无线协议栈固件 | 5 | 0-100 | 块读/写错误 |
| 安装无线协议栈固件 | 6 | 10 | SHCI Install 命令错误 |
| 80 | 等待命令状态出错 | ||
| Core2 忙 | 7 | 10 | 无法启动 C2 |
| 20 | 切换 C2 到 FUS 模式失败 | ||
| 30 | FUS 操作出错 | ||
| 50 | 切换 C2 到协议栈模式失败 | ||
| 校验 Option Bytes | 8 | yy | Option byte 代码 |
| 检查 DFU 文件 | 9 | 0 | 打开 DFU 文件出错 |
| 1-98 | 读取 DFU 文件出错 | ||
| 99-100 | DFU 文件损坏 | ||
| 写 Flash | 10 | 0-100 | 块读/写错误 |
| 校验 Flash | 11 | 0-100 | 块读/写错误 |
| 恢复配置 | 12 | 0-100 | 文件系统读/写错误 |
| 更新资源 | 13-15 | 0-100 | SD 卡读/写错误 |
阶段编号与源码中UpdateTaskStage枚举的顺序一致:UpdateTaskStageReadManifest(1)、UpdateTaskStageIntBackup(2)、UpdateTaskStageRadioImageValidate(3)、UpdateTaskStageRadioErase(4)、UpdateTaskStageRadioWrite(5)、UpdateTaskStageRadioInstall(6)、UpdateTaskStageRadioBusy(7)、UpdateTaskStageOBValidation(8)、UpdateTaskStageValidateDFUImage(9)、UpdateTaskStageFlashWrite(10)、UpdateTaskStageFlashValidate(11)、UpdateTaskStageIntRestore(12),而资源更新对应UpdateTaskStageResourcesFileCleanup、UpdateTaskStageResourcesDirCleanup、UpdateTaskStageResourcesFileUnpack(13-15)。
构建更新包
完整包(Full package)
构建包含固件、无线协议栈以及 SD 卡资源的完整更新包:
./fbt COMPACT=1 DEBUG=0 updater_packageCOMPACT=1 DEBUG=0用于生成精简、无调试符号的发布固件。
精简包(Minimal package)
仅包含固件的最小更新包:
./fbt COMPACT=1 DEBUG=0 updater_minpackage自定义更新捆绑包
默认更新包使用 Bluetooth Light 协议栈。如果你的固件版本支持其他协议栈,可以通过向fbt传入协议栈类型和二进制文件名来构建自定义捆绑包:
./fbt updater_package COMPACT=1 DEBUG=0 COPRO_OB_DATA=scripts/ob_custradio.data COPRO_STACK_BIN=stm32wb5x_BLE_Stack_full_fw.bin COPRO_STACK_TYPE=ble_full注意:
COPRO_OB_DATA必须指向scripts目录下一个有效的、与你的协议栈类型匹配的 Option Bytes 参考数据文件。- 在某些情况下,还需要在命令行中添加
COPRO_DISCLAIMER=...来确认你的操作意图——这对应 update.py 中的免责确认机制:当你捆绑非白名单协议栈类型(白名单为 BLE_FULL / BLE_LIGHT / BLE_BASIC,见 update.py 第 28-33 行)或内存布局可疑时,脚本会要求追加--I-understand-what-I-am-doing=yes(对应COPRO_DISCLAIMER)才会继续,否则会警告"你可能把设备砖化到需要 SWD 编程器才能修复的状态"。
构建部分更新包(Partial update packages)
直接调用scripts/update.py可以灵活定制包内容。例如,构建一个仅安装 BLE FULL 协议栈的包:
scripts/update.py generate \ -t f7 -d r13.3_full -v "BLE FULL 13.3" \ --stage dist/f7/flipper-z-f7-updater-*.bin \ --radio lib/stm32wb_copro/firmware/stm32wb5x_BLE_Stack_full_fw.bin \ --radiotype ble_fullupdate.py generate支持的关键参数(见 update.py 第 53-88 行):
| 参数 | 说明 |
|---|---|
-d/--directory | 输出目录(必填) |
-v/--version | 包的 Info 描述字符串(必填) |
-t/--target | 硬件目标,如f7(必填) |
--stage | Stage2 loader 文件(必填),即dist/f7/flipper-z-f7-updater-*.bin |
--dfu | 要烧写的固件.dfu文件(可选,与 radio 至少有一个) |
-r/--resources | 要打包为resources.ths的资源目录(可选) |
--radio | 无线协议栈镜像(可选) |
--radioaddr | 协议栈安装地址,十六进制(可选,默认从镜像签名推断) |
--radiotype | 协议栈类型,如ble_full(提供--radio时必填) |
--stackversion | 校验协议栈镜像内部签名版本是否匹配(可选) |
--obdata | Option Bytes 参考数据文件,用于生成 OB 三字段(可选) |
--splash | 更新完成后播放的开机动画图片(可选) |
--I-understand-what-I-am-doing | 非白名单协议栈/可疑布局的免责确认(可选) |
生成过程中 update.py 还会做内存布局检查(layout_check):当固件与协议栈地址都已知时,计算固件末尾到协议栈安装地址(FLASH_BASE = 0x8000000,FLASH_PAGE_SIZE = 4KB)之间的保留区大小,若固件镜像与 C2 区域重叠或保留区过小则给出警告并要求免责确认;同时若 Stage2 loader 超过UPDATER_SIZE_THRESHOLD = 128KB,会提示旧固件无法加载。资源的打包采用 Tar + Heatshrink 压缩(窗口 13 / 前瞻 6,见 update.py 第 43-44 行),并限制单个资源文件名长度不超过 100 字符。
关于update.py generate的全部参数,可随时运行以下命令查看帮助:
scripts/update.py generate -h小结
Flipper Zero 的 OTA 更新是一条完整的"备份 → 预检武装 → RAM 引导执行 → 协议栈/OB/固件处理 → 恢复配置"流水线:/int备份与恢复保证了跨版本重分区的数据安全;从 RAM 执行 updater 镜像赋予了对整片 Flash 的写权限;Update manifest 以 Flipper File Format 承载 loader、固件、协议栈、资源与 Option Bytes 的元数据,并支持字段级裁剪;[XX-YY]错误码与UpdateTaskStage枚举一一对应,让失败定位精确到阶段与进度。无论是发布固件还是为特殊无线协议栈定制包,fbt与 scripts/update.py 都提供了从完整包到部分包的完整构建能力。更多细节可继续阅读 documentation/OTA.md 及相关源码。
【免费下载链接】unleashed-firmwareFlipper Zero Unleashed Firmware项目地址: https://gitcode.com/GitHub_Trending/un/unleashed-firmware
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考