From f371321e05842ad5fbcad4e27d4a7e4db84c2595 Mon Sep 17 00:00:00 2001 From: Marcus Eliasson Date: Fri, 2 Oct 2026 14:39:19 +0200 Subject: [PATCH] Add platform dfu to restart the STM32 into USB DFU Sends the nRF51 the command that power-cycles the STM32 with BOOT0 high (bitcraze/crazyflie2-nrf-firmware#126), the same as holding the power button from off, and waits for the STM32 to show up on USB as 0483:df11. The nRF51 only gets the command over the radio, so a usb:// URI is refused up front. If no DFU device appears, the command fails and points at platform reboot to get the STM32 back into its firmware. --- docs/platform.md | 20 +++++++++++++++++++ src/cli.rs | 5 +++++ src/main.rs | 11 ++++++++++ src/modules/bootloader.rs | 42 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+) diff --git a/docs/platform.md b/docs/platform.md index d1184cd..3478539 100644 --- a/docs/platform.md +++ b/docs/platform.md @@ -55,3 +55,23 @@ This will power off the Crazyflie (same as pressing the power button): ```bash cfcli platform power-off ``` + +## Restart the STM32 into USB DFU + +This restarts the STM32 into its ROM bootloader, the same as holding the power button from off, +so it can be flashed over USB with a DFU tool such as `dfu-util`: + +```bash +cfcli platform dfu +``` + +The command is handled by the nRF51, so the Crazyflie has to be selected with a radio URI, and its +nRF51 firmware has to support it. With the Crazyflie connected over USB, `cfcli` waits for the STM32 +to show up in DFU mode (`0483:df11`): + +```text +STM32 is in USB DFU mode (0483:df11) +When done, restart it into its firmware with: cfcli platform reboot +``` + +Once flashing is done, `cfcli platform reboot` restarts the STM32 into its firmware. diff --git a/src/cli.rs b/src/cli.rs index ffa5be7..db95efd 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1088,6 +1088,11 @@ enum PlatformCommands { Sleep, /// Wake up the platform Wakeup, + /// Restart the STM32 into its USB DFU bootloader (radio URI only) + /// + /// Needs an nRF51 firmware that supports it. Flash with dfu-util, then + /// return to the firmware with `cfcli platform reboot`. + Dfu, } #[derive(Debug, Subcommand)] diff --git a/src/main.rs b/src/main.rs index 2476e55..224270f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1232,6 +1232,17 @@ async fn run() -> Result<()> { PlatformCommands::Wakeup => { crazyflie_lib::Crazyflie::power_on_stm32_domain(&link_context, uri.as_str()).await?; } + PlatformCommands::Dfu => { + if !modules::bootloader::stm32_dfu(&link_context, uri.as_str()).await? { + bail!( + "the STM32 did not show up on USB in DFU mode. The Crazyflie has to be connected \ + over USB, and its nRF51 firmware has to support the command. If the STM32 did \ + restart, `cfcli platform reboot` brings it back to its firmware." + ); + } + println!("STM32 is in USB DFU mode (0483:df11)"); + println!("When done, restart it into its firmware with: cfcli platform reboot"); + } } } diff --git a/src/modules/bootloader.rs b/src/modules/bootloader.rs index b34ded8..013b414 100644 --- a/src/modules/bootloader.rs +++ b/src/modules/bootloader.rs @@ -38,6 +38,12 @@ use cfloader::Bllink; const TARGET_STM32: u8 = 0xFF; const TARGET_NRF51: u8 = 0xFE; +// The STM32 ROM bootloader on USB, and how long it gets to show up there +// after the nRF51 has restarted the STM32 into it +const STM32_DFU_VID: u16 = 0x0483; +const STM32_DFU_PID: u16 = 0xDF11; +const STM32_DFU_ENUMERATION_TIMEOUT: Duration = Duration::from_secs(5); + #[derive(Debug)] struct BootloaderInfo { id: u8, @@ -54,6 +60,7 @@ struct BootloaderInfo { enum BootloaderCommand { ResetInit = 0xFF, Reset = 0xF0, + Stm32Dfu = 0x07, } pub fn get_hardcoded_list_of_targets() -> Vec<&'static str> { @@ -285,6 +292,41 @@ pub async fn bootloader_versions(link_context: &LinkContext, uri: &str) -> Resul Ok((stm32.protocol_version, nrf51.protocol_version)) } +/// Restart the STM32 into its ROM bootloader, so it can be flashed over USB DFU +/// +/// The nRF51 power-cycles the STM32 with BOOT0 high, as a long press on the +/// power button from off does. The command is handled by the nRF51 itself, +/// which only receives it over the radio: the STM32 firmware does not pass it +/// on from USB. Returns whether the STM32 then showed up on USB in DFU mode. +pub async fn stm32_dfu(link_context: &LinkContext, uri: &str) -> Result { + if uri.starts_with("usb://") { + bail!("Restarting the STM32 into DFU needs a radio URI, the nRF51 does not get this command over USB"); + } + + let link = link_context.open_link(uri).await?; + send_command(&link, BootloaderCommand::Stm32Dfu, None).await?; + + let deadline = tokio::time::Instant::now() + STM32_DFU_ENUMERATION_TIMEOUT; + while tokio::time::Instant::now() < deadline { + sleep(Duration::from_millis(200)).await; + if stm32_dfu_present()? { + return Ok(true); + } + } + + Ok(false) +} + +fn stm32_dfu_present() -> Result { + for device in rusb::devices()?.iter() { + let desc = device.device_descriptor()?; + if desc.vendor_id() == STM32_DFU_VID && desc.product_id() == STM32_DFU_PID { + return Ok(true); + } + } + Ok(false) +} + pub async fn reboot(link_context: &LinkContext, uri: &str,) -> Result<()> { let link = link_context.open_link(uri).await?;