From bfaf3b7b301a9f91c717cdda07bf26077dcacdfd Mon Sep 17 00:00:00 2001 From: Markus Kasten Date: Sun, 2 Feb 2025 18:57:49 +0100 Subject: [PATCH] Implement STM32 RTC alarms (WIP) Implements setting up and awaiting RTC alarms. Still quite WIP as the differnt stm32 lines differ quite a bit in EXTI/IRQs. So far tested on STM32 G0, G4, L0 and F3. --- embassy-stm32/src/rtc/alarm.rs | 322 ++++++++++++++++++++++++++++++++ embassy-stm32/src/rtc/mod.rs | 16 ++ embassy-stm32/src/rtc/v2.rs | 66 ++++--- embassy-stm32/src/rtc/v3.rs | 77 +++++--- examples/stm32g4/src/bin/rtc.rs | 42 +++++ examples/stm32l0/src/bin/rtc.rs | 42 +++++ 6 files changed, 516 insertions(+), 49 deletions(-) create mode 100644 embassy-stm32/src/rtc/alarm.rs create mode 100644 examples/stm32g4/src/bin/rtc.rs create mode 100644 examples/stm32l0/src/bin/rtc.rs diff --git a/embassy-stm32/src/rtc/alarm.rs b/embassy-stm32/src/rtc/alarm.rs new file mode 100644 index 0000000000..f8a01317d7 --- /dev/null +++ b/embassy-stm32/src/rtc/alarm.rs @@ -0,0 +1,322 @@ +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use embassy_sync::waitqueue::AtomicWaker; +use stm32_metapac::rtc::regs::{Alrmr, Alrmssr}; +#[cfg(rtc_v3)] +use stm32_metapac::rtc::vals::{Alrmf, Calrf}; +use stm32_metapac::rtc::vals::{AlrmrMsk, AlrmrPm, AlrmrWdsel}; + +use crate::interrupt; +use crate::peripherals::RTC; +use crate::rtc::SealedInstance; + +use super::datetime::day_of_week_to_u8; +use super::{byte_to_bcd2, DayOfWeek, Rtc}; + +cfg_if::cfg_if!( + if #[cfg(rtc_v2f2)] { + const ALARM_COUNT: usize = 1; + } else { + const ALARM_COUNT: usize = 2; + } +); + +static RTC_WAKERS: [AtomicWaker; ALARM_COUNT] = [const { AtomicWaker::new() }; ALARM_COUNT]; + +/// RTC alarm index +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Alarm { + /// Alarm A + A = 0, + // stm32wb0 also doesn't have alarm B? + #[cfg(not(any(rtc_v2f2)))] + /// Alarm B + B = 1, +} + +impl Alarm { + /// Get alarm index (0..1) + pub fn index(&self) -> usize { + *self as usize + } +} + +/// Kind of date used in alarm match. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum AlarmDate { + /// Match on day of month. + DayOfMonth(u8), + /// Match on weekday. + DayOfWeek(DayOfWeek), +} + +/// RTC alarm specification. Alarm is fired when a match occurs. +#[derive(Clone, Debug, PartialEq)] +pub struct RtcAlarmMatch { + /// If not [`None`], match subsecond to this value. If [`None`], alarm matches when second is incremented. + pub subsecond: Option, + /// If not [`None`], match second to this value. + pub second: Option, + /// If not [`None`], match minute to this value. + pub minute: Option, + /// If not [`None`], match hour to this value. + pub hour: Option, + /// Set to `true` when value from [`hour`] field is PM, if AM or 24h format set to `false`. + pub hour_is_pm: bool, + /// If not [`None`], match date to this value. + pub date: Option, +} + +impl RtcAlarmMatch { + /// Creates an alarm that fires everytime the second in incremented + pub fn every_second() -> Self { + Self { + subsecond: None, + second: None, + minute: None, + hour: None, + hour_is_pm: false, + date: None, + } + } + + pub(crate) fn to_alrmr(&self) -> Alrmr { + let mut alrmr = Alrmr::default(); + + if let Some(second) = self.second { + alrmr.set_msk1(AlrmrMsk::TO_MATCH); + let (st, su) = byte_to_bcd2(second); + alrmr.set_st(st); + alrmr.set_su(su); + } else { + alrmr.set_msk1(AlrmrMsk::NOT_MATCH); + } + + if let Some(minute) = self.minute { + alrmr.set_msk2(AlrmrMsk::TO_MATCH); + let (mnt, mnu) = byte_to_bcd2(minute); + alrmr.set_mnt(mnt); + alrmr.set_mnu(mnu); + } else { + alrmr.set_msk2(AlrmrMsk::NOT_MATCH); + } + + if let Some(hour) = self.hour { + alrmr.set_msk3(AlrmrMsk::TO_MATCH); + if self.hour_is_pm { + alrmr.set_pm(AlrmrPm::PM); + } else { + alrmr.set_pm(AlrmrPm::AM); + } + let (ht, hu) = byte_to_bcd2(hour); + alrmr.set_ht(ht); + alrmr.set_hu(hu); + } else { + alrmr.set_msk3(AlrmrMsk::NOT_MATCH); + } + + if let Some(date) = self.date { + alrmr.set_msk4(AlrmrMsk::TO_MATCH); + + let (date, wdsel) = match date { + AlarmDate::DayOfMonth(date) => (date, AlrmrWdsel::DATE_UNITS), + AlarmDate::DayOfWeek(day_of_week) => (day_of_week_to_u8(day_of_week), AlrmrWdsel::WEEK_DAY), + }; + alrmr.set_wdsel(wdsel); + let (dt, du) = byte_to_bcd2(date); + alrmr.set_dt(dt); + alrmr.set_du(du); + } else { + alrmr.set_msk4(AlrmrMsk::NOT_MATCH); + } + + alrmr + } + + pub(crate) fn to_alrmssr(&self) -> Alrmssr { + let mut alrmssr = Alrmssr::default(); + + if let Some(subsecond) = self.subsecond { + alrmssr.set_maskss(0b1111); // only implement matching all bits + alrmssr.set_ss(subsecond); + } else { + alrmssr.set_maskss(0); + } + + alrmssr + } +} + +impl Rtc { + /// Set alarm spec for specified alarm. + pub fn set_alarm(&mut self, alarm: Alarm, spec: RtcAlarmMatch) { + let alrmr = spec.to_alrmr(); + let alrmrss = spec.to_alrmssr(); + + self.write(false, |r| { + // disable alarm + r.cr().modify(|w| w.set_alre(alarm.index(), false)); + + // wait until update is allowed + #[cfg(rtc_v3)] + while !r.icsr().read().alrwf(alarm.index()) {} + + #[cfg(any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb + ))] + while !r.isr().read().alrwf(alarm.index()) {} + + r.alrmr(alarm.index()).write_value(alrmr); + r.alrmssr(alarm.index()).write_value(alrmrss); + }); + } + + /// Wait until the specified alarm fires. + pub async fn wait_for_alarm(&mut self, alarm: Alarm) { + RtcAlarmFuture::new(alarm).await + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct RtcAlarmFuture { + alarm: Alarm, +} + +impl RtcAlarmFuture { + fn new(alarm: Alarm) -> Self { + critical_section::with(|_| { + RTC::write(false, |rtc| { + // enable interrupt + rtc.cr().modify(|w| { + w.set_alre(alarm.index(), true); + w.set_alrie(alarm.index(), true); + }); + + // clear pending bit + #[cfg(rtc_v3)] + rtc.scr().write(|w| w.set_calrf(alarm.index(), Calrf::CLEAR)); + + #[cfg(any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb + ))] + rtc.isr().modify(|w| w.set_alrf(alarm.index(), false)) + }); + + use crate::pac::EXTI; + EXTI.rtsr(0).modify(|w| w.set_line(RTC::EXTI_ALARM_LINE, true)); + EXTI.imr(0).modify(|w| w.set_line(RTC::EXTI_ALARM_LINE, true)); + }); + + Self { alarm } + } +} + +impl Drop for RtcAlarmFuture { + fn drop(&mut self) { + // clear interrupt + critical_section::with(|_| { + RTC::write(false, |rtc| { + rtc.cr().modify(|w| { + w.set_alrie(self.alarm.index(), false); + }); + }); + }) + } +} + +impl Future for RtcAlarmFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + RTC_WAKERS[self.alarm as usize].register(cx.waker()); + defmt::info!("poll rtc future"); + + if !crate::pac::RTC.cr().read().alrie(self.alarm.index()) { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +unsafe fn on_irq() { + #[cfg(feature = "low-power")] + crate::low_power::on_wakeup_irq(); + + let reg = crate::pac::RTC; + #[cfg(rtc_v3)] + let misr = reg.misr().read(); + + #[cfg(any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb + ))] + let isr = reg.isr().read(); + + // defmt::info!("RTC IRQ (misr = {})", misr.0); + + for i in 0..ALARM_COUNT { + #[cfg(rtc_v3)] + let has_fired = misr.alrmf(i) == Alrmf::MATCH; + + #[cfg(any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb + ))] + let has_fired = isr.alrf(i); + + if has_fired { + // clear pending bit + #[cfg(rtc_v3)] + reg.scr().write(|w| w.set_calrf(i, Calrf::CLEAR)); + + #[cfg(any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb + ))] + reg.isr().modify(|w| w.set_alrf(i, false)); + + RTC::write(false, |regs| { + // disable the interrupt, this way the future knows the irq fired + regs.cr().modify(|w| w.set_alrie(i, false)); + }); + + // wake task + RTC_WAKERS[i].wake(); + } + } + + // the RTC exti line is configurable on some variants, other do not need + // the pending bit reset + + #[cfg(not(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50)))] + crate::pac::EXTI.pr(0).write(|w| w.set_line(RTC::EXTI_ALARM_LINE, true)); + + #[cfg(any(exti_c0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50))] + crate::pac::EXTI + .rpr(0) + .write(|w| w.set_line(RTC::EXTI_ALARM_LINE, true)); +} + +// TODO figure out IRQs for all variants.. + +#[cfg(any(stm32f0, stm32g0, stm32l0, stm32u0, stm32u5))] +foreach_interrupt! { + (RTC, rtc, $block:ident, TAMP, $irq:ident) => { + #[interrupt] + #[allow(non_snake_case)] + unsafe fn $irq() { + on_irq(); + } + }; +} + +#[cfg(any(stm32f1, stm32f3, stm32f4, stm32g4))] +foreach_interrupt! { + (RTC, rtc, $block:ident, ALARM, $irq:ident) => { + #[interrupt] + #[allow(non_snake_case)] + unsafe fn $irq() { + on_irq(); + } + }; +} diff --git a/embassy-stm32/src/rtc/mod.rs b/embassy-stm32/src/rtc/mod.rs index 66646de4c1..26500319bf 100644 --- a/embassy-stm32/src/rtc/mod.rs +++ b/embassy-stm32/src/rtc/mod.rs @@ -1,4 +1,5 @@ //! Real Time Clock (RTC) +mod alarm; mod datetime; #[cfg(feature = "low-power")] @@ -12,6 +13,7 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; #[cfg(feature = "low-power")] use embassy_sync::blocking_mutex::Mutex; +pub use self::alarm::{Alarm, AlarmDate, RtcAlarmMatch}; use self::datetime::{day_of_week_from_u8, day_of_week_to_u8}; pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError}; use crate::pac::rtc::regs::{Dr, Tr}; @@ -145,6 +147,12 @@ impl Rtc { #[cfg(not(any(stm32l0, stm32f3, stm32l1, stm32f0, stm32f2)))] crate::rcc::enable_and_reset::(); + use crate::interrupt::typelevel::Interrupt; + ::AlarmInterrupt::unpend(); + unsafe { + ::AlarmInterrupt::enable(); + } + let mut this = Self { #[cfg(feature = "low-power")] stop_time: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)), @@ -292,6 +300,10 @@ trait SealedInstance { #[cfg(feature = "low-power")] type WakeupInterrupt: crate::interrupt::typelevel::Interrupt; + const EXTI_ALARM_LINE: usize; + + type AlarmInterrupt: crate::interrupt::typelevel::Interrupt; + fn regs() -> crate::pac::rtc::Rtc { crate::pac::RTC } @@ -308,5 +320,9 @@ trait SealedInstance { /// retain their value when Vdd is switched off as long as V_BAT is powered. fn write_backup_register(rtc: crate::pac::rtc::Rtc, register: usize, value: u32); + fn write(init_mode: bool, f: F) -> R + where + F: FnOnce(crate::pac::rtc::Rtc) -> R; + // fn apply_config(&mut self, rtc_config: RtcConfig); } diff --git a/embassy-stm32/src/rtc/v2.rs b/embassy-stm32/src/rtc/v2.rs index b7d25635b7..346bc76dcd 100644 --- a/embassy-stm32/src/rtc/v2.rs +++ b/embassy-stm32/src/rtc/v2.rs @@ -97,31 +97,7 @@ impl super::Rtc { where F: FnOnce(crate::pac::rtc::Rtc) -> R, { - let r = RTC::regs(); - // Disable write protection. - // This is safe, as we're only writin the correct and expected values. - r.wpr().write(|w| w.set_key(0xca)); - r.wpr().write(|w| w.set_key(0x53)); - - // true if initf bit indicates RTC peripheral is in init mode - if init_mode && !r.isr().read().initf() { - // to update calendar date/time, time format, and prescaler configuration, RTC must be in init mode - r.isr().modify(|w| w.set_init(true)); - // wait till init state entered - // ~2 RTCCLK cycles - while !r.isr().read().initf() {} - } - - let result = f(r); - - if init_mode { - r.isr().modify(|w| w.set_init(false)); // Exits init mode - } - - // Re-enable write protection. - // This is safe, as the field accepts the full range of 8-bit values. - r.wpr().write(|w| w.set_key(0xff)); - result + RTC::write(init_mode, f) } } @@ -143,6 +119,15 @@ impl SealedInstance for crate::peripherals::RTC { #[cfg(all(feature = "low-power", stm32l0))] type WakeupInterrupt = crate::interrupt::typelevel::RTC; + // TODO figure out interrupts and exti lines for other variants + const EXTI_ALARM_LINE: usize = 17; + + #[cfg(stm32l0)] + type AlarmInterrupt = crate::interrupt::typelevel::RTC; + + #[cfg(stm32f3)] + type AlarmInterrupt = crate::interrupt::typelevel::RTC_ALARM; + fn read_backup_register(rtc: Rtc, register: usize) -> Option { if register < Self::BACKUP_REGISTER_COUNT { Some(rtc.bkpr(register).read().bkp()) @@ -156,4 +141,35 @@ impl SealedInstance for crate::peripherals::RTC { rtc.bkpr(register).write(|w| w.set_bkp(value)); } } + + fn write(init_mode: bool, f: F) -> R + where + F: FnOnce(crate::pac::rtc::Rtc) -> R, + { + let r = RTC::regs(); + // Disable write protection. + // This is safe, as we're only writin the correct and expected values. + r.wpr().write(|w| w.set_key(0xca)); + r.wpr().write(|w| w.set_key(0x53)); + + // true if initf bit indicates RTC peripheral is in init mode + if init_mode && !r.isr().read().initf() { + // to update calendar date/time, time format, and prescaler configuration, RTC must be in init mode + r.isr().modify(|w| w.set_init(true)); + // wait till init state entered + // ~2 RTCCLK cycles + while !r.isr().read().initf() {} + } + + let result = f(r); + + if init_mode { + r.isr().modify(|w| w.set_init(false)); // Exits init mode + } + + // Re-enable write protection. + // This is safe, as the field accepts the full range of 8-bit values. + r.wpr().write(|w| w.set_key(0xff)); + result + } } diff --git a/embassy-stm32/src/rtc/v3.rs b/embassy-stm32/src/rtc/v3.rs index 39aa6c5cbe..4b42f3285c 100644 --- a/embassy-stm32/src/rtc/v3.rs +++ b/embassy-stm32/src/rtc/v3.rs @@ -99,30 +99,7 @@ impl super::Rtc { where F: FnOnce(crate::pac::rtc::Rtc) -> R, { - let r = RTC::regs(); - // Disable write protection. - // This is safe, as we're only writin the correct and expected values. - r.wpr().write(|w| w.set_key(Key::DEACTIVATE1)); - r.wpr().write(|w| w.set_key(Key::DEACTIVATE2)); - - if init_mode && !r.icsr().read().initf() { - r.icsr().modify(|w| w.set_init(true)); - // wait till init state entered - // ~2 RTCCLK cycles - while !r.icsr().read().initf() {} - } - - let result = f(r); - - if init_mode { - r.icsr().modify(|w| w.set_init(false)); // Exits init mode - } - - // Re-enable write protection. - // This is safe, as the field accepts the full range of 8-bit values. - r.wpr().write(|w| w.set_key(Key::ACTIVATE)); - - result + RTC::write(init_mode, f) } } @@ -151,6 +128,28 @@ impl SealedInstance for crate::peripherals::RTC { } ); + // TODO figure out interrupts and exti lines for other variants + cfg_if::cfg_if!( + if #[cfg(stm32g4)] { + const EXTI_ALARM_LINE: usize = 17; + } else if #[cfg(stm32g0)] { + const EXTI_ALARM_LINE: usize = 19; + } else if #[cfg(stm32u0)] { + const EXTI_ALARM_LINE: usize = 28; + } else if #[cfg(any(stm32l5, stm32h5))] { + const EXTI_ALARM_LINE: usize = 17; // TODO + } + ); + cfg_if::cfg_if!( + if #[cfg(stm32g4)] { + type AlarmInterrupt = crate::interrupt::typelevel::RTC_ALARM; + } else if #[cfg(any(stm32g0, stm32u0))] { + type AlarmInterrupt = crate::interrupt::typelevel::RTC_TAMP; + } else if #[cfg(any(stm32l5, stm32h5, stm32u5))] { + type AlarmInterrupt = crate::interrupt::typelevel::RTC; + } + ); + fn read_backup_register(_rtc: Rtc, register: usize) -> Option { #[allow(clippy::if_same_then_else)] if register < Self::BACKUP_REGISTER_COUNT { @@ -167,4 +166,34 @@ impl SealedInstance for crate::peripherals::RTC { //self.rtc.bkpr()[register].write(|w| w.bits(value)) } } + + fn write(init_mode: bool, f: F) -> R + where + F: FnOnce(crate::pac::rtc::Rtc) -> R, + { + let r = RTC::regs(); + // Disable write protection. + // This is safe, as we're only writin the correct and expected values. + r.wpr().write(|w| w.set_key(Key::DEACTIVATE1)); + r.wpr().write(|w| w.set_key(Key::DEACTIVATE2)); + + if init_mode && !r.icsr().read().initf() { + r.icsr().modify(|w| w.set_init(true)); + // wait till init state entered + // ~2 RTCCLK cycles + while !r.icsr().read().initf() {} + } + + let result = f(r); + + if init_mode { + r.icsr().modify(|w| w.set_init(false)); // Exits init mode + } + + // Re-enable write protection. + // This is safe, as the field accepts the full range of 8-bit values. + r.wpr().write(|w| w.set_key(Key::ACTIVATE)); + + result + } } diff --git a/examples/stm32g4/src/bin/rtc.rs b/examples/stm32g4/src/bin/rtc.rs new file mode 100644 index 0000000000..d75feeb39d --- /dev/null +++ b/examples/stm32g4/src/bin/rtc.rs @@ -0,0 +1,42 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rtc::{Alarm, DateTime, DayOfWeek, Rtc, RtcAlarmMatch, RtcConfig}; +use embassy_stm32::Config; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let config = Config::default(); + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let now = DateTime::from(2023, 6, 14, DayOfWeek::Friday, 15, 59, 10); + + let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); + + rtc.set_datetime(now.unwrap()).expect("datetime not set"); + + rtc.set_alarm( + Alarm::A, + RtcAlarmMatch { + subsecond: None, + second: None, + minute: None, + hour: None, + hour_is_pm: false, + date: None, + }, + ); + + loop { + let now: DateTime = rtc.now().unwrap().into(); + + info!("{:02}:{:02}:{:02}", now.hour(), now.minute(), now.second()); + + rtc.wait_for_alarm(Alarm::A).await; + } +} diff --git a/examples/stm32l0/src/bin/rtc.rs b/examples/stm32l0/src/bin/rtc.rs new file mode 100644 index 0000000000..d75feeb39d --- /dev/null +++ b/examples/stm32l0/src/bin/rtc.rs @@ -0,0 +1,42 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rtc::{Alarm, DateTime, DayOfWeek, Rtc, RtcAlarmMatch, RtcConfig}; +use embassy_stm32::Config; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let config = Config::default(); + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let now = DateTime::from(2023, 6, 14, DayOfWeek::Friday, 15, 59, 10); + + let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); + + rtc.set_datetime(now.unwrap()).expect("datetime not set"); + + rtc.set_alarm( + Alarm::A, + RtcAlarmMatch { + subsecond: None, + second: None, + minute: None, + hour: None, + hour_is_pm: false, + date: None, + }, + ); + + loop { + let now: DateTime = rtc.now().unwrap().into(); + + info!("{:02}:{:02}:{:02}", now.hour(), now.minute(), now.second()); + + rtc.wait_for_alarm(Alarm::A).await; + } +}