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:
amsam0 2024-09-09 01:32:15 -07:00 committed by GitHub
parent d71434adfb
commit 82a9abfff8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 141 additions and 15 deletions

View File

@ -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) - Added sleep and wakeup support for esp32c2 (#1922)
- `Input`, `Output`, `OutputOpenDrain` and `Flex` now implement `Peripheral`. (#2094) - `Input`, `Output`, `OutputOpenDrain` and `Flex` now implement `Peripheral`. (#2094)
- Previously unavailable memory is available via `.dram2_uninit` section (#2079) - 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 ### 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) - 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) - Renamed `touch::Continous` to `touch::Continuous`. (#2094)
- The (previously undocumented) `ErasedPin` enum has been replaced with the `ErasedPin` struct. (#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 ### 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 `Peripherals::take`. Use `esp_hal::init` to obtain `Peripherals` (#1999)
- Removed `AnyInputOnlyPin` in favour of `AnyPin`. (#2071) - 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 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 ## [0.20.1] - 2024-08-30

View File

@ -19,6 +19,7 @@ bitflags = "2.6.0"
bytemuck = "1.17.1" bytemuck = "1.17.1"
bitfield = "0.16.1" bitfield = "0.16.1"
cfg-if = "1.0.0" cfg-if = "1.0.0"
chrono = { version = "0.4.38", default-features = false }
critical-section = "1.1.3" critical-section = "1.1.3"
defmt = { version = "0.3.8", optional = true } defmt = { version = "0.3.8", optional = true }
delegate = "0.12.0" delegate = "0.12.0"

View File

@ -80,3 +80,13 @@ let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
.map_err(|e| e.0) .map_err(|e| e.0)
.unwrap(); .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
```

View File

@ -66,6 +66,7 @@
//! } //! }
//! ``` //! ```
use chrono::{DateTime, NaiveDateTime};
#[cfg(not(any(esp32c6, esp32h2)))] #[cfg(not(any(esp32c6, esp32h2)))]
use fugit::HertzU32; use fugit::HertzU32;
use fugit::MicrosDurationU64; use fugit::MicrosDurationU64;
@ -78,7 +79,7 @@ use crate::efuse::Efuse;
#[cfg(not(any(esp32c6, esp32h2)))] #[cfg(not(any(esp32c6, esp32h2)))]
use crate::peripherals::{LPWR, TIMG0}; use crate::peripherals::{LPWR, TIMG0};
#[cfg(any(esp32c6, esp32h2))] #[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))] #[cfg(any(esp32, esp32s3, esp32c3, esp32c6, esp32c2))]
use crate::rtc_cntl::sleep::{RtcSleepConfig, WakeSource, WakeTriggers}; use crate::rtc_cntl::sleep::{RtcSleepConfig, WakeSource, WakeTriggers};
use crate::{ use crate::{
@ -214,8 +215,8 @@ impl<'d> Rtc<'d> {
RtcClock::estimate_xtal_frequency() RtcClock::estimate_xtal_frequency()
} }
/// Read the current value of the rtc time registers. /// Get the time since boot in the raw register units.
pub fn get_time_raw(&self) -> u64 { fn time_since_boot_raw(&self) -> u64 {
#[cfg(not(any(esp32c6, esp32h2)))] #[cfg(not(any(esp32c6, esp32h2)))]
let rtc_cntl = unsafe { &*LPWR::ptr() }; let rtc_cntl = unsafe { &*LPWR::ptr() };
#[cfg(any(esp32c6, esp32h2))] #[cfg(any(esp32c6, esp32h2))]
@ -253,14 +254,112 @@ impl<'d> Rtc<'d> {
((h as u64) << 32) | (l as u64) ((h as u64) << 32) | (l as u64)
} }
/// Read the current value of the rtc time registers in microseconds. /// Get the time since boot.
pub fn get_time_us(&self) -> u64 { pub fn time_since_boot(&self) -> MicrosDurationU64 {
self.get_time_raw() * 1_000_000 / RtcClock::get_slow_freq().frequency().to_Hz() as u64 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. /// Read the current value of the boot time registers in microseconds.
pub fn get_time_ms(&self) -> u64 { fn boot_time_us(&self) -> u64 {
self.get_time_raw() * 1_000 / RtcClock::get_slow_freq().frequency().to_Hz() as 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`. /// Enter deep sleep and wake with the provided `wake_sources`.

View File

@ -82,7 +82,7 @@ impl WakeSource for TimerWakeupSource {
let clock_hz = clock_freq.frequency().to_Hz() as u64; let clock_hz = clock_freq.frequency().to_Hz() as u64;
let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64; let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64;
// "alarm" time in slow rtc ticks // "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; let time_in_ticks = now + ticks;
unsafe { unsafe {
rtc_cntl rtc_cntl

View File

@ -136,7 +136,7 @@ impl WakeSource for TimerWakeupSource {
let clock_hz = clock_freq.frequency().to_Hz() as u64; let clock_hz = clock_freq.frequency().to_Hz() as u64;
let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64; let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64;
// "alarm" time in slow rtc ticks // "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; let time_in_ticks = now + ticks;
unsafe { unsafe {
rtc_cntl rtc_cntl

View File

@ -136,7 +136,7 @@ impl WakeSource for TimerWakeupSource {
let clock_hz = clock_freq.frequency().to_Hz() as u64; let clock_hz = clock_freq.frequency().to_Hz() as u64;
let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64; let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64;
// "alarm" time in slow rtc ticks // "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; let time_in_ticks = now + ticks;
unsafe { unsafe {
rtc_cntl rtc_cntl

View File

@ -46,7 +46,7 @@ impl WakeSource for TimerWakeupSource {
let clock_hz = clock_freq.frequency().to_Hz() as u64; let clock_hz = clock_freq.frequency().to_Hz() as u64;
let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64; let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64;
// "alarm" time in slow rtc ticks // "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; let time_in_ticks = now + ticks;
unsafe { unsafe {
lp_timer.tar0_high().write(|w| { lp_timer.tar0_high().write(|w| {

View File

@ -122,7 +122,7 @@ impl WakeSource for TimerWakeupSource {
let clock_hz = clock_freq.frequency().to_Hz() as u64; let clock_hz = clock_freq.frequency().to_Hz() as u64;
let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64; let ticks = self.duration.as_micros() as u64 * clock_hz / 1_000_000u64;
// "alarm" time in slow rtc ticks // "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; let time_in_ticks = now + ticks;
unsafe { unsafe {
rtc_cntl rtc_cntl

View File

@ -5,6 +5,8 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
use core::time::Duration;
use esp_backtrace as _; use esp_backtrace as _;
use esp_hal::{delay::Delay, prelude::*, rtc_cntl::Rtc}; use esp_hal::{delay::Delay, prelude::*, rtc_cntl::Rtc};
@ -16,7 +18,18 @@ fn main() -> ! {
let delay = Delay::new(); let delay = Delay::new();
loop { 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); 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);
} }
} }