diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 8f8dd753..7bcae9f9 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -17,6 +17,8 @@ pub mod pwr; pub mod rcc; #[cfg(feature = "_rng")] pub mod rng; +#[cfg(feature = "_timer")] +pub mod rtc; #[cfg(feature = "_sdmmc")] pub mod sdmmc; #[cfg(feature = "_spi")] diff --git a/embassy-stm32/src/rtc.rs b/embassy-stm32/src/rtc.rs new file mode 100644 index 00000000..1ccac0e2 --- /dev/null +++ b/embassy-stm32/src/rtc.rs @@ -0,0 +1,585 @@ +use core::cell::Cell; +use core::convert::TryInto; +use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; + +use embassy::interrupt::InterruptExt; +use embassy::time::{Clock, TICKS_PER_SECOND}; +use embassy::util::AtomicWaker; + +use crate::interrupt::{CriticalSection, Interrupt, Mutex}; +use crate::pac::timer::TimGp16; +use crate::time::Hertz; + +// RTC timekeeping works with something we call "periods", which are time intervals +// of 2^15 ticks. The RTC counter value is 16 bits, so one "overflow cycle" is 2 periods. +// +// A `period` count is maintained in parallel to the RTC hardware `counter`, like this: +// - `period` and `counter` start at 0 +// - `period` is incremented on overflow (at counter value 0) +// - `period` is incremented "midway" between overflows (at counter value 0x8000) +// +// Therefore, when `period` is even, counter is in 0..0x7FFF. When odd, counter is in 0x8000..0xFFFF +// This allows for now() to return the correct value even if it races an overflow. +// +// To get `now()`, `period` is read first, then `counter` is read. If the counter value matches +// the expected range for the `period` parity, we're done. If it doesn't, this means that +// a new period start has raced us between reading `period` and `counter`, so we assume the `counter` value +// corresponds to the next period. +// +// `period` is a 32bit integer, so It overflows on 2^32 * 2^15 / 32768 seconds of uptime, which is 136 years. +fn calc_now(period: u32, counter: u16) -> u64 { + ((period as u64) << 15) + ((counter as u32 ^ ((period & 1) << 15)) as u64) +} + +struct AlarmState { + timestamp: Cell, + #[allow(clippy::type_complexity)] + callback: Cell>, +} + +impl AlarmState { + fn new() -> Self { + Self { + timestamp: Cell::new(u64::MAX), + callback: Cell::new(None), + } + } +} + +// TODO: This is sometimes wasteful, try to find a better way +const ALARM_COUNT: usize = 3; + +/// RTC timer that can be used by the executor and to set alarms. +/// +/// It can work with Timers 2, 3, 4, 5, 9 and 12. Timers 9 and 12 only have one alarm available, +/// while the others have three each. +/// This timer works internally with a unit of 2^15 ticks, which means that if a call to +/// [`embassy::time::Clock::now`] is blocked for that amount of ticks the returned value will be +/// wrong (an old value). The current default tick rate is 32768 ticks per second. +pub struct RTC { + _inner: T, + irq: T::Interrupt, + /// Number of 2^23 periods elapsed since boot. + period: AtomicU32, + /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. + alarms: Mutex<[AlarmState; ALARM_COUNT]>, +} + +impl RTC { + pub fn new(peripheral: T, irq: T::Interrupt) -> Self { + Self { + _inner: peripheral, + irq, + period: AtomicU32::new(0), + alarms: Mutex::new([AlarmState::new(), AlarmState::new(), AlarmState::new()]), + } + } + + pub fn start(&'static self, pfreq: Hertz, ppre: u8) { + let inner = T::inner(); + + // NOTE(unsafe) Critical section to use the unsafe methods + critical_section::with(|_| { + unsafe { + inner.prepare(pfreq, ppre); + } + + self.irq.set_handler_context(self as *const _ as *mut _); + self.irq.set_handler(|ptr| unsafe { + let this = &*(ptr as *const () as *const Self); + this.on_interrupt(); + }); + self.irq.unpend(); + self.irq.enable(); + + unsafe { + inner.start_counter(); + } + }) + } + + fn on_interrupt(&self) { + let inner = T::inner(); + + // NOTE(unsafe) Use critical section to access the methods + // XXX: reduce the size of this critical section ? + critical_section::with(|cs| unsafe { + if inner.overflow_interrupt_status() { + inner.overflow_clear_flag(); + self.next_period(); + } + + // Half overflow + if inner.compare_interrupt_status(0) { + inner.compare_clear_flag(0); + self.next_period(); + } + + for n in 1..=ALARM_COUNT { + if inner.compare_interrupt_status(n) { + inner.compare_clear_flag(n); + self.trigger_alarm(n, cs); + } + } + }) + } + + fn next_period(&self) { + let inner = T::inner(); + + let period = self.period.fetch_add(1, Ordering::Relaxed) + 1; + let t = (period as u64) << 15; + + critical_section::with(move |cs| { + for n in 1..=ALARM_COUNT { + let alarm = &self.alarms.borrow(cs)[n - 1]; + let at = alarm.timestamp.get(); + + let diff = at - t; + if diff < 0xc000 { + inner.set_compare(n, at as u16); + // NOTE(unsafe) We're in a critical section + unsafe { + inner.set_compare_interrupt(n, true); + } + } + } + }) + } + + fn trigger_alarm(&self, n: usize, cs: CriticalSection) { + let inner = T::inner(); + // NOTE(unsafe) We have a critical section + unsafe { + inner.set_compare_interrupt(n, false); + } + + let alarm = &self.alarms.borrow(cs)[n - 1]; + alarm.timestamp.set(u64::MAX); + + // Call after clearing alarm, so the callback can set another alarm. + if let Some((f, ctx)) = alarm.callback.get() { + f(ctx); + } + } + + fn set_alarm_callback(&self, n: usize, callback: fn(*mut ()), ctx: *mut ()) { + critical_section::with(|cs| { + let alarm = &self.alarms.borrow(cs)[n - 1]; + alarm.callback.set(Some((callback, ctx))); + }) + } + + fn set_alarm(&self, n: usize, timestamp: u64) { + critical_section::with(|cs| { + let inner = T::inner(); + + let alarm = &self.alarms.borrow(cs)[n - 1]; + alarm.timestamp.set(timestamp); + + let t = self.now(); + if timestamp <= t { + self.trigger_alarm(n, cs); + return; + } + + let diff = timestamp - t; + if diff < 0xc000 { + let safe_timestamp = timestamp.max(t + 3); + inner.set_compare(n, safe_timestamp as u16); + + // NOTE(unsafe) We're in a critical section + unsafe { + inner.set_compare_interrupt(n, true); + } + } else { + unsafe { + inner.set_compare_interrupt(n, false); + } + } + }) + } + + pub fn alarm1(&'static self) -> Alarm { + Alarm { n: 1, rtc: self } + } + pub fn alarm2(&'static self) -> Alarm { + Alarm { n: 2, rtc: self } + } + pub fn alarm3(&'static self) -> Alarm { + Alarm { n: 3, rtc: self } + } +} + +impl embassy::time::Clock for RTC { + fn now(&self) -> u64 { + let inner = T::inner(); + + let period = self.period.load(Ordering::Relaxed); + compiler_fence(Ordering::Acquire); + let counter = inner.counter(); + calc_now(period, counter) + } +} + +pub struct Alarm { + n: usize, + rtc: &'static RTC, +} + +impl embassy::time::Alarm for Alarm { + fn set_callback(&self, callback: fn(*mut ()), ctx: *mut ()) { + self.rtc.set_alarm_callback(self.n, callback, ctx); + } + + fn set(&self, timestamp: u64) { + self.rtc.set_alarm(self.n, timestamp); + } + + fn clear(&self) { + self.rtc.set_alarm(self.n, u64::MAX); + } +} + +pub struct TimerInner(pub(crate) TimGp16); + +impl TimerInner { + unsafe fn prepare(&self, pfreq: Hertz, ppre: u8) { + self.stop_and_reset(); + + let multiplier = if ppre == 1 { 1 } else { 2 }; + let freq = pfreq.0 * multiplier; + let psc = freq / TICKS_PER_SECOND as u32 - 1; + let psc: u16 = psc.try_into().unwrap(); + + self.set_psc_arr(psc, u16::MAX); + // Mid-way point + self.set_compare(0, 0x8000); + self.set_compare_interrupt(0, true); + } + + unsafe fn start_counter(&self) { + self.0.cr1().modify(|w| w.set_cen(true)); + } + + unsafe fn stop_and_reset(&self) { + let regs = self.0; + + regs.cr1().modify(|w| w.set_cen(false)); + regs.cnt().write(|w| w.set_cnt(0)); + } + + fn overflow_interrupt_status(&self) -> bool { + // NOTE(unsafe) Atomic read with no side-effects + unsafe { self.0.sr().read().uif() } + } + + unsafe fn overflow_clear_flag(&self) { + self.0.sr().modify(|w| w.set_uif(false)); + } + + unsafe fn set_psc_arr(&self, psc: u16, arr: u16) { + use crate::pac::timer::vals::Urs; + + let regs = self.0; + + regs.psc().write(|w| w.set_psc(psc)); + regs.arr().write(|w| w.set_arr(arr)); + + // Set URS, generate update and clear URS + regs.cr1().modify(|w| w.set_urs(Urs::COUNTERONLY)); + regs.egr().write(|w| w.set_ug(true)); + regs.cr1().modify(|w| w.set_urs(Urs::ANYEVENT)); + } + + fn compare_interrupt_status(&self, n: usize) -> bool { + if n > 3 { + false + } else { + // NOTE(unsafe) Atomic read with no side-effects + unsafe { self.0.sr().read().ccif(n) } + } + } + + unsafe fn compare_clear_flag(&self, n: usize) { + if n > 3 { + return; + } + self.0.sr().modify(|w| w.set_ccif(n, false)); + } + + fn set_compare(&self, n: usize, value: u16) { + if n > 3 { + return; + } + // NOTE(unsafe) Atomic write + unsafe { + self.0.ccr(n).write(|w| w.set_ccr(value)); + } + } + + unsafe fn set_compare_interrupt(&self, n: usize, enable: bool) { + if n > 3 { + return; + } + self.0.dier().modify(|w| w.set_ccie(n, enable)); + } + + fn counter(&self) -> u16 { + // NOTE(unsafe) Atomic read with no side-effects + unsafe { self.0.cnt().read().cnt() } + } +} + +// ------------------------------------------------------ + +pub(crate) mod sealed { + use super::*; + pub trait Instance { + type Interrupt: Interrupt; + + fn inner() -> TimerInner; + fn state() -> &'static AtomicWaker; + } +} + +pub trait Instance: sealed::Instance + Sized + 'static {} + +/* + +#[allow(unused_macros)] +macro_rules! impl_timer { + ($module:ident: ($TYPE:ident, $INT:ident, $apbenr:ident, $enrbit:expr, $apbrstr:ident, $rstrbit:expr, $ppre:ident, $pclk: ident), 3) => { + mod $module { + use super::*; + + impl sealed::Instance for $TYPE {} + + impl Instance for $TYPE { + type Interrupt = interrupt::$INT; + + fn set_compare(&self, n: usize, value: u16) { + // NOTE(unsafe) these registers accept all the range of u16 values + match n { + 0 => self.ccr1.write(|w| unsafe { w.bits(value.into()) }), + 1 => self.ccr2.write(|w| unsafe { w.bits(value.into()) }), + 2 => self.ccr3.write(|w| unsafe { w.bits(value.into()) }), + 3 => self.ccr4.write(|w| unsafe { w.bits(value.into()) }), + _ => {} + } + } + + fn set_compare_interrupt(&self, n: usize, enable: bool) { + if n > 3 { + return; + } + let bit = n as u8 + 1; + unsafe { + if enable { + bb::set(&self.dier, bit); + } else { + bb::clear(&self.dier, bit); + } + } + } + + fn compare_interrupt_status(&self, n: usize) -> bool { + let status = self.sr.read(); + match n { + 0 => status.cc1if().bit_is_set(), + 1 => status.cc2if().bit_is_set(), + 2 => status.cc3if().bit_is_set(), + 3 => status.cc4if().bit_is_set(), + _ => false, + } + } + + fn compare_clear_flag(&self, n: usize) { + if n > 3 { + return; + } + let bit = n as u8 + 1; + unsafe { + bb::clear(&self.sr, bit); + } + } + + fn overflow_interrupt_status(&self) -> bool { + self.sr.read().uif().bit_is_set() + } + + fn overflow_clear_flag(&self) { + unsafe { + bb::clear(&self.sr, 0); + } + } + + fn set_psc_arr(&self, psc: u16, arr: u16) { + // NOTE(unsafe) All u16 values are valid + self.psc.write(|w| unsafe { w.bits(psc.into()) }); + self.arr.write(|w| unsafe { w.bits(arr.into()) }); + + unsafe { + // Set URS, generate update, clear URS + bb::set(&self.cr1, 2); + self.egr.write(|w| w.ug().set_bit()); + bb::clear(&self.cr1, 2); + } + } + + fn stop_and_reset(&self) { + unsafe { + bb::clear(&self.cr1, 0); + } + self.cnt.reset(); + } + + fn start(&self) { + unsafe { bb::set(&self.cr1, 0) } + } + + fn counter(&self) -> u16 { + self.cnt.read().bits() as u16 + } + + fn ppre(clocks: &Clocks) -> u8 { + clocks.$ppre() + } + + fn pclk(clocks: &Clocks) -> u32 { + clocks.$pclk().0 + } + } + } + }; + + ($module:ident: ($TYPE:ident, $INT:ident, $apbenr:ident, $enrbit:expr, $apbrstr:ident, $rstrbit:expr, $ppre:ident, $pclk: ident), 1) => { + mod $module { + use super::*; + use crate::hal::pac::{$TYPE, RCC}; + + impl sealed::Sealed for $TYPE {} + + impl Instance for $TYPE { + type Interrupt = interrupt::$INT; + const REAL_ALARM_COUNT: usize = 1; + + fn enable_clock(&self) { + // NOTE(unsafe) It will only be used for atomic operations + unsafe { + let rcc = &*RCC::ptr(); + + bb::set(&rcc.$apbenr, $enrbit); + bb::set(&rcc.$apbrstr, $rstrbit); + bb::clear(&rcc.$apbrstr, $rstrbit); + } + } + + fn set_compare(&self, n: usize, value: u16) { + // NOTE(unsafe) these registers accept all the range of u16 values + match n { + 0 => self.ccr1.write(|w| unsafe { w.bits(value.into()) }), + 1 => self.ccr2.write(|w| unsafe { w.bits(value.into()) }), + _ => {} + } + } + + fn set_compare_interrupt(&self, n: usize, enable: bool) { + if n > 1 { + return; + } + let bit = n as u8 + 1; + unsafe { + if enable { + bb::set(&self.dier, bit); + } else { + bb::clear(&self.dier, bit); + } + } + } + + fn compare_interrupt_status(&self, n: usize) -> bool { + let status = self.sr.read(); + match n { + 0 => status.cc1if().bit_is_set(), + 1 => status.cc2if().bit_is_set(), + _ => false, + } + } + + fn compare_clear_flag(&self, n: usize) { + if n > 1 { + return; + } + let bit = n as u8 + 1; + unsafe { + bb::clear(&self.sr, bit); + } + } + + fn overflow_interrupt_status(&self) -> bool { + self.sr.read().uif().bit_is_set() + } + + fn overflow_clear_flag(&self) { + unsafe { + bb::clear(&self.sr, 0); + } + } + + fn set_psc_arr(&self, psc: u16, arr: u16) { + // NOTE(unsafe) All u16 values are valid + self.psc.write(|w| unsafe { w.bits(psc.into()) }); + self.arr.write(|w| unsafe { w.bits(arr.into()) }); + + unsafe { + // Set URS, generate update, clear URS + bb::set(&self.cr1, 2); + self.egr.write(|w| w.ug().set_bit()); + bb::clear(&self.cr1, 2); + } + } + + fn stop_and_reset(&self) { + unsafe { + bb::clear(&self.cr1, 0); + } + self.cnt.reset(); + } + + fn start(&self) { + unsafe { bb::set(&self.cr1, 0) } + } + + fn counter(&self) -> u16 { + self.cnt.read().bits() as u16 + } + + fn ppre(clocks: &Clocks) -> u8 { + clocks.$ppre() + } + + fn pclk(clocks: &Clocks) -> u32 { + clocks.$pclk().0 + } + } + } + }; +} +*/ + +/* +impl_timer!(tim2: (TIM2, TIM2, apb1enr, 0, apb1rstr, 0, ppre1, pclk1), 3); + +impl_timer!(tim3: (TIM3, TIM3, apb1enr, 1, apb1rstr, 1, ppre1, pclk1), 3); + + +impl_timer!(tim4: (TIM4, TIM4, apb1enr, 2, apb1rstr, 2, ppre1, pclk1), 3); + +impl_timer!(tim5: (TIM5, TIM5, apb1enr, 3, apb1rstr, 3, ppre1, pclk1), 3); + +impl_timer!(tim9: (TIM9, TIM1_BRK_TIM9, apb2enr, 16, apb2rstr, 16, ppre2, pclk2), 1); + +#[cfg(not(any(feature = "stm32f401", feature = "stm32f410", feature = "stm32f411")))] +impl_timer!(tim12: (TIM12, TIM8_BRK_TIM12, apb1enr, 6, apb1rstr, 6, ppre1, pclk1), 1); +*/