diff --git a/rp2040-hal/Cargo.toml b/rp2040-hal/Cargo.toml index 205a5cc95..e43e43f36 100644 --- a/rp2040-hal/Cargo.toml +++ b/rp2040-hal/Cargo.toml @@ -161,6 +161,11 @@ required-features = ["critical-section-impl"] name = "dht11" required-features = ["critical-section-impl"] +[[example]] +# dormant_sleep example uses cortex-m-rt::interrupt, need rt feature for that +name = "dormant_sleep" +required-features = ["rt", "critical-section-impl"] + [[example]] name = "gpio_in_out" required-features = ["critical-section-impl"] diff --git a/rp2040-hal/examples/dormant_sleep.rs b/rp2040-hal/examples/dormant_sleep.rs new file mode 100644 index 000000000..ae3c709a2 --- /dev/null +++ b/rp2040-hal/examples/dormant_sleep.rs @@ -0,0 +1,298 @@ +//! # DORMANT low-power mode example +//! +//! This application demonstrates how to enter and exit the RP2040's lowest-power DORMANT mode +//! where all clocks and PLLs are stopped. +//! +//! Pulling GPIO 14 low (e.g. via a debounced momentary-contact button) alternately wakes the +//! RP2040 from DORMANT mode and a regular WFI sleep. A LED attached to GPIO 25 (the onboard LED +//! on the Raspberry Pi Pico) pulses once before entering DORMANT mode and twice before entering WFI sleep. +//! +//! Note: DORMANT mode breaks the debug connection. You may need to power cycle while pressing the +//! BOOTSEL button to regain debug access to the pico. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +#[allow(unused_imports)] +use panic_halt as _; + +use rp2040_hal as hal; + +use core::{cell::RefCell, ops::DerefMut}; + +use critical_section::Mutex; + +use embedded_hal::digital::StatefulOutputPin; + +use fugit::RateExtU32; + +use hal::{ + clocks::{ClockError, ClocksManager, InitError, StoppableClock}, + gpio, + gpio::{Interrupt::EdgeLow, Pins}, + pac, + pac::{interrupt, CLOCKS, PLL_SYS, PLL_USB, RESETS, XOSC}, + pll::{ + common_configs::{PLL_SYS_125MHZ, PLL_USB_48MHZ}, + setup_pll_blocking, start_pll_blocking, Disabled, Locked, PhaseLockedLoop, + }, + rosc::RingOscillator, + sio::Sio, + watchdog::Watchdog, + xosc::{setup_xosc_blocking, CrystalOscillator, Stable, Unstable}, + Clock, +}; + +use nb::block; + +type ClocksAndPlls = ( + ClocksManager, + CrystalOscillator, + PhaseLockedLoop, + PhaseLockedLoop, +); + +type RestartedClockAndPlls = ( + CrystalOscillator, + PhaseLockedLoop, + PhaseLockedLoop, +); + +/// The button input. +type ButtonPin = gpio::Pin; + +/// Devices shared between the foreground code and interrupt handlers. +static GLOBAL_DEVICES: Mutex>> = Mutex::new(RefCell::new(None)); + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency. +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +#[rp2040_hal::entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + let sio = Sio::new(pac.SIO); + + // Configure the clocks + let (mut clocks, mut xosc, mut pll_sys, mut pll_usb) = init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // Disable ring oscillator to maximise power savings - optional + let rosc = RingOscillator::new(pac.ROSC).initialize(); + rosc.disable(); + + // Set the pins to their default state + let pins = Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO 25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + + // Configure GPIO 14 as an input that wakes the RP2040 from a sleep state + let button_pin = pins.gpio14.reconfigure(); + button_pin.set_dormant_wake_enabled(EdgeLow, true); + button_pin.set_interrupt_enabled(EdgeLow, true); + + critical_section::with(|cs| { + GLOBAL_DEVICES.borrow(cs).replace(Some(button_pin)); + }); + + unsafe { + pac::NVIC::unmask(pac::Interrupt::IO_IRQ_BANK0); + } + + let mut use_dormant = true; + loop { + if use_dormant { + pulse(&mut led_pin, 1); + + let (disabled_pll_sys, disabled_pll_usb) = + prepare_clocks_and_plls_for_dormancy(&mut xosc, &mut clocks, pll_sys, pll_usb); + + // Stop the crystal oscillator and enter the RP2040's dormant state + let unstable_xosc = unsafe { xosc.dormant() }; + + match restart_clocks_and_plls( + &mut clocks, + unstable_xosc, + disabled_pll_sys, + disabled_pll_usb, + &mut pac.RESETS, + ) { + Ok((stable_xosc, stable_pll_sys, stable_pll_usb)) => { + xosc = stable_xosc; + pll_sys = stable_pll_sys; + pll_usb = stable_pll_usb; + } + Err(_) => { + panic!(); + } + } + + // Clear dormant wake interrupt status to enable wake next time + critical_section::with(|cs| { + let mut global_devices = GLOBAL_DEVICES.borrow(cs).borrow_mut(); + if let Some(ref mut trigger_pin) = global_devices.deref_mut() { + trigger_pin.clear_interrupt(EdgeLow); + } else { + panic!(); + }; + }); + } else { + pulse(&mut led_pin, 2); + + // Enter the regular RP2040 sleep state: clocks and PLLs stay running + cortex_m::asm::wfi(); + } + + use_dormant = !use_dormant; + } +} + +/// Pulse an LED-connected pin the specified number of times. +fn pulse(pin: &mut P, count: u32) { + const LED_PULSE_CYCLES: u32 = 2_000_000; + + for i in 0..count * 2 { + let _ = pin.toggle(); + // 1:10 duty cycle + cortex_m::asm::delay(LED_PULSE_CYCLES + (i % 2) * 9 * LED_PULSE_CYCLES); + } +} + +/// Initialize clocks and PLLs in much the same way as rp2040-hal::clocks::init_clocks_and_plls(). +/// Returns the crystal oscillator and the PLLs so we can reconfigure them later. +fn init_clocks_and_plls( + xosc_crystal_freq: u32, + xosc_dev: XOSC, + clocks_dev: CLOCKS, + pll_sys_dev: PLL_SYS, + pll_usb_dev: PLL_USB, + resets: &mut RESETS, + watchdog: &mut Watchdog, +) -> Result { + let xosc = setup_xosc_blocking(xosc_dev, xosc_crystal_freq.Hz()).unwrap(); + + // Configure watchdog tick generation to tick over every microsecond + watchdog.enable_tick_generation((xosc_crystal_freq / 1_000_000) as u8); + + let mut clocks = ClocksManager::new(clocks_dev); + + let pll_sys = setup_pll_blocking( + pll_sys_dev, + xosc.operating_frequency(), + PLL_SYS_125MHZ, + &mut clocks, + resets, + ) + .map_err(InitError::PllError)?; + let pll_usb = setup_pll_blocking( + pll_usb_dev, + xosc.operating_frequency(), + PLL_USB_48MHZ, + &mut clocks, + resets, + ) + .map_err(InitError::PllError)?; + + clocks + .init_default(&xosc, &pll_sys, &pll_usb) + .map_err(InitError::ClockError)?; + + Ok((clocks, xosc, pll_sys, pll_usb)) +} + +/// Switch clocks to the crystal oscillator or disable them as appropriate, and stop PLLs so +/// that we're ready to go dormant. +fn prepare_clocks_and_plls_for_dormancy( + xosc: &mut CrystalOscillator, + clocks: &mut ClocksManager, + pll_sys: PhaseLockedLoop, + pll_usb: PhaseLockedLoop, +) -> ( + PhaseLockedLoop, + PhaseLockedLoop, +) { + // switch system clock from pll_sys to xosc so that we can stop the system PLL + nb::block!(clocks.system_clock.reset_source_await()).unwrap(); + + clocks.usb_clock.disable(); + clocks.adc_clock.disable(); + + clocks + .rtc_clock + .configure_clock(xosc, 46875u32.Hz()) + .unwrap(); + clocks + .peripheral_clock + .configure_clock(&clocks.system_clock, clocks.system_clock.freq()) + .unwrap(); + + (pll_sys.disable(), pll_usb.disable()) +} + +/// Restart the PLLs and start/reconfigure the clocks back to how they were before going dormant. +fn restart_clocks_and_plls( + clocks: &mut ClocksManager, + unstable_xosc: CrystalOscillator, + disabled_pll_sys: PhaseLockedLoop, + disabled_pll_usb: PhaseLockedLoop, + resets: &mut RESETS, +) -> Result { + // Wait for the restarted XOSC to stabilise + let stable_xosc_token = block!(unstable_xosc.await_stabilization()).unwrap(); + let xosc = unstable_xosc.get_stable(stable_xosc_token); + + let pll_sys = start_pll_blocking(disabled_pll_sys, resets).unwrap(); + let pll_usb = start_pll_blocking(disabled_pll_usb, resets).unwrap(); + + clocks + .init_default(&xosc, &pll_sys, &pll_usb) + .map(|_| (xosc, pll_sys, pll_usb)) +} + +#[interrupt] +fn IO_IRQ_BANK0() { + critical_section::with(|cs| { + let mut global_devices = GLOBAL_DEVICES.borrow(cs).borrow_mut(); + if let Some(ref mut button_pin) = global_devices.deref_mut() { + // Check if the interrupt source is from the push button going from high-to-low. + // Note: this will always be true in this example, as that is the only enabled GPIO interrupt source + if button_pin.interrupt_status(EdgeLow) { + // Our interrupt doesn't clear itself. + // Do that now so we don't immediately jump back to this interrupt handler. + button_pin.clear_interrupt(EdgeLow); + } + } else { + panic!(); + }; + }); +} + +// End of file diff --git a/rp2040-hal/src/clocks/macros.rs b/rp2040-hal/src/clocks/macros.rs index 6a298a2df..a193fb005 100644 --- a/rp2040-hal/src/clocks/macros.rs +++ b/rp2040-hal/src/clocks/macros.rs @@ -196,14 +196,14 @@ macro_rules! clock { self.set_div(div); } - // If switching a glitchless slice (ref or sys) to an aux source, switch - // away from aux *first* to avoid passing glitches when changing aux mux. - // Assume (!!!) glitchless source 0 is no faster than the aux source. - nb::block!(self.reset_source_await()).unwrap(); - // Set aux mux first, and then glitchless mux if this self has one let token = if src.is_aux() { + // If switching a glitchless slice (ref or sys) to an aux source, switch + // away from aux *first* to avoid passing glitches when changing aux mux. + // Assume (!!!) glitchless source 0 is no faster than the aux source. + nb::block!(self.reset_source_await()).unwrap(); + self.set_aux(src); self.set_self_aux_src() } else { diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index 56b14e7f5..dbff55a80 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -278,6 +278,25 @@ impl PhaseLockedLoop { pub fn operating_frequency(&self) -> HertzU32 { self.state.frequency } + + /// Shut down the PLL. The returned PLL is configured the same as it was originally. + pub fn disable(self) -> PhaseLockedLoop { + let fbdiv = self.device.fbdiv_int().read().fbdiv_int().bits(); + let refdiv = self.device.cs().read().refdiv().bits(); + let prim = self.device.prim().read(); + let frequency = self.state.frequency; + + self.device.pwr().reset(); + self.device.fbdiv_int().reset(); + + self.transition(Disabled { + refdiv, + fbdiv, + post_div1: prim.postdiv1().bits(), + post_div2: prim.postdiv2().bits(), + frequency, + }) + } } /// Blocking helper method to setup the PLL without going through all the steps. @@ -293,8 +312,18 @@ pub fn setup_pll_blocking( nb::block!(clocks.reference_clock.reset_source_await()).unwrap(); - let initialized_pll = - PhaseLockedLoop::new(dev, xosc_frequency.convert(), config)?.initialize(resets); + start_pll_blocking( + PhaseLockedLoop::new(dev, xosc_frequency.convert(), config)?, + resets, + ) +} + +/// Blocking helper method to (re)start a PLL. +pub fn start_pll_blocking( + disabled_pll: PhaseLockedLoop, + resets: &mut RESETS, +) -> Result, Error> { + let initialized_pll = disabled_pll.initialize(resets); let locked_pll_token = nb::block!(initialized_pll.await_lock()).unwrap(); diff --git a/rp2040-hal/src/rosc.rs b/rp2040-hal/src/rosc.rs index efdcfc483..d05025295 100644 --- a/rp2040-hal/src/rosc.rs +++ b/rp2040-hal/src/rosc.rs @@ -28,15 +28,10 @@ pub struct Enabled { freq_hz: HertzU32, } -/// ROSC is in dormant mode (see Chapter 2, Section 17, §7) -pub struct Dormant; - impl State for Disabled {} impl Sealed for Disabled {} impl State for Enabled {} impl Sealed for Enabled {} -impl State for Dormant {} -impl Sealed for Dormant {} /// A Ring Oscillator. pub struct RingOscillator { @@ -109,20 +104,21 @@ impl RingOscillator { self.device.randombit().read().randombit().bit() } - /// Put the ROSC in DORMANT state. + /// Put the ROSC in DORMANT state. The method returns after the processor awakens. + /// + /// After waking up from the DORMANT state, ROSC restarts in approximately 1µs. /// /// # Safety /// This method is marked unsafe because prior to switch the ROSC into DORMANT state, /// PLLs must be stopped and IRQs have to be properly configured. /// This method does not do any of that, it merely switches the ROSC to DORMANT state. + /// It should only be called if this oscillator is the clock source for the system clock. /// See Chapter 2, Section 16, §5) for details. - pub unsafe fn dormant(self) -> RingOscillator { + pub unsafe fn dormant(&self) { //taken from the C SDK const ROSC_DORMANT_VALUE: u32 = 0x636f6d61; self.device.dormant().write(|w| w.bits(ROSC_DORMANT_VALUE)); - - self.transition(Dormant) } } diff --git a/rp2040-hal/src/xosc.rs b/rp2040-hal/src/xosc.rs index 3a52f8bbe..2252a4e58 100644 --- a/rp2040-hal/src/xosc.rs +++ b/rp2040-hal/src/xosc.rs @@ -15,27 +15,22 @@ pub trait State: Sealed {} /// XOSC is disabled (typestate) pub struct Disabled; -/// XOSC is initialized, ie we've given parameters (typestate) -pub struct Initialized { +/// XOSC is initialized but has not yet stabilized (typestate) +pub struct Unstable { freq_hz: HertzU32, } -/// Stable state (typestate) +/// XOSC is stable (typestate) pub struct Stable { freq_hz: HertzU32, } -/// XOSC is in dormant mode (see Chapter 2, Section 16, §5) -pub struct Dormant; - impl State for Disabled {} impl Sealed for Disabled {} -impl State for Initialized {} -impl Sealed for Initialized {} +impl State for Unstable {} +impl Sealed for Unstable {} impl State for Stable {} impl Sealed for Stable {} -impl State for Dormant {} -impl Sealed for Dormant {} /// Possible errors when initializing the CrystalOscillator #[derive(Debug)] @@ -121,7 +116,7 @@ impl CrystalOscillator { self, frequency: HertzU32, startup_delay_multiplier: u32, - ) -> Result, Error> { + ) -> Result, Error> { const ALLOWED_FREQUENCY_RANGE: RangeInclusive = HertzU32::MHz(1)..=HertzU32::MHz(15); //1 ms = 10e-3 sec and Freq = 1/T where T is in seconds so 1ms converts to 1000Hz @@ -161,17 +156,17 @@ impl CrystalOscillator { w }); - Ok(self.transition(Initialized { freq_hz: frequency })) + Ok(self.transition(Unstable { freq_hz: frequency })) } } -/// A token that's given when the oscillator is stablilzed, and can be exchanged to proceed to the next stage. +/// A token that's given when the oscillator is stabilized, and can be exchanged to proceed to the next stage. pub struct StableOscillatorToken { _private: (), } -impl CrystalOscillator { - /// One has to wait for the startup delay before using the oscillator, ie awaiting stablilzation of the XOSC +impl CrystalOscillator { + /// One has to wait for the startup delay before using the oscillator, ie awaiting stabilization of the XOSC pub fn await_stabilization(&self) -> nb::Result { if self.device.status().read().stable().bit_is_clear() { return Err(WouldBlock); @@ -180,7 +175,7 @@ impl CrystalOscillator { Ok(StableOscillatorToken { _private: () }) } - /// Returns the stablilzed oscillator + /// Returns the stabilized oscillator pub fn get_stable(self, _token: StableOscillatorToken) -> CrystalOscillator { let freq_hz = self.state.freq_hz; self.transition(Stable { freq_hz }) @@ -203,14 +198,17 @@ impl CrystalOscillator { self.transition(Disabled) } - /// Put the XOSC in DORMANT state. + /// Put the XOSC in DORMANT state. The method returns after the processor awakens. + /// + /// After waking up from the DORMANT state, XOSC needs to re-stabilise. /// /// # Safety /// This method is marked unsafe because prior to switch the XOSC into DORMANT state, /// PLLs must be stopped and IRQs have to be properly configured. /// This method does not do any of that, it merely switches the XOSC to DORMANT state. + /// It should only be called if this oscillator is the clock source for the system clock. /// See Chapter 2, Section 16, §5) for details. - pub unsafe fn dormant(self) -> CrystalOscillator { + pub unsafe fn dormant(self) -> CrystalOscillator { //taken from the C SDK const XOSC_DORMANT_VALUE: u32 = 0x636f6d61; @@ -219,6 +217,7 @@ impl CrystalOscillator { w }); - self.transition(Dormant) + let freq_hz = self.state.freq_hz; + self.transition(Unstable { freq_hz }) } }