#![cfg_attr(docsrs, procmacros::doc_replace)]
#![cfg_attr(
lp_timer_driver_supported,
doc = r#"
## Examples
### Get time in ms from the RTC Timer
```rust, no_run
# {before_snippet}
# use core::time::Duration;
# use esp_hal::{delay::Delay, rtc_cntl::Rtc};
let rtc = Rtc::new(peripherals.LPWR);
let delay = Delay::new();
loop {
// Print the current RTC time in milliseconds
let time_ms = rtc.current_time_us() / 1000;
delay.delay_millis(1000);
// Set the time to half a second in the past
let new_time = rtc.current_time_us() - 500_000;
rtc.set_current_time_us(new_time);
}
# }
```
### RWDT usage
```rust, no_run
# {before_snippet}
# use core::cell::RefCell;
# use critical_section::Mutex;
# use esp_hal::delay::Delay;
# use esp_hal::rtc_cntl::Rtc;
# use esp_hal::rtc_cntl::Rwdt;
# use esp_hal::rtc_cntl::RwdtStage;
static RWDT: Mutex<RefCell<Option<Rwdt>>> = Mutex::new(RefCell::new(None));
let mut delay = Delay::new();
let mut rtc = Rtc::new(peripherals.LPWR);
rtc.set_interrupt_handler(interrupt_handler);
rtc.rwdt
.set_timeout(RwdtStage::Stage0, Duration::from_millis(2000));
rtc.rwdt.listen();
critical_section::with(|cs| RWDT.borrow_ref_mut(cs).replace(rtc.rwdt));
# {after_snippet}
// Where the `LP_WDT` interrupt handler is defined as:
# use core::cell::RefCell;
# use critical_section::Mutex;
# use esp_hal::rtc_cntl::Rwdt;
# use esp_hal::rtc_cntl::RwdtStage;
static RWDT: Mutex<RefCell<Option<Rwdt>>> = Mutex::new(RefCell::new(None));
// Handle the corresponding interrupt
#[esp_hal::handler]
fn interrupt_handler() {
critical_section::with(|cs| {
println!("RWDT Interrupt");
let mut rwdt = RWDT.borrow_ref_mut(cs);
if let Some(rwdt) = rwdt.as_mut() {
rwdt.clear_interrupt();
println!("Restarting in 5 seconds...");
rwdt.set_timeout(RwdtStage::Stage0, Duration::from_millis(5000));
rwdt.unlisten();
}
});
}
```
### Get time in ms from the RTC Timer
```rust, no_run
# {before_snippet}
# use esp_hal::{delay::Delay, rtc_cntl::Rtc};
let rtc = Rtc::new(peripherals.LPWR);
let delay = Delay::new();
loop {
// Get the current RTC time in milliseconds
let time_ms = rtc.current_time_us() / 1000;
delay.delay_millis(1000);
// Set the time to half a second in the past
let new_time = rtc.current_time_us() - 500_000;
rtc.set_current_time_us(new_time);
}
# }
```
"#
)]
pub use self::rtc::SocResetReason;
#[cfg(sleep_driver_supported)]
use crate::rtc_cntl::sleep::{RtcSleepConfig, WakeSource, WakeTriggers};
#[cfg_attr(not(lp_timer_driver_supported), expect(unused))]
use crate::{
interrupt::{self, InterruptHandler},
peripherals::Interrupt,
};
use crate::{
peripherals::LPWR,
system::{Cpu, SleepSource},
time::Duration,
};
#[cfg(sleep_driver_supported)]
pub mod sleep;
#[cfg_attr(esp32, path = "rtc/esp32.rs")]
#[cfg_attr(esp32c2, path = "rtc/esp32c2.rs")]
#[cfg_attr(esp32c3, path = "rtc/esp32c3.rs")]
#[cfg_attr(esp32c5, path = "rtc/esp32c5.rs")]
#[cfg_attr(esp32c6, path = "rtc/esp32c6.rs")]
#[cfg_attr(esp32c61, path = "rtc/esp32c61.rs")]
#[cfg_attr(esp32h2, path = "rtc/esp32h2.rs")]
#[cfg_attr(esp32s2, path = "rtc/esp32s2.rs")]
#[cfg_attr(esp32s3, path = "rtc/esp32s3.rs")]
pub(crate) mod rtc;
cfg_if::cfg_if! {
if #[cfg(soc_has_lp_wdt)] {
use crate::peripherals::LP_WDT;
#[cfg(lp_timer_driver_supported)]
use crate::peripherals::LP_TIMER;
use crate::peripherals::LP_AON;
} else {
use crate::peripherals::LPWR as LP_WDT;
use crate::peripherals::LPWR as LP_TIMER;
use crate::peripherals::LPWR as LP_AON;
}
}
bitflags::bitflags! {
#[allow(unused)]
struct WakeupReason: u32 {
const NoSleep = 0;
#[cfg(pm_support_ext0_wakeup)]
const ExtEvent0Trig = 1 << 0;
#[cfg(pm_support_ext1_wakeup)]
const ExtEvent1Trig = 1 << 1;
const GpioTrigEn = 1 << 2;
#[cfg(not(any(esp32c6, esp32h2)))]
const TimerTrigEn = 1 << 3;
#[cfg(any(esp32c6, esp32h2))]
const TimerTrigEn = 1 << 4;
#[cfg(pm_support_wifi_wakeup)]
const WifiTrigEn = 1 << 5;
const Uart0TrigEn = 1 << 6;
const Uart1TrigEn = 1 << 7;
#[cfg(pm_support_touch_sensor_wakeup)]
const TouchTrigEn = 1 << 8;
#[cfg(ulp_supported)]
const UlpTrigEn = 1 << 9;
#[cfg(pm_support_bt_wakeup)]
const BtTrigEn = 1 << 10;
#[cfg(riscv_coproc_supported)]
const CocpuTrigEn = 1 << 11;
#[cfg(riscv_coproc_supported)]
const CocpuTrapTrigEn = 1 << 13;
}
}
pub struct Rtc<'d> {
_inner: LPWR<'d>,
pub rwdt: Rwdt,
#[cfg(swd)]
pub swd: Swd,
}
impl<'d> Rtc<'d> {
pub fn new(rtc_cntl: LPWR<'d>) -> Self {
Self {
_inner: rtc_cntl,
rwdt: Rwdt(()),
#[cfg(swd)]
swd: Swd(()),
}
}
#[cfg(lp_timer_driver_supported)]
fn time_since_boot_raw(&self) -> u64 {
let rtc_cntl = LP_TIMER::regs();
cfg_if::cfg_if! {
if #[cfg(esp32)] {
rtc_cntl.time_update().write(|w| w.time_update().set_bit());
while rtc_cntl.time_update().read().time_valid().bit_is_clear() {
crate::rom::ets_delay_us(1);
}
let h = rtc_cntl.time1().read().time_hi().bits();
let l = rtc_cntl.time0().read().time_lo().bits();
} else if #[cfg(any(esp32c6, esp32h2))] {
rtc_cntl.update().write(|w| w.main_timer_update().set_bit());
let h = rtc_cntl
.main_buf0_high()
.read()
.main_timer_buf0_high()
.bits();
let l = rtc_cntl.main_buf0_low().read().main_timer_buf0_low().bits();
} else {
rtc_cntl.time_update().write(|w| w.time_update().set_bit());
let h = rtc_cntl.time_high0().read().timer_value0_high().bits();
let l = rtc_cntl.time_low0().read().timer_value0_low().bits();
}
}
((h as u64) << 32) | (l as u64)
}
#[cfg(lp_timer_driver_supported)]
pub fn time_since_power_up(&self) -> Duration {
Duration::from_micros(crate::clock::rtc_ticks_to_us(self.time_since_boot_raw()))
}
#[cfg(lp_timer_driver_supported)]
fn boot_time_us(&self) -> u64 {
let rtc_cntl = LP_AON::regs();
let l = rtc_cntl.store2().read().bits() as u64;
let h = rtc_cntl.store3().read().bits() as u64;
l + (h << 32)
}
#[cfg(lp_timer_driver_supported)]
fn set_boot_time_us(&self, boot_time_us: u64) {
let rtc_cntl = LP_AON::regs();
rtc_cntl
.store2() .write(|w| unsafe { w.bits((boot_time_us & 0xffff_ffff) as u32) });
rtc_cntl
.store3() .write(|w| unsafe { w.bits((boot_time_us >> 32) as u32) });
}
#[procmacros::doc_replace]
#[cfg(lp_timer_driver_supported)]
pub fn current_time_us(&self) -> u64 {
let rtc_time_us = self.time_since_power_up().as_micros();
let boot_time_us = self.boot_time_us();
let wrapped_boot_time_us = u64::MAX - boot_time_us;
if rtc_time_us > wrapped_boot_time_us {
rtc_time_us - wrapped_boot_time_us
} else {
boot_time_us + rtc_time_us
}
}
#[cfg(lp_timer_driver_supported)]
pub fn set_current_time_us(&self, current_time_us: u64) {
let rtc_time_us = self.time_since_power_up().as_micros();
if current_time_us < rtc_time_us {
self.set_boot_time_us(u64::MAX - rtc_time_us + current_time_us)
} else {
self.set_boot_time_us(current_time_us - rtc_time_us)
}
}
#[cfg(sleep_deep_sleep)]
pub fn sleep_deep(&mut self, wake_sources: &[&dyn WakeSource]) -> ! {
let config = RtcSleepConfig::deep();
self.sleep(&config, wake_sources);
unreachable!();
}
#[cfg(sleep_light_sleep)]
pub fn sleep_light(&mut self, wake_sources: &[&dyn WakeSource]) {
let config = RtcSleepConfig::default();
self.sleep(&config, wake_sources);
}
#[cfg(sleep_driver_supported)]
pub fn sleep(&mut self, config: &RtcSleepConfig, wake_sources: &[&dyn WakeSource]) {
let mut config = *config;
let mut wakeup_triggers = WakeTriggers::default();
for wake_source in wake_sources {
wake_source.apply(self, &mut wakeup_triggers, &mut config)
}
config.apply();
config.start_sleep(wakeup_triggers);
config.finish_sleep();
}
pub(crate) const RTC_DISABLE_ROM_LOG: u32 = 1;
pub fn disable_rom_message_printing(&self) {
LP_AON::regs()
.store4()
.modify(|r, w| unsafe { w.bits(r.bits() | Self::RTC_DISABLE_ROM_LOG) });
}
#[instability::unstable]
#[cfg(lp_timer_driver_supported)]
pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
cfg_if::cfg_if! {
if #[cfg(any(esp32c6, esp32h2))] {
let interrupt = Interrupt::LP_WDT;
} else {
let interrupt = Interrupt::RTC_CORE;
}
}
for core in crate::system::Cpu::other() {
crate::interrupt::disable(core, interrupt);
}
interrupt::bind_handler(interrupt, handler);
}
}
impl crate::private::Sealed for Rtc<'_> {}
#[instability::unstable]
#[cfg(lp_timer_driver_supported)]
impl crate::interrupt::InterruptConfigurable for Rtc<'_> {
fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
self.set_interrupt_handler(handler);
}
}
#[allow(unused)]
#[derive(Debug, Clone, Copy)]
pub enum RwdtStageAction {
Off = 0,
Interrupt = 1,
ResetCpu = 2,
ResetCore = 3,
ResetSystem = 4,
}
#[derive(Debug, Clone, Copy)]
pub enum RwdtStage {
Stage0,
Stage1,
Stage2,
Stage3,
}
pub struct Rwdt(());
impl Rwdt {
pub fn enable(&mut self) {
self.set_enabled(true);
}
pub fn disable(&mut self) {
self.set_enabled(false);
}
pub fn listen(&mut self) {
let rtc_cntl = LP_WDT::regs();
self.set_write_protection(false);
rtc_cntl
.wdtconfig0()
.modify(|_, w| unsafe { w.wdt_stg0().bits(RwdtStageAction::Interrupt as u8) });
rtc_cntl.int_ena().modify(|_, w| w.wdt().set_bit());
self.set_write_protection(true);
}
pub fn unlisten(&mut self) {
let rtc_cntl = LP_WDT::regs();
self.set_write_protection(false);
rtc_cntl
.wdtconfig0()
.modify(|_, w| unsafe { w.wdt_stg0().bits(RwdtStageAction::ResetSystem as u8) });
rtc_cntl.int_ena().modify(|_, w| w.wdt().clear_bit());
self.set_write_protection(true);
}
pub fn clear_interrupt(&mut self) {
self.set_write_protection(false);
LP_WDT::regs()
.int_clr()
.write(|w| w.wdt().clear_bit_by_one());
self.set_write_protection(true);
}
pub fn is_interrupt_set(&self) -> bool {
LP_WDT::regs().int_st().read().wdt().bit_is_set()
}
pub fn feed(&mut self) {
self.set_write_protection(false);
LP_WDT::regs().wdtfeed().write(|w| w.wdt_feed().set_bit());
self.set_write_protection(true);
}
fn set_write_protection(&mut self, enable: bool) {
let wkey = if enable { 0u32 } else { 0x50D8_3AA1 };
LP_WDT::regs()
.wdtwprotect()
.write(|w| unsafe { w.bits(wkey) });
}
fn set_enabled(&mut self, enable: bool) {
let rtc_cntl = LP_WDT::regs();
self.set_write_protection(false);
if !enable {
rtc_cntl.wdtconfig0().modify(|_, w| unsafe { w.bits(0) });
} else {
rtc_cntl
.wdtconfig0()
.write(|w| w.wdt_flashboot_mod_en().bit(false));
rtc_cntl
.wdtconfig0()
.modify(|_, w| w.wdt_en().bit(enable).wdt_pause_in_slp().bit(enable));
unsafe {
rtc_cntl.wdtconfig0().modify(|_, w| {
w.wdt_stg0().bits(RwdtStageAction::ResetSystem as u8);
w.wdt_cpu_reset_length().bits(7);
w.wdt_sys_reset_length().bits(7);
w.wdt_stg1().bits(RwdtStageAction::Off as u8);
w.wdt_stg2().bits(RwdtStageAction::Off as u8);
w.wdt_stg3().bits(RwdtStageAction::Off as u8);
w.wdt_en().set_bit()
});
}
}
self.set_write_protection(true);
}
pub fn set_timeout(&mut self, stage: RwdtStage, timeout: Duration) {
let rtc_cntl = LP_WDT::regs();
let timeout_raw = crate::clock::us_to_rtc_ticks(timeout.as_micros()) as u32;
self.set_write_protection(false);
let config_reg = match stage {
RwdtStage::Stage0 => rtc_cntl.wdtconfig1(),
RwdtStage::Stage1 => rtc_cntl.wdtconfig2(),
RwdtStage::Stage2 => rtc_cntl.wdtconfig3(),
RwdtStage::Stage3 => rtc_cntl.wdtconfig4(),
};
#[cfg(not(esp32))]
let timeout_raw = timeout_raw >> (1 + crate::efuse::rwdt_multiplier());
config_reg.modify(|_, w| unsafe { w.hold().bits(timeout_raw) });
self.set_write_protection(true);
}
pub fn set_stage_action(&mut self, stage: RwdtStage, action: RwdtStageAction) {
self.set_write_protection(false);
LP_WDT::regs().wdtconfig0().modify(|_, w| unsafe {
match stage {
RwdtStage::Stage0 => w.wdt_stg0().bits(action as u8),
RwdtStage::Stage1 => w.wdt_stg1().bits(action as u8),
RwdtStage::Stage2 => w.wdt_stg2().bits(action as u8),
RwdtStage::Stage3 => w.wdt_stg3().bits(action as u8),
}
});
self.set_write_protection(true);
}
}
#[cfg(swd)]
pub struct Swd(());
#[cfg(swd)]
impl Swd {
pub fn enable(&mut self) {
self.set_enabled(true);
}
pub fn disable(&mut self) {
self.set_enabled(false);
}
fn set_write_protection(&mut self, enable: bool) {
#[cfg(not(any(esp32c6, esp32h2)))]
let wkey = if enable { 0u32 } else { 0x8F1D_312A };
#[cfg(any(esp32c6, esp32h2))]
let wkey = if enable { 0u32 } else { 0x50D8_3AA1 };
LP_WDT::regs()
.swd_wprotect()
.write(|w| unsafe { w.swd_wkey().bits(wkey) });
}
fn set_enabled(&mut self, enable: bool) {
self.set_write_protection(false);
LP_WDT::regs()
.swd_conf()
.write(|w| w.swd_auto_feed_en().bit(!enable));
self.set_write_protection(true);
}
}
pub fn reset_reason(cpu: Cpu) -> Option<SocResetReason> {
let reason = crate::rom::rtc_get_reset_reason(cpu as u32);
SocResetReason::from_repr(reason as usize)
}
pub fn wakeup_cause() -> SleepSource {
if reset_reason(Cpu::ProCpu) != Some(SocResetReason::CoreDeepSleep) {
return SleepSource::Undefined;
}
cfg_if::cfg_if! {
if #[cfg(esp32)] {
let wakeup_cause_bits = LPWR::regs().wakeup_state().read().wakeup_cause().bits() as u32;
} else if #[cfg(soc_has_intpri)] {
let wakeup_cause_bits = crate::peripherals::PMU::regs()
.slp_wakeup_status0()
.read()
.wakeup_cause()
.bits();
} else {
let wakeup_cause_bits = LPWR::regs().slp_wakeup_cause().read().wakeup_cause().bits();
}
}
let wakeup_cause = WakeupReason::from_bits_retain(wakeup_cause_bits);
if wakeup_cause.contains(WakeupReason::TimerTrigEn) {
return SleepSource::Timer;
}
if wakeup_cause.contains(WakeupReason::GpioTrigEn) {
return SleepSource::Gpio;
}
if wakeup_cause.intersects(WakeupReason::Uart0TrigEn | WakeupReason::Uart1TrigEn) {
return SleepSource::Uart;
}
#[cfg(pm_support_ext0_wakeup)]
if wakeup_cause.contains(WakeupReason::ExtEvent0Trig) {
return SleepSource::Ext0;
}
#[cfg(pm_support_ext1_wakeup)]
if wakeup_cause.contains(WakeupReason::ExtEvent1Trig) {
return SleepSource::Ext1;
}
#[cfg(pm_support_touch_sensor_wakeup)]
if wakeup_cause.contains(WakeupReason::TouchTrigEn) {
return SleepSource::TouchPad;
}
#[cfg(ulp_supported)]
if wakeup_cause.contains(WakeupReason::UlpTrigEn) {
return SleepSource::Ulp;
}
#[cfg(pm_support_wifi_wakeup)]
if wakeup_cause.contains(WakeupReason::WifiTrigEn) {
return SleepSource::Wifi;
}
#[cfg(pm_support_bt_wakeup)]
if wakeup_cause.contains(WakeupReason::BtTrigEn) {
return SleepSource::BT;
}
#[cfg(riscv_coproc_supported)]
if wakeup_cause.contains(WakeupReason::CocpuTrigEn) {
return SleepSource::Ulp;
} else if wakeup_cause.contains(WakeupReason::CocpuTrapTrigEn) {
return SleepSource::CocpuTrapTrig;
}
SleepSource::Undefined
}