Allow setting RTC time (#1883)
* Initial WIP RTC set implementation * Deprecate get_time_raw and add docs + some cleanup * Update rtc time example * Format * Update changelog * Add some comments linking the PR * Small compilation fixes * C6 and H2 fixes * Remove parantheses from if statement lol * Remove accidental changelog change * Implement boot time wrapping to avoid overflows * Remove unused get_rtc_time_ms and get_rtc_time_us functions * Make get_rtc_time_us public and re-add get_rtc_time_ms as public * Update changelog * Remove get_time_raw and replace with public get_rtc_time_raw * Changelog reordering * Function renaming * Use fugit and update changelog * Small typo fix * Fix changelog addition from merging * Use chrono for current_time and set_current_time * Fix changelog * Update example * Fix merge errors * Rename `time::current_time` to `time::uptime` * Revert "Rename `time::current_time` to `time::uptime`" This reverts commit fe8446899747c88d5b9f945f319e1133b90773ee. * Format * Add info to migration guide * Fix compilation for esp32c2 * Remove information about setting RTC time from migration guide since it isn't really relevant --------- Co-authored-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
This commit is contained in:
parent
d71434adfb
commit
82a9abfff8
@ -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
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
```
|
||||
|
||||
@ -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`.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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| {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user