diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index fe5dc443a..150014afe 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" src_base = "https://github.com/embassy-rs/embassy/blob/embassy-stm32-v$VERSION/embassy-stm32/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-stm32/src/" -features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "exti", "time-driver-any", "time"] +features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "exti", "time-driver-any", "time", "low-power"] flavors = [ { regex_feature = "stm32f0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32f1.*", target = "thumbv7m-none-eabi" }, @@ -38,6 +38,7 @@ embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", fea embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional = true } +embassy-executor = { version = "0.3.0", path = "../embassy-executor", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1", optional = true} @@ -88,6 +89,8 @@ rt = ["stm32-metapac/rt"] defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-embedded-hal/defmt", "embassy-hal-internal/defmt", "embedded-io-async?/defmt-03", "embassy-usb-driver?/defmt", "embassy-net-driver/defmt", "embassy-time?/defmt"] exti = [] +low-power = [ "dep:embassy-executor", "embassy-executor/arch-cortex-m" ] +embassy-executor = [] ## Automatically generate `memory.x` file using [`stm32-metapac`](https://docs.rs/stm32-metapac/) memory-x = ["stm32-metapac/memory-x"] diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 8a731620f..6c364f7bb 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -356,6 +356,8 @@ fn main() { } fn enable() { critical_section::with(|_| { + #[cfg(feature = "low-power")] + crate::rcc::clock_refcount_add(); crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(true)); #after_enable }) @@ -363,6 +365,8 @@ fn main() { fn disable() { critical_section::with(|_| { crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(false)); + #[cfg(feature = "low-power")] + crate::rcc::clock_refcount_sub(); }) } fn reset() { diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index adb3054d8..8c87ea7d5 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -47,6 +47,8 @@ pub mod i2c; pub mod i2s; #[cfg(stm32wb)] pub mod ipcc; +#[cfg(feature = "low-power")] +pub mod low_power; #[cfg(quadspi)] pub mod qspi; #[cfg(rng)] diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs new file mode 100644 index 000000000..7814fa384 --- /dev/null +++ b/embassy-stm32/src/low_power.rs @@ -0,0 +1,139 @@ +use core::arch::asm; +use core::marker::PhantomData; + +use cortex_m::peripheral::SCB; +use embassy_executor::*; +use embassy_time::Duration; + +use crate::interrupt; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::EXTI; +use crate::rcc::low_power_ready; + +const THREAD_PENDER: usize = usize::MAX; +const THRESHOLD: Duration = Duration::from_millis(500); + +use crate::rtc::{Rtc, RtcInstant}; + +static mut RTC: Option<&'static Rtc> = None; + +foreach_interrupt! { + (RTC, rtc, $block:ident, WKUP, $irq:ident) => { + #[interrupt] + unsafe fn $irq() { + Executor::on_wakeup_irq(); + } + }; +} + +pub fn stop_with_rtc(rtc: &'static Rtc) { + crate::interrupt::typelevel::RTC_WKUP::unpend(); + unsafe { crate::interrupt::typelevel::RTC_WKUP::enable() }; + + EXTI.rtsr(0).modify(|w| w.set_line(22, true)); + EXTI.imr(0).modify(|w| w.set_line(22, true)); + + unsafe { RTC = Some(rtc) }; +} + +pub fn start_wakeup_alarm(requested_duration: embassy_time::Duration) -> RtcInstant { + unsafe { RTC }.unwrap().start_wakeup_alarm(requested_duration) +} + +pub fn stop_wakeup_alarm() -> RtcInstant { + unsafe { RTC }.unwrap().stop_wakeup_alarm() +} + +/// Thread mode executor, using WFE/SEV. +/// +/// This is the simplest and most common kind of executor. It runs on +/// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction +/// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction +/// is executed, to make the `WFE` exit from sleep and poll the task. +/// +/// This executor allows for ultra low power consumption for chips where `WFE` +/// triggers low-power sleep without extra steps. If your chip requires extra steps, +/// you may use [`raw::Executor`] directly to program custom behavior. +pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, +} + +impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(THREAD_PENDER as *mut ()), + not_send: PhantomData, + } + } + + unsafe fn on_wakeup_irq() { + info!("on wakeup irq"); + + cortex_m::asm::bkpt(); + } + + fn time_until_next_alarm(&self) -> Duration { + Duration::from_secs(3) + } + + fn get_scb() -> SCB { + unsafe { cortex_m::Peripherals::steal() }.SCB + } + + fn configure_pwr(&self) { + trace!("configure_pwr"); + + if !low_power_ready() { + return; + } + + let time_until_next_alarm = self.time_until_next_alarm(); + if time_until_next_alarm < THRESHOLD { + return; + } + + trace!("low power stop required"); + + critical_section::with(|_| { + trace!("executor: set wakeup alarm..."); + + start_wakeup_alarm(time_until_next_alarm); + + trace!("low power wait for rtc ready..."); + + Self::get_scb().set_sleepdeep(); + }); + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { + self.inner.poll(); + self.configure_pwr(); + asm!("wfe"); + }; + } + } +} diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index ac9ae9c6a..3c75923e5 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -26,6 +26,8 @@ use crate::time::Hertz; #[cfg_attr(any(rcc_h5, rcc_h50), path = "h5.rs")] mod _version; pub use _version::*; +#[cfg(feature = "low-power")] +use atomic_polyfill::{AtomicU32, Ordering}; #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -79,6 +81,25 @@ pub struct Clocks { pub rtc: Option, } +#[cfg(feature = "low-power")] +static CLOCK_REFCOUNT: AtomicU32 = AtomicU32::new(0); + +#[cfg(feature = "low-power")] +pub fn low_power_ready() -> bool { + CLOCK_REFCOUNT.load(Ordering::SeqCst) == 0 +} + +#[cfg(feature = "low-power")] +pub(crate) fn clock_refcount_add() { + // We don't check for overflow because constructing more than u32 peripherals is unlikely + CLOCK_REFCOUNT.fetch_add(1, Ordering::Relaxed); +} + +#[cfg(feature = "low-power")] +pub(crate) fn clock_refcount_sub() { + assert!(CLOCK_REFCOUNT.fetch_sub(1, Ordering::Relaxed) != 0); +} + /// Frozen clock frequencies /// /// The existence of this value indicates that the clock configuration can no longer be changed diff --git a/embassy-stm32/src/rtc/v2.rs b/embassy-stm32/src/rtc/v2.rs index 53d0161d6..bcb127ecb 100644 --- a/embassy-stm32/src/rtc/v2.rs +++ b/embassy-stm32/src/rtc/v2.rs @@ -44,25 +44,15 @@ impl core::ops::Sub for RtcInstant { fn sub(self, rhs: Self) -> Self::Output { use embassy_time::{Duration, TICK_HZ}; - trace!("self st: {}", self.st); - trace!("other st: {}", rhs.st); - - trace!("self ssr: {}", self.ssr); - trace!("other ssr: {}", rhs.ssr); - let st = if self.st < rhs.st { self.st + 60 } else { self.st }; - trace!("self st: {}", st); + // TODO: read prescaler let self_ticks = st as u32 * 256 + (255 - self.ssr as u32); let other_ticks = rhs.st as u32 * 256 + (255 - rhs.ssr as u32); let rtc_ticks = self_ticks - other_ticks; - trace!("self ticks: {}", self_ticks); - trace!("other ticks: {}", other_ticks); - trace!("rtc ticks: {}", rtc_ticks); - - // TODO: read prescaler + trace!("self, other, rtc ticks: {}, {}, {}", self_ticks, other_ticks, rtc_ticks); Duration::from_ticks( ((((st as u32 * 256 + (255u32 - self.ssr as u32)) - (rhs.st as u32 * 256 + (255u32 - rhs.ssr as u32))) @@ -73,7 +63,7 @@ impl core::ops::Sub for RtcInstant { } #[allow(dead_code)] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub(crate) enum WakeupPrescaler { Div2, Div4, @@ -162,10 +152,9 @@ impl super::Rtc { /// /// note: this api is exposed for testing purposes until low power is implemented. /// it is not intended to be public - pub fn start_wakeup_alarm(requested_duration: embassy_time::Duration) -> RtcInstant { + pub(crate) fn start_wakeup_alarm(&self, requested_duration: embassy_time::Duration) -> RtcInstant { use embassy_time::{Duration, TICK_HZ}; - use crate::interrupt::typelevel::Interrupt; use crate::rcc::get_freqs; let rtc_hz = unsafe { get_freqs() }.rtc.unwrap().0 as u64; @@ -187,27 +176,17 @@ impl super::Rtc { trace!("set wakeup timer for {} ms", duration.as_millis()); - RTC::regs().wpr().write(|w| w.set_key(0xca)); - RTC::regs().wpr().write(|w| w.set_key(0x53)); + self.write(false, |regs| { + regs.cr().modify(|w| w.set_wutie(true)); - RTC::regs().wutr().modify(|w| w.set_wut(rtc_ticks)); + regs.cr().modify(|w| w.set_wute(false)); + regs.isr().modify(|w| w.set_wutf(false)); + while !regs.isr().read().wutwf() {} - RTC::regs().cr().modify(|w| { - w.set_wucksel(prescaler.into()); - - w.set_wutie(true); - w.set_wute(true); + regs.cr().modify(|w| w.set_wucksel(prescaler.into())); + regs.cr().modify(|w| w.set_wute(true)); }); - if !RTC::regs().cr().read().wute() { - trace!("wakeup timer not enabled"); - } else { - trace!("wakeup timer enabled"); - } - - crate::interrupt::typelevel::RTC_WKUP::unpend(); - unsafe { crate::interrupt::typelevel::RTC_WKUP::enable() }; - RtcInstant::now() } @@ -217,26 +196,14 @@ impl super::Rtc { /// /// note: this api is exposed for testing purposes until low power is implemented. /// it is not intended to be public - pub fn stop_wakeup_alarm() -> RtcInstant { - use crate::interrupt::typelevel::Interrupt; - - crate::interrupt::typelevel::RTC_WKUP::disable(); - + pub(crate) fn stop_wakeup_alarm(&self) -> RtcInstant { trace!("disable wakeup timer..."); - RTC::regs().cr().modify(|w| { - w.set_wute(false); + self.write(false, |regs| { + regs.cr().modify(|w| w.set_wute(false)); + regs.isr().modify(|w| w.set_wutf(false)); }); - trace!("wait for wakeup timer stop..."); - - // Wait for the wakeup timer to stop - // while !RTC::regs().isr().read().wutf() {} - // - // RTC::regs().isr().modify(|w| w.set_wutf(false)); - - trace!("wait for wakeup timer stop...done"); - RtcInstant::now() } @@ -388,7 +355,7 @@ impl super::Rtc { }) } - pub(super) fn write(&mut self, init_mode: bool, f: F) -> R + pub(super) fn write(&self, init_mode: bool, f: F) -> R where F: FnOnce(&crate::pac::rtc::Rtc) -> R, {