From 22bae2d80153003af4637c4b0cc26d52858f5228 Mon Sep 17 00:00:00 2001 From: MathisDerooNXP <52401665+MathisDeroo@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:42:46 -0800 Subject: Rtc support v2 (#91) Signed-off-by: Mathis Deroo Signed-off-by: Felipe Balbi --- examples/src/bin/rtc_alarm.rs | 39 ++------- src/lib.rs | 1 - src/rtc.rs | 198 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 182 insertions(+), 56 deletions(-) diff --git a/examples/src/bin/rtc_alarm.rs b/examples/src/bin/rtc_alarm.rs index a7800a2d1..fe355888b 100644 --- a/examples/src/bin/rtc_alarm.rs +++ b/examples/src/bin/rtc_alarm.rs @@ -2,23 +2,14 @@ #![no_main] use embassy_executor::Spawner; -use embassy_mcxa as hal; -use hal::rtc::{RtcDateTime, RtcInterruptEnable}; -use hal::InterruptExt; - -type MyRtc = hal::rtc::Rtc<'static, hal::rtc::Rtc0>; - use embassy_mcxa::bind_interrupts; -use {defmt_rtt as _, panic_probe as _}; +use hal::rtc::{InterruptHandler, Rtc, RtcDateTime}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; bind_interrupts!(struct Irqs { - RTC => hal::rtc::RtcHandler; + RTC => InterruptHandler; }); -#[used] -#[no_mangle] -static KEEP_RTC: unsafe extern "C" fn() = RTC; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = hal::init(hal::config::Config::default()); @@ -27,7 +18,7 @@ async fn main(_spawner: Spawner) { let rtc_config = hal::rtc::get_default_config(); - let rtc = MyRtc::new(p.RTC0, rtc_config); + let mut rtc = Rtc::new(p.RTC0, Irqs, rtc_config); let now = RtcDateTime { year: 2025, @@ -46,29 +37,11 @@ async fn main(_spawner: Spawner) { let mut alarm = now; alarm.second += 10; - rtc.set_alarm(alarm); defmt::info!("Alarm set for: 2025-10-15 14:30:10 (+10 seconds)"); - - rtc.set_interrupt(RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE); - - unsafe { - hal::interrupt::RTC.enable(); - } - - unsafe { - cortex_m::interrupt::enable(); - } - - rtc.start(); - defmt::info!("RTC started, waiting for alarm..."); - loop { - if rtc.is_alarm_triggered() { - defmt::info!("*** ALARM TRIGGERED! ***"); - break; - } - } + rtc.wait_for_alarm(alarm).await; + defmt::info!("*** ALARM TRIGGERED! ***"); defmt::info!("Example complete - Test PASSED!"); } diff --git a/src/lib.rs b/src/lib.rs index fb204d27b..c6d8adc8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -333,7 +333,6 @@ pub use interrupt::InterruptExt; pub use mcxa_pac as pac; #[cfg(not(feature = "unstable-pac"))] pub(crate) use mcxa_pac as pac; -pub use rtc::Rtc0 as Rtc0Token; /// Initialize HAL with configuration (mirrors embassy-imxrt style). Minimal: just take peripherals. /// Also applies configurable NVIC priority for the OSTIMER OS_EVENT interrupt (no enabling). diff --git a/src/rtc.rs b/src/rtc.rs index b750a97ea..f975d9c9f 100644 --- a/src/rtc.rs +++ b/src/rtc.rs @@ -1,36 +1,52 @@ //! RTC DateTime driver. -use core::sync::atomic::{AtomicBool, Ordering}; +use core::marker::PhantomData; use embassy_hal_internal::{Peri, PeripheralType}; +use maitake_sync::WaitCell; use crate::clocks::with_clocks; +use crate::interrupt::typelevel::{Handler, Interrupt}; use crate::pac; use crate::pac::rtc0::cr::Um; type Regs = pac::rtc0::RegisterBlock; -static ALARM_TRIGGERED: AtomicBool = AtomicBool::new(false); +/// Global wait cell for alarm notifications +static WAKER: WaitCell = WaitCell::new(); -// Token-based instance pattern like embassy-imxrt +/// RTC interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +/// Trait for RTC peripheral instances pub trait Instance: PeripheralType { + type Interrupt: Interrupt; fn ptr() -> *const Regs; } /// Token for RTC0 pub type Rtc0 = crate::peripherals::RTC0; impl Instance for crate::peripherals::RTC0 { + type Interrupt = crate::interrupt::typelevel::RTC; #[inline(always)] fn ptr() -> *const Regs { pac::Rtc0::ptr() } } +/// Number of days in a standard year const DAYS_IN_A_YEAR: u32 = 365; +/// Number of seconds in a day const SECONDS_IN_A_DAY: u32 = 86400; +/// Number of seconds in an hour const SECONDS_IN_A_HOUR: u32 = 3600; +/// Number of seconds in a minute const SECONDS_IN_A_MINUTE: u32 = 60; +/// Unix epoch start year const YEAR_RANGE_START: u16 = 1970; +/// Date and time structure for RTC operations #[derive(Debug, Clone, Copy)] pub struct RtcDateTime { pub year: u16, @@ -51,6 +67,7 @@ pub struct RtcConfig { compensation_time: u8, } +/// RTC interrupt enable flags #[derive(Copy, Clone)] pub struct RtcInterruptEnable; impl RtcInterruptEnable { @@ -60,6 +77,19 @@ impl RtcInterruptEnable { pub const RTC_SECONDS_INTERRUPT_ENABLE: u32 = 1 << 4; } +/// Converts a DateTime structure to Unix timestamp (seconds since 1970-01-01) +/// +/// # Arguments +/// +/// * `datetime` - The date and time to convert +/// +/// # Returns +/// +/// Unix timestamp as u32 +/// +/// # Note +/// +/// This function handles leap years correctly. pub fn convert_datetime_to_seconds(datetime: &RtcDateTime) -> u32 { let month_days: [u16; 13] = [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; @@ -80,6 +110,19 @@ pub fn convert_datetime_to_seconds(datetime: &RtcDateTime) -> u32 { seconds } +/// Converts Unix timestamp to DateTime structure +/// +/// # Arguments +/// +/// * `seconds` - Unix timestamp (seconds since 1970-01-01) +/// +/// # Returns +/// +/// RtcDateTime structure with the converted date and time +/// +/// # Note +/// +/// This function handles leap years correctly. pub fn convert_seconds_to_datetime(seconds: u32) -> RtcDateTime { let mut seconds_remaining = seconds; let mut days = seconds_remaining / SECONDS_IN_A_DAY + 1; @@ -142,6 +185,15 @@ pub fn convert_seconds_to_datetime(seconds: u32) -> RtcDateTime { } } +/// Returns default RTC configuration +/// +/// # Returns +/// +/// RtcConfig with sensible default values: +/// - No wakeup selection +/// - Update mode 0 (immediate updates) +/// - No supervisor access restriction +/// - No compensation pub fn get_default_config() -> RtcConfig { RtcConfig { wakeup_select: false, @@ -157,8 +209,12 @@ pub struct Rtc<'a, I: Instance> { } impl<'a, I: Instance> Rtc<'a, I> { - /// initialize RTC - pub fn new(_inst: Peri<'a, I>, config: RtcConfig) -> Self { + /// Create a new instance of the real time clock. + pub fn new( + _inst: Peri<'a, I>, + _irq: impl crate::interrupt::typelevel::Binding> + 'a, + config: RtcConfig, + ) -> Self { let rtc = unsafe { &*I::ptr() }; // The RTC is NOT gated by the MRCC, but we DO need to make sure the 16k clock @@ -170,7 +226,7 @@ impl<'a, I: Instance> Rtc<'a, I> { Some(Some(_)) => {} } - /* RTC reset */ + // RTC reset rtc.cr().modify(|_, w| w.swr().set_bit()); rtc.cr().modify(|_, w| w.swr().clear_bit()); rtc.tsr().write(|w| unsafe { w.bits(1) }); @@ -184,23 +240,60 @@ impl<'a, I: Instance> Rtc<'a, I> { .bits(config.compensation_time) }); + // Enable RTC interrupt + I::Interrupt::unpend(); + unsafe { I::Interrupt::enable() }; + Self { _inst: core::marker::PhantomData, } } + /// Set the current date and time + /// + /// # Arguments + /// + /// * `datetime` - The date and time to set + /// + /// # Note + /// + /// The datetime is converted to Unix timestamp and written to the time seconds register. pub fn set_datetime(&self, datetime: RtcDateTime) { let rtc = unsafe { &*I::ptr() }; let seconds = convert_datetime_to_seconds(&datetime); rtc.tsr().write(|w| unsafe { w.bits(seconds) }); } + /// Get the current date and time + /// + /// # Returns + /// + /// Current date and time as RtcDateTime + /// + /// # Note + /// + /// Reads the current Unix timestamp from the time seconds register and converts it. pub fn get_datetime(&self) -> RtcDateTime { let rtc = unsafe { &*I::ptr() }; let seconds = rtc.tsr().read().bits(); convert_seconds_to_datetime(seconds) } + /// Set the alarm date and time + /// + /// # Arguments + /// + /// * `alarm` - The date and time when the alarm should trigger + /// + /// # Note + /// + /// This function: + /// - Clears any existing alarm by writing 0 to the alarm register + /// - Waits for the clear operation to complete + /// - Sets the new alarm time + /// - Waits for the write operation to complete + /// - Uses timeouts to prevent infinite loops + /// - Enables the alarm interrupt after setting pub fn set_alarm(&self, alarm: RtcDateTime) { let rtc = unsafe { &*I::ptr() }; let seconds = convert_datetime_to_seconds(&alarm); @@ -217,24 +310,59 @@ impl<'a, I: Instance> Rtc<'a, I> { while rtc.tar().read().bits() != seconds && timeout > 0 { timeout -= 1; } + + self.set_interrupt(RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE); } + /// Get the current alarm date and time + /// + /// # Returns + /// + /// Alarm date and time as RtcDateTime + /// + /// # Note + /// + /// Reads the alarm timestamp from the time alarm register and converts it. pub fn get_alarm(&self) -> RtcDateTime { let rtc = unsafe { &*I::ptr() }; let alarm_seconds = rtc.tar().read().bits(); convert_seconds_to_datetime(alarm_seconds) } + /// Start the RTC time counter + /// + /// # Note + /// + /// Sets the Time Counter Enable (TCE) bit in the status register. pub fn start(&self) { let rtc = unsafe { &*I::ptr() }; rtc.sr().modify(|_, w| w.tce().set_bit()); } + /// Stop the RTC time counter + /// + /// # Note + /// + /// Clears the Time Counter Enable (TCE) bit in the status register. pub fn stop(&self) { let rtc = unsafe { &*I::ptr() }; rtc.sr().modify(|_, w| w.tce().clear_bit()); } + /// Enable specific RTC interrupts + /// + /// # Arguments + /// + /// * `mask` - Bitmask of interrupts to enable (use RtcInterruptEnable constants) + /// + /// # Note + /// + /// This function enables the specified interrupt types and resets the alarm occurred flag. + /// Available interrupts: + /// - Time Invalid Interrupt + /// - Time Overflow Interrupt + /// - Alarm Interrupt + /// - Seconds Interrupt pub fn set_interrupt(&self, mask: u32) { let rtc = unsafe { &*I::ptr() }; @@ -250,10 +378,17 @@ impl<'a, I: Instance> Rtc<'a, I> { if (RtcInterruptEnable::RTC_SECONDS_INTERRUPT_ENABLE & mask) != 0 { rtc.ier().modify(|_, w| w.tsie().tsie_1()); } - - ALARM_TRIGGERED.store(false, Ordering::SeqCst); } + /// Disable specific RTC interrupts + /// + /// # Arguments + /// + /// * `mask` - Bitmask of interrupts to disable (use RtcInterruptEnable constants) + /// + /// # Note + /// + /// This function disables the specified interrupt types. pub fn disable_interrupt(&self, mask: u32) { let rtc = unsafe { &*I::ptr() }; @@ -271,29 +406,48 @@ impl<'a, I: Instance> Rtc<'a, I> { } } + /// Clear the alarm interrupt flag + /// + /// # Note + /// + /// This function clears the Time Alarm Interrupt Enable bit. pub fn clear_alarm_flag(&self) { let rtc = unsafe { &*I::ptr() }; rtc.ier().modify(|_, w| w.taie().clear_bit()); } - pub fn is_alarm_triggered(&self) -> bool { - ALARM_TRIGGERED.load(Ordering::Relaxed) - } -} + /// Wait for an RTC alarm to trigger. + /// + /// # Arguments + /// + /// * `alarm` - The date and time when the alarm should trigger + /// This function will wait until the RTC alarm is triggered. + /// If no alarm is scheduled, it will wait indefinitely until one is scheduled and triggered. + pub async fn wait_for_alarm(&mut self, alarm: RtcDateTime) { + let wait = WAKER.subscribe().await; -pub fn on_interrupt() { - let rtc = unsafe { &*pac::Rtc0::ptr() }; - // Check if this is actually a time alarm interrupt - let sr = rtc.sr().read(); - if sr.taf().bit_is_set() { - rtc.ier().modify(|_, w| w.taie().clear_bit()); - ALARM_TRIGGERED.store(true, Ordering::SeqCst); + self.set_alarm(alarm); + self.start(); + + // REVISIT: propagate error? + let _ = wait.await; + + // Clear the interrupt and disable the alarm after waking up + self.disable_interrupt(RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE); } } -pub struct RtcHandler; -impl crate::interrupt::typelevel::Handler for RtcHandler { +/// RTC interrupt handler +/// +/// This struct implements the interrupt handler for RTC events. +impl Handler for InterruptHandler { unsafe fn on_interrupt() { - on_interrupt(); + let rtc = &*pac::Rtc0::ptr(); + // Check if this is actually a time alarm interrupt + let sr = rtc.sr().read(); + if sr.taf().bit_is_set() { + rtc.ier().modify(|_, w| w.taie().clear_bit()); + WAKER.wake(); + } } } -- cgit