aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2021-02-20 01:51:53 +0100
committerGitHub <[email protected]>2021-02-20 01:51:53 +0100
commit67c03e1a383f56b1f6ca126f29cc8a806ed31a24 (patch)
tree36953d9b0ef48246666b9f1d1f0f5845cd8d19da
parent68a345eff88f94ae9f76040d625fe3516124815a (diff)
parent91aaea761e5ab6117fb3613d5e0e20308ee65343 (diff)
Merge pull request #47 from akiles/simpler-rtc
SImplify rtc overflow handling
-rw-r--r--embassy-nrf/src/rtc.rs54
1 files changed, 38 insertions, 16 deletions
diff --git a/embassy-nrf/src/rtc.rs b/embassy-nrf/src/rtc.rs
index 616254025..1ddc460e2 100644
--- a/embassy-nrf/src/rtc.rs
+++ b/embassy-nrf/src/rtc.rs
@@ -1,6 +1,6 @@
1use core::cell::Cell; 1use core::cell::Cell;
2use core::ops::Deref; 2use core::ops::Deref;
3use core::sync::atomic::{AtomicU32, Ordering}; 3use core::sync::atomic::{compiler_fence, AtomicU32, Ordering};
4 4
5use embassy::time::Clock; 5use embassy::time::Clock;
6 6
@@ -8,10 +8,26 @@ use crate::interrupt;
8use crate::interrupt::{CriticalSection, Mutex, OwnedInterrupt}; 8use crate::interrupt::{CriticalSection, Mutex, OwnedInterrupt};
9use crate::pac::rtc0; 9use crate::pac::rtc0;
10 10
11// RTC timekeeping works with something we call "periods", which are time intervals
12// of 2^23 ticks. The RTC counter value is 24 bits, so one "overflow cycle" is 2 periods.
13//
14// A `period` count is maintained in parallel to the RTC hardware `counter`, like this:
15// - `period` and `counter` start at 0
16// - `period` is incremented on overflow (at counter value 0)
17// - `period` is incremented "midway" between overflows (at counter value 0x800000)
18//
19// Therefore, when `period` is even, counter is in 0..0x7fffff. When odd, counter is in 0x800000..0xFFFFFF
20// This allows for now() to return the correct value even if it races an overflow.
21//
22// To get `now()`, `period` is read first, then `counter` is read. If the counter value matches
23// the expected range for the `period` parity, we're done. If it doesn't, this means that
24// a new period start has raced us between reading `period` and `counter`, so we assume the `counter` value
25// corresponds to the next period.
26//
27// `period` is a 32bit integer, so It overflows on 2^32 * 2^23 / 32768 seconds of uptime, which is 34865 years.
28
11fn calc_now(period: u32, counter: u32) -> u64 { 29fn calc_now(period: u32, counter: u32) -> u64 {
12 let shift = ((period & 1) << 23) + 0x400000; 30 ((period as u64) << 23) + ((counter ^ ((period & 1) << 23)) as u64)
13 let counter_shifted = (counter + shift) & 0xFFFFFF;
14 ((period as u64) << 23) + counter_shifted as u64 - 0x400000
15} 31}
16 32
17fn compare_n(n: usize) -> u32 { 33fn compare_n(n: usize) -> u32 {
@@ -27,12 +43,12 @@ mod test {
27 assert_eq!(calc_now(0, 0x000000), 0x0_000000); 43 assert_eq!(calc_now(0, 0x000000), 0x0_000000);
28 assert_eq!(calc_now(0, 0x000001), 0x0_000001); 44 assert_eq!(calc_now(0, 0x000001), 0x0_000001);
29 assert_eq!(calc_now(0, 0x7FFFFF), 0x0_7FFFFF); 45 assert_eq!(calc_now(0, 0x7FFFFF), 0x0_7FFFFF);
30 assert_eq!(calc_now(1, 0x7FFFFF), 0x0_7FFFFF); 46 assert_eq!(calc_now(1, 0x7FFFFF), 0x1_7FFFFF);
31 assert_eq!(calc_now(0, 0x800000), 0x0_800000); 47 assert_eq!(calc_now(0, 0x800000), 0x0_800000);
32 assert_eq!(calc_now(1, 0x800000), 0x0_800000); 48 assert_eq!(calc_now(1, 0x800000), 0x0_800000);
33 assert_eq!(calc_now(1, 0x800001), 0x0_800001); 49 assert_eq!(calc_now(1, 0x800001), 0x0_800001);
34 assert_eq!(calc_now(1, 0xFFFFFF), 0x0_FFFFFF); 50 assert_eq!(calc_now(1, 0xFFFFFF), 0x0_FFFFFF);
35 assert_eq!(calc_now(2, 0xFFFFFF), 0x0_FFFFFF); 51 assert_eq!(calc_now(2, 0xFFFFFF), 0x1_FFFFFF);
36 assert_eq!(calc_now(1, 0x000000), 0x1_000000); 52 assert_eq!(calc_now(1, 0x000000), 0x1_000000);
37 assert_eq!(calc_now(2, 0x000000), 0x1_000000); 53 assert_eq!(calc_now(2, 0x000000), 0x1_000000);
38 } 54 }
@@ -59,15 +75,6 @@ pub struct RTC<T: Instance> {
59 irq: T::Interrupt, 75 irq: T::Interrupt,
60 76
61 /// Number of 2^23 periods elapsed since boot. 77 /// Number of 2^23 periods elapsed since boot.
62 ///
63 /// This is incremented by 1
64 /// - on overflow (counter value 0)
65 /// - on "midway" between overflows (at counter value 0x800000)
66 ///
67 /// Therefore: When even, counter is in 0..0x7fffff. When odd, counter is in 0x800000..0xFFFFFF
68 /// This allows for now() to return the correct value even if it races an overflow.
69 ///
70 /// It overflows on 2^32 * 2^23 / 32768 seconds of uptime, which is 34865 years.
71 period: AtomicU32, 78 period: AtomicU32,
72 79
73 /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. 80 /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
@@ -177,21 +184,34 @@ impl<T: Instance> RTC<T> {
177 alarm.timestamp.set(timestamp); 184 alarm.timestamp.set(timestamp);
178 185
179 let t = self.now(); 186 let t = self.now();
187
188 // If alarm timestamp has passed, trigger it instantly.
180 if timestamp <= t { 189 if timestamp <= t {
181 self.trigger_alarm(n, cs); 190 self.trigger_alarm(n, cs);
182 return; 191 return;
183 } 192 }
184 193
194 // If it hasn't triggered yet, setup it in the compare channel.
185 let diff = timestamp - t; 195 let diff = timestamp - t;
186 if diff < 0xc00000 { 196 if diff < 0xc00000 {
187 // nrf52 docs say: 197 // nrf52 docs say:
188 // If the COUNTER is N, writing N or N+1 to a CC register may not trigger a COMPARE event. 198 // If the COUNTER is N, writing N or N+1 to a CC register may not trigger a COMPARE event.
189 // To workaround this, we never write a timestamp smaller than N+3. 199 // To workaround this, we never write a timestamp smaller than N+3.
190 // N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc. 200 // N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc.
201 //
202 // It is impossible for rtc to tick more than once because
203 // - this code takes less time than 1 tick
204 // - it runs with interrupts disabled so nothing else can preempt it.
205 //
206 // This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
207 // by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
208 // and we don't do that here.
191 let safe_timestamp = timestamp.max(t + 3); 209 let safe_timestamp = timestamp.max(t + 3);
192 self.rtc.cc[n].write(|w| unsafe { w.bits(safe_timestamp as u32 & 0xFFFFFF) }); 210 self.rtc.cc[n].write(|w| unsafe { w.bits(safe_timestamp as u32 & 0xFFFFFF) });
193 self.rtc.intenset.write(|w| unsafe { w.bits(compare_n(n)) }); 211 self.rtc.intenset.write(|w| unsafe { w.bits(compare_n(n)) });
194 } else { 212 } else {
213 // If it's too far in the future, don't setup the compare channel yet.
214 // It will be setup later by `next_period`.
195 self.rtc.intenclr.write(|w| unsafe { w.bits(compare_n(n)) }); 215 self.rtc.intenclr.write(|w| unsafe { w.bits(compare_n(n)) });
196 } 216 }
197 }) 217 })
@@ -210,8 +230,10 @@ impl<T: Instance> RTC<T> {
210 230
211impl<T: Instance> embassy::time::Clock for RTC<T> { 231impl<T: Instance> embassy::time::Clock for RTC<T> {
212 fn now(&self) -> u64 { 232 fn now(&self) -> u64 {
213 let counter = self.rtc.counter.read().bits(); 233 // `period` MUST be read before `counter`, see comment at the top for details.
214 let period = self.period.load(Ordering::Relaxed); 234 let period = self.period.load(Ordering::Relaxed);
235 compiler_fence(Ordering::Acquire);
236 let counter = self.rtc.counter.read().bits();
215 calc_now(period, counter) 237 calc_now(period, counter)
216 } 238 }
217} 239}