Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add alarms for RTC #1985

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions embassy-stm32/src/low_power.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,28 @@ 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<Executor> = None;

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();
// }
Expand Down Expand Up @@ -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");

Expand Down
37 changes: 37 additions & 0 deletions embassy-stm32/src/rtc/alarm.rs
Original file line number Diff line number Diff line change
@@ -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<AlarmDate>,
/// None, or 0..23
pub hour: Option<u8>,
/// None, or 0..59
pub minute: Option<u8>,
/// 0..59
pub second: Option<u8>,
// todo: add subseconds
}

impl From<chrono::NaiveDateTime> 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),
}
}
}
10 changes: 10 additions & 0 deletions embassy-stm32/src/rtc/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//! RTC peripheral abstraction
#[cfg(feature = "chrono")]
pub mod alarm;
mod datetime;

#[cfg(feature = "low-power")]
Expand Down Expand Up @@ -85,6 +87,7 @@ impl core::ops::Sub for RtcInstant {

/// RTC Abstraction
pub struct Rtc {
rtc_alarms: Mutex<CriticalSectionRawMutex, [RtcAlarmState; RTC_ALARM_COUNT]>,
#[cfg(feature = "low-power")]
stop_time: Mutex<CriticalSectionRawMutex, Cell<Option<RtcInstant>>>,
}
Expand Down Expand Up @@ -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<P = RTC>, rtc_config: RtcConfig) -> Self {
#[cfg(any(rcc_wb, rcc_f4, rcc_f410))]
Expand All @@ -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)),
};
Expand Down
206 changes: 190 additions & 16 deletions embassy-stm32/src/rtc/v2.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -60,7 +83,169 @@ impl WakeupPrescaler {
}
}

#[derive(Debug)]
pub(crate) struct RtcAlarmState {
callback: Cell<Option<NonNull<*const ()>>>,
}

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<RtcAlarmHandle, ()> {
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
Expand Down Expand Up @@ -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")]
Expand All @@ -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();
Expand Down
20 changes: 20 additions & 0 deletions embassy-stm32/src/time_driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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<RtcAlarmHandle, ()> {
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 {
Expand Down