From 18a642918d339688fe6a12ac096d5fe6128639ed Mon Sep 17 00:00:00 2001 From: Andrew Herbert Date: Thu, 12 Oct 2023 12:15:31 +1100 Subject: [PATCH 01/12] Add [RX]OSC transitions from Dormant back to the relevant wakeup state --- rp2040-hal/src/rosc.rs | 16 ++++++++++++++-- rp2040-hal/src/xosc.rs | 15 +++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/rp2040-hal/src/rosc.rs b/rp2040-hal/src/rosc.rs index 0ad85f299..6ad8ed1cf 100644 --- a/rp2040-hal/src/rosc.rs +++ b/rp2040-hal/src/rosc.rs @@ -17,7 +17,9 @@ pub struct Enabled { } /// ROSC is in dormant mode (see Chapter 2, Section 17, §7) -pub struct Dormant; +pub struct Dormant { + freq_hz: HertzU32, +} impl State for Disabled {} impl Sealed for Disabled {} @@ -110,7 +112,17 @@ impl RingOscillator { self.device.dormant.write(|w| w.bits(ROSC_DORMANT_VALUE)); - self.transition(Dormant) + let freq_hz = self.state.freq_hz; + self.transition(Dormant { freq_hz }) + } +} + +impl RingOscillator { + /// After waking up from the DORMANT state, ROSC restarts in approximately 1µs. + /// See Chapter 2, Section 16, §5) for details. + pub fn get_enabled(self) -> RingOscillator { + let freq_hz = self.state.freq_hz; + self.transition(Enabled { freq_hz }) } } diff --git a/rp2040-hal/src/xosc.rs b/rp2040-hal/src/xosc.rs index 255c9bb22..2c7607de6 100644 --- a/rp2040-hal/src/xosc.rs +++ b/rp2040-hal/src/xosc.rs @@ -26,7 +26,9 @@ pub struct Stable { } /// XOSC is in dormant mode (see Chapter 2, Section 16, §5) -pub struct Dormant; +pub struct Dormant { + freq_hz: HertzU32, +} impl State for Disabled {} impl Sealed for Disabled {} @@ -184,6 +186,15 @@ impl CrystalOscillator { w }); - self.transition(Dormant) + let freq_hz = self.state.freq_hz; + self.transition(Dormant { freq_hz }) + } +} + +impl CrystalOscillator { + /// After waking up from the DORMANT state, XOSC is initialized but needs to re-stabilise. + pub fn get_initialized(self) -> CrystalOscillator { + let freq_hz = self.state.freq_hz; + self.transition(Initialized { freq_hz }) } } From c49fbb52f9e48fe5134a66803df47c374010b601 Mon Sep 17 00:00:00 2001 From: Andrew Herbert Date: Fri, 13 Oct 2023 18:27:06 +1100 Subject: [PATCH 02/12] Allow running PLLs to be powered down, as needed for low-power DORMANT mode --- rp2040-hal/src/pll.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index 786b60a5d..04496579d 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,15 @@ 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(); From be0f81a8b1066a7a2bf52aa05800b3e6f2c8a824 Mon Sep 17 00:00:00 2001 From: Andrew Herbert Date: Sun, 15 Oct 2023 19:25:17 +1100 Subject: [PATCH 03/12] No need to switch a glitchless clock back to its default source first when changing to a non-aux source (e.g. reference clock changing to xosc). This allows us to turn off rosc if we want to. --- rp2040-hal/src/clocks/macros.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rp2040-hal/src/clocks/macros.rs b/rp2040-hal/src/clocks/macros.rs index e387a5258..dd7aa0263 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 { From a564d2e28de5969e381ddb1e334b7eb6475064fd Mon Sep 17 00:00:00 2001 From: Andrew Herbert Date: Sun, 15 Oct 2023 19:27:45 +1100 Subject: [PATCH 04/12] DORMANT sleep example --- rp2040-hal/Cargo.toml | 7 +- rp2040-hal/examples/dormant_sleep.rs | 262 +++++++++++++++++++++++++++ 2 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 rp2040-hal/examples/dormant_sleep.rs diff --git a/rp2040-hal/Cargo.toml b/rp2040-hal/Cargo.toml index 325455935..20a490444 100644 --- a/rp2040-hal/Cargo.toml +++ b/rp2040-hal/Cargo.toml @@ -119,6 +119,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"] @@ -197,4 +202,4 @@ required-features = ["critical-section-impl"] [[example]] name = "gpio_dyn_pin_array" -required-features = ["critical-section-impl"] \ No newline at end of file +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..746fa309d --- /dev/null +++ b/rp2040-hal/examples/dormant_sleep.rs @@ -0,0 +1,262 @@ +//! # 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. +//! +//! 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::v2::ToggleableOutputPin; + +use fugit::RateExtU32; + +use hal::{ + clocks::{ClockError, ClocksManager, InitError, StoppableClock}, + gpio, + gpio::{Interrupt::EdgeLow, Pins}, + pac, + pac::{interrupt, CLOCKS, PLL_SYS, PLL_USB, RESETS, ROSC, XOSC}, + pll::{Disabled, Locked, PhaseLockedLoop, common_configs::{PLL_SYS_125MHZ, PLL_USB_48MHZ}, setup_pll_blocking, start_pll_blocking}, + sio::Sio, + watchdog::Watchdog, + xosc::{CrystalOscillator, Dormant, setup_xosc_blocking, Stable}, + rosc::RingOscillator, + Clock, +}; + +use nb::block; + +/// 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.ROSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // 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 dormant_xosc = unsafe { xosc.dormant() }; + + match restart_clocks_and_plls(&mut clocks, dormant_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(); + let Some(ref mut trigger_pin) = global_devices.deref_mut() else { + panic!() + }; + + trigger_pin.clear_interrupt(EdgeLow); + }); + } 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(); + cortex_m::asm::delay(LED_PULSE_CYCLES + (i % 2) * 9 * LED_PULSE_CYCLES); // 1:10 duty cycle + } +} + +/// 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, + rosc_dev: ROSC, + clocks_dev: CLOCKS, + pll_sys_dev: PLL_SYS, + pll_usb_dev: PLL_USB, + resets: &mut RESETS, + watchdog: &mut Watchdog, +) -> Result<(ClocksManager, CrystalOscillator, PhaseLockedLoop, PhaseLockedLoop), InitError> { + 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)?; + + let rosc = RingOscillator::new(rosc_dev).initialize(); + rosc.disable(); // disable ring oscillator to maximise power savings + + 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, + dormant_xosc: CrystalOscillator, + disabled_pll_sys: PhaseLockedLoop, + disabled_pll_usb: PhaseLockedLoop, + resets: &mut RESETS, +) -> Result<(CrystalOscillator, PhaseLockedLoop, PhaseLockedLoop), ClockError> { + // Wait for the restarted XOSC to stabilise + let initialized_xosc = dormant_xosc.get_initialized(); + let stable_xosc_token = block!(initialized_xosc.await_stabilization()).unwrap(); + let xosc = initialized_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(); + let Some(ref mut button_pin) = global_devices.deref_mut() else { + panic!() + }; + + // 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); + } + }); +} + +// End of file From 0e4593bf652a645f5fd448ca5c845e07ff512735 Mon Sep 17 00:00:00 2001 From: Andrew Herbert Date: Sun, 15 Oct 2023 19:33:36 +1100 Subject: [PATCH 05/12] format to rust standard --- rp2040-hal/examples/dormant_sleep.rs | 73 +++++++++++++++++++++------- rp2040-hal/src/pll.rs | 5 +- 2 files changed, 59 insertions(+), 19 deletions(-) diff --git a/rp2040-hal/examples/dormant_sleep.rs b/rp2040-hal/examples/dormant_sleep.rs index 746fa309d..9aef7d31c 100644 --- a/rp2040-hal/examples/dormant_sleep.rs +++ b/rp2040-hal/examples/dormant_sleep.rs @@ -33,11 +33,14 @@ use hal::{ gpio::{Interrupt::EdgeLow, Pins}, pac, pac::{interrupt, CLOCKS, PLL_SYS, PLL_USB, RESETS, ROSC, XOSC}, - pll::{Disabled, Locked, PhaseLockedLoop, common_configs::{PLL_SYS_125MHZ, PLL_USB_48MHZ}, setup_pll_blocking, start_pll_blocking}, + 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::{CrystalOscillator, Dormant, setup_xosc_blocking, Stable}, - rosc::RingOscillator, + xosc::{setup_xosc_blocking, CrystalOscillator, Dormant, Stable}, Clock, }; @@ -78,8 +81,8 @@ fn main() -> ! { &mut pac.RESETS, &mut watchdog, ) - .ok() - .unwrap(); + .ok() + .unwrap(); // Set the pins to their default state let pins = Pins::new( @@ -110,17 +113,24 @@ fn main() -> ! { 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); + 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 dormant_xosc = unsafe { xosc.dormant() }; - match restart_clocks_and_plls(&mut clocks, dormant_xosc, disabled_pll_sys, disabled_pll_usb, &mut pac.RESETS) { + match restart_clocks_and_plls( + &mut clocks, + dormant_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!(); } @@ -150,9 +160,10 @@ fn main() -> ! { fn pulse(pin: &mut P, count: u32) { const LED_PULSE_CYCLES: u32 = 2_000_000; - for i in 0..count*2 { + for i in 0..count * 2 { let _ = pin.toggle(); - cortex_m::asm::delay(LED_PULSE_CYCLES + (i % 2) * 9 * LED_PULSE_CYCLES); // 1:10 duty cycle + // 1:10 duty cycle + cortex_m::asm::delay(LED_PULSE_CYCLES + (i % 2) * 9 * LED_PULSE_CYCLES); } } @@ -167,7 +178,15 @@ fn init_clocks_and_plls( pll_usb_dev: PLL_USB, resets: &mut RESETS, watchdog: &mut Watchdog, -) -> Result<(ClocksManager, CrystalOscillator, PhaseLockedLoop, PhaseLockedLoop), InitError> { +) -> Result< + ( + ClocksManager, + CrystalOscillator, + PhaseLockedLoop, + PhaseLockedLoop, + ), + InitError, +> { let xosc = setup_xosc_blocking(xosc_dev, xosc_crystal_freq.Hz()).unwrap(); // Configure watchdog tick generation to tick over every microsecond @@ -182,7 +201,7 @@ fn init_clocks_and_plls( &mut clocks, resets, ) - .map_err(InitError::PllError)?; + .map_err(InitError::PllError)?; let pll_usb = setup_pll_blocking( pll_usb_dev, xosc.operating_frequency(), @@ -190,7 +209,7 @@ fn init_clocks_and_plls( &mut clocks, resets, ) - .map_err(InitError::PllError)?; + .map_err(InitError::PllError)?; clocks .init_default(&xosc, &pll_sys, &pll_usb) @@ -209,15 +228,24 @@ fn prepare_clocks_and_plls_for_dormancy( clocks: &mut ClocksManager, pll_sys: PhaseLockedLoop, pll_usb: PhaseLockedLoop, -) -> (PhaseLockedLoop, 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(); + 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()) } @@ -229,7 +257,14 @@ fn restart_clocks_and_plls( disabled_pll_sys: PhaseLockedLoop, disabled_pll_usb: PhaseLockedLoop, resets: &mut RESETS, -) -> Result<(CrystalOscillator, PhaseLockedLoop, PhaseLockedLoop), ClockError> { +) -> Result< + ( + CrystalOscillator, + PhaseLockedLoop, + PhaseLockedLoop, + ), + ClockError, +> { // Wait for the restarted XOSC to stabilise let initialized_xosc = dormant_xosc.get_initialized(); let stable_xosc_token = block!(initialized_xosc.await_stabilization()).unwrap(); @@ -238,7 +273,9 @@ fn restart_clocks_and_plls( 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)) + clocks + .init_default(&xosc, &pll_sys, &pll_usb) + .map(|_| (xosc, pll_sys, pll_usb)) } #[interrupt] diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index 04496579d..5ae209186 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -312,7 +312,10 @@ pub fn setup_pll_blocking( nb::block!(clocks.reference_clock.reset_source_await()).unwrap(); - start_pll_blocking(PhaseLockedLoop::new(dev, xosc_frequency.convert(), config)?, resets) + start_pll_blocking( + PhaseLockedLoop::new(dev, xosc_frequency.convert(), config)?, + resets, + ) } /// Blocking helper method to (re)start a PLL. From 57c52db8e971f059be67838a1706b26ad833aa49 Mon Sep 17 00:00:00 2001 From: Andrew Herbert Date: Fri, 20 Oct 2023 18:34:31 +1100 Subject: [PATCH 06/12] Incorporated feedback from @ithinuel --- rp2040-hal/examples/dormant_sleep.rs | 13 ++++---- rp2040-hal/src/rosc.rs | 25 +++------------- rp2040-hal/src/xosc.rs | 45 ++++++++++------------------ 3 files changed, 26 insertions(+), 57 deletions(-) diff --git a/rp2040-hal/examples/dormant_sleep.rs b/rp2040-hal/examples/dormant_sleep.rs index 9aef7d31c..aa07574d4 100644 --- a/rp2040-hal/examples/dormant_sleep.rs +++ b/rp2040-hal/examples/dormant_sleep.rs @@ -40,7 +40,7 @@ use hal::{ rosc::RingOscillator, sio::Sio, watchdog::Watchdog, - xosc::{setup_xosc_blocking, CrystalOscillator, Dormant, Stable}, + xosc::{setup_xosc_blocking, CrystalOscillator, Unstable, Stable}, Clock, }; @@ -117,11 +117,11 @@ fn main() -> ! { 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 dormant_xosc = unsafe { xosc.dormant() }; + let unstable_xosc = unsafe { xosc.dormant() }; match restart_clocks_and_plls( &mut clocks, - dormant_xosc, + unstable_xosc, disabled_pll_sys, disabled_pll_usb, &mut pac.RESETS, @@ -253,7 +253,7 @@ fn prepare_clocks_and_plls_for_dormancy( /// Restart the PLLs and start/reconfigure the clocks back to how they were before going dormant. fn restart_clocks_and_plls( clocks: &mut ClocksManager, - dormant_xosc: CrystalOscillator, + unstable_xosc: CrystalOscillator, disabled_pll_sys: PhaseLockedLoop, disabled_pll_usb: PhaseLockedLoop, resets: &mut RESETS, @@ -266,9 +266,8 @@ fn restart_clocks_and_plls( ClockError, > { // Wait for the restarted XOSC to stabilise - let initialized_xosc = dormant_xosc.get_initialized(); - let stable_xosc_token = block!(initialized_xosc.await_stabilization()).unwrap(); - let xosc = initialized_xosc.get_stable(stable_xosc_token); + 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(); diff --git a/rp2040-hal/src/rosc.rs b/rp2040-hal/src/rosc.rs index 6ad8ed1cf..abcc3efc1 100644 --- a/rp2040-hal/src/rosc.rs +++ b/rp2040-hal/src/rosc.rs @@ -16,17 +16,10 @@ pub struct Enabled { freq_hz: HertzU32, } -/// ROSC is in dormant mode (see Chapter 2, Section 17, §7) -pub struct Dormant { - freq_hz: HertzU32, -} - 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 { @@ -99,30 +92,20 @@ 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. /// 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)); - - let freq_hz = self.state.freq_hz; - self.transition(Dormant { freq_hz }) - } -} - -impl RingOscillator { - /// After waking up from the DORMANT state, ROSC restarts in approximately 1µs. - /// See Chapter 2, Section 16, §5) for details. - pub fn get_enabled(self) -> RingOscillator { - let freq_hz = self.state.freq_hz; - self.transition(Enabled { freq_hz }) } } diff --git a/rp2040-hal/src/xosc.rs b/rp2040-hal/src/xosc.rs index 2c7607de6..a79243bf3 100644 --- a/rp2040-hal/src/xosc.rs +++ b/rp2040-hal/src/xosc.rs @@ -15,29 +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 { - freq_hz: HertzU32, -} - 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)] @@ -93,7 +86,7 @@ impl CrystalOscillator { } /// Initializes the XOSC : frequency range is set, startup delay is calculated and set. - pub fn initialize(self, frequency: HertzU32) -> Result, Error> { + pub fn initialize(self, frequency: HertzU32) -> 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 @@ -128,17 +121,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); @@ -147,7 +140,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 }) @@ -170,14 +163,16 @@ 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. /// 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; @@ -187,14 +182,6 @@ impl CrystalOscillator { }); let freq_hz = self.state.freq_hz; - self.transition(Dormant { freq_hz }) - } -} - -impl CrystalOscillator { - /// After waking up from the DORMANT state, XOSC is initialized but needs to re-stabilise. - pub fn get_initialized(self) -> CrystalOscillator { - let freq_hz = self.state.freq_hz; - self.transition(Initialized { freq_hz }) + self.transition(Unstable { freq_hz }) } } From 1f92a95bdcb534f703cb7434410443e6e7be029a Mon Sep 17 00:00:00 2001 From: Andrew Herbert Date: Wed, 15 Nov 2023 20:51:41 +1100 Subject: [PATCH 07/12] format example to standard --- rp2040-hal/examples/dormant_sleep.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rp2040-hal/examples/dormant_sleep.rs b/rp2040-hal/examples/dormant_sleep.rs index aa07574d4..84187faa1 100644 --- a/rp2040-hal/examples/dormant_sleep.rs +++ b/rp2040-hal/examples/dormant_sleep.rs @@ -40,7 +40,7 @@ use hal::{ rosc::RingOscillator, sio::Sio, watchdog::Watchdog, - xosc::{setup_xosc_blocking, CrystalOscillator, Unstable, Stable}, + xosc::{setup_xosc_blocking, CrystalOscillator, Stable, Unstable}, Clock, }; From f823a81aa8a54da70ac065436e4af684ec0b5045 Mon Sep 17 00:00:00 2001 From: Andrew Herbert Date: Wed, 15 Nov 2023 21:33:39 +1100 Subject: [PATCH 08/12] fix clippy warnings --- rp2040-hal/examples/dormant_sleep.rs | 71 +++++++++++++--------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/rp2040-hal/examples/dormant_sleep.rs b/rp2040-hal/examples/dormant_sleep.rs index 84187faa1..9c295f3a3 100644 --- a/rp2040-hal/examples/dormant_sleep.rs +++ b/rp2040-hal/examples/dormant_sleep.rs @@ -32,7 +32,7 @@ use hal::{ gpio, gpio::{Interrupt::EdgeLow, Pins}, pac, - pac::{interrupt, CLOCKS, PLL_SYS, PLL_USB, RESETS, ROSC, XOSC}, + 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, @@ -46,6 +46,19 @@ use hal::{ use nb::block; +type ClocksAndPlls = ( + ClocksManager, + CrystalOscillator, + PhaseLockedLoop, + PhaseLockedLoop, +); + +type RestartedClockAndPlls = ( + CrystalOscillator, + PhaseLockedLoop, + PhaseLockedLoop, +); + /// The button input. type ButtonPin = gpio::Pin; @@ -74,7 +87,6 @@ fn main() -> ! { let (mut clocks, mut xosc, mut pll_sys, mut pll_usb) = init_clocks_and_plls( XTAL_FREQ_HZ, pac.XOSC, - pac.ROSC, pac.CLOCKS, pac.PLL_SYS, pac.PLL_USB, @@ -84,6 +96,10 @@ fn main() -> ! { .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, @@ -139,11 +155,11 @@ fn main() -> ! { // Clear dormant wake interrupt status to enable wake next time critical_section::with(|cs| { let mut global_devices = GLOBAL_DEVICES.borrow(cs).borrow_mut(); - let Some(ref mut trigger_pin) = global_devices.deref_mut() else { - panic!() + if let Some(ref mut trigger_pin) = global_devices.deref_mut() { + trigger_pin.clear_interrupt(EdgeLow); + } else { + panic!(); }; - - trigger_pin.clear_interrupt(EdgeLow); }); } else { pulse(&mut led_pin, 2); @@ -172,21 +188,12 @@ fn pulse(pin: &mut P, count: u32) { fn init_clocks_and_plls( xosc_crystal_freq: u32, xosc_dev: XOSC, - rosc_dev: ROSC, clocks_dev: CLOCKS, pll_sys_dev: PLL_SYS, pll_usb_dev: PLL_USB, resets: &mut RESETS, watchdog: &mut Watchdog, -) -> Result< - ( - ClocksManager, - CrystalOscillator, - PhaseLockedLoop, - PhaseLockedLoop, - ), - InitError, -> { +) -> Result { let xosc = setup_xosc_blocking(xosc_dev, xosc_crystal_freq.Hz()).unwrap(); // Configure watchdog tick generation to tick over every microsecond @@ -215,9 +222,6 @@ fn init_clocks_and_plls( .init_default(&xosc, &pll_sys, &pll_usb) .map_err(InitError::ClockError)?; - let rosc = RingOscillator::new(rosc_dev).initialize(); - rosc.disable(); // disable ring oscillator to maximise power savings - Ok((clocks, xosc, pll_sys, pll_usb)) } @@ -257,14 +261,7 @@ fn restart_clocks_and_plls( disabled_pll_sys: PhaseLockedLoop, disabled_pll_usb: PhaseLockedLoop, resets: &mut RESETS, -) -> Result< - ( - CrystalOscillator, - PhaseLockedLoop, - PhaseLockedLoop, - ), - ClockError, -> { +) -> 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); @@ -281,17 +278,17 @@ fn restart_clocks_and_plls( fn IO_IRQ_BANK0() { critical_section::with(|cs| { let mut global_devices = GLOBAL_DEVICES.borrow(cs).borrow_mut(); - let Some(ref mut button_pin) = global_devices.deref_mut() else { - panic!() + 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!(); }; - - // 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); - } }); } From 01da9fd7c1e8676c75f400de9f41c5597f2e4838 Mon Sep 17 00:00:00 2001 From: Andrew Herbert Date: Wed, 15 Nov 2023 21:35:12 +1100 Subject: [PATCH 09/12] additional safety comments for rosc/xosc dormant() - thanks @jannic --- rp2040-hal/src/rosc.rs | 1 + rp2040-hal/src/xosc.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/rp2040-hal/src/rosc.rs b/rp2040-hal/src/rosc.rs index abcc3efc1..7cc99ea8d 100644 --- a/rp2040-hal/src/rosc.rs +++ b/rp2040-hal/src/rosc.rs @@ -100,6 +100,7 @@ impl RingOscillator { /// 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) { //taken from the C SDK diff --git a/rp2040-hal/src/xosc.rs b/rp2040-hal/src/xosc.rs index a79243bf3..94254fe62 100644 --- a/rp2040-hal/src/xosc.rs +++ b/rp2040-hal/src/xosc.rs @@ -171,6 +171,7 @@ impl CrystalOscillator { /// 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 { //taken from the C SDK From 020c5b1bf6068a7ee7b533bd882694d2bad81a9d Mon Sep 17 00:00:00 2001 From: Andrew H Date: Thu, 16 Nov 2023 21:40:58 +1100 Subject: [PATCH 10/12] Apply suggestions from code review Co-authored-by: Jan Niehusmann --- rp2040-hal/examples/dormant_sleep.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rp2040-hal/examples/dormant_sleep.rs b/rp2040-hal/examples/dormant_sleep.rs index 9c295f3a3..bb3ae2c14 100644 --- a/rp2040-hal/examples/dormant_sleep.rs +++ b/rp2040-hal/examples/dormant_sleep.rs @@ -7,6 +7,9 @@ //! 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. @@ -60,7 +63,7 @@ type RestartedClockAndPlls = ( ); /// The button input. -type ButtonPin = gpio::Pin; +type ButtonPin = gpio::Pin; /// Devices shared between the foreground code and interrupt handlers. static GLOBAL_DEVICES: Mutex>> = Mutex::new(RefCell::new(None)); From 1bde6fd11987966b99b7e5afabd81f9e60f7ee7f Mon Sep 17 00:00:00 2001 From: Andrew Herbert Date: Thu, 16 Nov 2023 21:52:06 +1100 Subject: [PATCH 11/12] fix greedy rosc `dormant()` - thanks @ithinuel --- rp2040-hal/src/rosc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rp2040-hal/src/rosc.rs b/rp2040-hal/src/rosc.rs index 7cc99ea8d..cbdceb4d5 100644 --- a/rp2040-hal/src/rosc.rs +++ b/rp2040-hal/src/rosc.rs @@ -102,7 +102,7 @@ impl RingOscillator { /// 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) { + pub unsafe fn dormant(&self) { //taken from the C SDK const ROSC_DORMANT_VALUE: u32 = 0x636f6d61; From 0b274b65ffdf68f6f22b2b5e525d527356ded988 Mon Sep 17 00:00:00 2001 From: Andrew Herbert Date: Sat, 2 Mar 2024 18:59:14 +1100 Subject: [PATCH 12/12] update for recent HAL API changes --- rp2040-hal/examples/dormant_sleep.rs | 4 ++-- rp2040-hal/src/pll.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rp2040-hal/examples/dormant_sleep.rs b/rp2040-hal/examples/dormant_sleep.rs index bb3ae2c14..ae3c709a2 100644 --- a/rp2040-hal/examples/dormant_sleep.rs +++ b/rp2040-hal/examples/dormant_sleep.rs @@ -26,7 +26,7 @@ use core::{cell::RefCell, ops::DerefMut}; use critical_section::Mutex; -use embedded_hal::digital::v2::ToggleableOutputPin; +use embedded_hal::digital::StatefulOutputPin; use fugit::RateExtU32; @@ -176,7 +176,7 @@ fn main() -> ! { } /// Pulse an LED-connected pin the specified number of times. -fn pulse(pin: &mut P, count: u32) { +fn pulse(pin: &mut P, count: u32) { const LED_PULSE_CYCLES: u32 = 2_000_000; for i in 0..count * 2 { diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index 7ca25c2b2..dbff55a80 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -281,13 +281,13 @@ impl PhaseLockedLoop { /// 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 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.device.pwr().reset(); + self.device.fbdiv_int().reset(); self.transition(Disabled { refdiv,