From 1a12942f530df6b3dbd316ca29daf0b9d83ec36d Mon Sep 17 00:00:00 2001 From: 1-rafael-1 Date: Fri, 16 May 2025 23:22:34 +0200 Subject: embassy-rp (rp2040): Rtc wait_for_alarm --- embassy-rp/src/rtc/datetime_no_deps.rs | 1 + embassy-rp/src/rtc/filter.rs | 3 +- embassy-rp/src/rtc/mod.rs | 122 ++++++++++++++++++++++++++++++++- examples/rp/src/bin/rtc.rs | 8 ++- examples/rp/src/bin/rtc_alarm.rs | 69 +++++++++++++++++++ 5 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 examples/rp/src/bin/rtc_alarm.rs diff --git a/embassy-rp/src/rtc/datetime_no_deps.rs b/embassy-rp/src/rtc/datetime_no_deps.rs index 5de00e6b4..77d4a3055 100644 --- a/embassy-rp/src/rtc/datetime_no_deps.rs +++ b/embassy-rp/src/rtc/datetime_no_deps.rs @@ -46,6 +46,7 @@ pub struct DateTime { /// A day of the week #[repr(u8)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[allow(missing_docs)] pub enum DayOfWeek { Sunday = 0, diff --git a/embassy-rp/src/rtc/filter.rs b/embassy-rp/src/rtc/filter.rs index d4a3bab2f..433053613 100644 --- a/embassy-rp/src/rtc/filter.rs +++ b/embassy-rp/src/rtc/filter.rs @@ -4,7 +4,8 @@ use crate::pac::rtc::regs::{IrqSetup0, IrqSetup1}; /// A filter used for [`RealTimeClock::schedule_alarm`]. /// /// [`RealTimeClock::schedule_alarm`]: struct.RealTimeClock.html#method.schedule_alarm -#[derive(Default)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct DateTimeFilter { /// The year that this alarm should trigger on, `None` if the RTC alarm should not trigger on a year value. pub year: Option, diff --git a/embassy-rp/src/rtc/mod.rs b/embassy-rp/src/rtc/mod.rs index 63cf91d28..7289e46af 100644 --- a/embassy-rp/src/rtc/mod.rs +++ b/embassy-rp/src/rtc/mod.rs @@ -1,7 +1,12 @@ //! RTC driver. mod filter; +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, AtomicBool, Ordering}; +use core::task::Poll; + use embassy_hal_internal::{Peri, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; pub use self::filter::DateTimeFilter; @@ -11,6 +16,13 @@ mod datetime; pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError}; use crate::clocks::clk_rtc_freq; +use crate::interrupt::typelevel::Binding; +use crate::interrupt::{self, InterruptExt}; + +// Static waker for the interrupt handler +static WAKER: AtomicWaker = AtomicWaker::new(); +// Static flag to indicate if an alarm has occurred +static ALARM_OCCURRED: AtomicBool = AtomicBool::new(false); /// A reference to the real time clock of the system pub struct Rtc<'d, T: Instance> { @@ -23,10 +35,15 @@ impl<'d, T: Instance> Rtc<'d, T> { /// # Errors /// /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. - pub fn new(inner: Peri<'d, T>) -> Self { + pub fn new(inner: Peri<'d, T>, _irq: impl Binding) -> Self { // Set the RTC divider inner.regs().clkdiv_m1().write(|w| w.set_clkdiv_m1(clk_rtc_freq() - 1)); + // Setup the IRQ + // Clear any pending interrupts from the RTC_IRQ interrupt and enable it, so we do not have unexpected interrupts after initialization + interrupt::RTC_IRQ.unpend(); + unsafe { interrupt::RTC_IRQ.enable() }; + Self { inner } } @@ -174,6 +191,109 @@ impl<'d, T: Instance> Rtc<'d, T> { pub fn clear_interrupt(&mut self) { self.disable_alarm(); } + + /// Check if an alarm is scheduled. + /// + /// This function checks if the RTC alarm is currently active. If it is, it returns the alarm configuration + /// as a [`DateTimeFilter`]. Otherwise, it returns `None`. + pub fn alarm_scheduled(&self) -> Option { + // Check if alarm is active + if !self.inner.regs().irq_setup_0().read().match_active() { + return None; + } + + // Get values from both alarm registers + let irq_0 = self.inner.regs().irq_setup_0().read(); + let irq_1 = self.inner.regs().irq_setup_1().read(); + + // Create a DateTimeFilter and populate it based on which fields are enabled + let mut filter = DateTimeFilter::default(); + + if irq_0.year_ena() { + filter.year = Some(irq_0.year()); + } + + if irq_0.month_ena() { + filter.month = Some(irq_0.month()); + } + + if irq_0.day_ena() { + filter.day = Some(irq_0.day()); + } + + if irq_1.dotw_ena() { + // Convert day of week value to DayOfWeek enum + let day_of_week = match irq_1.dotw() { + 0 => DayOfWeek::Sunday, + 1 => DayOfWeek::Monday, + 2 => DayOfWeek::Tuesday, + 3 => DayOfWeek::Wednesday, + 4 => DayOfWeek::Thursday, + 5 => DayOfWeek::Friday, + 6 => DayOfWeek::Saturday, + _ => return None, // Invalid day of week + }; + filter.day_of_week = Some(day_of_week); + } + + if irq_1.hour_ena() { + filter.hour = Some(irq_1.hour()); + } + + if irq_1.min_ena() { + filter.minute = Some(irq_1.min()); + } + + if irq_1.sec_ena() { + filter.second = Some(irq_1.sec()); + } + + Some(filter) + } + + /// Wait for an RTC alarm to trigger. + /// + /// This function will wait until the RTC alarm is triggered. If the alarm is already triggered, it will return immediately. + /// If no alarm is scheduled, it will wait indefinitely until one is scheduled and triggered. + pub async fn wait_for_alarm(&mut self) { + poll_fn(|cx| { + WAKER.register(cx.waker()); + + // If the alarm has occured, we will clear the interrupt and return ready + if ALARM_OCCURRED.load(Ordering::SeqCst) { + // Clear the alarm occurred flag + ALARM_OCCURRED.store(false, Ordering::SeqCst); + + // Clear the interrupt and disable the alarm + self.clear_interrupt(); + + // Return ready + compiler_fence(Ordering::SeqCst); + return Poll::Ready(()); + } else { + // If the alarm has not occurred, we will return pending + return Poll::Pending; + } + }) + .await; + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _empty: (), +} + +impl crate::interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + // Disable the alarm first thing, to prevent unexpected re-entry + let rtc = crate::pac::RTC; + rtc.irq_setup_0().modify(|w| w.set_match_ena(false)); + + // Set the alarm occurred flag and wake the waker + ALARM_OCCURRED.store(true, Ordering::SeqCst); + WAKER.wake(); + } } /// Errors that can occur on methods on [Rtc] diff --git a/examples/rp/src/bin/rtc.rs b/examples/rp/src/bin/rtc.rs index e9a5e43a8..1692bdf36 100644 --- a/examples/rp/src/bin/rtc.rs +++ b/examples/rp/src/bin/rtc.rs @@ -5,16 +5,22 @@ use defmt::*; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::rtc::{DateTime, DayOfWeek, Rtc}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; +// Bind the RTC interrupt to the handler +bind_interrupts!(struct Irqs { + RTC_IRQ => embassy_rp::rtc::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); info!("Wait for 20s"); - let mut rtc = Rtc::new(p.RTC); + let mut rtc = Rtc::new(p.RTC, Irqs); if !rtc.is_running() { info!("Start RTC"); diff --git a/examples/rp/src/bin/rtc_alarm.rs b/examples/rp/src/bin/rtc_alarm.rs new file mode 100644 index 000000000..83421014f --- /dev/null +++ b/examples/rp/src/bin/rtc_alarm.rs @@ -0,0 +1,69 @@ +//! This example shows how to use RTC (Real Time Clock) for scheduling alarms and reacting to them. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::select::{select, Either}; +use embassy_rp::bind_interrupts; +use embassy_rp::rtc::{DateTime, DateTimeFilter, DayOfWeek, Rtc}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +// Bind the RTC interrupt to the handler +bind_interrupts!(struct Irqs { + RTC_IRQ => embassy_rp::rtc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rtc = Rtc::new(p.RTC, Irqs); + + if !rtc.is_running() { + info!("Start RTC"); + let now = DateTime { + year: 2000, + month: 1, + day: 1, + day_of_week: DayOfWeek::Saturday, + hour: 0, + minute: 0, + second: 0, + }; + rtc.set_datetime(now).unwrap(); + } + + loop { + // Wait for 5 seconds or until the alarm is triggered + match select(Timer::after_secs(5), rtc.wait_for_alarm()).await { + // Timer expired + Either::First(_) => { + let dt = rtc.now().unwrap(); + info!( + "Now: {}-{:02}-{:02} {}:{:02}:{:02}", + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, + ); + + // See if the alarm is already scheduled, if not, schedule it + match rtc.alarm_scheduled() { + None => { + info!("Scheduling alarm for 30 seconds from now"); + let next_30s = if dt.second == 59 { 0 } else { dt.second + 30 }; + let dtf = DateTimeFilter::default().second(next_30s); + + rtc.schedule_alarm(dtf); + + info!("Alarm scheduled: {}", rtc.alarm_scheduled().unwrap()); + } + Some(_) => {} + } + } + // Alarm triggered + Either::Second(_) => { + info!("ALARM TRIGGERED!"); + } + } + } +} -- cgit