ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于 embassy 的 STM32WBA USB DFU 应用开发:从分区布局到固件下载实战

2026/9/25 2:52:26 拓冰建站 浏览量
基于 embassy 的 STM32WBA USB DFU 应用开发:从分区布局到固件下载实战 嵌入式物联网异步编程【免费下载链接】embassyModern embedded framework, using Rust and async.项目地址https://gitcode.com/gh_mirrors/em/embassy点击查看免费下载导读本文围绕 Embassy 仓库中 examples/boot/application/stm32wba-dfu/README.md 所描述的 STM32WBA USB DFUDevice Firmware Update设备固件升级应用示例展开讲解如何让运行在 STM32WBA65RI 上的应用程序在 DFU 模式下通过 USB 接收新固件、借助embassy-boot的状态机安全完成升级。读完本文你将掌握 bootloader 与 DFU 应用的 flash 分区设计、BlockingFirmwareState状态标记、基于embassy-usb-dfu的 DFU 类注册、Windows 下 WinUSB/MSOS 免驱配置以及完整的“签名—烧录—验证”升级工作流。示例全景一个可复现的 USB DFU 应用示例文件结构该示例的完整目录如下examples/boot/application/stm32wba-dfu/ ├── secrets/ │ └── key.sec # 用于签名固件的私钥示例专用 ├── src/ │ └── main.rs # DFU 应用主程序 ├── Cargo.toml # 依赖与构建元数据 ├── README.md # 使用说明 └── memory.x # flash 分区布局它对应的 bootloader 示例位于 examples/boot/bootloader/stm32wba-dfu含src/main.rs、secrets/key.pub.short公钥文件等两者通过同一套 linker 分区符号协同工作。烧录方式原文档给出了唯一的烧录命令cargo flash --release --chip STM32WBA65RI该命令使用cargo-flash工具以 release 模式将编译产物烧录到STM32WBA65RI芯片。前提是已安装 Rust 工具链nightly仓库根目录的 rust-toolchain.toml 与 rust-toolchain-nightly.toml 指定了版本已安装cargo-flash配合 probe-rs 使用已安装cargo-binutils用于cargo objcopy生成裸二进制已安装dfu-util用于下载升级固件。构建目标从 examples/boot/application/stm32wba-dfu/Cargo.toml 可以看到[package.metadata.embassy] build [ { target thumbv8m.main-none-eabihf, artifact-dir out/examples/boot/application/stm32wba-dfu } ]ST32WBA 系列基于 Cortex-M33 内核因此目标三元组为thumbv8m.main-none-eabihf。该示例声明为#![no_std]、#![no_main]的裸机程序panic 行为由panic-resetcrate 接管设备崩溃后自动复位方便 DFU 恢复。双工程协作bootloader 与应用的职责划分完整升级链路由两个工程协作完成bootloaderexamples/boot/bootloader/stm32wba-dfu上电先执行检查升级状态。若状态为DfuDetach则进入 DFU 模式USB 枚举、接收新固件否则直接加载 ACTIVE 分区中的应用。DFU 应用本文主题示例正常运行的用户程序。它提供两个入口——运行期通过 USB DFU 类对外暴露升级能力当用户触发升级时调用mark_dfu()写入“待升级”状态并复位把控制权交还给 bootloader 的 DFU 流程。bootloader 侧的关键逻辑bootloader 主程序src/main.rs的核心判断如下if bl.state State::DfuDetach { // 创建 USB 驱动、注册 DFU 类、等待 dfu-util 下载固件 let updater BlockingFirmwareUpdater::new(fw_config, mut buffer.0[..]); let mut state new_state(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate); ... embassy_futures::block_on(dev.run()); } unsafe { bl.load(BANK1_REGION.base() active_offset) }只有状态为DfuDetach时才初始化 USB DFU 并阻塞运行否则直接跳转到 ACTIVE 分区基址 偏移执行应用。BootLoader 的加载逻辑在 embassy-boot/src/boot_loader.rs 中由BootLoaderConfig::from_linkerfile_blocking与BootLoader::prepare::_, _, _, 2048完成其中2048是拷贝时使用的对齐缓冲区大小。memory.x 分区布局升级安全的地基整个升级体系的安全性和灵活性都建立在 flash 分区布局上。应用侧与 bootloader 侧的memory.x分区必须严格一致。应用侧examples/boot/application/stm32wba-dfu/memory.xMEMORY { BOOTLOADER : ORIGIN 0x08000000, LENGTH 80K BOOTLOADER_STATE : ORIGIN 0x08014000, LENGTH 8K FLASH : ORIGIN 0x08016000, LENGTH 120K DFU : ORIGIN 0x0803C000, LENGTH 160K RAM (rwx) : ORIGIN 0x20000000, LENGTH 400K } __bootloader_state_start ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); __bootloader_state_end ORIGIN(BOOTLOADER_STATE) LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); __bootloader_dfu_start ORIGIN(DFU) - ORIGIN(BOOTLOADER); __bootloader_dfu_end ORIGIN(DFU) LENGTH(DFU) - ORIGIN(BOOTLOADER);bootloader 侧examples/boot/bootloader/stm32wba-dfu/memory.xMEMORY { FLASH : ORIGIN 0x08000000, LENGTH 80K BOOTLOADER_STATE : ORIGIN 0x08014000, LENGTH 8K ACTIVE : ORIGIN 0x08016000, LENGTH 120K DFU : ORIGIN 0x0803C000, LENGTH 160K RAM (rwx) : ORIGIN 0x20000000, LENGTH 400K }分区表解读分区地址区间大小用途BOOTLOADER / FLASH0x0800_0000起80Kbootloader 自身代码BOOTLOADER_STATE0x0801_4000起8K升级状态区magic、swap 信息ACTIVE / FLASH应用0x0801_6000起120K当前运行的应用固件DFU0x0803_C000起160K新固件的暂存区升级缓冲区几点关键设计DFU 分区容量160K大于 ACTIVE 分区120K保证能容纳新固件后进行校验与写入memory.x中定义的__bootloader_state_start/end、__bootloader_dfu_start/end符号被 embassy-boot/src/firmware_updater/blocking.rs 中的FirmwareUpdaterConfig::from_linkerfile_blocking通过extern C声明后读取从而自动获得分区边界unsafe extern C { static __bootloader_state_start: u32; static __bootloader_state_end: u32; static __bootloader_dfu_start: u32; static __bootloader_dfu_end: u32; }该接口要求这些地址必须与memory.x精确一致否则会产生未定义行为UBBlockingPartition内部通过start与end - start构造一个限定范围内的 flash 访问接口embassy-embedded-hal/src/flash把应用对 flash 的操作约束在分区内避免越界破坏 bootloader。DFU 应用主程序逐段剖析src/main.rs 完整展示了从启动到 USB 枚举的整条链路下面分阶段解读。1. 中断绑定与 USB 驱动bind_interrupts!(struct Irqs { USB_OTG_HS usb::InterruptHandlerperipherals::USB_OTG_HS; });STM32WBA65RI 使用 USB OTG HS 外设。应用通过bind_interrupts!把USB_OTG_HS中断绑定到 embassy-stm32 的 USB 中断处理器上。2. RCC 时钟配置为 USB 准备 48 MHzUSB 外设需要精确的 48 MHz 时钟。示例通过 PLL1 从内部 HSI16 MHz合成config.rcc.pll1 Some(Pll { source: PllSource::Hsi, prediv: PllPreDiv::Div1, // PLLM 1 → HSI / 1 16 MHz mul: PllMul::Mul30, // PLLN 30 → 16 MHz * 30 480 MHz VCO divr: Some(PllDiv::Div5), // PLLR 5 → 96 MHz (Sysclk) divq: Some(PllDiv::Div10), // PLLQ 10 → 48 MHz divp: Some(PllDiv::Div30), // PLLP 30 → 16 MHz (USB_OTG_HS) frac: Some(0), // Fractional part (disabled) }); config.rcc.mux.otghssel mux::Otghssel::Pll1P; config.rcc.sys Sysclk::Pll1R;系统时钟Sysclk取自PLL1R96 MHzPLL1Q输出 48 MHz可供其他外设USB_OTG_HS 时钟源通过mux.otghssel Pll1P选择而PLL1P被配置为 16 MHzUSB 外设时钟路径上的分频由芯片内部再处理这里展示了otghssel多路复用器的接法。3. 初始化 flash 与固件状态let p embassy_stm32::init(config); let flash Flash::new_blocking(p.FLASH); let flash Mutex::new(RefCell::new(flash)); let config FirmwareUpdaterConfig::from_linkerfile_blocking(flash, flash); let mut magic AlignedBuffer([0; WRITE_SIZE]); let mut firmware_state BlockingFirmwareState::from_config(config, mut magic.0); firmware_state.mark_booted().expect(Failed to mark booted);Flash::new_blocking创建阻塞式 flash 驱动Mutex RefCell包装以便在中断/多任务环境共享AlignedBuffer([0; WRITE_SIZE])提供满足 flash 写入对齐要求WRITE_SIZE的缓冲区这是BlockingFirmwareState::from_config的硬性要求mark_booted()是应用启动后必须调用的关键动作它在状态区写入 BOOT_MAGIC告诉 bootloader“当前 ACTIVE 固件是有效的、可以启动”。对应实现位于 embassy-boot/src/firmware_updater/blocking.rspub fn mark_booted(mut self) - Result(), FirmwareUpdaterError { self.state.mark_booted() }如果不调用mark_booted()bootloader 会认为固件未成功启动可能回滚到旧版本。4. USB 设备配置与 MSOS 免驱let mut ep_out_buffer [0u8; 256]; let mut config embassy_stm32::usb::Config::default(); config.vbus_detection false; let driver Driver::new_hs(p.USB_OTG_HS, p.PD6, p.PD7, Irqs, mut ep_out_buffer, config); let mut config embassy_usb::Config::new(0xc0de, 0xcafe); config.manufacturer Some(Embassy); config.product Some(USB-DFU Runtime example); config.serial_number Some(1235678);Driver::new_hs使用 HS 引脚PD6/PD7并传入 256 字节的端点 OUT 缓冲区vbus_detection false表示不依赖 VBUS 检测USB VID:PID 为0xc0de:0xcafebootloader 侧相同保证dfu-util -d c0de:cafe能够识别。为了让 Windows 自动加载 WinUSB 驱动否则需要 Zadig 等工具手动指定示例注册了 MSOS 描述符const DEVICE_INTERFACE_GUIDS: [str] [{EAA9A5DC-30BA-44BC-9232-606CDC875321}]; builder.msos_descriptor(msos::windows_version::WIN8_1, 2); builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new(WINUSB, )); builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( DeviceInterfaceGUIDs, msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), ));设备级 MSOS 功能注册在 src/main.rs若设备还暴露了其他接口复合设备还应在函数级重复注册 MSOS 功能见同文件 src/main.rs示例中使用的 GUID 是随机生成的生产环境必须替换为你自己的 GUID。5. 注册 DFU 类与处理器struct DfuHandlerd, FLASH: embedded_storage::nor_flash::NorFlash { firmware_state: BlockingFirmwareStated, FLASH, } implFLASH: embedded_storage::nor_flash::NorFlash Handler for DfuHandler_, FLASH { fn enter_dfu(mut self) { self.firmware_state.mark_dfu().expect(Failed to mark DFU mode); cortex_m::peripheral::SCB::sys_reset(); } }DfuHandler实现embassy-usb-dfu定义的Handlertraitembassy-usb-dfu/src/application.rs在进入 DFU 时回调enter_dfuenter_dfu中调用mark_dfu()写入 DFU_DETACH_MAGIC对应 embassy-boot/src/firmware_updater/blocking.rs然后执行系统复位——重启后 bootloader 读到DfuDetach状态进入 DFU 升级流程。这是“应用侧触发 → bootloader 接管”的关键一环。随后构造 DFU 状态机与 USB builderlet state DfuState::new(handler, DfuAttributes::CAN_DOWNLOAD, Duration::from_millis(2500)); let mut builder Builder::new(driver, config, mut config_descriptor, mut bos_descriptor, mut [], mut control_buf); usb_dfu(mut builder, mut state, |func| { ... }); let mut dev builder.build(); dev.run().awaitDfuAttributes::CAN_DOWNLOAD声明本设备支持固件下载即可写入Duration::from_millis(2500)是 DFU 下载时的超时时间usb_dfu把 DFU 类注册到 USB builder 上dev.run().await启动异步 USB 事件循环。DFU 状态机与升级工作流运行期 DFUruntime DFUDfuState::new(handler, ...)对应“应用运行期间”的 DFU 状态机设备作为一个正常的 USB DFU 设备被主机如dfu-util访问支持DfuGetStatus、DfuDownload等 DFU 标准请求。当主机发起 detach 请求时enter_dfu被触发进入重启流程。下载与状态魔法值embassy-boot在状态区使用多种 magic 值驱动状态机定义于 embassy-boot/src/lib.rs魔法值含义BOOT_MAGIC应用已成功启动由mark_booted()写入DFU_DETACH_MAGIC请求进入 DFU 模式由mark_dfu()写入SWAP_MAGIC待执行 swap固件交换STATE_ERASE_VALUE状态区擦除后的默认值bootloader 在启动时读取这些值决定是加载应用、执行 swap还是进入 DFU。这套机制保证了即使升级中途断电设备也能根据状态区内容恢复到一个已知状态。完整升级流程端到端设备正常运行应用调用mark_booted()表明健康主机通过 USB DFU 接口触发 detach或用户直接通过其他途径触发应用enter_dfu调用mark_dfu()并复位bootloader 启动读到DfuDetach状态枚举为 USB DFU 设备主机用dfu-util下载新固件到 DFU 分区bootloader 校验可选签名验证并执行 swap把新固件写入 ACTIVE 分区复位后 bootloader 从 ACTIVE 分区加载新固件新应用启动后再次mark_booted()确认升级成功。签名验证与安全升级bootloader 示例支持verifyfeatureexamples/boot/bootloader/stm32wba-dfu/Cargo.tomlverify [embassy-usb-dfu/ed25519-salty]启用后bootloader 通过include_bytes!(../secrets/key.pub.short)内嵌公钥并在接收固件时调用new_state(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate, PUBLIC_SIGNING_KEY)进行 ed25519 签名验证src/main.rs。生成密钥对与签名更新包依据 examples/boot/bootloader/stm32wba-dfu/README.md 的步骤# 1. 生成密钥对公钥 key.pub、私钥 key.sec signify-openbsd -G -n -p secrets/key.pub -s secrets/key.sec # 2. 提取 32 字节公钥去掉 signify 头部 tail -n1 secrets/key.pub | base64 -d -i - | dd ibs10 skip1 secrets/key.pub.short签名并下发更新固件cargo objcopy --release -- -O binary fw.bin shasum -a 512 -b fw.bin | head -c128 | xxd -p -r target/fw-hash.txt signify-openbsd -S -s secrets/key.sec -m target/fw-hash.txt -x target/fw-hash.sig cp fw.bin fw-signed.bin tail -n1 target/fw-hash.sig | base64 -d -i - | dd ibs10 skip1 fw-signed.bin dfu-util -d c0de:cafe -w -D fw-signed.bin当前embassy-usb-dfu对签名验证的实现较为简化签名被直接拼接在更新二进制文件的末尾应用侧使用私钥secrets/key.secbootloader 侧内嵌公钥secrets/key.pub.short两者配对使用密钥管理有安全前提示例中的步骤不适合生产环境私钥必须妥善保护绝不能提交到版本控制系统。dfu-util 使用与疑难排查基本命令dfu-util -d c0de:cafe -w -D fw.bin-d c0de:cafe按 VID:PID 过滤设备-w等待设备出现设备可能先复位再枚举-D fw.bin下载固件到设备。常见问题排查根据 examples/boot/bootloader/stm32wba-dfu/README.md 的“Troubleshooting”一节设备未进入 DFU 模式先确认设备当前确实处于 DFU 模式设备被识别为c0de:cafeUSB 识别不到设备检查 USB 连接与枚举日志确认 VID:PID 与设备匹配传输大小不匹配dfu-util默认使用设备上报的传输大小若与 bootloader 配置不一致可用-t选项手动指定control_buf 与 BLOCK_SIZE 的关系control_buf的容量必须大于或等于usb_dfu的BLOCK_SIZE参数。本示例中两者都设置为 4096 字节应用侧control_buf [0; 64]用于标准控制传输bootloader 侧control_buf [0; 4096]用于 DFU 数据块保持“control_buf ≥ BLOCK_SIZE”的约束才能保证 DFU 下载不丢数据。从源码结构看可扩展方向从示例与源码结构可以推断以下扩展方向多接口复合设备应用已经演示了函数级 MSOS 注册若在usb_dfu之外再注册串口、HID 等接口即可构建“DFU 业务接口”的复合 USB 设备异步固件状态embassy-boot同时提供异步版本 APIembassy-boot/src/firmware_updater/asynch.rs 中的mark_dfu().await/mark_booted().await配合异步 flash 驱动可在不阻塞执行器的前提下完成升级自定义复位行为embassy-usb-dfu的Resettraitembassy-usb-dfu/src/lib.rs允许替换ResetImmediate实现进入 DFU 前保存上下文、关闭外设等自定义逻辑更换芯片系列仓库中还有同构的 STM32WB DFU 示例examples/boot/application/stm32wb-dfu 与 examples/boot/bootloader/stm32wb-dfu以及面向其他厂商的 DFU 应用examples/boot/application/nrf、examples/boot/application/rp同一套embassy-bootAPI 可平移到不同平台。小结本文以 examples/boot/application/stm32wba-dfu/README.md 为骨架结合 examples/boot/application/stm32wba-dfu/src/main.rs、examples/boot/bootloader/stm32wba-dfu/src/main.rs 及 embassy-boot、embassy-usb-dfu、embassy-stm32 的实现源码完整还原了 STM32WBA 上 USB DFU 的落地方法memory.x四分区布局、mark_booted/mark_dfu状态标记、usb_dfu类注册、WinUSB/MSOS 免驱、签名验证升级与dfu-util实战排查。你可以直接以该示例为模板替换为自己的 VID:PID、GUID 与签名密钥快速构建具备 OTA 能力的量产固件升级方案。赞分享嵌入式物联网异步编程【免费下载链接】embassyModern embedded framework, using Rust and async.项目地址https://gitcode.com/gh_mirrors/em/embassy点击查看免费下载相关推荐STM32WBA USB DFU 引导加载程序实战基于 embassy-boot 与 embassy-usb-dfu 的固件烧录与 ed25519 签名验证STM32WBA USB DFU 引导加载程序实战基于 embassy boot 与 embassy usb dfu 的固件烧录与 ed25519 签名验证嵌入式物联网异步编程embassy-boot STM32L1 示例实战基于按键触发的 DFU 固件更新与分区布局解析embassy boot STM32L1 示例实战基于按键触发的 DFU 固件更新与分区布局解析 导读 本文以 examples/boot/applicati嵌入式物联网异步编程embassy-usb-dfu 实践指南基于 embassy-boot 的 USB DFU 1.1 固件升级协议实现embassy usb dfu 实践指南基于 embassy boot 的 USB DFU 1.1 固件升级协议实现 导读 embassy usb dfu 是嵌入式物联网异步编程上一篇Lottie Converter强大的Lottie动画和即时通讯贴纸转换工具下一篇gstack vs Claude Code原生 vs OpenClaw2026年AI编程工作流如何组合才最强创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考