diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml index b7bdf83..ab16570 100644 --- a/examples/embassy/Cargo.toml +++ b/examples/embassy/Cargo.toml @@ -9,9 +9,11 @@ cortex-m-rt = "0.7" embedded-hal = "1" rtt-target = { version = "0.5" } panic-rtt-target = { version = "0.1" } +critical-section = "1" embassy-sync = { version = "0.6.0" } embassy-time = { version = "0.3.2", features = ["tick-hz-32_768"] } +embassy-time-driver = { version = "0.1" } [dependencies.embassy-executor] version = "0.6.0" diff --git a/examples/embassy/src/lib.rs b/examples/embassy/src/lib.rs new file mode 100644 index 0000000..11a7f9d --- /dev/null +++ b/examples/embassy/src/lib.rs @@ -0,0 +1,202 @@ +#![no_std] +use core::{ + cell::Cell, + mem, ptr, + sync::atomic::{AtomicU32, AtomicU8, Ordering}, u32, +}; +use critical_section::CriticalSection; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex; + +use embassy_time_driver::{time_driver_impl, AlarmHandle, Driver}; +use va416xx_hal::pac; + +fn alarm_tim() -> &'static pac::tim0::RegisterBlock { + unsafe { &*pac::Tim23::ptr() } +} + +fn timekeeping_tim() -> &'static pac::tim0::RegisterBlock { + unsafe { &*pac::Tim23::ptr() } +} + +struct AlarmState { + timestamp: Cell, + + // This is really a Option<(fn(*mut ()), *mut ())> + // but fn pointers aren't allowed in const yet + callback: Cell<*const ()>, + ctx: Cell<*mut ()>, +} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: Cell::new(u64::MAX), + callback: Cell::new(ptr::null()), + ctx: Cell::new(ptr::null_mut()), + } + } +} + +unsafe impl Send for AlarmState {} + +const ALARM_COUNT: usize = 1; + +pub struct EmbassyVa416xxTimeDriver { + periods: AtomicU32, + alarm_count: AtomicU8, + /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. + alarms: Mutex, +} + +impl EmbassyVa416xxTimeDriver { + fn on_interrupt_timekeeping(&self) { + self.next_period(); + } + + fn on_interrupt_alarm(&self, idx: usize) { + critical_section::with(|cs| { + let alarm = &self.alarms.borrow(cs)[idx]; + let at = alarm.timestamp.get(); + let period = self.periods.load(Ordering::Relaxed); + let t = (period as u64) << 32; + + if at < t + u32::MAX as u64 { + //alarm_tim(). + // just enable it. `set_alarm` has already set the correct CC val. + alarm_tim().ctrl().modify(|_, w| w.irq_enb().set_bit()); + } + }) + } + + fn next_period(&self) { + self.periods.fetch_add(1, Ordering::Release); + } + + 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 = alarm_tim(); + r.ctrl().modify(|_, w| w.irq_enb().clear_bit()); + + let alarm = &self.alarms.borrow(cs)[n]; + // Setting the maximum value disables the alarm. + 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 EmbassyVa416xxTimeDriver { + fn now(&self) -> u64 { + let mut period1: u32; + let mut period2: u32; + let mut counter_val: u32; + loop { + // Acquire ensures that we get the latest value of `periods` and + // no instructions can be reordered before the load. + period1 = self.periods.load(Ordering::Acquire); + + counter_val = timekeeping_tim().cnt_value().read().bits(); + + period2 = self.periods.load(Ordering::Acquire); + if period1 == period2 { + break; + } + } + ((period1 as u64) << 32) | counter_val as u64 + } + + unsafe fn allocate_alarm(&self) -> Option { + critical_section::with(|_| { + let id = self.alarm_count.load(Ordering::Relaxed); + if id < ALARM_COUNT as u8 { + self.alarm_count.store(id + 1, Ordering::Relaxed); + Some(AlarmHandle::new(id)) + } else { + None + } + }) + } + + fn set_alarm_callback( + &self, + alarm: embassy_time_driver::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: embassy_time_driver::AlarmHandle, timestamp: u64) -> bool { + critical_section::with(|cs| { + let n = alarm.id() as _; + let alarm = self.get_alarm(cs, alarm); + alarm.timestamp.set(timestamp); + + let r = alarm_tim(); + + let t = self.now(); + if timestamp <= t { + r.ctrl().modify(|_, w| w.irq_enb().clear_bit()); + + alarm.timestamp.set(u64::MAX); + + return false; + } + + // 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)) }); + } + + true + }) + } +} + +time_driver_impl!( + static DRIVER: EmbassyVa416xxTimeDriver = EmbassyVa416xxTimeDriver { + periods: AtomicU32::new(0), + alarm_count: AtomicU8::new(0), + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [AlarmState::new(); ALARM_COUNT]) +});