Skip to content

Commit 3f274fe

Browse files
Merge #466
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 <fandabohacek@gmail.com>
2 parents 4e902d2 + 32123a6 commit 3f274fe

File tree

2 files changed

+135
-3
lines changed

2 files changed

+135
-3
lines changed

examples/rtc.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use panic_semihosting as _;
99
use cortex_m_semihosting::hprintln;
1010

1111
use cortex_m_rt::entry;
12+
use fugit::RateExtU32;
13+
use stm32f1xx_hal::rtc::RestoredOrNewRtc::{New, Restored};
1214
use stm32f1xx_hal::{pac, prelude::*, rtc::Rtc};
1315

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

22-
let rtc = Rtc::new(p.RTC, &mut backup_domain);
24+
// Initializes rtc every startup, use only if you don't have a battery.
25+
// let rtc = Rtc::new(p.RTC, &mut backup_domain);
26+
27+
// Restores Rtc: that happens in case it was already running, a battery is connected,
28+
// and it was already initialized before.
29+
// If you are going to use ::new with battery, the time will lack behind
30+
// due to unnecessary reinitialization of the crystal,
31+
// as well as reset of the selected frequency.
32+
// Else, the rtc is initialized.
33+
let rtc = match Rtc::restore_or_new(p.RTC, &mut backup_domain) {
34+
Restored(rtc) => rtc, // The rtc is restored from previous configuration. You may verify the frequency you want if needed.
35+
New(mut rtc) => {
36+
// The rtc was just initialized, the clock source selected, frequency is 1.Hz()
37+
// Initialize rtc with desired parameters
38+
rtc.select_frequency(2u32.Hz()); // Set the frequency to 2 Hz. This will stay same after reset
39+
rtc
40+
}
41+
};
2342

2443
loop {
2544
hprintln!("time: {}", rtc.current_time());

src/rtc.rs

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ pub struct RtcClkLse;
2020
/// RTC clock source LSI oscillator clock (type state)
2121
pub struct RtcClkLsi;
2222

23+
pub enum RestoredOrNewRtc<CS> {
24+
Restored(Rtc<CS>),
25+
New(Rtc<CS>),
26+
}
27+
2328
/**
2429
Real time clock
2530
@@ -44,14 +49,19 @@ pub struct Rtc<CS = RtcClkLse> {
4449

4550
impl Rtc<RtcClkLse> {
4651
/**
47-
Initialises the RTC. The `BackupDomain` struct is created by
48-
`Rcc.bkp.constrain()`.
52+
Initialises the RTC with low-speed external crystal source (lse).
53+
The `BackupDomain` struct is created by `Rcc.bkp.constrain()`.
4954
5055
The frequency is set to 1 Hz.
5156
5257
Since the RTC is part of the backup domain, The RTC counter is not reset by normal resets or
5358
power cycles where (VBAT) still has power. Use [set_time](#method.set_time) if you want to
5459
reset the counter.
60+
61+
In case application is running of a battery on VBAT,
62+
this method will reset the RTC every time, leading to lost time,
63+
you may want to use
64+
[`restore_or_new`](Rtc::<RtcClkLse>::restore_or_new) instead.
5565
*/
5666
pub fn new(regs: RTC, bkp: &mut BackupDomain) -> Self {
5767
let mut result = Rtc {
@@ -72,6 +82,37 @@ impl Rtc<RtcClkLse> {
7282
result
7383
}
7484

85+
/// Tries to obtain currently running RTC to prevent a reset in case it was running from VBAT.
86+
/// If the RTC is not running, or is not LSE, it will be reinitialized.
87+
///
88+
/// # Examples
89+
/// ```
90+
/// let rtc = match Rtc::restore_or_new(p.RTC, &mut backup_domain) {
91+
/// Restored(rtc) => rtc, // The rtc is restored from previous configuration. You may verify the frequency you want if needed.
92+
/// New(rtc) => { // The rtc was just initialized, the clock source selected, frequency is 1.Hz()
93+
/// // Initialize rtc with desired parameters
94+
/// rtc.select_frequency(2u16.Hz()); // Set the frequency to 2 Hz. This will stay same after reset
95+
/// rtc
96+
/// }
97+
/// };
98+
/// ```
99+
pub fn restore_or_new(regs: RTC, bkp: &mut BackupDomain) -> RestoredOrNewRtc<RtcClkLse> {
100+
if !Self::is_enabled() {
101+
RestoredOrNewRtc::New(Rtc::new(regs, bkp))
102+
} else {
103+
RestoredOrNewRtc::Restored(Rtc {
104+
regs,
105+
_clock_source: PhantomData,
106+
})
107+
}
108+
}
109+
110+
/// Returns whether the RTC is currently enabled and LSE is selected.
111+
fn is_enabled() -> bool {
112+
let rcc = unsafe { &*RCC::ptr() };
113+
rcc.bdcr.read().rtcen().bit() && rcc.bdcr.read().rtcsel().is_lse()
114+
}
115+
75116
/// Enables the RTC device with the lse as the clock
76117
fn enable_rtc(_bkp: &mut BackupDomain) {
77118
// NOTE: Safe RCC access because we are only accessing bdcr
@@ -93,6 +134,21 @@ impl Rtc<RtcClkLse> {
93134
}
94135

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

171+
/// Tries to obtain currently running RTC to prevent reset in case it was running from VBAT.
172+
/// If the RTC is not running, or is not LSI, it will be reinitialized.
173+
pub fn restore_or_new_lsi(regs: RTC, bkp: &mut BackupDomain) -> RestoredOrNewRtc<RtcClkLsi> {
174+
if !Rtc::<RtcClkLsi>::is_enabled() {
175+
RestoredOrNewRtc::New(Rtc::new_lsi(regs, bkp))
176+
} else {
177+
RestoredOrNewRtc::Restored(Rtc {
178+
regs,
179+
_clock_source: PhantomData,
180+
})
181+
}
182+
}
183+
184+
/// Returns whether the RTC is currently enabled and LSI is selected.
185+
fn is_enabled() -> bool {
186+
let rcc = unsafe { &*RCC::ptr() };
187+
rcc.bdcr.read().rtcen().bit() && rcc.bdcr.read().rtcsel().is_lsi()
188+
}
189+
115190
/// Enables the RTC device with the lsi as the clock
116191
fn enable_rtc(_bkp: &mut BackupDomain) {
117192
// NOTE: Safe RCC access because we are only accessing bdcr
@@ -136,6 +211,22 @@ impl Rtc<RtcClkLsi> {
136211
}
137212

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

249+
/// Tries to obtain currently running RTC to prevent reset in case it was running from VBAT.
250+
/// If the RTC is not running, or is not HSE, it will be reinitialized.
251+
pub fn restore_or_new_hse(
252+
regs: RTC,
253+
bkp: &mut BackupDomain,
254+
hse: Hertz,
255+
) -> RestoredOrNewRtc<RtcClkHseDiv128> {
256+
if !Self::is_enabled() {
257+
RestoredOrNewRtc::New(Rtc::new_hse(regs, bkp, hse))
258+
} else {
259+
RestoredOrNewRtc::Restored(Rtc {
260+
regs,
261+
_clock_source: PhantomData,
262+
})
263+
}
264+
}
265+
266+
fn is_enabled() -> bool {
267+
let rcc = unsafe { &*RCC::ptr() };
268+
rcc.bdcr.read().rtcen().bit() && rcc.bdcr.read().rtcsel().is_hse()
269+
}
270+
158271
/// Enables the RTC device with the lsi as the clock
159272
fn enable_rtc(_bkp: &mut BackupDomain) {
160273
// NOTE: Safe RCC access because we are only accessing bdcr

0 commit comments

Comments
 (0)