ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

embassy-boot-stm32:为 STM32 构建断电安全的 A/B 分区固件引导加载器

embassy-boot-stm32:为 STM32 构建断电安全的 A/B 分区固件引导加载器 嵌入式物联网异步编程【免费下载链接】embassyModern embedded framework, using Rust and async.项目地址https://gitcode.com/gh_mirrors/em/embassy点击查看免费下载embassy-boot-stm32是 Embassy 异步嵌入式框架中面向 STM32 微控制器的引导加载器适配 crate它是平台无关引导加载器 embassy-boot 在 STM32 上的最小平台 shim。它支持基于链接脚本linker script配置引导分区、从 ACTIVE 分区加载应用程序并通过底层的分页交换算法实现断电安全power-fail-safe的固件升级、试运行trial boot与自动回滚rollback。读完本文你将掌握如何为 STM32 工程搭建 bootloader 双应用分区的完整升级方案理解分区的公式化计算、状态魔法字magic机制以及如何用FirmwareUpdater在应用侧写入新固件。项目定位Embassy 引导链中的 STM32 适配层embassy-boot-stm32的 README 将其定位为 An adaptation ofembassy-bootfor STM32。这意味着它本身不重复实现交换算法而是提供两条 STM32 特有的能力基于链接脚本配置引导分区通过memory.x中定义的符号symbol自动切分出 BOOTLOADER、BOOTLOADER_STATE、ACTIVE、DFU 四个分区免去手工计算分区地址的繁琐从 ACTIVE 分区加载应用程序在完成必要的状态检查和分区交换后将栈指针SP与复位向量Reset Vector重定向到 ACTIVE 分区起始地址跳转执行用户固件。从源码结构看lib.rs 中BootLoader的全部核心逻辑都委托给embassy_boot::BootLoader而 STM32 侧只负责 Flash 初始化、链接脚本符号读取与最终跳转这种平台无关算法 平台适配 shim的分层设计与embassy-boot的架构一脉相承。四个引导分区与各自的约束条件引导加载器将片上存储划分为 4 个主分区可以在创建 bootloader 实例时手动配置也可以通过链接脚本自动推导。以 embassy-boot 的文档为准四个分区如下分区用途关键约束BOOTLOADER存放 bootloader 自身代码本身约占用 8KB Flash如需用 probe-rs 调试建议扩大到 24KBACTIVE存放当前主应用bootloader 启动时从该分区起始处加载最小尺寸为应用固件大小只有 bootloader 能写入DFU存放待交换的新固件由应用侧写入必须比 ACTIVE 至少大 1 页page交换算法依赖这块额外空间保证断电安全BOOTLOADER STATE记录是否需要交换分区及交换进度须能容纳魔法字段与交换进度数据同时任意分区都必须满足两个前置条件分区必须按页大小对齐aligned on the page size分区大小必须是页大小的整数倍a multiple of the page size。交换算法实际采用的页大小由 ACTIVE 与 DFU 页大小的最小公倍数决定从 boot_loader.rs 看代码取两者ERASE_SIZE的最大值作为PAGE_SIZE并在编译期断言其为各分区WRITE_SIZE/ERASE_SIZE的整数倍。分区尺寸的公式化计算DFU 分区与 STATE 分区的尺寸可以按公式精确计算均以字节为单位Partition Size(DFU) Partition Size(ACTIVE) Page Size(ACTIVE) Partition Size(STATE) (2 × Write Size(STATE)) (4 × Write Size(STATE) × Partition Size(ACTIVE) / Page Size(ACTIVE))STATE 分区的第二个公式来源于 boot_loader.rs 中描述的状态区布局需要存储 2 个字魔法字 进度有效性标志外加 ACTIVE 分区每页 4 个字的交换/回滚进度索引swap 与 revert 各占 2N合计 4N。提示ACTIVE及 BOOTLOADER、DFU 与 BOOTLOADER_STATE 可以放置在不同的 Flash 中例如 DFU 放外部 SPI NORSTATE 放片上 Flash。STM32 上最常见的做法是全部使用片上 Flash 的不同 bank/region。基于链接脚本的分区配置机制embassy-boot-stm32的核心特性之一就是由链接脚本驱动分区配置。以仓库中的 bootloader 示例 memory.x 为例MEMORY { /* NOTE 1 K 1 KiBi 1024 bytes */ FLASH : ORIGIN 0x08000000, LENGTH 24K BOOTLOADER_STATE : ORIGIN 0x08006000, LENGTH 8K ACTIVE : ORIGIN 0x08008000, LENGTH 32K DFU : ORIGIN 0x08010000, LENGTH 36K RAM (rwx) : ORIGIN 0x20000000, LENGTH 16K } __bootloader_state_start ORIGIN(BOOTLOADER_STATE) - ORIGIN(FLASH); __bootloader_state_end ORIGIN(BOOTLOADER_STATE) LENGTH(BOOTLOADER_STATE) - ORIGIN(FLASH); __bootloader_active_start ORIGIN(ACTIVE) - ORIGIN(FLASH); __bootloader_active_end ORIGIN(ACTIVE) LENGTH(ACTIVE) - ORIGIN(FLASH); __bootloader_dfu_start ORIGIN(DFU) - ORIGIN(FLASH); __bootloader_dfu_end ORIGIN(DFU) LENGTH(DFU) - ORIGIN(FLASH);注意其中定义的下划线符号__bootloader_active_start/end、__bootloader_dfu_start/end、__bootloader_state_start/end。这些符号以相对于 Flash 基址的偏移量形式定义ORIGIN(...) - ORIGIN(FLASH)因为在 STM32 上 Flash 通常映射在 0x08000000而 bootloader 内的 Flash 分区句柄需要的是相对偏移。BootLoaderConfig::from_linkerfile_blocking定义于 embassy-boot/src/boot_loader.rs通过unsafe extern C声明这些链接器符号读取其地址后用BlockingPartition::new(flash, start, end - start)构造 ACTIVE、DFU、STATE 三个分区。这就是配置基于链接脚本的底层实现——memory.x 一改分区自动跟着变。在 bootloader 示例中三种 Flash 句柄都来自同一个 STM32 Flash 外设的不同 regionlet layout Flash::new_blocking(p.FLASH).into_blocking_regions(); let flash Mutex::new(RefCell::new(layout.bank1_region)); let config BootLoaderConfig::from_linkerfile_blocking(flash, flash, flash); let active_offset config.active.offset(); let bl BootLoader::prepare::_, _, _, 2048(config); unsafe { bl.load(BANK1_REGION.base() active_offset) }这段代码出自 examples/boot/bootloader/stm32/src/main.rs是 STM32 bootloader 的完整启动流程初始化外设 → 把 Bank1 切成 blocking region → 从链接脚本构造配置 → 准备启动可能触发交换/回滚→ 跳转到 ACTIVE 分区基址。BootLoader APIprepare 与 load 的完整生命周期embassy-boot-stm32/src/lib.rs 中定义了两个关键阶段prepare准备BootLoader::prepare和BootLoader::try_prepare接受BootLoaderConfigACTIVE, DFU, STATE与一个BUFFER_SIZE常量泛型内部构造一个AlignedBuffer([0; BUFFER_SIZE])后调用底层embassy_boot::BootLoader::prepare_boot。prepare 会读取 STATE 分区中的魔法字并根据状态执行分区交换或回滚。两者的区别仅在于错误处理prepare出错时直接panic!(Boot prepare error)注释明确说明是为了让 panic 能正确路由到 defmt 等日志设施try_prepare则返回ResultSelf, BootError。load加载unsafe fn load(self, start: u32) - !是真正的跳转动作其关键步骤为let mut p cortex_m::Peripherals::steal(); #[cfg(not(armv6m))] p.SCB.invalidate_icache(); p.SCB.vtor.write(start); cortex_m::asm::bootload(start as *const u32)即写入VTOR指向应用的中断向量表 → 非 Cortex-M0armv6m平台先失效指令缓存 → 通过cortex_m::asm::bootload直接装载 SP 与 PC 跳转。注释强调该函数会修改栈指针与复位向量并在 ACTIVE 分区执行代码因此标记为unsafe。BUFFER_SIZE的选择直接影响交换算法的性能与内存占用示例中使用2048即 2KB 对齐缓冲区交换时按此粒度分块搬运 Flash 页面。缓冲区还必须满足PAGE_SIZE % BUFFER_SIZE 0等编译期/运行期断言见 boot_loader.rs。状态机与魔法字Swap、Revert、Boot、DfuDetachSTATE 分区中写入的第一个魔法字决定了 bootloader 上电后的行为。四个状态定义于 embassy-boot/src/lib.rs状态魔法值含义Boot—其余情况直接启动 ACTIVE 分区Swap0xF0(SWAP_MAGIC)将 DFU 与 ACTIVE 交换后再启动Revert0xC0(REVERT_MAGIC)已交换但应用未确认启动成功执行回滚DfuDetach0xE0(DFU_DETACH_MAGIC)应用请求重启进入 DFU 模式以应用更新STATE 分区内部布局单位为WRITE_SIZE的倍数在 boot_loader.rs 中有明确注释范围内容0..1魔法字BOOT_MAGIC0xD0 表示直接启动SWAP_MAGIC 表示交换1..2进度有效性等于擦除值表示有效否则无效2..(22N)交换swap进度索引(22N)..(24N)回滚revert进度索引其中 N ACTIVE 分区大小 ÷ WRITE_SIZE。进度索引按页记录复制进度使得任意时刻断电都能从断点继续这正是断电安全性的来源。断电安全的交换 / 回滚算法prepare_boot在检测到State::Swap时boot_loader.rs检查is_swapped若尚未交换完成继续执行swap若交换已完成progress page_count * 2但应用从未调用mark_booted确认成功则执行revert回滚写入REVERT_MAGIC。交换swap从后向前复制假设 ACTIVE 为 3 页、DFU 为 4 页第一步把 ACTIVE 的页 3 复制到 DFU 的页 4利用 DFU 多出的那一页作为暂存再把 DFU 的页 3 复制到 ACTIVE 的页 3随后逐页向前推进直到新固件完全占据 ACTIVE。每完成一页就在 STATE 的进度区写一个标记且标记写入是原子的页写粒度避免了进度写了一半的竞态。回滚revert从前向后复制从空余的 DFU 页开始把 ACTIVE 中残留的旧固件页逐页拷回 DFU逐步还原出完整的旧固件。回滚进度与交换进度存放在不同的地址区间因此回滚本身也能在断电后继续。整个过程可形象地概括为更新时不直接覆盖 ACTIVE而是先写 DFU → 重启时逐页交换 → 新固件自检通过后 mark_booted 固化若自检失败或超时未确认则自动回滚到旧版本。这就是trial boots and rollbacks试运行与回滚的完整含义。应用侧升级FirmwareUpdater 与完整升级流程bootloader 本身不提供任何网络能力设计上如此见 embassy-boot/README.md下载新固件的网络/传输功能由应用自行实现再通过FirmwareUpdater把固件写入 DFU 分区。仓库为 STM32 提供了多套演示应用以 examples/boot/application/stm32l4/src/bin/a.rs 最为直观use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_stm32::flash::{Flash, WRITE_SIZE}; use embassy_sync::mutex::Mutex; static APP_B: [u8] include_bytes!(../../b.bin); // 新固件编译期嵌入 #[embassy_executor::main] async fn main(_spawner: Spawner) { let p embassy_stm32::init(Default::default()); let flash Flash::new_blocking(p.FLASH); let flash Mutex::new(BlockingAsync::new(flash)); let mut button ExtiInput::new(p.PC13, p.EXTI13, Pull::Up, Irqs); let mut led Output::new(p.PB14, Level::Low, Speed::Low); led.set_high(); let config FirmwareUpdaterConfig::from_linkerfile(flash, flash); let mut magic AlignedBuffer([0; WRITE_SIZE]); let mut updater FirmwareUpdater::new(config, mut magic.0); button.wait_for_falling_edge().await; // 按键触发升级 let mut offset 0; for chunk in APP_B.chunks(2048) { // 按 2048 字节分块写入 let mut buf: [u8; 2048] [0; 2048]; buf[..chunk.len()].copy_from_slice(chunk); updater.write_firmware(offset, buf).await.unwrap(); offset chunk.len(); } updater.mark_updated().await.unwrap(); // 标记下次重启执行交换 led.set_low(); cortex_m::peripheral::SCB::sys_reset(); // 重启进入 bootloader }该示例的完整链路为应用 A 运行时按键 → 把内嵌的 b.bin 分块写入 DFU →mark_updated写入 SWAP_MAGIC → 系统复位 → bootloader 检测到 Swap 状态 → 逐页交换 → 启动新固件 B。FirmwareUpdater 关键 API以 blocking 版为例embassy-boot/src/firmware_updater/blocking.rs 中BlockingFirmwareUpdater的核心方法方法作用要点new(config, aligned)创建更新器aligned缓冲区须满足 STATE 分区的对齐要求write_firmware(offset, data)写入固件到 DFU自动按扇区擦除记录last_erased_dfu_sector_index避免重复擦除data长度须为WRITE_SIZE整数倍写入前会先verify_booted防止把旧固件覆盖成坏状态prepare_update()擦除整个 DFU 并返回mut DFU适合自定义写入流程的优化 APImark_updated()写入 SWAP_MAGIC下次重启交换需在全部固件写完后再调用mark_dfu()写入 DFU_DETACH_MAGIC用于进入 USB DFU 等下载模式mark_booted()固化新固件停止回滚新固件自检通过后必须调用否则下次启动会回滚verify_and_mark_updated()校验签名后标记更新见下文固件验证get_state()查询当前状态用于判断是否刚完成 swap从而决定何时mark_booted对应的异步版本FirmwareUpdater位于 firmware_updater/asynch.rsAPI 形态一致write_firmware/mark_updated为async fn示例中通过BlockingAsync适配器将 blocking Flash 包装成异步接口使用。固件验证ed25519 数字签名bootloader 支持对写入 DFU 的固件进行 ed25519 签名校验详见 docs/pages/bootloader.adoc。开启方式是在依赖embassy-boot时启用ed25519-dalek或ed25519-saltyfeature当前推荐ed25519-salty体积更小然后在应用侧调用verify_and_mark_updated(public_key, signature, update_len)替代mark_updated。校验失败则不会标记更新新固件被拒绝。签名通常随固件一起传输而不写入 Flash公钥推荐以include_bytes!(key.pub)内嵌进固件。仓库文档给出用signify生成密钥与签名固件的完整命令序列密钥生成、剥离文件头、SHA-512 摘要、签名、拼接签名到固件末尾等步骤其中私钥key.sec必须妥善保管。从 blocking.rs 源码看两种实现均为读取固件逐块计算 SHA-512 摘要 → 用公钥验证签名 → 通过后写入 SWAP_MAGIC。STM32 上的双 Bank 与多芯片适配embassy-boot-stm32特别针对 STM32 双 Bank Flash如 H7 系列提供了示例 examples/boot/bootloader/stm32-dual-bank/src/main.rs将 Bank1 用作 ACTIVE/STATEBank2 用作 DFU两个 bank 各自独立的MutexRefCell...句柄传入BootLoaderConfig::from_linkerfile_blocking(flash_bank1, flash_bank2, flash_bank1)。其配套 memory.x 展示了 ACTIVE 位于 Bank10x08040000而 DFU 位于 Bank20x08100000H7 的第二个 bank 基址的典型布局。仓库中面向不同 STM32 系列的引导示例还包括stm32f3、stm32f7含 flash-boot.sh 一键烧写脚本与 memory-bl.x bootloader 专用内存布局、stm32h7、stm32l0、stm32l1、stm32l4、stm32wl以及基于 USB DFU 的 stm32wb-dfu / stm32wba-dfu。整体上覆盖了 STM32 的 L4、WB、WL、L1、L0、F3、F7、H7 等主要系列见 docs/pages/bootloader.adoc 的 Hardware support 一节特别地STM32L0x1 系列需要启用flash-erase-zerofeature该系列 Flash 擦除后为 0x00 而非 0xFFlib.rs 通过该 feature 切换STATE_ERASE_VALUE。工程集成依赖、feature 与构建配置在 Cargo.toml 中引入embassy-boot-stm32当前仓库版本为 0.8.0[dependencies] embassy-boot-stm32 { version 0.8.0, path embassy-boot-stm32, features [] } embassy-stm32 { version 0.6.0, path embassy-stm32, features [stm32l496zg] }其依赖关系为embassy-boot算法核心、embassy-stm32Flash 驱动default-features false、embassy-sync互斥锁、cortex-m/cortex-m-rtCortex-M 运行时、embedded-storage/embedded-storage-asyncNorFlash traitCargo.toml。Feature flagsdefmt与log二选一同时开启会在编译期报错见 src/fmt.rs分别启用 defmt 或 log 日志输出两者都关闭时日志宏退化为空操作。构建配置仓库为嵌入式工程预设了[profile.release]lto fat、opt-level z、codegen-units 1与[profile.dev]保留调试信息与断言、同样opt-level z压缩体积等优化组合并提供了编译期断言保证分区约束。烧录与运行bootloader 示例的烧录方式见 examples/boot/bootloader/stm32/README.mdcargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx需要根据实际芯片替换embassy-stm32的 chip feature 与--chip参数。应用侧示例的完整流程可参考 examples/boot/application/stm32f7/README.md# 1) 烧写 bootloader脚本临时替换 memory.x 为 bootloader 布局 ./flash-boot.sh # 2) 构建新固件 b cargo build --release --bin b # 3) 生成二进制 cargo objcopy --release --bin b -- -O binary b.bin # 4) 烧写应用 a其中内嵌了 b.bin按键触发升级 cargo flash --release --bin a --chip STM32F767ZITx应用侧的build.rs参考 examples/boot/application/stm32l4/build.rs负责把memory.x复制到链接器搜索路径并通过cargo:rerun-if-changedmemory.x保证修改分区布局后自动重编——这也是链接脚本驱动分区能落地的工程基础。分区布局图bootloader 的四分区布局与固件流向可参考仓库文档配图该图对应 docs/pages/bootloader.adoc 中的 Design 章节最上方是 Bootloader 代码区其下是记录升级进度/状态的状态区再下方是当前正在运行的固件ACTIVE最底部是待应用的下一个固件DFU。新固件总是先落入 DFU经状态区标记后由 bootloader 在重启时交换进 ACTIVE。使用要点与限制小结分区规划是第一步先按公式确定 ACTIVE 大小再推导 DFU ACTIVE 1 页、STATE 按进度公式计算最后写入memory.x并定义__bootloader_*符号bootloader 与应用各自的memory.x结构相似但 FLASH 区域必须分别指向 BOOTLOADER 分区和 ACTIVE 分区。对齐与倍数所有分区必须页对齐且为页大小整数倍PAGE_SIZE由 ACTIVE/DFU 的擦除页大小决定。新固件必须调用mark_booted否则 bootloader 判定试运行失败并在下次启动回滚这是trial boot rollback机制的核心约定。断电安全得益于分页交换 进度持久化任意时刻断电都能在下次上电时从断点继续或回滚无需担心固件损坏。网络能力刻意缺失固件的获取方式Wi-Fi、BLE、USB、串口等完全由应用层实现bootloader 只负责落盘与切换这种职责划分让它可以复用于任何传输通道。赞分享嵌入式物联网异步编程【免费下载链接】embassyModern embedded framework, using Rust and async.项目地址https://gitcode.com/gh_mirrors/em/embassy点击查看免费下载相关推荐embassy-boot 引导加载器完全指南断电安全的 A/B 固件升级、试运行与回滚embassy boot 引导加载器完全指南断电安全的 A/B 固件升级、试运行与回滚 本篇技术指南基于 embassy boot/README.md htt嵌入式物联网异步编程embassy-boot-stm32 版本演进与 STM32 引导加载器实现深度解析embassy boot stm32 版本演进与 STM32 引导加载器实现深度解析 embassy boot stm32 是 Embassy 生态中面向 ST嵌入式物联网异步编程embassy-boot-rp 实战指南为 RP2040 构建断电安全的固件升级 Bootloaderembassy boot rp 实战指南为 RP2040 构建断电安全的固件升级 Bootloader embassy boot rp 是 Embassy 生嵌入式物联网异步编程上一篇React Native 验证码输入框组件教程下一篇BookReader 开源项目教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表