diff --git a/examples/deep_sleep.rs b/examples/deep_sleep.rs new file mode 100644 index 00000000000..1710022e738 --- /dev/null +++ b/examples/deep_sleep.rs @@ -0,0 +1,56 @@ +//! Tests deep sleep +//! +//! Enables multiple deep sleep wakeup sources and then enter deep sleep. +//! There is no loop here, since the program will not continue after deep sleep. +//! For ESP32c3, only timer wakeup is supported. +//! The program starts by printing reset and wakeup reason, since the deep +//! sleep effectively ends the program, this is how we get information about +//! the previous run. + +use core::time::Duration; +use esp_idf_hal::peripherals::Peripherals; +use esp_idf_hal::reset::{ResetReason, WakeupReason}; +use esp_idf_hal::sleep::*; + +fn print_wakeup_result() { + let reset_reason = ResetReason::get(); + let wakeup_reason = WakeupReason::get(); + println!( + "reset after {:?} wakeup due to {:?}", + reset_reason, wakeup_reason + ); +} + +fn main() -> anyhow::Result<()> { + esp_idf_sys::link_patches(); + + print_wakeup_result(); + + #[cfg(any(esp32, esp32s2, esp32s3))] + let peripherals = Peripherals::take().unwrap(); + + // RTC wakeup definitions + #[cfg(esp32)] + let rtc_pins = [ + RtcWakeupPin::new(peripherals.pins.gpio4.into(), false, true), + RtcWakeupPin::new(peripherals.pins.gpio27.into(), false, true), + ]; + #[cfg(any(esp32s2, esp32s3))] + let rtc_pins = [ + RtcWakeupPin::new(peripherals.pins.gpio1.into(), false, true), + RtcWakeupPin::new(peripherals.pins.gpio2.into(), false, true), + ]; + #[cfg(any(esp32, esp32s2, esp32s3))] + let rtc_wakeup = Some(RtcWakeup::new(&rtc_pins, RtcWakeLevel::AnyHigh)); + + let dsleep = DeepSleep { + timer: Some(TimerWakeup::new(Duration::from_secs(5))), + #[cfg(any(esp32, esp32s2, esp32s3))] + rtc: rtc_wakeup, + ..Default::default() + }; + + println!("Deep sleep with: {:?}", dsleep); + dsleep.prepare()?; + dsleep.sleep(); +} diff --git a/examples/light_sleep.rs b/examples/light_sleep.rs new file mode 100644 index 00000000000..414e5cf5c68 --- /dev/null +++ b/examples/light_sleep.rs @@ -0,0 +1,140 @@ +//! Tests light sleep +//! +//! Enables multiple light sleep wakeup sources and do sleeps in a loop. +//! Prints wakeup reason and sleep time on wakeup. + +use core::time::Duration; +use esp_idf_hal::gpio::{self, AnyInputPin, PinDriver}; +use esp_idf_hal::peripherals::Peripherals; +use esp_idf_hal::prelude::*; +use esp_idf_hal::reset::WakeupReason; +use esp_idf_hal::sleep::*; +use esp_idf_hal::uart::config::Config; +use esp_idf_hal::uart::UartDriver; +use std::thread; +use std::time::Instant; + +use crate::gpio::Level; + +fn print_wakeup_result(time_before: Instant) { + let time_after = Instant::now(); + + let wakeup_reason = WakeupReason::get(); + println!( + "wake up from light sleep due to {:?} which lasted for {:?}", + wakeup_reason, + time_after - time_before + ); +} + +fn main() -> anyhow::Result<()> { + esp_idf_sys::link_patches(); + + // run in a thread with increased stack size to prevent overflow + let builder = std::thread::Builder::new().stack_size(8 * 1024); + let th = builder.spawn(move || -> anyhow::Result<()> { + let peripherals = Peripherals::take().unwrap(); + + // RTC wakeup definitions + #[cfg(esp32)] + let rtc_pins = [ + RtcWakeupPin::new(peripherals.pins.gpio26.into(), false, true), + RtcWakeupPin::new(peripherals.pins.gpio27.into(), false, true), + ]; + #[cfg(any(esp32s2, esp32s3))] + let rtc_pins = [ + RtcWakeupPin::new(peripherals.pins.gpio1.into(), false, true), + RtcWakeupPin::new(peripherals.pins.gpio2.into(), false, true), + ]; + #[cfg(any(esp32, esp32s2, esp32s3))] + let rtc_wakeup = Some(RtcWakeup::new(&rtc_pins, RtcWakeLevel::AnyHigh)); + + // GPIO wakeup definitions + #[cfg(esp32)] + let gpio_pin1 = PinDriver::input(AnyInputPin::from(peripherals.pins.gpio16))?; + #[cfg(esp32)] + let gpio_pin2 = PinDriver::input(AnyInputPin::from(peripherals.pins.gpio17))?; + + #[cfg(any(esp32s2, esp32s3))] + let gpio_pin1 = PinDriver::input(AnyInputPin::from(peripherals.pins.gpio37))?; + #[cfg(any(esp32s2, esp32s3))] + let gpio_pin2 = PinDriver::input(AnyInputPin::from(peripherals.pins.gpio38))?; + + #[cfg(esp32c3)] + let gpio_pin1 = PinDriver::input(AnyInputPin::from(peripherals.pins.gpio6))?; + #[cfg(esp32c3)] + let gpio_pin2 = PinDriver::input(AnyInputPin::from(peripherals.pins.gpio7))?; + + let gpio_pins = [ + GpioWakeupPin::new(gpio_pin1, Level::High)?, + GpioWakeupPin::new(gpio_pin2, Level::High)?, + ]; + #[cfg(any(esp32, esp32c3, esp32s2, esp32s3))] + let gpio_wakeup = Some(GpioWakeup::new(&gpio_pins)); + #[cfg(not(any(esp32, esp32c3, esp32s2, esp32s3)))] + let gpio_wakeup: Option = None; + + // UART definitions + let config = Config::new().baudrate(Hertz(115_200)); + #[cfg(any(esp32))] + let uart = UartDriver::new( + peripherals.uart0, + peripherals.pins.gpio4, + peripherals.pins.gpio3, + Option::::None, + Option::::None, + &config, + )?; + #[cfg(any(esp32s2, esp32s3))] + let uart = UartDriver::new( + peripherals.uart0, + peripherals.pins.gpio43, + peripherals.pins.gpio44, + Option::::None, + Option::::None, + &config, + )?; + #[cfg(esp32c3)] + let uart = UartDriver::new( + peripherals.uart0, + peripherals.pins.gpio21, + peripherals.pins.gpio20, + Option::::None, + Option::::None, + &config, + )?; + #[cfg(any(esp32, esp32s2, esp32s3, esp32c3))] + let uart_wakeup = Some(UartWakeup::new(&uart, 3)); + #[cfg(not(any(esp32, esp32s2, esp32s3, esp32c3)))] + let uart_wakeup: Option = None; + + let lsleep = LightSleep { + timer: Some(TimerWakeup::new(Duration::from_secs(5))), + #[cfg(any(esp32, esp32s2, esp32s3))] + rtc: rtc_wakeup, + gpio: gpio_wakeup, + uart: uart_wakeup, + ..Default::default() + }; + + loop { + println!("Light sleep with: {:?}", lsleep); + // short sleep to flush stdout + thread::sleep(Duration::from_millis(60)); + + let time_before = Instant::now(); + let err = lsleep.sleep(); + match err { + Ok(_) => print_wakeup_result(time_before), + Err(e) => { + println!("failed to do sleep: {:?}", e); + thread::sleep(Duration::from_secs(1)); + } + } + println!("---"); + } + })?; + + let _err = th.join(); + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 9003942d575..588d541ad55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,6 +70,8 @@ pub mod rmt; #[cfg(not(feature = "riscv-ulp-hal"))] pub mod rom; #[cfg(not(feature = "riscv-ulp-hal"))] +pub mod sleep; +#[cfg(not(feature = "riscv-ulp-hal"))] pub mod spi; pub mod sys; #[cfg(not(feature = "riscv-ulp-hal"))] diff --git a/src/sleep.rs b/src/sleep.rs new file mode 100644 index 00000000000..bedb41549d6 --- /dev/null +++ b/src/sleep.rs @@ -0,0 +1,376 @@ +//! Driver for light and deep sleep. +//! +//! The ESP can be put into light or deep sleep. In light sleep, the CPU and peripherals +//! are still powered, but do not run (clock-gated). When woken up, the CPU and its +//! peripherals keep their states and the CPU continues running from the same place in the +//! code. +//! +//! Deep sleep is similar to light sleep, but the CPU is also powered down. Only RTC and +//! possibly ULP co processor is running. When woking up, the CPU does not keep its state, +//! and the program starts from the beginning. +//! +//! When enter either light or deep sleep, one or more wakeup sources must be enabled. +//! In this driver, the various wakeup sources are defined as different structs, which +//! can be added to a light or deep sleep struct. This struct can then be used to perform +//! a sleep operation with the given wakeup sources. +//! +//! The wakeup sources available depends on the ESP chip and type of sleep. The driver +//! intends to enforce these constraints at compile time where possible, i.e. if it builds +//! it should work. +//! + +use core::fmt; +#[cfg(not(any(esp32, esp32s2, esp32s3)))] +use core::marker::PhantomData; +use core::time::Duration; +use esp_idf_sys::*; + +#[cfg(any(esp32, esp32s2, esp32s3))] +use crate::gpio::Pin; +use crate::gpio::{AnyInputPin, Input, Level, PinDriver}; +use crate::uart::UartDriver; + +/// Will wake the CPU up after a given duration +#[derive(Debug)] +pub struct TimerWakeup { + pub duration: Duration, +} + +impl TimerWakeup { + pub fn new(duration: Duration) -> Self { + Self { duration } + } + + pub fn apply(&self) -> Result<(), EspError> { + esp!(unsafe { esp_sleep_enable_timer_wakeup(self.duration.as_micros() as u64) })?; + Ok(()) + } +} + +/// Will wake up the CPU based on changes in RTC GPIO pins. Is available for both light and +/// deep sleep. However, for light sleep, the GpioWakeup struct offers more flexibility. +/// Pullup and pulldown can be enabled for each pin. This settings will be applied when +/// deep sleep is enabled. +#[cfg(any(esp32, esp32s2, esp32s3))] +pub struct RtcWakeup<'a> { + pub pins: &'a [RtcWakeupPin], + pub wake_level: RtcWakeLevel, +} + +#[cfg(any(esp32, esp32s2, esp32s3))] +impl<'a> RtcWakeup<'a> { + pub fn new(pins: &'a [RtcWakeupPin], wake_level: RtcWakeLevel) -> Self { + Self { pins, wake_level } + } +} + +#[cfg(any(esp32, esp32s2, esp32s3))] +impl<'a> RtcWakeup<'a> { + fn mask(&self) -> u64 { + let mut m: u64 = 0; + for pin in self.pins.iter() { + m |= 1 << pin.pin.pin(); + } + m + } + + pub fn apply(&self) -> Result<(), EspError> { + #[cfg(any(esp32, esp32s3))] + for pin in self.pins { + if pin.pullup || pin.pulldown { + esp!(unsafe { + esp_sleep_pd_config( + esp_sleep_pd_domain_t_ESP_PD_DOMAIN_RTC_PERIPH, + esp_sleep_pd_option_t_ESP_PD_OPTION_ON, + ) + })?; + + if pin.pullup { + esp!(unsafe { rtc_gpio_pullup_en(pin.pin.pin()) })?; + } + if pin.pulldown { + esp!(unsafe { rtc_gpio_pulldown_en(pin.pin.pin()) })?; + } + } + } + + if !self.pins.is_empty() { + /* Do not use ext0, as ext1 with one pin does the same */ + esp!(unsafe { esp_sleep_enable_ext1_wakeup(self.mask(), self.wake_level.mode()) })?; + } + + Ok(()) + } +} + +#[cfg(any(esp32, esp32s2, esp32s3))] +impl<'a> fmt::Debug for RtcWakeup<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "RtcWakeup {{ pins: [")?; + + for pin in self.pins.iter() { + write!(f, "{:?} ", pin.pin.pin())?; + } + write!( + f, + "] mask: {:#x} wake_level: {:?} }}", + self.mask(), + self.wake_level + ) + } +} + +#[cfg(any(esp32, esp32s2, esp32s3))] +#[derive(Debug)] +pub enum RtcWakeLevel { + AllLow, + AnyHigh, +} + +#[cfg(any(esp32, esp32s2, esp32s3))] +impl RtcWakeLevel { + pub fn mode(&self) -> u32 { + match self { + RtcWakeLevel::AllLow => esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ALL_LOW, + RtcWakeLevel::AnyHigh => esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_HIGH, + } + } +} + +#[cfg(any(esp32, esp32s2, esp32s3))] +pub struct RtcWakeupPin { + pub pin: AnyInputPin, // this should use RtcInput somehow + pub pullup: bool, + pub pulldown: bool, +} + +#[cfg(any(esp32, esp32s2, esp32s3))] +impl RtcWakeupPin { + pub fn new(pin: AnyInputPin, pullup: bool, pulldown: bool) -> Self { + Self { + pin, + pullup, + pulldown, + } + } +} + +#[cfg(any(esp32, esp32s2, esp32s3))] +impl fmt::Debug for RtcWakeupPin { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "RtcWakeupPin {{ pin: {:?} pullup {:?} pulldown {:?}}}", + self.pin.pin(), + self.pullup, + self.pulldown + ) + } +} + +/// Will wake up the CPU based on changes in GPIO pins. Is only available for light sleep. +/// It takes PinDriver as input, which means that any required configurations on the pin +/// can be done before adding it to the GpioWakeup struct. +#[derive(Debug)] +pub struct GpioWakeup<'a> { + pub pins: &'a [GpioWakeupPin<'a>], +} + +impl<'a> GpioWakeup<'a> { + pub fn new(pins: &'a [GpioWakeupPin<'a>]) -> Self { + Self { pins } + } +} + +impl<'a> GpioWakeup<'a> { + pub fn apply(&self) -> Result<(), EspError> { + for pin in self.pins.iter() { + let intr_level = match pin.wake_level { + Level::Low => gpio_int_type_t_GPIO_INTR_LOW_LEVEL, + Level::High => gpio_int_type_t_GPIO_INTR_HIGH_LEVEL, + }; + esp!(unsafe { gpio_wakeup_enable(pin.pin.pin(), intr_level) })?; + } + esp!(unsafe { esp_sleep_enable_gpio_wakeup() })?; + Ok(()) + } +} + +pub struct GpioWakeupPin<'a> { + pub pin: PinDriver<'a, AnyInputPin, Input>, + pub wake_level: Level, +} + +impl<'a> GpioWakeupPin<'a> { + pub fn new( + pin: PinDriver<'a, AnyInputPin, Input>, + wake_level: Level, + ) -> Result { + Ok(Self { pin, wake_level }) + } +} + +impl<'a> fmt::Debug for GpioWakeupPin<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "GpioWakeupPin {{ pin: {:?} wake_level: {:?} }}", + self.pin.pin(), + self.wake_level + ) + } +} + +/// Will wake up the CPU when the number of positive edges higher than a threshold +/// is detected on the UART RX pin. Is only available for light sleep. +pub struct UartWakeup<'a> { + pub uart: &'a UartDriver<'a>, + pub threshold: i32, +} + +impl<'a> UartWakeup<'a> { + pub fn new(uart: &'a UartDriver<'a>, threshold: i32) -> Self { + Self { uart, threshold } + } + + pub fn apply(&self) -> Result<(), EspError> { + esp!(unsafe { uart_set_wakeup_threshold(self.uart.port(), self.threshold) })?; + esp!(unsafe { esp_sleep_enable_uart_wakeup(self.uart.port()) })?; + Ok(()) + } +} + +impl<'a> fmt::Debug for UartWakeup<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "UartWakeup") + } +} + +/// Will wake up the CPU when a touchpad is touched. +#[cfg(any(esp32, esp32s2, esp32s3))] +#[derive(Debug)] +pub struct TouchWakeup {} + +#[cfg(any(esp32, esp32s2, esp32s3))] +impl TouchWakeup { + pub fn apply(&self) -> Result<(), EspError> { + esp!(unsafe { esp_sleep_enable_touchpad_wakeup() })?; + Ok(()) + } +} + +/// Will let the ULP co-processor wake up the CPU. Requires that the ULP is started. +#[cfg(any(esp32, esp32s2, esp32s3))] +#[derive(Debug)] +pub struct UlpWakeup {} + +#[cfg(any(esp32, esp32s2, esp32s3))] +impl UlpWakeup { + pub fn apply(&self) -> Result<(), EspError> { + esp!(unsafe { esp_sleep_enable_ulp_wakeup() })?; + Ok(()) + } +} + +/// Struct for light sleep. Add wakeup sources to this struct, and then call sleep(). +#[derive(Debug, Default)] +pub struct LightSleep<'a> { + pub timer: Option, + #[cfg(any(esp32, esp32s2, esp32s3))] + pub rtc: Option>, + pub gpio: Option>, + pub uart: Option>, + #[cfg(any(esp32, esp32s2, esp32s3))] + pub touch: Option, + #[cfg(any(esp32, esp32s2, esp32s3))] + pub ulp: Option, +} + +impl<'a> LightSleep<'a> { + pub fn sleep(&self) -> Result<(), EspError> { + esp!(unsafe { esp_sleep_disable_wakeup_source(esp_sleep_source_t_ESP_SLEEP_WAKEUP_ALL) })?; + + if let Some(timer) = &self.timer { + timer.apply()?; + } + + #[cfg(any(esp32, esp32s2, esp32s3))] + if let Some(rtc) = &self.rtc { + rtc.apply()?; + } + + if let Some(gpio) = &self.gpio { + gpio.apply()?; + } + + if let Some(uart) = &self.uart { + uart.apply()?; + } + + #[cfg(any(esp32, esp32s2, esp32s3))] + if let Some(touch) = &self.touch { + touch.apply()?; + } + + #[cfg(any(esp32, esp32s2, esp32s3))] + if let Some(ulp) = &self.ulp { + ulp.apply()?; + } + + esp!(unsafe { esp_light_sleep_start() })?; + Ok(()) + } +} + +/// Struct for deep sleep. Add wakeup sources to this struct, and then call sleep(). +#[derive(Debug, Default)] +pub struct DeepSleep<'a> { + pub timer: Option, + #[cfg(any(esp32, esp32s2, esp32s3))] + pub rtc: Option>, + #[cfg(any(esp32, esp32s2, esp32s3))] + pub touch: Option, + #[cfg(any(esp32, esp32s2, esp32s3))] + pub ulp: Option, + #[cfg(not(any(esp32, esp32s2, esp32s3)))] + pub _p: PhantomData<&'a ()>, +} + +impl<'a> DeepSleep<'a> { + pub fn prepare(&self) -> Result<(), EspError> { + esp!(unsafe { esp_sleep_disable_wakeup_source(esp_sleep_source_t_ESP_SLEEP_WAKEUP_ALL) })?; + + if let Some(timer) = &self.timer { + timer.apply()?; + } + + #[cfg(any(esp32, esp32s2, esp32s3))] + if let Some(rtc) = &self.rtc { + rtc.apply()?; + } + + #[cfg(any(esp32, esp32s2, esp32s3))] + if let Some(touch) = &self.touch { + touch.apply()?; + } + + #[cfg(any(esp32, esp32s2, esp32s3))] + if let Some(ulp) = &self.ulp { + ulp.apply()?; + } + Ok(()) + } + + pub fn sleep(&self) -> ! { + unsafe { esp_deep_sleep_start() }; + + // Ensure that funciton never returns for esp idf version 5.x + #[cfg(esp_idf_version_major = "5")] + panic!(); + } + + pub fn prepare_and_sleep(&self) -> Result<(), EspError> { + self.prepare()?; + self.sleep(); + } +}