diff --git a/embassy-rp/CHANGELOG.md b/embassy-rp/CHANGELOG.md index 841c9f068..d1265ffc4 100644 --- a/embassy-rp/CHANGELOG.md +++ b/embassy-rp/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add PIO SPI - Add PIO I2S input - Add PIO onewire parasite power strong pullup +- add `wait_for_alarm` and `alarm_scheduled` methods to rtc module ([#4216](https://github.com/embassy-rs/embassy/pull/4216)) ## 0.8.0 - 2025-08-26 @@ -55,7 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.4.0 - 2025-03-09 -- Add PIO functions. ([#3857](https://github.com/embassy-rs/embassy/pull/3857)) +- Add PIO functions. ([#3857](https://github.com/embassy-rs/embassy/pull/3857)) The functions added in this change are `get_addr` `get_tx_threshold`, `set_tx_threshold`, `get_rx_threshold`, `set_rx_threshold`, `set_thresholds`. - Expose the watchdog reset reason. ([#3877](https://github.com/embassy-rs/embassy/pull/3877)) - Update pio-rs, reexport, move instr methods to SM. ([#3865](https://github.com/embassy-rs/embassy/pull/3865)) @@ -96,7 +97,7 @@ Small release fixing a few gnarly bugs, upgrading is strongly recommended. - Add Clone and Copy to Error types - fix spinlocks staying locked after reset. - wait until read matches for PSM accesses. -- Remove generics +- Remove generics - fix drop implementation of BufferedUartRx and BufferedUartTx - implement `embedded_storage_async::nor_flash::MultiwriteNorFlash` - rp usb: wake ep-wakers after stalling 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..8b0deed21 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,110 @@ 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()); + + // Atomically check and clear the alarm occurred flag to prevent race conditions + if critical_section::with(|_| { + let occurred = ALARM_OCCURRED.load(Ordering::SeqCst); + if occurred { + ALARM_OCCURRED.store(false, Ordering::SeqCst); + } + occurred + }) { + // Clear the interrupt and disable the alarm + self.clear_interrupt(); + + compiler_fence(Ordering::SeqCst); + return Poll::Ready(()); + } else { + 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..94b5fbd27 --- /dev/null +++ b/examples/rp/src/bin/rtc_alarm.rs @@ -0,0 +1,66 @@ +//! 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 + if rtc.alarm_scheduled().is_none() { + info!("Scheduling alarm for 30 seconds from now"); + rtc.schedule_alarm(DateTimeFilter::default().second((dt.second + 30) % 60)); + info!("Alarm scheduled: {}", rtc.alarm_scheduled().unwrap()); + } + } + // Alarm triggered + Either::Second(_) => { + let dt = rtc.now().unwrap(); + info!( + "ALARM TRIGGERED! Now: {}-{:02}-{:02} {}:{:02}:{:02}", + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, + ); + } + } + } +} diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml index 809346bed..19461520a 100644 --- a/tests/rp/Cargo.toml +++ b/tests/rp/Cargo.toml @@ -64,6 +64,12 @@ name = "float" path = "src/bin/float.rs" required-features = [ "rp2040",] +# RTC is only available on RP2040 +[[bin]] +name = "rtc" +path = "src/bin/rtc.rs" +required-features = [ "rp2040",] + [profile.dev] debug = 2 debug-assertions = true diff --git a/tests/rp/src/bin/rtc.rs b/tests/rp/src/bin/rtc.rs new file mode 100644 index 000000000..c66981d95 --- /dev/null +++ b/tests/rp/src/bin/rtc.rs @@ -0,0 +1,125 @@ +#![no_std] +#![no_main] +#[cfg(feature = "rp2040")] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert, *}; +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::{Duration, Instant, 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); + + info!("RTC test started"); + + // Initialize RTC if not running + if !rtc.is_running() { + info!("Starting 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(); + Timer::after_millis(100).await; + } + + // Test 1: Basic RTC functionality - read current time + let initial_time = rtc.now().unwrap(); + info!( + "Initial time: {}-{:02}-{:02} {}:{:02}:{:02}", + initial_time.year, + initial_time.month, + initial_time.day, + initial_time.hour, + initial_time.minute, + initial_time.second + ); + + // Test 2: Schedule and wait for alarm + info!("Testing alarm scheduling"); + + // Wait until we're at a predictable second, then schedule for a future second + loop { + let current = rtc.now().unwrap(); + if current.second <= 55 { + break; + } + Timer::after_millis(100).await; + } + + // Now schedule alarm for 3 seconds from current time + let current_time = rtc.now().unwrap(); + let alarm_second = (current_time.second + 3) % 60; + let alarm_filter = DateTimeFilter::default().second(alarm_second); + + info!("Scheduling alarm for second: {}", alarm_second); + rtc.schedule_alarm(alarm_filter); + + // Verify alarm is scheduled + let scheduled = rtc.alarm_scheduled(); + assert!(scheduled.is_some(), "Alarm should be scheduled"); + info!("Alarm scheduled successfully: {}", scheduled.unwrap()); + + // Wait for alarm with timeout + let alarm_start = Instant::now(); + match select(Timer::after_secs(5), rtc.wait_for_alarm()).await { + Either::First(_) => { + core::panic!("Alarm timeout - alarm should have triggered by now"); + } + Either::Second(_) => { + let alarm_duration = Instant::now() - alarm_start; + info!("ALARM TRIGGERED after {:?}", alarm_duration); + + // Verify timing is reasonable (should be around 3 seconds) + assert!( + alarm_duration >= Duration::from_secs(2) && alarm_duration <= Duration::from_secs(4), + "Alarm timing incorrect: {:?}", + alarm_duration + ); + } + } + + // Test 3: Verify RTC is still running and time has advanced + let final_time = rtc.now().unwrap(); + info!( + "Final time: {}-{:02}-{:02} {}:{:02}:{:02}", + final_time.year, final_time.month, final_time.day, final_time.hour, final_time.minute, final_time.second + ); + + // Verify time has advanced (allowing for minute/hour rollover) + let time_diff = if final_time.second >= initial_time.second { + final_time.second - initial_time.second + } else { + 60 - initial_time.second + final_time.second + }; + + assert!(time_diff >= 3, "RTC should have advanced by at least 3 seconds"); + info!("Time advanced by {} seconds", time_diff); + + // Test 4: Verify alarm is no longer scheduled after triggering + let post_alarm_scheduled = rtc.alarm_scheduled(); + assert!( + post_alarm_scheduled.is_none(), + "Alarm should not be scheduled after triggering" + ); + info!("Alarm correctly cleared after triggering"); + + info!("Test OK"); + cortex_m::asm::bkpt(); +}