Skip to main content

esp_hal/rtc_cntl/sleep/
mod.rs

1//! # RTC Control Sleep Module
2//!
3//! ## Overview
4//! The `sleep` module allows configuring various wakeup sources and setting up
5//! the sleep behavior based on those sources. The supported wakeup sources
6//! include:
7//!    * `GPIO` pins - light sleep only
8//!    * timers
9//!    * `SDIO (Secure Digital Input/Output) - light sleep only`
10//!    * `MAC (Media Access Control)` wake - light sleep only
11//!    * `UART0` - light sleep only
12//!    * `UART1` - light sleep only
13//!    * `touch`
14//!    * `ULP (Ultra-Low Power)` wake
15//!    * `BT (Bluetooth) wake` - light sleep only
16
17use crate::{
18    gpio,
19    peripherals::LPWR,
20    rtc_cntl::{Rtc, WakeupSource},
21};
22
23#[cfg(soc_has_pmu)]
24mod pmu_common;
25
26#[cfg_attr(esp32, path = "esp32.rs")]
27#[cfg_attr(esp32s2, path = "esp32s2.rs")]
28#[cfg_attr(esp32s3, path = "esp32s3.rs")]
29#[cfg_attr(esp32c3, path = "esp32c3.rs")]
30#[cfg_attr(esp32c5, path = "esp32c5.rs")]
31#[cfg_attr(esp32c61, path = "esp32c61.rs")]
32#[cfg_attr(esp32c6, path = "esp32c6.rs")]
33#[cfg_attr(esp32c2, path = "esp32c2.rs")]
34#[cfg_attr(esp32h2, path = "esp32h2.rs")]
35#[cfg_attr(esp32p4, path = "esp32p4.rs")]
36mod sleep_impl;
37pub use sleep_impl::*;
38
39#[cfg(sleep_has_wakeup_source_timer)]
40mod timer;
41
42mod wakeup;
43pub(crate) use wakeup::*;
44
45/// Prepares the sleep hardware, and clears the wakeup sources of the previous run.
46///
47/// The wakeup-enable mask survives a deep-sleep wake, so here it still holds the request of the run
48/// that went to sleep. Two steps need the mask, in this order. First, the code releases the pads
49/// that the previous run armed. Then it clears the mask, because a program starts with no wakeup
50/// sources, and the drivers of the new run build the mask again.
51pub(crate) fn init(rtc: &Rtc<'_>) {
52    // First, because the pads that ended a deep sleep are readable only until the code below
53    // changes a path.
54    gpio::wakeup::record_wakeup();
55
56    // Release the pads after a deep-sleep wake only, and only if the previous
57    // run armed an IO wake source.
58    if super::reset_reason(crate::system::Cpu::ProCpu) == Some(super::SocResetReason::CoreDeepSleep)
59        && gpio::wakeup::wake_enabled()
60    {
61        gpio::wakeup::wake_io_reset();
62    }
63
64    RtcSleepConfig::base_settings(rtc);
65
66    set_mask(0);
67}
68
69/// Low-power management.
70///
71/// The sleep calls do not take the wakeup sources that end the sleep. Each driver enables the
72/// source that it owns, and the hardware wakeup-enable mask keeps that request until the driver
73/// clears it. The mask keeps it through a light sleep, and through a deep-sleep wake. A sleep call
74/// reads the mask back, and calculates the rest of the configuration from it.
75#[instability::unstable]
76pub struct LowPower<'d> {
77    _inner: LPWR<'d>,
78}
79
80impl<'d> LowPower<'d> {
81    /// Creates a new `LowPower` driver.
82    pub fn new(lpwr: LPWR<'d>) -> Self {
83        Self { _inner: lpwr }
84    }
85
86    /// Arms the sleep alarm for `deadline`, and enables the timer wakeup source.
87    ///
88    /// The deadline is absolute, so the time between this call and the sleep does not make the
89    /// sleep shorter. The deadline is a standing request. The wake that it causes does not
90    /// disarm it, a later call replaces it, and [`Self::clear_wakeup_deadline`] removes it.
91    ///
92    /// A deadline in the past ends a light sleep immediately, and makes [`Self::sleep_deep`] panic.
93    #[cfg(sleep_has_wakeup_source_timer)]
94    pub fn set_wakeup_deadline(&mut self, deadline: crate::time::Instant) {
95        timer::set_deadline(deadline);
96    }
97
98    /// Disarms the sleep alarm, and disables the timer wakeup source.
99    #[cfg(sleep_has_wakeup_source_timer)]
100    pub fn clear_wakeup_deadline(&mut self) {
101        timer::clear_deadline();
102    }
103
104    /// Enters deep sleep, and does not return.
105    ///
106    /// In deep sleep the CPUs, most of the RAM, and all digital peripherals that are clocked from
107    /// APB_CLK are powered off. The wake resets the chip, so use the
108    /// [`#[esp_hal::ram(persistent)]`][procmacros::ram] attribute to keep a variable through the
109    /// sleep.
110    ///
111    /// The hardware cannot reject this sleep, because the function cannot return to report the
112    /// rejection. Use [`Self::sleep_deep_with_rejection`] for that.
113    ///
114    /// # Panics
115    ///
116    /// Panics if no wakeup source is enabled, because then nothing can end the sleep. Panics also
117    /// if the armed wakeup deadline is too near for the sleep transition to catch it. In both
118    /// cases the chip never wakes again, and it gives no report of the cause.
119    #[cfg(sleep_deep_sleep)]
120    pub fn sleep_deep(&mut self, config: RtcSleepConfig) -> ! {
121        #[cfg(sleep_has_wakeup_source_timer)]
122        if enabled_sources().contains(WakeupSource::Timer) {
123            assert!(
124                !timer::deadline_missed(),
125                "the wakeup deadline is too near to be caught by the sleep transition"
126            );
127        }
128
129        self.sleep(config, SleepKind::Deep, false);
130
131        unreachable!("deep sleep without rejection cannot return")
132    }
133
134    /// Enters deep sleep, and returns only if the hardware rejects the request.
135    ///
136    /// The hardware rejects a sleep if one of its wakeup sources is already asserted. Without the
137    /// rejection, the chip sleeps through the event that the caller wants to wake on. The return of
138    /// this function is the complete report, so it gives no other result.
139    ///
140    /// A rejected request returns the wake pads to their drivers, but it cannot return every pad.
141    /// Sleep entry disconnects the pads that no hold keeps, on the chips that need that step to
142    /// reach the deep-sleep current, and it cannot know their earlier configuration. Configure
143    /// those pads again if this function returns. ESP-IDF has the same limit in
144    /// `esp_deep_sleep_try_to_start`.
145    ///
146    /// # Panics
147    ///
148    /// Panics if no wakeup source is enabled.
149    #[cfg(sleep_deep_sleep)]
150    pub fn sleep_deep_with_rejection(&mut self, config: RtcSleepConfig) {
151        self.sleep(config, SleepKind::Deep, true);
152    }
153
154    /// Enters light sleep, and returns when a wakeup source ends it.
155    ///
156    /// Light sleep keeps the state of the digital domain, so the program continues at the same
157    /// place.
158    ///
159    /// The function also returns immediately, without a sleep, if no wakeup source is enabled, or
160    /// if the hardware rejects the request because a wakeup source is already asserted. It
161    /// reports neither case. For the caller, a refused sleep, a rejected sleep and a very short
162    /// sleep have the same result.
163    #[cfg(sleep_light_sleep)]
164    pub fn sleep_light(&mut self, config: RtcSleepConfig) {
165        self.sleep(config, SleepKind::Light, true);
166    }
167
168    /// Calculates the sleep configuration from the wakeup-enable mask, and enters the sleep.
169    #[cfg(sleep_driver_supported)]
170    #[crate::ram]
171    fn sleep(&mut self, config: RtcSleepConfig, kind: SleepKind, allow_reject: bool) {
172        let rtc = Rtc::new(unsafe { crate::peripherals::RTC_TIMER::steal() });
173
174        let mut config = config;
175        config.set_sleep_kind(kind);
176
177        // The hooks run before `apply`, so that a request to keep a power domain powered reaches
178        // the hardware. They also run before the last read of the mask, because a hook can
179        // enable another source. The GPIO hook does this while it allocates its pins to the
180        // paths.
181        run_entry_hooks(&mut config);
182
183        config.apply();
184
185        // A sleep with no wakeup source never ends. No counter overflow ends it either.
186        let wakeup_mask = mask();
187        if wakeup_mask == 0 {
188            match kind {
189                // A refused sleep gives the same result as a rejected sleep, and light sleep does
190                // not report that case either.
191                SleepKind::Light => return,
192                SleepKind::Deep => {
193                    panic!("no wakeup source is enabled, so nothing could end the sleep")
194                }
195            }
196        }
197
198        let reject_mask = if allow_reject { reject_mask() } else { 0 };
199
200        sleep_uart_prepare();
201
202        // Last, because this step takes the pads away from the peripherals that drove them. The
203        // wakeup sources have their holds now, and no later step needs a pad.
204        #[cfg(sleep_deep_sleep_needs_gpio_isolation)]
205        if kind == SleepKind::Deep {
206            gpio::wakeup::isolate_pads_for_deep_sleep();
207        }
208
209        // Latch the systimer value *before* sleeping. The systimer keeps running during
210        // the sleep enter/exit sequences, so we must not advance from the post-wake
211        // value (that would count the enter/exit time twice). Instead we set an absolute
212        // target of `before + slept`, measured by the always-running LP timer.
213        let before_ticks = crate::time::implem::raw_counter();
214        let before = rtc.time_since_boot_raw();
215
216        let _uart0_sclk_guard = crate::system::ensure_uart0_sclk_enabled();
217        let rejected = {
218            // A chip can keep a guard for the length of the sleep, to restore what sleep entry
219            // changed for the sleep only. The guard must therefore outlive the wait below.
220            #[allow(clippy::let_unit_value)]
221            let _sleep_guard = config.start_sleep(wakeup_mask, reject_mask);
222            let rejected = wait_for_sleep_result();
223
224            if config.is_deep_sleep() && !rejected {
225                // The chip is entering deep sleep, and the wake resets it. Because RTC is in a
226                // slower clock domain than the CPU, the power-down can take several CPU cycles.
227                loop {
228                    core::hint::spin_loop();
229                }
230            }
231
232            rejected
233        };
234
235        config.finish_sleep();
236
237        let after = rtc.time_since_boot_raw();
238
239        let slept_us = crate::clock::rtc_ticks_to_us(after.wrapping_sub(before));
240        let slept_ticks = crate::time::implem::us_to_ticks(slept_us);
241
242        unsafe { crate::time::implem::update_counter(before_ticks + slept_ticks) };
243        sleep_uart_resume();
244
245        run_exit_hooks();
246
247        // Unlike deep sleep, light sleep does not reset the chip, so `wakeup_cause` cannot rely on
248        // the reset reason to tell whether a wakeup occurred. A rejected request is not a wakeup,
249        // and it must not name a wakeup source.
250        // https://github.com/espressif/esp-idf/blob/a45d713b03fd96d8805d1cc116f02a4415b360c7/components/esp_hw_support/sleep_modes.c#L2158
251        if !config.is_deep_sleep() && !rejected {
252            super::LIGHT_SLEEP_WAKEUP.store(true, portable_atomic::Ordering::Relaxed);
253        }
254
255        // Last, because this call reads the wakeup cause, and after a light sleep the cause is
256        // available only after the line above.
257        gpio::wakeup::record_wakeup();
258    }
259}
260
261/// Waits for the hardware to report the result of the sleep request, and returns whether the
262/// hardware rejected the request.
263///
264/// A deep sleep powers the CPU down inside this loop, and a light sleep stops the CPU here until a
265/// wakeup source ends the sleep. A rejected request does neither, so the reject interrupt is the
266/// only report of that case. ESP-IDF waits in the same place, in `rtc_sleep_start` and in
267/// `pmu_sleep_start`.
268#[cfg(sleep_driver_supported)]
269fn wait_for_sleep_result() -> bool {
270    loop {
271        cfg_select! {
272            soc_has_pmu => {
273                let int_raw = crate::peripherals::PMU::regs().int_raw().read();
274                if int_raw.soc_wakeup().bit_is_set() || int_raw.soc_sleep_reject().bit_is_set() {
275                    return int_raw.soc_sleep_reject().bit_is_set();
276                }
277            }
278            _ => {
279                let int_raw = LPWR::regs().int_raw().read();
280                if int_raw.slp_wakeup().bit_is_set() || int_raw.slp_reject().bit_is_set() {
281                    return int_raw.slp_reject().bit_is_set();
282                }
283            }
284        }
285    }
286}
287
288#[cfg(sleep_driver_supported)]
289fn sleep_uart_prepare() {
290    use crate::uart::Instance;
291    for_each_uart! {
292        ($id:literal, $inst:ident, $peri:ident, $rxd:ident, $txd:ident, $cts:ident, $rts:ident, wakeup_source = $_:literal) => {
293            unsafe {
294                crate::peripherals::$inst::steal().info().suspend_for_sleep();
295            }
296        };
297    }
298}
299
300#[cfg(sleep_driver_supported)]
301fn sleep_uart_resume() {
302    use crate::uart::Instance;
303    for_each_uart! {
304        ($id:literal, $inst:ident, $peri:ident, $rxd:ident, $txd:ident, $cts:ident, $rts:ident, wakeup_source = $_:literal) => {
305            unsafe {
306                crate::peripherals::$inst::steal().info().resume_from_sleep();
307            }
308        };
309    }
310}