diff options
| author | Dario Nieuwenhuis <[email protected]> | 2021-02-20 01:51:53 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2021-02-20 01:51:53 +0100 |
| commit | 67c03e1a383f56b1f6ca126f29cc8a806ed31a24 (patch) | |
| tree | 36953d9b0ef48246666b9f1d1f0f5845cd8d19da | |
| parent | 68a345eff88f94ae9f76040d625fe3516124815a (diff) | |
| parent | 91aaea761e5ab6117fb3613d5e0e20308ee65343 (diff) | |
Merge pull request #47 from akiles/simpler-rtc
SImplify rtc overflow handling
| -rw-r--r-- | embassy-nrf/src/rtc.rs | 54 |
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 @@ | |||
| 1 | use core::cell::Cell; | 1 | use core::cell::Cell; |
| 2 | use core::ops::Deref; | 2 | use core::ops::Deref; |
| 3 | use core::sync::atomic::{AtomicU32, Ordering}; | 3 | use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; |
| 4 | 4 | ||
| 5 | use embassy::time::Clock; | 5 | use embassy::time::Clock; |
| 6 | 6 | ||
| @@ -8,10 +8,26 @@ use crate::interrupt; | |||
| 8 | use crate::interrupt::{CriticalSection, Mutex, OwnedInterrupt}; | 8 | use crate::interrupt::{CriticalSection, Mutex, OwnedInterrupt}; |
| 9 | use crate::pac::rtc0; | 9 | use 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 | |||
| 11 | fn calc_now(period: u32, counter: u32) -> u64 { | 29 | fn 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 | ||
| 17 | fn compare_n(n: usize) -> u32 { | 33 | fn 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 | ||
| 211 | impl<T: Instance> embassy::time::Clock for RTC<T> { | 231 | impl<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 | } |
