diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 9aba2306a..f7d858f42 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added sleep and wakeup support for esp32c2 (#1922) - `Input`, `Output`, `OutputOpenDrain` and `Flex` now implement `Peripheral`. (#2094) - Previously unavailable memory is available via `.dram2_uninit` section (#2079) +- Added `Rtc::set_current_time` to allow setting RTC time, and `Rtc::current_time` to getting RTC time while taking into account boot time (#1883) ### Changed @@ -31,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - To avoid confusion with the `Rtc::current_time` wall clock time APIs, we've renamed `esp_hal::time::current_time` to `esp_hal::time::now`. (#2091) - Renamed `touch::Continous` to `touch::Continuous`. (#2094) - The (previously undocumented) `ErasedPin` enum has been replaced with the `ErasedPin` struct. (#2094) +- Renamed and merged `Rtc::get_time_us` and `Rtc::get_time_ms` into `Rtc::time_since_boot` (#1883) ### Fixed @@ -48,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed `Peripherals::take`. Use `esp_hal::init` to obtain `Peripherals` (#1999) - Removed `AnyInputOnlyPin` in favour of `AnyPin`. (#2071) - Removed the following functions from `GpioPin`: `is_high`, `is_low`, `set_high`, `set_low`, `set_state`, `is_set_high`, `is_set_low`, `toggle`. (#2094) +- Removed `Rtc::get_time_raw` (#1883) ## [0.20.1] - 2024-08-30 diff --git a/esp-hal/Cargo.toml b/esp-hal/Cargo.toml index ce9ba0ec8..4f26cff6a 100644 --- a/esp-hal/Cargo.toml +++ b/esp-hal/Cargo.toml @@ -19,6 +19,7 @@ bitflags = "2.6.0" bytemuck = "1.17.1" bitfield = "0.16.1" cfg-if = "1.0.0" +chrono = { version = "0.4.38", default-features = false } critical-section = "1.1.3" defmt = { version = "0.3.8", optional = true } delegate = "0.12.0" diff --git a/esp-hal/MIGRATING-0.20.md b/esp-hal/MIGRATING-0.20.md index b5fb14d45..a860eb6f4 100644 --- a/esp-hal/MIGRATING-0.20.md +++ b/esp-hal/MIGRATING-0.20.md @@ -80,3 +80,13 @@ let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap(); .map_err(|e| e.0) .unwrap(); ``` + +## RTC Wall Clock APIs + +Instead of the `get_time_ms`, `get_time_us`, and `get_time_raw` functions, the `Rtc` struct now provides the `current_time` function, using `chrono`'s `NaiveDateTime` struct. + +```diff +let rtc = Rtc::new(peripherals.LPWR); +- let current_time_ms = rtc.get_time_ms(); ++ let current_time_ms = rtc.current_time().and_utc().timestamp_millis(); // assuming UTC +``` diff --git a/esp-hal/src/rtc_cntl/mod.rs b/esp-hal/src/rtc_cntl/mod.rs index a44a33037..97d8ed957 100644 --- a/esp-hal/src/rtc_cntl/mod.rs +++ b/esp-hal/src/rtc_cntl/mod.rs @@ -66,6 +66,7 @@ //! } //! ``` +use chrono::{DateTime, NaiveDateTime}; #[cfg(not(any(esp32c6, esp32h2)))] use fugit::HertzU32; use fugit::MicrosDurationU64; @@ -78,7 +79,7 @@ use crate::efuse::Efuse; #[cfg(not(any(esp32c6, esp32h2)))] use crate::peripherals::{LPWR, TIMG0}; #[cfg(any(esp32c6, esp32h2))] -use crate::peripherals::{LP_TIMER, LP_WDT}; +use crate::peripherals::{LP_AON, LP_TIMER, LP_WDT}; #[cfg(any(esp32, esp32s3, esp32c3, esp32c6, esp32c2))] use crate::rtc_cntl::sleep::{RtcSleepConfig, WakeSource, WakeTriggers}; use crate::{ @@ -214,8 +215,8 @@ impl<'d> Rtc<'d> { RtcClock::estimate_xtal_frequency() } - /// Read the current value of the rtc time registers. - pub fn get_time_raw(&self) -> u64 { + /// Get the time since boot in the raw register units. + fn time_since_boot_raw(&self) -> u64 { #[cfg(not(any(esp32c6, esp32h2)))] let rtc_cntl = unsafe { &*LPWR::ptr() }; #[cfg(any(esp32c6, esp32h2))] @@ -253,14 +254,112 @@ impl<'d> Rtc<'d> { ((h as u64) << 32) | (l as u64) } - /// Read the current value of the rtc time registers in microseconds. - pub fn get_time_us(&self) -> u64 { - self.get_time_raw() * 1_000_000 / RtcClock::get_slow_freq().frequency().to_Hz() as u64 + /// Get the time since boot. + pub fn time_since_boot(&self) -> MicrosDurationU64 { + MicrosDurationU64::micros( + self.time_since_boot_raw() * 1_000_000 + / RtcClock::get_slow_freq().frequency().to_Hz() as u64, + ) } - /// Read the current value of the rtc time registers in milliseconds. - pub fn get_time_ms(&self) -> u64 { - self.get_time_raw() * 1_000 / RtcClock::get_slow_freq().frequency().to_Hz() as u64 + /// Read the current value of the boot time registers in microseconds. + fn boot_time_us(&self) -> u64 { + // For more info on about how RTC setting works and what it has to do with boot time, see https://github.com/esp-rs/esp-hal/pull/1883 + + // In terms of registers, STORE2 and STORE3 are used on all current chips + // (esp32, esp32p4, esp32h2, esp32c2, esp32c3, esp32c5, esp32c6, esp32c61, + // esp32s2, esp32s3) + + // In terms of peripherals: + + // - LPWR is used on the following chips: esp32, esp32p4, esp32c2, esp32c3, + // esp32s2, esp32s3 + + // - LP_AON is used on the following chips: esp32c5, esp32c6, esp32c61, esp32h2 + + // For registers and peripherals used in esp-idf, see https://github.com/search?q=repo%3Aespressif%2Fesp-idf+RTC_BOOT_TIME_LOW_REG+RTC_BOOT_TIME_HIGH_REG+path%3A**%2Frtc.h&type=code + + #[cfg(not(any(esp32c6, esp32h2)))] + let rtc_cntl = unsafe { &*LPWR::ptr() }; + #[cfg(any(esp32c6, esp32h2))] + let rtc_cntl = unsafe { &*LP_AON::ptr() }; + + let (l, h) = (rtc_cntl.store2(), rtc_cntl.store3()); + + let l = l.read().bits() as u64; + let h = h.read().bits() as u64; + + // https://github.com/espressif/esp-idf/blob/23e4823f17a8349b5e03536ff7653e3e584c9351/components/newlib/port/esp_time_impl.c#L115 + l + (h << 32) + } + + /// Set the current value of the boot time registers in microseconds. + fn set_boot_time_us(&self, boot_time_us: u64) { + // Please see `boot_time_us` for documentation on registers and peripherals + // used for certain SOCs. + + #[cfg(not(any(esp32c6, esp32h2)))] + let rtc_cntl = unsafe { &*LPWR::ptr() }; + #[cfg(any(esp32c6, esp32h2))] + let rtc_cntl = unsafe { &*LP_AON::ptr() }; + + let (l, h) = (rtc_cntl.store2(), rtc_cntl.store3()); + + // https://github.com/espressif/esp-idf/blob/23e4823f17a8349b5e03536ff7653e3e584c9351/components/newlib/port/esp_time_impl.c#L102-L103 + l.write(|w| unsafe { w.bits((boot_time_us & 0xffffffff) as u32) }); + h.write(|w| unsafe { w.bits((boot_time_us >> 32) as u32) }); + } + + /// Get the current time. + pub fn current_time(&self) -> NaiveDateTime { + // Current time is boot time + time since boot + + let rtc_time_us = self.time_since_boot().to_micros(); + let boot_time_us = self.boot_time_us(); + let wrapped_boot_time_us = u64::MAX - boot_time_us; + + // We can detect if we wrapped the boot time by checking if rtc time is greater + // than the amount of time we would've wrapped. + let current_time_us = if rtc_time_us > wrapped_boot_time_us { + // We also just checked that this won't overflow + rtc_time_us - wrapped_boot_time_us + } else { + boot_time_us + rtc_time_us + }; + + DateTime::from_timestamp_micros(current_time_us as i64) + .unwrap() + .naive_utc() + } + + /// Set the current time. + /// + /// # Panics + /// + /// Panics if `current_time` is before the Unix epoch (meaning the + /// underlying timestamp is negative). + pub fn set_current_time(&self, current_time: NaiveDateTime) { + let current_time_us: u64 = current_time + .and_utc() + .timestamp_micros() + .try_into() + .expect("current_time is negative"); + + // Current time is boot time + time since boot (rtc time) + // So boot time = current time - time since boot (rtc time) + + let rtc_time_us = self.time_since_boot().to_micros(); + if current_time_us < rtc_time_us { + // An overflow would happen if we subtracted rtc_time_us from current_time_us. + // To work around this, we can wrap around u64::MAX by subtracting the + // difference between the current time and the time since boot. + // Subtracting time since boot and adding current new time is equivalent and + // avoids overflow. We just checked that rtc_time_us is less than time_us + // so this won't overflow. + 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) + } } /// Enter deep sleep and wake with the provided `wake_sources`. diff --git a/esp-hal/src/rtc_cntl/sleep/esp32.rs b/esp-hal/src/rtc_cntl/sleep/esp32.rs index 774350351..f8c344693 100644 --- a/esp-hal/src/rtc_cntl/sleep/esp32.rs +++ b/esp-hal/src/rtc_cntl/sleep/esp32.rs @@ -82,7 +82,7 @@ impl WakeSource for TimerWakeupSource { let clock_hz = clock_freq.frequency().to_Hz() as u64; let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64; // "alarm" time in slow rtc ticks - let now = rtc.get_time_raw(); + let now = rtc.time_since_boot_raw(); let time_in_ticks = now + ticks; unsafe { rtc_cntl diff --git a/esp-hal/src/rtc_cntl/sleep/esp32c2.rs b/esp-hal/src/rtc_cntl/sleep/esp32c2.rs index c0fe69a50..7fb003fd1 100644 --- a/esp-hal/src/rtc_cntl/sleep/esp32c2.rs +++ b/esp-hal/src/rtc_cntl/sleep/esp32c2.rs @@ -136,7 +136,7 @@ impl WakeSource for TimerWakeupSource { let clock_hz = clock_freq.frequency().to_Hz() as u64; let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64; // "alarm" time in slow rtc ticks - let now = rtc.get_time_raw(); + let now = rtc.time_since_boot_raw(); let time_in_ticks = now + ticks; unsafe { rtc_cntl diff --git a/esp-hal/src/rtc_cntl/sleep/esp32c3.rs b/esp-hal/src/rtc_cntl/sleep/esp32c3.rs index 5c1cec90f..39d165ce2 100644 --- a/esp-hal/src/rtc_cntl/sleep/esp32c3.rs +++ b/esp-hal/src/rtc_cntl/sleep/esp32c3.rs @@ -136,7 +136,7 @@ impl WakeSource for TimerWakeupSource { let clock_hz = clock_freq.frequency().to_Hz() as u64; let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64; // "alarm" time in slow rtc ticks - let now = rtc.get_time_raw(); + let now = rtc.time_since_boot_raw(); let time_in_ticks = now + ticks; unsafe { rtc_cntl diff --git a/esp-hal/src/rtc_cntl/sleep/esp32c6.rs b/esp-hal/src/rtc_cntl/sleep/esp32c6.rs index fb052aed9..af051b074 100644 --- a/esp-hal/src/rtc_cntl/sleep/esp32c6.rs +++ b/esp-hal/src/rtc_cntl/sleep/esp32c6.rs @@ -46,7 +46,7 @@ impl WakeSource for TimerWakeupSource { let clock_hz = clock_freq.frequency().to_Hz() as u64; let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64; // "alarm" time in slow rtc ticks - let now = rtc.get_time_raw(); + let now = rtc.time_since_boot_raw(); let time_in_ticks = now + ticks; unsafe { lp_timer.tar0_high().write(|w| { diff --git a/esp-hal/src/rtc_cntl/sleep/esp32s3.rs b/esp-hal/src/rtc_cntl/sleep/esp32s3.rs index ea97060da..65c70abe8 100644 --- a/esp-hal/src/rtc_cntl/sleep/esp32s3.rs +++ b/esp-hal/src/rtc_cntl/sleep/esp32s3.rs @@ -122,7 +122,7 @@ impl WakeSource for TimerWakeupSource { let clock_hz = clock_freq.frequency().to_Hz() as u64; let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64; // "alarm" time in slow rtc ticks - let now = rtc.get_time_raw(); + let now = rtc.time_since_boot_raw(); let time_in_ticks = now + ticks; unsafe { rtc_cntl diff --git a/examples/src/bin/rtc_time.rs b/examples/src/bin/rtc_time.rs index e0c809da0..182d045c7 100644 --- a/examples/src/bin/rtc_time.rs +++ b/examples/src/bin/rtc_time.rs @@ -5,6 +5,8 @@ #![no_std] #![no_main] +use core::time::Duration; + use esp_backtrace as _; use esp_hal::{delay::Delay, prelude::*, rtc_cntl::Rtc}; @@ -16,7 +18,18 @@ fn main() -> ! { let delay = Delay::new(); loop { - esp_println::println!("rtc time in milliseconds is {}", rtc.get_time_ms()); + esp_println::println!( + "rtc time in milliseconds is {}", + rtc.current_time().and_utc().timestamp_millis() + ); delay.delay_millis(1000); + + // Set the time to half a second in the past + let new_time = rtc.current_time() - Duration::from_millis(500); + esp_println::println!( + "setting rtc time to {} milliseconds", + new_time.and_utc().timestamp_millis() + ); + rtc.set_current_time(new_time); } }