From 29d4ade2866e6c8d2114b393853354ded1e61db7 Mon Sep 17 00:00:00 2001 From: xoviat Date: Sun, 16 Nov 2025 07:50:49 -0600 Subject: [PATCH] low_power: misc cleanups and allow main macro --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/lib.rs | 5 +- embassy-stm32/src/low_power.rs | 88 ++++++++++++----------- embassy-stm32/src/rcc/l.rs | 43 ----------- embassy-stm32/src/rcc/mod.rs | 4 ++ embassy-stm32/src/rtc/mod.rs | 7 +- embassy-stm32/src/time_driver.rs | 7 +- examples/stm32h5/src/bin/stop.rs | 12 +--- examples/stm32l5/src/bin/stop.rs | 12 +--- examples/stm32wle5/src/bin/adc.rs | 12 +--- examples/stm32wle5/src/bin/blinky.rs | 12 +--- examples/stm32wle5/src/bin/button_exti.rs | 12 +--- examples/stm32wle5/src/bin/i2c.rs | 13 +--- tests/stm32/src/bin/stop.rs | 14 +--- 14 files changed, 72 insertions(+), 170 deletions(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index df832d15f..8e3e802a4 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- feat: allow embassy_executor::main for low power - feat: Add waveform methods to ComplementaryPwm - fix: Avoid generating timer update events when updating the frequency ([#4890](https://github.com/embassy-rs/embassy/pull/4890)) - chore: cleanup low-power add time diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 6e492946a..7c3770643 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -649,10 +649,7 @@ fn init_hw(config: Config) -> Peripherals { rcc::init_rcc(cs, config.rcc); #[cfg(feature = "low-power")] - crate::rtc::init_rtc(cs, config.rtc); - - #[cfg(feature = "low-power")] - crate::time_driver::get_driver().set_min_stop_pause(cs, config.min_stop_pause); + rtc::init_rtc(cs, config.rtc, config.min_stop_pause); } p diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index 36c7e2242..cf8f2b393 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -14,7 +14,7 @@ //! //! Since entering and leaving low-power modes typically incurs a significant latency, the //! low-power executor will only attempt to enter when the next timer event is at least -//! [`time_driver::MIN_STOP_PAUSE`] in the future. +//! [`time_driver::min_stop_pause`] in the future. //! //! Currently there is no macro analogous to `embassy_executor::main` for this executor; //! consequently one must define their entrypoint manually. Moreover, you must relinquish control @@ -22,21 +22,16 @@ //! //! ```rust,no_run //! use embassy_executor::Spawner; -//! use embassy_stm32::low_power::Executor; +//! use embassy_stm32::low_power; //! use embassy_stm32::rtc::{Rtc, RtcConfig}; -//! use static_cell::StaticCell; +//! use embassy_time::Duration; //! -//! #[cortex_m_rt::entry] -//! fn main() -> ! { -//! Executor::take().run(|spawner| { -//! spawner.spawn(unwrap!(async_main(spawner))); -//! }); -//! } -//! -//! #[embassy_executor::task] +//! #[embassy_executor::main(executor = "low_power::Executor")] //! async fn async_main(spawner: Spawner) { //! // initialize the platform... //! let mut config = embassy_stm32::Config::default(); +//! // the default value, but can be adjusted +//! config.min_stop_pause = Duration::from_millis(250); //! // when enabled the power-consumption is much higher during stop, but debugging and RTT is working //! config.enable_debug_during_sleep = false; //! let p = embassy_stm32::init(config); @@ -45,11 +40,9 @@ //! } //! ``` -// TODO: Usage of `static mut` here is unsound. Fix then remove this `allow`.` -#![allow(static_mut_refs)] - use core::arch::asm; use core::marker::PhantomData; +use core::mem; use core::sync::atomic::{Ordering, compiler_fence}; use cortex_m::peripheral::SCB; @@ -57,11 +50,12 @@ use critical_section::CriticalSection; use embassy_executor::*; use crate::interrupt; +use crate::rcc::{REFCOUNT_STOP1, REFCOUNT_STOP2}; use crate::time_driver::get_driver; const THREAD_PENDER: usize = usize::MAX; -static mut EXECUTOR: Option = None; +static mut EXECUTOR_TAKEN: bool = false; /// Prevent the device from going into the stop mode if held pub struct DeviceBusy(StopMode); @@ -182,42 +176,47 @@ impl Into for StopMode { pub struct Executor { inner: raw::Executor, not_send: PhantomData<*mut ()>, - scb: SCB, } impl Executor { /// Create a new Executor. - pub fn take() -> &'static mut Self { - critical_section::with(|_| unsafe { - assert!(EXECUTOR.is_none()); + pub fn new() -> Self { + unsafe { + if EXECUTOR_TAKEN { + panic!("Low power executor can only be taken once."); + } else { + EXECUTOR_TAKEN = true; + } + } - EXECUTOR = Some(Self { - inner: raw::Executor::new(THREAD_PENDER as *mut ()), - not_send: PhantomData, - scb: cortex_m::Peripherals::steal().SCB, - }); - - let executor = EXECUTOR.as_mut().unwrap(); - - executor - }) + Self { + inner: raw::Executor::new(THREAD_PENDER as *mut ()), + not_send: PhantomData, + } } pub(crate) unsafe fn on_wakeup_irq() { critical_section::with(|cs| { #[cfg(stm32wlex)] { - let extscr = crate::pac::PWR.extscr().read(); + use crate::pac::rcc::vals::Sw; + use crate::pac::{PWR, RCC}; + use crate::rcc::{RCC_CONFIG, init as init_rcc}; + + let extscr = PWR.extscr().read(); if extscr.c1stop2f() || extscr.c1stopf() { // when we wake from any stop mode we need to re-initialize the rcc - crate::rcc::apply_resume_config(); + while RCC.cfgr().read().sws() != Sw::MSI {} + + init_rcc(RCC_CONFIG.unwrap()); + if extscr.c1stop2f() { // when we wake from STOP2, we need to re-initialize the time driver - crate::time_driver::init_timer(cs); + get_driver().init_timer(cs); // reset the refcounts for STOP2 and STOP1 (initializing the time driver will increment one of them for the timer) // and given that we just woke from STOP2, we can reset them - crate::rcc::REFCOUNT_STOP2 = 0; - crate::rcc::REFCOUNT_STOP1 = 0; + REFCOUNT_STOP2 = 0; + REFCOUNT_STOP1 = 0; } } } @@ -226,11 +225,15 @@ impl Executor { }); } + const fn get_scb() -> SCB { + unsafe { mem::transmute(()) } + } + fn stop_mode(_cs: CriticalSection) -> Option { - if unsafe { crate::rcc::REFCOUNT_STOP2 == 0 && crate::rcc::REFCOUNT_STOP1 == 0 } { + if unsafe { REFCOUNT_STOP2 == 0 && REFCOUNT_STOP1 == 0 } { trace!("low power: stop 2"); Some(StopMode::Stop2) - } else if unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } { + } else if unsafe { REFCOUNT_STOP1 == 0 } { trace!("low power: stop 1"); Some(StopMode::Stop1) } else { @@ -240,7 +243,7 @@ impl Executor { } #[allow(unused_variables)] - fn configure_stop(&mut self, stop_mode: StopMode) { + fn configure_stop(&self, stop_mode: StopMode) { #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0, stm32wba, stm32wlex))] crate::pac::PWR.cr1().modify(|m| m.set_lpms(stop_mode.into())); #[cfg(stm32h5)] @@ -251,8 +254,8 @@ impl Executor { }); } - fn configure_pwr(&mut self) { - self.scb.clear_sleepdeep(); + fn configure_pwr(&self) { + Self::get_scb().clear_sleepdeep(); // Clear any previous stop flags #[cfg(stm32wlex)] crate::pac::PWR.extscr().modify(|w| { @@ -271,7 +274,7 @@ impl Executor { self.configure_stop(stop_mode); #[cfg(not(feature = "low-power-debug-with-sleep"))] - self.scb.set_sleepdeep(); + Self::get_scb().set_sleepdeep(); }); } @@ -294,12 +297,11 @@ impl Executor { /// /// This function never returns. pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { - let executor = unsafe { EXECUTOR.as_mut().unwrap() }; - init(executor.inner.spawner()); + init(self.inner.spawner()); loop { unsafe { - executor.inner.poll(); + self.inner.poll(); self.configure_pwr(); asm!("wfe"); #[cfg(stm32wlex)] diff --git a/embassy-stm32/src/rcc/l.rs b/embassy-stm32/src/rcc/l.rs index 584957c6d..2e1cbd702 100644 --- a/embassy-stm32/src/rcc/l.rs +++ b/embassy-stm32/src/rcc/l.rs @@ -1,6 +1,3 @@ -#[cfg(all(feature = "low-power", stm32wlex))] -use core::mem::MaybeUninit; - #[cfg(any(stm32l0, stm32l1))] pub use crate::pac::pwr::vals::Vos as VoltageScale; use crate::pac::rcc::regs::Cfgr; @@ -14,42 +11,6 @@ use crate::time::Hertz; /// HSI speed pub const HSI_FREQ: Hertz = Hertz(16_000_000); -/// Saved RCC Config -/// -/// Used when exiting STOP2 to re-enable clocks to their last configured state -/// for chips that need it. -#[cfg(all(feature = "low-power", stm32wlex))] -static mut RESUME_RCC_CONFIG: MaybeUninit = MaybeUninit::uninit(); - -/// Set the rcc config to be restored when exiting STOP2 -/// -/// Safety: Sets a mutable global. -#[cfg(all(feature = "low-power", stm32wlex))] -pub(crate) unsafe fn set_resume_config(config: Config) { - trace!("rcc set_resume_config()"); - RESUME_RCC_CONFIG = MaybeUninit::new(config); -} - -/// Get the rcc config to be restored when exiting STOP2 -/// -/// Safety: Reads a mutable global. -#[cfg(all(feature = "low-power", stm32wlex))] -pub(crate) unsafe fn get_resume_config() -> Config { - *(*core::ptr::addr_of_mut!(RESUME_RCC_CONFIG)).assume_init_ref() -} - -#[cfg(all(feature = "low-power", stm32wlex))] -/// Safety: should only be called from low power executable just after resuming from STOP2 -pub(crate) unsafe fn apply_resume_config() { - trace!("rcc apply_resume_config()"); - - while RCC.cfgr().read().sws() != Sysclk::MSI {} - - let config = get_resume_config(); - - init(config); -} - #[derive(Clone, Copy, Eq, PartialEq)] pub enum HseMode { /// crystal/ceramic oscillator (HSEBYP=0) @@ -193,10 +154,6 @@ fn msi_enable(range: MSIRange) { } pub(crate) unsafe fn init(config: Config) { - // save the rcc config because if we enter stop 2 we need to re-apply it on wakeup - #[cfg(all(feature = "low-power", stm32wlex))] - set_resume_config(config); - // Switch to MSI to prevent problems with PLL configuration. if !RCC.cr().read().msion() { // Turn on MSI and configure it to 4MHz. diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index ca7c28cbc..66ee06e17 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -49,6 +49,9 @@ pub(crate) static mut REFCOUNT_STOP1: u32 = 0; /// May be read without a critical section pub(crate) static mut REFCOUNT_STOP2: u32 = 0; +#[cfg(feature = "low-power")] +pub(crate) static mut RCC_CONFIG: Option = None; + #[cfg(backup_sram)] pub(crate) static mut BKSRAM_RETAINED: bool = false; @@ -408,6 +411,7 @@ pub(crate) fn init_rcc(_cs: CriticalSection, config: Config) { #[cfg(feature = "low-power")] { + RCC_CONFIG = Some(config); REFCOUNT_STOP2 = 0; REFCOUNT_STOP1 = 0; } diff --git a/embassy-stm32/src/rtc/mod.rs b/embassy-stm32/src/rtc/mod.rs index 116b3c7ed..e88bd7ab2 100644 --- a/embassy-stm32/src/rtc/mod.rs +++ b/embassy-stm32/src/rtc/mod.rs @@ -379,13 +379,16 @@ trait SealedInstance { } #[cfg(feature = "low-power")] -pub(crate) fn init_rtc(cs: CriticalSection, config: RtcConfig) { +pub(crate) fn init_rtc(cs: CriticalSection, config: RtcConfig, min_stop_pause: embassy_time::Duration) { + use crate::time_driver::get_driver; + #[cfg(feature = "_allow-disable-rtc")] if config._disable_rtc { return; } - crate::time_driver::get_driver().set_rtc(cs, Rtc::new_inner(config)); + get_driver().set_rtc(cs, Rtc::new_inner(config)); + get_driver().set_min_stop_pause(cs, min_stop_pause); trace!("low power: stop with rtc configured"); } diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs index 6d93b430a..0b75aef92 100644 --- a/embassy-stm32/src/time_driver.rs +++ b/embassy-stm32/src/time_driver.rs @@ -245,7 +245,7 @@ embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { impl RtcDriver { /// initialize the timer, but don't start it. Used for chips like stm32wle5 /// for low power where the timer config is lost in STOP2. - fn init_timer(&'static self, cs: critical_section::CriticalSection) { + pub(crate) fn init_timer(&'static self, cs: critical_section::CriticalSection) { let r = regs_gp16(); rcc::enable_and_reset_with_cs::(cs); @@ -516,8 +516,3 @@ pub(crate) const fn get_driver() -> &'static RtcDriver { pub(crate) fn init(cs: CriticalSection) { DRIVER.init(cs) } - -#[cfg(all(feature = "low-power", stm32wlex))] -pub(crate) fn init_timer(cs: CriticalSection) { - DRIVER.init_timer(cs) -} diff --git a/examples/stm32h5/src/bin/stop.rs b/examples/stm32h5/src/bin/stop.rs index caebc9daf..8d5456b80 100644 --- a/examples/stm32h5/src/bin/stop.rs +++ b/examples/stm32h5/src/bin/stop.rs @@ -7,20 +7,12 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::gpio::{AnyPin, Level, Output, Speed}; -use embassy_stm32::low_power::Executor; use embassy_stm32::rcc::{HSIPrescaler, LsConfig}; -use embassy_stm32::{Config, Peri}; +use embassy_stm32::{Config, Peri, low_power}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[cortex_m_rt::entry] -fn main() -> ! { - Executor::take().run(|spawner| { - spawner.spawn(unwrap!(async_main(spawner))); - }) -} - -#[embassy_executor::task] +#[embassy_executor::main(executor = "low_power::Executor")] async fn async_main(spawner: Spawner) { defmt::info!("Program Start"); diff --git a/examples/stm32l5/src/bin/stop.rs b/examples/stm32l5/src/bin/stop.rs index 3d119f90f..fde804fb7 100644 --- a/examples/stm32l5/src/bin/stop.rs +++ b/examples/stm32l5/src/bin/stop.rs @@ -4,20 +4,12 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::gpio::{AnyPin, Level, Output, Speed}; -use embassy_stm32::low_power::Executor; use embassy_stm32::rcc::LsConfig; -use embassy_stm32::{Config, Peri}; +use embassy_stm32::{Config, Peri, low_power}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[cortex_m_rt::entry] -fn main() -> ! { - Executor::take().run(|spawner| { - spawner.spawn(unwrap!(async_main(spawner))); - }) -} - -#[embassy_executor::task] +#[embassy_executor::main(executor = "low_power::Executor")] async fn async_main(spawner: Spawner) { let mut config = Config::default(); config.rcc.ls = LsConfig::default_lsi(); diff --git a/examples/stm32wle5/src/bin/adc.rs b/examples/stm32wle5/src/bin/adc.rs index 4e0574d97..ea91fb063 100644 --- a/examples/stm32wle5/src/bin/adc.rs +++ b/examples/stm32wle5/src/bin/adc.rs @@ -6,20 +6,12 @@ use defmt::*; use defmt_rtt as _; use embassy_executor::Spawner; use embassy_stm32::adc::{Adc, SampleTime}; -use embassy_stm32::low_power::Executor; +use embassy_stm32::low_power; use embassy_time::Timer; use panic_probe as _; use static_cell::StaticCell; -#[cortex_m_rt::entry] -fn main() -> ! { - info!("main: Starting!"); - Executor::take().run(|spawner| { - spawner.spawn(unwrap!(async_main(spawner))); - }); -} - -#[embassy_executor::task] +#[embassy_executor::main(executor = "low_power::Executor")] async fn async_main(_spawner: Spawner) { let mut config = embassy_stm32::Config::default(); // enable HSI clock diff --git a/examples/stm32wle5/src/bin/blinky.rs b/examples/stm32wle5/src/bin/blinky.rs index b2745fdaf..9f0c04672 100644 --- a/examples/stm32wle5/src/bin/blinky.rs +++ b/examples/stm32wle5/src/bin/blinky.rs @@ -6,20 +6,12 @@ use defmt::*; use defmt_rtt as _; use embassy_executor::Spawner; use embassy_stm32::gpio::{Level, Output, Speed}; -use embassy_stm32::low_power::Executor; +use embassy_stm32::low_power; use embassy_time::Timer; use panic_probe as _; use static_cell::StaticCell; -#[cortex_m_rt::entry] -fn main() -> ! { - info!("main: Starting!"); - Executor::take().run(|spawner| { - spawner.spawn(unwrap!(async_main(spawner))); - }); -} - -#[embassy_executor::task] +#[embassy_executor::main(executor = "low_power::Executor")] async fn async_main(_spawner: Spawner) { let mut config = embassy_stm32::Config::default(); // enable HSI clock diff --git a/examples/stm32wle5/src/bin/button_exti.rs b/examples/stm32wle5/src/bin/button_exti.rs index db1bff0be..878eca7d0 100644 --- a/examples/stm32wle5/src/bin/button_exti.rs +++ b/examples/stm32wle5/src/bin/button_exti.rs @@ -7,19 +7,11 @@ use defmt_rtt as _; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::gpio::Pull; -use embassy_stm32::low_power::Executor; +use embassy_stm32::low_power; use panic_probe as _; use static_cell::StaticCell; -#[cortex_m_rt::entry] -fn main() -> ! { - info!("main: Starting!"); - Executor::take().run(|spawner| { - spawner.spawn(unwrap!(async_main(spawner))); - }); -} - -#[embassy_executor::task] +#[embassy_executor::main(executor = "low_power::Executor")] async fn async_main(_spawner: Spawner) { let mut config = embassy_stm32::Config::default(); // enable HSI clock diff --git a/examples/stm32wle5/src/bin/i2c.rs b/examples/stm32wle5/src/bin/i2c.rs index c31c673c9..68c17a672 100644 --- a/examples/stm32wle5/src/bin/i2c.rs +++ b/examples/stm32wle5/src/bin/i2c.rs @@ -6,9 +6,8 @@ use defmt::*; use defmt_rtt as _; use embassy_executor::Spawner; use embassy_stm32::i2c::I2c; -use embassy_stm32::low_power::Executor; use embassy_stm32::time::Hertz; -use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use embassy_stm32::{bind_interrupts, i2c, low_power, peripherals}; use embassy_time::{Duration, Timer}; use panic_probe as _; use static_cell::StaticCell; @@ -18,15 +17,7 @@ bind_interrupts!(struct IrqsI2C{ I2C2_ER => i2c::ErrorInterruptHandler; }); -#[cortex_m_rt::entry] -fn main() -> ! { - info!("main: Starting!"); - Executor::take().run(|spawner| { - spawner.spawn(unwrap!(async_main(spawner))); - }); -} - -#[embassy_executor::task] +#[embassy_executor::main(executor = "low_power::Executor")] async fn async_main(_spawner: Spawner) { let mut config = embassy_stm32::Config::default(); // enable HSI clock diff --git a/tests/stm32/src/bin/stop.rs b/tests/stm32/src/bin/stop.rs index 1fe65d867..83c375bc5 100644 --- a/tests/stm32/src/bin/stop.rs +++ b/tests/stm32/src/bin/stop.rs @@ -7,21 +7,13 @@ mod common; use chrono::NaiveDate; use common::*; -use cortex_m_rt::entry; use embassy_executor::Spawner; -use embassy_stm32::Config; -use embassy_stm32::low_power::{Executor, StopMode, stop_ready}; +use embassy_stm32::low_power::{StopMode, stop_ready}; use embassy_stm32::rcc::LsConfig; use embassy_stm32::rtc::Rtc; +use embassy_stm32::{Config, low_power}; use embassy_time::Timer; -#[entry] -fn main() -> ! { - Executor::take().run(|spawner| { - spawner.spawn(unwrap!(async_main(spawner))); - }); -} - #[embassy_executor::task] async fn task_1() { for _ in 0..9 { @@ -43,7 +35,7 @@ async fn task_2() { cortex_m::asm::bkpt(); } -#[embassy_executor::task] +#[embassy_executor::main(executor = "low_power::Executor")] async fn async_main(spawner: Spawner) { let _ = config();