diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index a3398023..7b25ecff 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -1,3 +1,4 @@ +use core::arch::asm; use core::marker::PhantomData; use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; @@ -6,6 +7,7 @@ use pac::clocks::vals::*; use crate::gpio::sealed::Pin; use crate::gpio::AnyPin; +use crate::pac::common::{Reg, RW}; use crate::{pac, reset, Peripheral}; // NOTE: all gpin handling is commented out for future reference. @@ -873,3 +875,131 @@ impl rand_core::RngCore for RoscRng { dest.fill_with(Self::next_u8) } } +/// Enter the `DORMANT` sleep state. This will stop *all* internal clocks +/// and can only be exited through resets, dormant-wake GPIO interrupts, +/// and RTC interrupts. If RTC is clocked from an internal clock source +/// it will be stopped and not function as a wakeup source. +#[cfg(target_arch = "arm")] +pub fn dormant_sleep() { + struct Set(Reg, T, F); + + impl Drop for Set { + fn drop(&mut self) { + self.0.write_value(self.1); + self.2(); + } + } + + fn set_with_post_restore After>( + reg: Reg, + f: F, + ) -> Set { + reg.modify(|w| { + let old = *w; + let after = f(w); + Set(reg, old, after) + }) + } + + fn set(reg: Reg, f: F) -> Set { + set_with_post_restore(reg, |r| { + f(r); + || () + }) + } + + // disable all clocks that are not vital in preparation for disabling clock sources. + // we'll keep gpout and rtc clocks untouched, gpout because we don't care about them + // and rtc because it's a possible wakeup source. if clk_rtc is not configured for + // gpin we'll never wake from rtc, but that's what the user asked for then. + let _stop_adc = set(pac::CLOCKS.clk_adc_ctrl(), |w| w.set_enable(false)); + let _stop_usb = set(pac::CLOCKS.clk_usb_ctrl(), |w| w.set_enable(false)); + let _stop_peri = set(pac::CLOCKS.clk_peri_ctrl(), |w| w.set_enable(false)); + // set up rosc. we could ask the use to tell us which clock source to wake from like + // the C SDK does, but that seems rather unfriendly. we *may* disturb rtc by changing + // rosc configuration if it's currently the rtc clock source, so we'll configure rosc + // to the slowest frequency to minimize that impact. + let _configure_rosc = ( + set(pac::ROSC.ctrl(), |w| { + w.set_enable(pac::rosc::vals::Enable::ENABLE); + w.set_freq_range(pac::rosc::vals::FreqRange::LOW); + }), + // div=32 + set(pac::ROSC.div(), |w| w.set_div(pac::rosc::vals::Div(0xaa0))), + ); + while !pac::ROSC.status().read().stable() {} + // switch over to rosc as the system clock source. this will change clock sources for + // watchdog and timer clocks, but timers won't be a concern and the watchdog won't + // speed up by enough to worry about (unless it's clocked from gpin, which we don't + // support anyway). + let _switch_clk_ref = set(pac::CLOCKS.clk_ref_ctrl(), |w| { + w.set_src(pac::clocks::vals::ClkRefCtrlSrc::ROSC_CLKSRC_PH); + }); + let _switch_clk_sys = set(pac::CLOCKS.clk_sys_ctrl(), |w| { + w.set_src(pac::clocks::vals::ClkSysCtrlSrc::CLK_REF); + }); + // oscillator dormancy does not power down plls, we have to do that ourselves. we'll + // restore them to their prior glory when woken though since the system may be clocked + // from either (and usb/adc will probably need the USB PLL anyway) + let _stop_pll_sys = set_with_post_restore(pac::PLL_SYS.pwr(), |w| { + let wake = !w.pd() && !w.vcopd(); + w.set_pd(true); + w.set_vcopd(true); + move || while wake && !pac::PLL_SYS.cs().read().lock() {} + }); + let _stop_pll_usb = set_with_post_restore(pac::PLL_USB.pwr(), |w| { + let wake = !w.pd() && !w.vcopd(); + w.set_pd(true); + w.set_vcopd(true); + move || while wake && !pac::PLL_USB.cs().read().lock() {} + }); + // dormancy only stops the oscillator we're telling to go dormant, the other remains + // running. nothing can use xosc at this point any more. not doing this costs an 200µA. + let _stop_xosc = set_with_post_restore(pac::XOSC.ctrl(), |w| { + let wake = w.enable() == pac::xosc::vals::Enable::ENABLE; + if wake { + w.set_enable(pac::xosc::vals::Enable::DISABLE); + } + move || while wake && !pac::XOSC.status().read().stable() {} + }); + let _power_down_xip_cache = set(pac::XIP_CTRL.ctrl(), |w| w.set_power_down(true)); + + // only power down memory if we're running from XIP (or ROM? how?). + // powering down memory otherwise would require a lot of exacting checks that + // are better done by the user in a local copy of this function. + // powering down memories saves ~100µA, so it's well worth doing. + unsafe { + let is_in_flash = { + // we can't rely on the address of this function as rust sees it since linker + // magic or even boot2 may place it into ram. + let pc: usize; + asm!( + "mov {pc}, pc", + pc = out (reg) pc + ); + pc < 0x20000000 + }; + if is_in_flash { + // we will be powering down memories, so we must be *absolutely* + // certain that we're running entirely from XIP and registers until + // memories are powered back up again. accessing memory that's powered + // down may corrupt memory contents (see section 2.11.4 of the manual). + // additionally a 20ns wait time is needed after powering up memories + // again. rosc is likely to run at only a few MHz at most, so the + // inter-instruction delay alone will be enough to satisfy this bound. + asm!( + "ldr {old_mem}, [{mempowerdown}]", + "str {power_down_mems}, [{mempowerdown}]", + "str {coma}, [{dormant}]", + "str {old_mem}, [{mempowerdown}]", + old_mem = out (reg) _, + mempowerdown = in (reg) pac::SYSCFG.mempowerdown().as_ptr(), + power_down_mems = in (reg) 0b11111111, + dormant = in (reg) pac::ROSC.dormant().as_ptr(), + coma = in (reg) 0x636f6d61, + ); + } else { + pac::ROSC.dormant().write_value(0x636f6d61); + } + } +} diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 8c7dd38c..723c5029 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -57,7 +57,7 @@ sdio-host = "0.5.0" embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true } critical-section = "1.1" atomic-polyfill = "1.0.1" -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-1f8ab493e029fc601edebc6bac105a63cc9858fe" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-82a24863823a3daf0ca664c7fdf008379d0a0d42" } vcell = "0.1.3" bxcan = "0.7.0" nb = "1.0.0" @@ -75,7 +75,7 @@ critical-section = { version = "1.1", features = ["std"] } [build-dependencies] proc-macro2 = "1.0.36" quote = "1.0.15" -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-1f8ab493e029fc601edebc6bac105a63cc9858fe", default-features = false, features = ["metadata"]} +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-82a24863823a3daf0ca664c7fdf008379d0a0d42", default-features = false, features = ["metadata"]} [features] default = ["rt"] diff --git a/examples/stm32f334/Cargo.toml b/examples/stm32f334/Cargo.toml index 6410891a..d8f6b8fe 100644 --- a/examples/stm32f334/Cargo.toml +++ b/examples/stm32f334/Cargo.toml @@ -1,6 +1,6 @@ [package] edition = "2021" -name = "embassy-stm32f3-examples" +name = "embassy-stm32f334-examples" version = "0.1.0" license = "MIT OR Apache-2.0" diff --git a/tests/rp/src/bin/adc.rs b/tests/rp/src/bin/adc.rs index d6d58f0c..b29a3a7c 100644 --- a/tests/rp/src/bin/adc.rs +++ b/tests/rp/src/bin/adc.rs @@ -8,7 +8,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler, Sample}; use embassy_rp::bind_interrupts; -use embassy_rp::gpio::Pull; +use embassy_rp::gpio::{Level, Output, Pull}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { @@ -18,6 +18,8 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut p = embassy_rp::init(Default::default()); + let _power_reg_pwm_mode = Output::new(p.PIN_23, Level::High); + let _wifi_off = Output::new(p.PIN_25, Level::High); let mut adc = Adc::new(p.ADC, Irqs, Config::default()); {