Provides an HFCLK timer driver

The existing (default) timer driver for nRF implements one using the RTC, which uses the LFCLK. An additional feature has been provided where the HFCLK can be used for the timer driver by using TIMER1. An STM example has also been ported so that either driver implementation can be exercised.

To use the HFCLK driver, simply use the `time-driver-timer1` feature in place of the `time-driver-rtc1` one when depending on embassy-nrf.
This commit is contained in:
huntc 2022-02-18 16:00:40 +11:00
parent 59f909e665
commit a5f7801d7b
4 changed files with 412 additions and 175 deletions

View File

@ -34,6 +34,7 @@ nrf9160-ns = ["_nrf9160"]
gpiote = [] gpiote = []
time-driver-rtc1 = ["_time-driver"] time-driver-rtc1 = ["_time-driver"]
time-driver-timer1 = ["_time-driver"]
# Features starting with `_` are for internal use only. They're not intended # Features starting with `_` are for internal use only. They're not intended
# to be enabled by other crates, and are not covered by semver guarantees. # to be enabled by other crates, and are not covered by semver guarantees.

View File

@ -191,7 +191,7 @@ pub fn init(config: config::Config) -> Peripherals {
// init RTC time driver // init RTC time driver
#[cfg(feature = "_time-driver")] #[cfg(feature = "_time-driver")]
time_driver::init(config.time_interrupt_priority); time_driver::driver_impl::init(config.time_interrupt_priority);
peripherals peripherals
} }

View File

@ -10,10 +10,6 @@ use embassy::time::driver::{AlarmHandle, Driver};
use crate::interrupt; use crate::interrupt;
use crate::pac; use crate::pac;
fn rtc() -> &'static pac::rtc0::RegisterBlock {
unsafe { &*pac::RTC1::ptr() }
}
// RTC timekeeping works with something we call "periods", which are time intervals // RTC timekeeping works with something we call "periods", which are time intervals
// of 2^23 ticks. The RTC counter value is 24 bits, so one "overflow cycle" is 2 periods. // of 2^23 ticks. The RTC counter value is 24 bits, so one "overflow cycle" is 2 periods.
// //
@ -83,194 +79,407 @@ impl AlarmState {
const ALARM_COUNT: usize = 3; const ALARM_COUNT: usize = 3;
struct RtcDriver { #[cfg(feature = "time-driver-rtc1")]
/// Number of 2^23 periods elapsed since boot. pub mod driver_impl {
period: AtomicU32, use super::*;
alarm_count: AtomicU8,
/// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
alarms: Mutex<[AlarmState; ALARM_COUNT]>,
}
const ALARM_STATE_NEW: AlarmState = AlarmState::new(); fn rtc() -> &'static pac::rtc0::RegisterBlock {
embassy::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { unsafe { &*pac::RTC1::ptr() }
period: AtomicU32::new(0),
alarm_count: AtomicU8::new(0),
alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [ALARM_STATE_NEW; ALARM_COUNT]),
});
impl RtcDriver {
fn init(&'static self, irq_prio: crate::interrupt::Priority) {
let r = rtc();
r.cc[3].write(|w| unsafe { w.bits(0x800000) });
r.intenset.write(|w| {
let w = w.ovrflw().set();
let w = w.compare3().set();
w
});
r.tasks_clear.write(|w| unsafe { w.bits(1) });
r.tasks_start.write(|w| unsafe { w.bits(1) });
// Wait for clear
while r.counter.read().bits() != 0 {}
let irq = unsafe { interrupt::RTC1::steal() };
irq.set_priority(irq_prio);
irq.enable();
} }
fn on_interrupt(&self) { /// Provides a Real Time Counter using the LFCLK.
let r = rtc(); struct RtcDriver {
if r.events_ovrflw.read().bits() == 1 { /// Number of 2^23 periods elapsed since boot.
r.events_ovrflw.write(|w| w); period: AtomicU32,
self.next_period(); alarm_count: AtomicU8,
} /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
alarms: Mutex<[AlarmState; ALARM_COUNT]>,
if r.events_compare[3].read().bits() == 1 {
r.events_compare[3].write(|w| w);
self.next_period();
}
for n in 0..ALARM_COUNT {
if r.events_compare[n].read().bits() == 1 {
r.events_compare[n].write(|w| w);
critical_section::with(|cs| {
self.trigger_alarm(n, cs);
})
}
}
} }
fn next_period(&self) { const ALARM_STATE_NEW: AlarmState = AlarmState::new();
critical_section::with(|cs| { embassy::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver {
period: AtomicU32::new(0),
alarm_count: AtomicU8::new(0),
alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [ALARM_STATE_NEW; ALARM_COUNT]),
});
impl RtcDriver {
fn init(&'static self, irq_prio: crate::interrupt::Priority) {
let r = rtc(); let r = rtc();
let period = self.period.fetch_add(1, Ordering::Relaxed) + 1; r.cc[3].write(|w| unsafe { w.bits(0x800000) });
let t = (period as u64) << 23;
for n in 0..ALARM_COUNT { r.intenset.write(|w| {
let alarm = &self.alarms.borrow(cs)[n]; let w = w.ovrflw().set();
let at = alarm.timestamp.get(); let w = w.compare3().set();
w
if at < t + 0xc00000 {
// just enable it. `set_alarm` has already set the correct CC val.
r.intenset.write(|w| unsafe { w.bits(compare_n(n)) });
}
}
})
}
fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState {
// safety: we're allowed to assume the AlarmState is created by us, and
// we never create one that's out of bounds.
unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) }
}
fn trigger_alarm(&self, n: usize, cs: CriticalSection) {
let r = rtc();
r.intenclr.write(|w| unsafe { w.bits(compare_n(n)) });
let alarm = &self.alarms.borrow(cs)[n];
alarm.timestamp.set(u64::MAX);
// Call after clearing alarm, so the callback can set another alarm.
// safety:
// - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`.
// - other than that we only store valid function pointers into alarm.callback
let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) };
f(alarm.ctx.get());
}
}
impl Driver for RtcDriver {
fn now(&self) -> u64 {
// `period` MUST be read before `counter`, see comment at the top for details.
let period = self.period.load(Ordering::Relaxed);
compiler_fence(Ordering::Acquire);
let counter = rtc().counter.read().bits();
calc_now(period, counter)
}
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
let id = self
.alarm_count
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| {
if x < ALARM_COUNT as u8 {
Some(x + 1)
} else {
None
}
}); });
match id { r.tasks_clear.write(|w| unsafe { w.bits(1) });
Ok(id) => Some(AlarmHandle::new(id)), r.tasks_start.write(|w| unsafe { w.bits(1) });
Err(_) => None,
// Wait for clear
while r.counter.read().bits() != 0 {}
let irq = unsafe { interrupt::RTC1::steal() };
irq.set_priority(irq_prio);
irq.enable();
}
fn on_interrupt(&self) {
let r = rtc();
if r.events_ovrflw.read().bits() == 1 {
r.events_ovrflw.write(|w| w);
self.next_period();
}
if r.events_compare[3].read().bits() == 1 {
r.events_compare[3].write(|w| w);
self.next_period();
}
for n in 0..ALARM_COUNT {
if r.events_compare[n].read().bits() == 1 {
r.events_compare[n].write(|w| w);
critical_section::with(|cs| {
self.trigger_alarm(n, cs);
})
}
}
}
fn next_period(&self) {
critical_section::with(|cs| {
let r = rtc();
let period = self.period.fetch_add(1, Ordering::Relaxed) + 1;
let t = (period as u64) << 23;
for n in 0..ALARM_COUNT {
let alarm = &self.alarms.borrow(cs)[n];
let at = alarm.timestamp.get();
if at < t + 0xc00000 {
// just enable it. `set_alarm` has already set the correct CC val.
r.intenset.write(|w| unsafe { w.bits(compare_n(n)) });
}
}
})
}
fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState {
// safety: we're allowed to assume the AlarmState is created by us, and
// we never create one that's out of bounds.
unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) }
}
fn trigger_alarm(&self, n: usize, cs: CriticalSection) {
let r = rtc();
r.intenclr.write(|w| unsafe { w.bits(compare_n(n)) });
let alarm = &self.alarms.borrow(cs)[n];
alarm.timestamp.set(u64::MAX);
// Call after clearing alarm, so the callback can set another alarm.
// safety:
// - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`.
// - other than that we only store valid function pointers into alarm.callback
let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) };
f(alarm.ctx.get());
} }
} }
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { impl Driver for RtcDriver {
critical_section::with(|cs| { fn now(&self) -> u64 {
let alarm = self.get_alarm(cs, alarm); // `period` MUST be read before `counter`, see comment at the top for details.
let period = self.period.load(Ordering::Relaxed);
compiler_fence(Ordering::Acquire);
let counter = rtc().counter.read().bits();
calc_now(period, counter)
}
alarm.callback.set(callback as *const ()); unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
alarm.ctx.set(ctx); let id = self
}) .alarm_count
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| {
if x < ALARM_COUNT as u8 {
Some(x + 1)
} else {
None
}
});
match id {
Ok(id) => Some(AlarmHandle::new(id)),
Err(_) => None,
}
}
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
critical_section::with(|cs| {
let alarm = self.get_alarm(cs, alarm);
alarm.callback.set(callback as *const ());
alarm.ctx.set(ctx);
})
}
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) {
critical_section::with(|cs| {
let n = alarm.id() as _;
let alarm = self.get_alarm(cs, alarm);
alarm.timestamp.set(timestamp);
let t = self.now();
// If alarm timestamp has passed, trigger it instantly.
if timestamp <= t {
self.trigger_alarm(n, cs);
return;
}
let r = rtc();
// If it hasn't triggered yet, setup it in the compare channel.
// Write the CC value regardless of whether we're going to enable it now or not.
// This way, when we enable it later, the right value is already set.
// nrf52 docs say:
// If the COUNTER is N, writing N or N+1 to a CC register may not trigger a COMPARE event.
// To workaround this, we never write a timestamp smaller than N+3.
// N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc.
//
// It is impossible for rtc to tick more than once because
// - this code takes less time than 1 tick
// - it runs with interrupts disabled so nothing else can preempt it.
//
// This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
// by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
// and we don't do that here.
let safe_timestamp = timestamp.max(t + 3);
r.cc[n].write(|w| unsafe { w.bits(safe_timestamp as u32 & 0xFFFFFF) });
let diff = timestamp - t;
if diff < 0xc00000 {
r.intenset.write(|w| unsafe { w.bits(compare_n(n)) });
} else {
// If it's too far in the future, don't setup the compare channel yet.
// It will be setup later by `next_period`.
r.intenclr.write(|w| unsafe { w.bits(compare_n(n)) });
}
})
}
} }
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { #[interrupt]
critical_section::with(|cs| { fn RTC1() {
let n = alarm.id() as _; DRIVER.on_interrupt()
let alarm = self.get_alarm(cs, alarm); }
alarm.timestamp.set(timestamp);
let t = self.now(); pub(crate) fn init(irq_prio: crate::interrupt::Priority) {
DRIVER.init(irq_prio)
// If alarm timestamp has passed, trigger it instantly.
if timestamp <= t {
self.trigger_alarm(n, cs);
return;
}
let r = rtc();
// If it hasn't triggered yet, setup it in the compare channel.
// Write the CC value regardless of whether we're going to enable it now or not.
// This way, when we enable it later, the right value is already set.
// nrf52 docs say:
// If the COUNTER is N, writing N or N+1 to a CC register may not trigger a COMPARE event.
// To workaround this, we never write a timestamp smaller than N+3.
// N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc.
//
// It is impossible for rtc to tick more than once because
// - this code takes less time than 1 tick
// - it runs with interrupts disabled so nothing else can preempt it.
//
// This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
// by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
// and we don't do that here.
let safe_timestamp = timestamp.max(t + 3);
r.cc[n].write(|w| unsafe { w.bits(safe_timestamp as u32 & 0xFFFFFF) });
let diff = timestamp - t;
if diff < 0xc00000 {
r.intenset.write(|w| unsafe { w.bits(compare_n(n)) });
} else {
// If it's too far in the future, don't setup the compare channel yet.
// It will be setup later by `next_period`.
r.intenclr.write(|w| unsafe { w.bits(compare_n(n)) });
}
})
} }
} }
#[interrupt] #[cfg(feature = "time-driver-timer1")]
fn RTC1() { pub mod driver_impl {
DRIVER.on_interrupt() use crate::timer::Frequency;
}
pub(crate) fn init(irq_prio: crate::interrupt::Priority) { use super::*;
DRIVER.init(irq_prio)
fn timer() -> &'static pac::timer0::RegisterBlock {
unsafe { &*pac::TIMER1::ptr() }
}
/// Provides a Real Time Counter using the HFCLK. Uses
/// the TIMER peripheral in counter mode.
struct TimerDriver {
/// Number of 2^23 periods elapsed since boot.
period: AtomicU32,
alarm_count: AtomicU8,
/// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
alarms: Mutex<[AlarmState; ALARM_COUNT]>,
}
const ALARM_STATE_NEW: AlarmState = AlarmState::new();
embassy::time_driver_impl!(static DRIVER: TimerDriver = TimerDriver {
period: AtomicU32::new(0),
alarm_count: AtomicU8::new(0),
alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [ALARM_STATE_NEW; ALARM_COUNT]),
});
impl TimerDriver {
fn init(&'static self, irq_prio: crate::interrupt::Priority) {
let r = timer();
r.mode.write(|w| w.mode().timer());
r.prescaler
.write(|w| unsafe { w.prescaler().bits(Frequency::F62500Hz as u8) });
r.bitmode.write(|w| w.bitmode()._32bit());
r.cc[3].write(|w| unsafe { w.bits(0x800000) });
r.intenset.write(|w| w.compare3().set());
r.shorts
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << 3)) });
r.tasks_clear.write(|w| unsafe { w.bits(1) });
r.tasks_start.write(|w| unsafe { w.bits(1) });
let irq = unsafe { interrupt::TIMER1::steal() };
irq.set_priority(irq_prio);
irq.enable();
}
fn on_interrupt(&self) {
let r = timer();
if r.events_compare[3].read().bits() == 1 {
r.events_compare[3].write(|w| w);
self.next_period();
}
for n in 0..ALARM_COUNT {
if r.events_compare[n].read().bits() == 1 {
r.events_compare[n].write(|w| w);
critical_section::with(|cs| {
self.trigger_alarm(n, cs);
})
}
}
}
fn next_period(&self) {
critical_section::with(|cs| {
let r = timer();
let period = self.period.fetch_add(1, Ordering::Relaxed) + 1;
let t = (period as u64) << 23;
for n in 0..ALARM_COUNT {
let alarm = &self.alarms.borrow(cs)[n];
let at = alarm.timestamp.get();
if at < t + 0xc00000 {
// just enable it. `set_alarm` has already set the correct CC val.
r.intenset.write(|w| unsafe { w.bits(compare_n(n)) });
}
}
})
}
fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState {
// safety: we're allowed to assume the AlarmState is created by us, and
// we never create one that's out of bounds.
unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) }
}
fn trigger_alarm(&self, n: usize, cs: CriticalSection) {
let r = timer();
r.intenclr.write(|w| unsafe { w.bits(compare_n(n)) });
let alarm = &self.alarms.borrow(cs)[n];
alarm.timestamp.set(u64::MAX);
// Call after clearing alarm, so the callback can set another alarm.
// safety:
// - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`.
// - other than that we only store valid function pointers into alarm.callback
let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) };
f(alarm.ctx.get());
}
}
impl Driver for TimerDriver {
fn now(&self) -> u64 {
// `period` MUST be read before `counter`, see comment at the top for details.
let period = self.period.load(Ordering::Relaxed);
compiler_fence(Ordering::Acquire);
let r = timer();
r.tasks_capture[3].write(|w| unsafe { w.bits(1) });
let counter = r.cc[3].read().bits();
calc_now(period, counter)
}
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
let id = self
.alarm_count
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| {
if x < ALARM_COUNT as u8 {
Some(x + 1)
} else {
None
}
});
match id {
Ok(id) => Some(AlarmHandle::new(id)),
Err(_) => None,
}
}
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
critical_section::with(|cs| {
let alarm = self.get_alarm(cs, alarm);
alarm.callback.set(callback as *const ());
alarm.ctx.set(ctx);
})
}
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) {
critical_section::with(|cs| {
let n = alarm.id() as _;
let alarm = self.get_alarm(cs, alarm);
alarm.timestamp.set(timestamp);
let t = self.now();
// If alarm timestamp has passed, trigger it instantly.
if timestamp <= t {
self.trigger_alarm(n, cs);
return;
}
let r = timer();
// If it hasn't triggered yet, setup it in the compare channel.
// Write the CC value regardless of whether we're going to enable it now or not.
// This way, when we enable it later, the right value is already set.
// nrf52 docs say:
// If the COUNTER is N, writing N or N+1 to a CC register may not trigger a COMPARE event.
// To workaround this, we never write a timestamp smaller than N+3.
// N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc.
//
// It is impossible for rtc to tick more than once because
// - this code takes less time than 1 tick
// - it runs with interrupts disabled so nothing else can preempt it.
//
// This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
// by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
// and we don't do that here.
let safe_timestamp = timestamp.max(t + 3);
r.cc[n].write(|w| unsafe { w.bits(safe_timestamp as u32 & 0xFFFFFF) });
let diff = timestamp - t;
if diff < 0xc00000 {
r.intenset.write(|w| unsafe { w.bits(compare_n(n)) });
} else {
// If it's too far in the future, don't setup the compare channel yet.
// It will be setup later by `next_period`.
r.intenclr.write(|w| unsafe { w.bits(compare_n(n)) });
}
})
}
}
#[interrupt]
fn TIMER1() {
DRIVER.on_interrupt()
}
pub(crate) fn init(irq_prio: crate::interrupt::Priority) {
DRIVER.init(irq_prio)
}
} }

View File

@ -0,0 +1,27 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
#[path = "../example_common.rs"]
mod example_common;
use defmt::assert;
use embassy::executor::Spawner;
use embassy::time::{Duration, Instant, Timer};
use embassy_nrf::Peripherals;
use example_common::*;
#[embassy::main]
async fn main(_spawner: Spawner, _p: Peripherals) {
info!("Hello World!");
let start = Instant::now();
Timer::after(Duration::from_millis(100)).await;
let end = Instant::now();
let ms = (end - start).as_millis();
info!("slept for {} ms", ms);
assert!(ms >= 99);
assert!(ms < 110);
info!("Test OK");
cortex_m::asm::bkpt();
}