low_power: misc cleanups and allow main macro

This commit is contained in:
xoviat 2025-11-16 07:50:49 -06:00
parent a51533c0b4
commit 29d4ade286
14 changed files with 72 additions and 170 deletions

View File

@ -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

View File

@ -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

View File

@ -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<Executor> = 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<Lpms> 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<StopMode> {
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)]

View File

@ -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<Config> = 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.

View File

@ -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<Config> = 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;
}

View File

@ -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");
}

View File

@ -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::<T>(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)
}

View File

@ -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");

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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<peripherals::I2C2>;
});
#[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

View File

@ -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();