Skip to content

Commit

Permalink
Merge #466
Browse files Browse the repository at this point in the history
466: Allow restoring currently running rtc (lost time on initialization) r=burrbull a=Rutherther

Hi, I came across a problem when using the Rtc.
The problem is that if the microcontroller is reset, some time will be lost due to the reinitialization of the oscillator.

In order to solve this, I added methods restore_or_new, restore_or_new_lsi, restore_or_new_hse.
These methods check whether the rtc is enabled and the source is correct. If this is the case, the initialization is skipped.
After testing this, (for now, only with lse) it worked as intended, no time was lost anymore and the rtc was still counting.

What do you think about this? Are there any other things needed to be done, or should I change the behavior somehow?

Co-authored-by: František Boháček <[email protected]>
  • Loading branch information
bors[bot] and Rutherther authored Jul 28, 2023
2 parents 4e902d2 + 32123a6 commit 3f274fe
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 3 deletions.
21 changes: 20 additions & 1 deletion examples/rtc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use panic_semihosting as _;
use cortex_m_semihosting::hprintln;

use cortex_m_rt::entry;
use fugit::RateExtU32;
use stm32f1xx_hal::rtc::RestoredOrNewRtc::{New, Restored};
use stm32f1xx_hal::{pac, prelude::*, rtc::Rtc};

#[entry]
Expand All @@ -19,7 +21,24 @@ fn main() -> ! {
let rcc = p.RCC.constrain();
let mut backup_domain = rcc.bkp.constrain(p.BKP, &mut pwr);

let rtc = Rtc::new(p.RTC, &mut backup_domain);
// Initializes rtc every startup, use only if you don't have a battery.
// let rtc = Rtc::new(p.RTC, &mut backup_domain);

// Restores Rtc: that happens in case it was already running, a battery is connected,
// and it was already initialized before.
// If you are going to use ::new with battery, the time will lack behind
// due to unnecessary reinitialization of the crystal,
// as well as reset of the selected frequency.
// Else, the rtc is initialized.
let rtc = match Rtc::restore_or_new(p.RTC, &mut backup_domain) {
Restored(rtc) => rtc, // The rtc is restored from previous configuration. You may verify the frequency you want if needed.
New(mut rtc) => {
// The rtc was just initialized, the clock source selected, frequency is 1.Hz()
// Initialize rtc with desired parameters
rtc.select_frequency(2u32.Hz()); // Set the frequency to 2 Hz. This will stay same after reset
rtc
}
};

loop {
hprintln!("time: {}", rtc.current_time());
Expand Down
117 changes: 115 additions & 2 deletions src/rtc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ pub struct RtcClkLse;
/// RTC clock source LSI oscillator clock (type state)
pub struct RtcClkLsi;

pub enum RestoredOrNewRtc<CS> {
Restored(Rtc<CS>),
New(Rtc<CS>),
}

/**
Real time clock
Expand All @@ -44,14 +49,19 @@ pub struct Rtc<CS = RtcClkLse> {

impl Rtc<RtcClkLse> {
/**
Initialises the RTC. The `BackupDomain` struct is created by
`Rcc.bkp.constrain()`.
Initialises the RTC with low-speed external crystal source (lse).
The `BackupDomain` struct is created by `Rcc.bkp.constrain()`.
The frequency is set to 1 Hz.
Since the RTC is part of the backup domain, The RTC counter is not reset by normal resets or
power cycles where (VBAT) still has power. Use [set_time](#method.set_time) if you want to
reset the counter.
In case application is running of a battery on VBAT,
this method will reset the RTC every time, leading to lost time,
you may want to use
[`restore_or_new`](Rtc::<RtcClkLse>::restore_or_new) instead.
*/
pub fn new(regs: RTC, bkp: &mut BackupDomain) -> Self {
let mut result = Rtc {
Expand All @@ -72,6 +82,37 @@ impl Rtc<RtcClkLse> {
result
}

/// Tries to obtain currently running RTC to prevent a reset in case it was running from VBAT.
/// If the RTC is not running, or is not LSE, it will be reinitialized.
///
/// # Examples
/// ```
/// let rtc = match Rtc::restore_or_new(p.RTC, &mut backup_domain) {
/// Restored(rtc) => rtc, // The rtc is restored from previous configuration. You may verify the frequency you want if needed.
/// New(rtc) => { // The rtc was just initialized, the clock source selected, frequency is 1.Hz()
/// // Initialize rtc with desired parameters
/// rtc.select_frequency(2u16.Hz()); // Set the frequency to 2 Hz. This will stay same after reset
/// rtc
/// }
/// };
/// ```
pub fn restore_or_new(regs: RTC, bkp: &mut BackupDomain) -> RestoredOrNewRtc<RtcClkLse> {
if !Self::is_enabled() {
RestoredOrNewRtc::New(Rtc::new(regs, bkp))
} else {
RestoredOrNewRtc::Restored(Rtc {
regs,
_clock_source: PhantomData,
})
}
}

/// Returns whether the RTC is currently enabled and LSE is selected.
fn is_enabled() -> bool {
let rcc = unsafe { &*RCC::ptr() };
rcc.bdcr.read().rtcen().bit() && rcc.bdcr.read().rtcsel().is_lse()
}

/// Enables the RTC device with the lse as the clock
fn enable_rtc(_bkp: &mut BackupDomain) {
// NOTE: Safe RCC access because we are only accessing bdcr
Expand All @@ -93,6 +134,21 @@ impl Rtc<RtcClkLse> {
}

impl Rtc<RtcClkLsi> {
/**
Initialises the RTC with low-speed internal oscillator source (lsi).
The `BackupDomain` struct is created by `Rcc.bkp.constrain()`.
The frequency is set to 1 Hz.
Since the RTC is part of the backup domain, The RTC counter is not reset by normal resets or
power cycles where (VBAT) still has power. Use [set_time](#method.set_time) if you want to
reset the counter.
In case application is running of a battery on VBAT,
this method will reset the RTC every time, leading to lost time,
you may want to use
[`restore_or_new_lsi`](Rtc::<RtcClkLsi>::restore_or_new_lsi) instead.
*/
pub fn new_lsi(regs: RTC, bkp: &mut BackupDomain) -> Self {
let mut result = Rtc {
regs,
Expand All @@ -112,6 +168,25 @@ impl Rtc<RtcClkLsi> {
result
}

/// Tries to obtain currently running RTC to prevent reset in case it was running from VBAT.
/// If the RTC is not running, or is not LSI, it will be reinitialized.
pub fn restore_or_new_lsi(regs: RTC, bkp: &mut BackupDomain) -> RestoredOrNewRtc<RtcClkLsi> {
if !Rtc::<RtcClkLsi>::is_enabled() {
RestoredOrNewRtc::New(Rtc::new_lsi(regs, bkp))
} else {
RestoredOrNewRtc::Restored(Rtc {
regs,
_clock_source: PhantomData,
})
}
}

/// Returns whether the RTC is currently enabled and LSI is selected.
fn is_enabled() -> bool {
let rcc = unsafe { &*RCC::ptr() };
rcc.bdcr.read().rtcen().bit() && rcc.bdcr.read().rtcsel().is_lsi()
}

/// Enables the RTC device with the lsi as the clock
fn enable_rtc(_bkp: &mut BackupDomain) {
// NOTE: Safe RCC access because we are only accessing bdcr
Expand All @@ -136,6 +211,22 @@ impl Rtc<RtcClkLsi> {
}

impl Rtc<RtcClkHseDiv128> {
/**
Initialises the RTC with high-speed external oscillator source (hse)
divided by 128.
The `BackupDomain` struct is created by `Rcc.bkp.constrain()`.
The frequency is set to 1 Hz.
Since the RTC is part of the backup domain, The RTC counter is not reset by normal resets or
power cycles where (VBAT) still has power. Use [set_time](#method.set_time) if you want to
reset the counter.
In case application is running of a battery on VBAT,
this method will reset the RTC every time, leading to lost time,
you may want to use
[`restore_or_new_hse`](Rtc::<RtcClkHseDiv128>::restore_or_new_hse) instead.
*/
pub fn new_hse(regs: RTC, bkp: &mut BackupDomain, hse: Hertz) -> Self {
let mut result = Rtc {
regs,
Expand All @@ -155,6 +246,28 @@ impl Rtc<RtcClkHseDiv128> {
result
}

/// Tries to obtain currently running RTC to prevent reset in case it was running from VBAT.
/// If the RTC is not running, or is not HSE, it will be reinitialized.
pub fn restore_or_new_hse(
regs: RTC,
bkp: &mut BackupDomain,
hse: Hertz,
) -> RestoredOrNewRtc<RtcClkHseDiv128> {
if !Self::is_enabled() {
RestoredOrNewRtc::New(Rtc::new_hse(regs, bkp, hse))
} else {
RestoredOrNewRtc::Restored(Rtc {
regs,
_clock_source: PhantomData,
})
}
}

fn is_enabled() -> bool {
let rcc = unsafe { &*RCC::ptr() };
rcc.bdcr.read().rtcen().bit() && rcc.bdcr.read().rtcsel().is_hse()
}

/// Enables the RTC device with the lsi as the clock
fn enable_rtc(_bkp: &mut BackupDomain) {
// NOTE: Safe RCC access because we are only accessing bdcr
Expand Down

0 comments on commit 3f274fe

Please sign in to comment.