diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index b42b674e12..c18b46e15c 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -11,7 +11,7 @@ use crate::time_driver::{get_driver, RtcDriver}; const THREAD_PENDER: usize = usize::MAX; -use crate::rtc::Rtc; +use crate::rtc::{Rtc, EXIT_LINE_RTC_ALARM, EXTI_LINE_RTC_WKUP}; static mut EXECUTOR: Option = None; @@ -19,11 +19,20 @@ foreach_interrupt! { (RTC, rtc, $block:ident, WKUP, $irq:ident) => { #[interrupt] unsafe fn $irq() { - unsafe { EXECUTOR.as_mut().unwrap() }.on_wakeup_irq(); + // check which EXTI line triggered + if crate::pac::EXTI.pr(0).read().line(EXTI_LINE_RTC_WKUP) { + unsafe { EXECUTOR.as_mut().unwrap() }.on_wakeup_irq(); + } + + if crate::pac::EXTI.pr(0).read().line(EXIT_LINE_RTC_ALARM) { + unsafe { EXECUTOR.as_ref().unwrap() }.on_rtc_alarm_irq(); + } } }; } +fn test() {} + // pub fn timer_driver_pause_time() { // pause_time(); // } @@ -83,6 +92,10 @@ impl Executor { } } + unsafe fn on_rtc_alarm_irq(&self) { + self.time_driver.on_rtc_alarm_interrupt(); + } + unsafe fn on_wakeup_irq(&mut self) { trace!("low power: on wakeup irq"); diff --git a/embassy-stm32/src/rtc/alarm.rs b/embassy-stm32/src/rtc/alarm.rs new file mode 100644 index 0000000000..39333c275e --- /dev/null +++ b/embassy-stm32/src/rtc/alarm.rs @@ -0,0 +1,37 @@ +use chrono::{Datelike, Timelike}; + +use super::DayOfWeek; + +pub enum AlarmDate { + /// 1..28,29,30,31 depending on month + Date(u8), + /// + WeekDay(DayOfWeek), +} + +pub struct Alarm { + pub date: Option, + /// None, or 0..23 + pub hour: Option, + /// None, or 0..59 + pub minute: Option, + /// 0..59 + pub second: Option, + // todo: add subseconds +} + +impl From for Alarm { + fn from(value: chrono::NaiveDateTime) -> Self { + let date = value.day() as u8; + let hour = value.hour() as u8; + let minute = value.minute() as u8; + let second = value.second() as u8; + + Self { + date: Some(AlarmDate::Date(date)), + hour: Some(hour), + minute: Some(minute), + second: Some(second), + } + } +} diff --git a/embassy-stm32/src/rtc/mod.rs b/embassy-stm32/src/rtc/mod.rs index a1133a80be..dc762b5828 100644 --- a/embassy-stm32/src/rtc/mod.rs +++ b/embassy-stm32/src/rtc/mod.rs @@ -1,4 +1,6 @@ //! RTC peripheral abstraction +#[cfg(feature = "chrono")] +pub mod alarm; mod datetime; #[cfg(feature = "low-power")] @@ -85,6 +87,7 @@ impl core::ops::Sub for RtcInstant { /// RTC Abstraction pub struct Rtc { + rtc_alarms: Mutex, #[cfg(feature = "low-power")] stop_time: Mutex>>, } @@ -122,6 +125,12 @@ impl Default for RtcCalibrationCyclePeriod { } } +#[cfg(any(stm32l0))] +const RTC_STATES: [RtcAlarmState; 2] = [RtcAlarmState::new(), RtcAlarmState::new()]; + +#[cfg(not(any(stm32l0)))] +const RTC_STATES: [RtcAlarmState; 0] = []; + impl Rtc { pub fn new(_rtc: impl Peripheral

, rtc_config: RtcConfig) -> Self { #[cfg(any(rcc_wb, rcc_f4, rcc_f410))] @@ -131,6 +140,7 @@ impl Rtc { BackupDomain::enable_rtc(); let mut this = Self { + rtc_alarms: Mutex::const_new(CriticalSectionRawMutex::new(), RTC_STATES), #[cfg(feature = "low-power")] stop_time: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)), }; diff --git a/embassy-stm32/src/rtc/v2.rs b/embassy-stm32/src/rtc/v2.rs index 1fa9f2fe3f..4013556da0 100644 --- a/embassy-stm32/src/rtc/v2.rs +++ b/embassy-stm32/src/rtc/v2.rs @@ -1,10 +1,33 @@ -use stm32_metapac::rtc::vals::{Init, Osel, Pol}; +use core::borrow::BorrowMut; +use core::cell::{Cell, OnceCell}; +use core::ptr::NonNull; +use core::{mem, ptr}; -use super::sealed; +use stm32_metapac::rtc::vals::{Alrwf, Init, Osel, Pol}; + +use super::alarm::{Alarm, AlarmDate}; +use super::datetime::day_of_week_to_u8; +use super::{byte_to_bcd2, sealed}; use crate::pac::rtc::Rtc; use crate::peripherals::RTC; use crate::rtc::sealed::Instance; +#[cfg(stm32l0)] +pub(crate) const RTC_ALARM_COUNT: usize = 2; + +// todo: change to correct count for other chips +#[cfg(not(stm32l0))] +pub(crate) const RTC_ALARM_COUNT: usize = 0; + +#[cfg(stm32l0)] +pub(crate) const EXTI_LINE_RTC_WKUP: usize = 20; + +#[cfg(not(stm32l0))] +pub(crate) const EXTI_LINE_RTC_WKUP: usize = 22; + +#[cfg(stm32l0)] +pub(crate) const EXIT_LINE_RTC_ALARM: usize = 17; + #[allow(dead_code)] #[repr(u8)] #[derive(Clone, Copy, Debug)] @@ -60,7 +83,169 @@ impl WakeupPrescaler { } } +#[derive(Debug)] +pub(crate) struct RtcAlarmState { + callback: Cell>>, +} + +unsafe impl Send for RtcAlarmState {} + +impl RtcAlarmState { + pub(crate) const fn new() -> Self { + Self { + callback: Cell::new(None), + } + } + + pub(crate) fn call_callback(&self) { + // call the callback + let f: fn() = unsafe { mem::transmute(self.callback.get()) }; + f(); + + self.clear_callback(); + } + + pub(crate) fn clear_callback(&self) { + // set the pointer back to None + self.callback.set(None); + } +} + +pub struct RtcAlarmHandle { + n: usize, +} + +impl RtcAlarmHandle { + const fn new(n: usize) -> Self { + Self { n } + } +} + impl super::Rtc { + pub(crate) fn set_rtc_alarm(&self, rtc_alarm: Alarm, callback: fn()) -> Result { + critical_section::with(|cs| { + // search for one alarm which is unused (callback==null) + let alarm = self + .rtc_alarms + .borrow(cs) + .iter() + .enumerate() + .find(|alarm| alarm.1.callback.get().is_none()); + + if let Some((n, alarm)) = alarm { + alarm.callback.set(NonNull::new(callback as *mut *const ())); + + let rtc = RTC::regs(); + + if rtc.isr().read().alrwf(n) == Alrwf::UPDATENOTALLOWED { + // we aren't allowed to update the alarm + return Err(()); + } + + if let Some(date) = rtc_alarm.date { + rtc.alrmr(0) + .modify(|w| w.set_msk4(stm32_metapac::rtc::vals::AlrmrMsk::MASK)); + match date { + AlarmDate::Date(date) => { + let (dt, du) = byte_to_bcd2(date); + rtc.alrmr(0).modify(|w| w.set_dt(dt)); + rtc.alrmr(0).modify(|w| w.set_du(du)); + } + AlarmDate::WeekDay(day_of_week) => { + rtc.alrmr(0).modify(|w| w.set_du(day_of_week_to_u8(day_of_week))); + } + } + } else { + rtc.alrmr(0) + .modify(|w| w.set_msk4(stm32_metapac::rtc::vals::AlrmrMsk::NOTMASK)); + } + + if let Some(hour) = rtc_alarm.hour { + rtc.alrmr(0) + .modify(|w| w.set_msk3(stm32_metapac::rtc::vals::AlrmrMsk::MASK)); + let (ht, hu) = byte_to_bcd2(hour); + rtc.alrmr(0).modify(|w| w.set_ht(ht)); + rtc.alrmr(0).modify(|w| w.set_hu(hu)); + } else { + rtc.alrmr(0) + .modify(|w| w.set_msk3(stm32_metapac::rtc::vals::AlrmrMsk::NOTMASK)); + } + + // always set am/24h notation + rtc.alrmr(0).modify(|w| w.set_pm(stm32_metapac::rtc::vals::AlrmrPm::AM)); + + if let Some(minute) = rtc_alarm.minute { + rtc.alrmr(0) + .modify(|w| w.set_msk2(stm32_metapac::rtc::vals::AlrmrMsk::MASK)); + let (mnt, mnu) = byte_to_bcd2(minute); + rtc.alrmr(0).modify(|w| w.set_mnt(mnt)); + rtc.alrmr(0).modify(|w| w.set_mnu(mnu)); + } else { + rtc.alrmr(0) + .modify(|w| w.set_msk2(stm32_metapac::rtc::vals::AlrmrMsk::NOTMASK)); + } + + if let Some(second) = rtc_alarm.second { + rtc.alrmr(0) + .modify(|w| w.set_msk1(stm32_metapac::rtc::vals::AlrmrMsk::MASK)); + let (st, su) = byte_to_bcd2(second); + rtc.alrmr(0).modify(|w| w.set_st(st)); + rtc.alrmr(0).modify(|w| w.set_su(su)); + } else { + rtc.alrmr(0) + .modify(|w| w.set_msk1(stm32_metapac::rtc::vals::AlrmrMsk::NOTMASK)); + } + + // enable the alarm + rtc.cr().write(|w| w.set_alre(n, true)); + + // enable the alarm interrupt + rtc.cr().write(|w| w.set_alrie(n, true)); + + Ok(RtcAlarmHandle::new(n)) + } else { + // no unused alarm found + Err(()) + } + }) + } + + pub(crate) fn abort_rtc_alarm(&self, rtc_alarm: RtcAlarmHandle) { + let rtc = RTC::regs(); + + let n = rtc_alarm.n; + + // disable the alarm interrupt + rtc.cr().write(|w| w.set_alrie(n, false)); + + // disable the alarm + rtc.cr().write(|w| w.set_alre(n, false)); + + // clear the callback + critical_section::with(|cs| self.rtc_alarms.borrow(cs).get(n).unwrap().clear_callback()); + } + + pub(crate) fn on_rtc_alarm_interrupt(&self) { + let rtc = RTC::regs(); + for n in 0..=1 { + let alarm_triggered = rtc.isr().read().alrf(n); + + if alarm_triggered { + // reset the alarm flag + rtc.isr().write(|w| w.set_alrf(n, false)); + + // disable the alarm interrupt + rtc.cr().write(|w| w.set_alrie(n, false)); + + // disable the alarm + rtc.cr().write(|w| w.set_alre(n, false)); + + // call the callback + critical_section::with(|cs| self.rtc_alarms.borrow(cs).get(n).unwrap().call_callback()); + } + } + } + #[cfg(feature = "low-power")] /// start the wakeup alarm and wtih a duration that is as close to but less than /// the requested duration, and record the instant the wakeup alarm was started @@ -114,15 +299,8 @@ impl super::Rtc { pub(crate) fn enable_wakeup_line(&self) { use crate::pac::EXTI; - #[cfg(stm32l0)] - EXTI.rtsr(0).modify(|w| w.set_line(20, true)); - #[cfg(stm32l0)] - EXTI.imr(0).modify(|w| w.set_line(20, true)); - - #[cfg(not(stm32l0))] - EXTI.rtsr(0).modify(|w| w.set_line(22, true)); - #[cfg(not(stm32l0))] - EXTI.imr(0).modify(|w| w.set_line(22, true)); + EXTI.rtsr(0).modify(|w| w.set_line(EXTI_LINE_RTC_WKUP, true)); + EXTI.imr(0).modify(|w| w.set_line(EXTI_LINE_RTC_WKUP, true)); } #[cfg(feature = "low-power")] @@ -138,11 +316,7 @@ impl super::Rtc { regs.cr().modify(|w| w.set_wute(false)); regs.isr().modify(|w| w.set_wutf(false)); - #[cfg(not(stm32l0))] - crate::pac::EXTI.pr(0).modify(|w| w.set_line(22, true)); - - #[cfg(stm32l0)] - crate::pac::EXTI.pr(0).modify(|w| w.set_line(20, true)); + crate::pac::EXTI.pr(0).modify(|w| w.set_line(EXTI_LINE_RTC_WKUP, true)); #[cfg(not(stm32l0))] crate::interrupt::typelevel::RTC_WKUP::unpend(); diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs index 99d423d086..bae48923f8 100644 --- a/embassy-stm32/src/time_driver.rs +++ b/embassy-stm32/src/time_driver.rs @@ -14,8 +14,10 @@ use stm32_metapac::timer::regs; use crate::interrupt::typelevel::Interrupt; use crate::pac::timer::vals; use crate::rcc::sealed::RccPeripheral; +use crate::rtc::alarm::Alarm; #[cfg(feature = "low-power")] use crate::rtc::Rtc; +use crate::rtc::RtcAlarmHandle; use crate::timer::sealed::{Basic16bitInstance as BasicInstance, GeneralPurpose16bitInstance as Instance}; use crate::{interrupt, peripherals}; @@ -376,6 +378,24 @@ impl RtcDriver { T::regs_gp16().cr1().modify(|w| w.set_cen(true)); } + + /// sets a rtc alarm + /// returns an error when no rtc alarms are available or rtc is not set + pub(crate) fn set_rtc_alarm(&self, alarm: Alarm, callback: fn()) -> Result { + critical_section::with(|cs| { + if let Some(rtc) = self.rtc.borrow(cs).get() { + rtc.set_rtc_alarm(alarm, callback) + } else { + Err(()) + } + }) + } + + pub(crate) fn on_rtc_alarm_interrupt(&self) { + critical_section::with(|cs| { + self.rtc.borrow(cs).get().unwrap().on_rtc_alarm_interrupt(); + }) + } } impl Driver for RtcDriver {