22
33use core:: marker:: PhantomData ;
44
5+ use embassy_hal_internal:: interrupt:: InterruptExt ;
6+ use embassy_sync:: waitqueue:: AtomicWaker ;
57use embedded_mcu_hal:: time:: { Datetime , DatetimeClock , DatetimeClockError } ;
68use embedded_mcu_hal:: { Nvram , NvramStorage } ;
79
8- use crate :: { Peri , pac, peripherals} ;
10+ use crate :: { Peri , interrupt , pac, peripherals} ;
911
1012/// Number of general-purpose registers in the RTC NVRAM
1113// If you need to consume some of these registers for internal HAL use, write a feature flag that reduces this count,
@@ -19,6 +21,27 @@ unsafe fn rtc() -> &'static pac::rtc::RegisterBlock {
1921 unsafe { & * pac:: Rtc :: ptr ( ) }
2022}
2123
24+ /// Static waker for RTC alarm interrupts.
25+ ///
26+ /// This is a **global** `AtomicWaker` used by the RTC alarm interrupt handler to
27+ /// wake an asynchronous task waiting for the next alarm.
28+ ///
29+ /// # Concurrency and usage constraints
30+ ///
31+ /// - Only **one** alarm can be pending at a time when using this waker. Registering
32+ /// a new waker (e.g. by polling a different future that also uses this waker)
33+ /// will replace the previously registered waker.
34+ /// - This value is a low-level primitive and does **not** itself represent a
35+ /// future. Consumers are expected to implement their own `Future` wrapper
36+ /// (for example, a type like `RtcAlarm`) that:
37+ /// - registers its task's waker with `RTC_ALARM_WAKER`,
38+ /// - programs/configures the hardware alarm, and
39+ /// - completes when the alarm interrupt fires and calls `wake()`.
40+ ///
41+ /// Using this waker directly from multiple independent alarm abstractions at the
42+ /// same time is not supported and may lead to lost wakeups.
43+ static RTC_ALARM_WAKER : AtomicWaker = AtomicWaker :: new ( ) ;
44+
2245/// Represents the real-time clock (RTC) peripheral and provides access to its datetime clock and NVRAM functionality.
2346pub struct Rtc < ' r > {
2447 _p : Peri < ' r , peripherals:: RTC > ,
@@ -49,10 +72,10 @@ pub struct RtcDatetimeClock<'r> {
4972 _phantom : PhantomData < & ' r Peri < ' r , peripherals:: RTC > > ,
5073}
5174
52- /// Implementation for `RtcDatetime `.
75+ /// Implementation for `RtcDatetimeClock `.
5376impl < ' r > RtcDatetimeClock < ' r > {
5477 /// Set the datetime in seconds since the Unix time epoch (January 1, 1970).
55- fn set_datetime_in_secs ( & self , secs : u64 ) -> Result < ( ) , DatetimeClockError > {
78+ fn set_datetime_in_secs ( & mut self , secs : u64 ) -> Result < ( ) , DatetimeClockError > {
5679 // SAFETY: We have sole ownership of the RTC peripheral and we enforce that there is only one instance of RtcDatetime,
5780 // so we can safely access it as long as it's always from an object that has the handle-to-RTC.
5881 let r = unsafe { rtc ( ) } ;
@@ -87,6 +110,119 @@ impl<'r> RtcDatetimeClock<'r> {
87110
88111 Ok ( secs. into ( ) )
89112 }
113+
114+ /// Sets the RTC wake alarm via the match register to wake after the given time in seconds.
115+ ///
116+ /// WARNING:
117+ /// * Only one RTC alarm can be present in the system at a time.
118+ /// Setting a new alarm will cancel an existing one.
119+ /// * After an alarm is set, changing the RTC time will NOT adjust the alarm accordingly.
120+ /// The alarm will still fire at the originally configured absolute time value
121+ /// in the hardware register.
122+ ///
123+ /// # Parameters
124+ ///
125+ /// * `secs_from_now` - A relative offset in seconds from the current RTC time
126+ /// after which the alarm should fire.
127+ ///
128+ /// # Returns
129+ ///
130+ /// `u64` - The absolute RTC time in seconds at which the alarm is scheduled to fire.
131+ pub fn set_alarm_from_now ( & mut self , secs_from_now : u64 ) -> Result < u64 , DatetimeClockError > {
132+ let secs = self
133+ . get_datetime_in_secs ( ) ?
134+ . checked_add ( secs_from_now)
135+ . ok_or ( DatetimeClockError :: UnsupportedDatetime ) ?;
136+ self . set_alarm_at ( secs) ?;
137+ Ok ( secs)
138+ }
139+
140+ /// Sets the RTC wake alarm via the match register to wake at the given Datetime.
141+ ///
142+ /// WARNING:
143+ /// * Only one RTC alarm can be present in the system at a time.
144+ /// Setting a new alarm will cancel an existing one.
145+ /// * After an alarm is set, changing the RTC time will NOT adjust the alarm accordingly.
146+ /// The alarm will still fire at the originally configured absolute time value
147+ /// in the hardware register.
148+ ///
149+ /// # Parameters
150+ ///
151+ /// * `unix_time_secs` - An absolute RTC time in seconds at which the alarm should fire.
152+ pub fn set_alarm_at ( & mut self , unix_time_secs : u64 ) -> Result < ( ) , DatetimeClockError > {
153+ // Check that the alarm end time is not in the past
154+ if self . get_datetime_in_secs ( ) ? > unix_time_secs {
155+ return Err ( DatetimeClockError :: UnsupportedDatetime ) ;
156+ }
157+
158+ // Convert seconds to u32 to interface with 32 bit hardware RTC
159+ let secs: u32 = unix_time_secs
160+ . try_into ( )
161+ . map_err ( |_| DatetimeClockError :: UnsupportedDatetime ) ?;
162+
163+ // SAFETY: We have sole ownership of the RTC peripheral and we enforce that there is only one instance of RtcDatetime,
164+ // so we can safely access it as long as it's always from an object that has the handle-to-RTC.
165+ // The handle was retrieved in get_datetime_in_secs(), which has since completed and gone out of scope.
166+ let r = unsafe { rtc ( ) } ;
167+
168+ critical_section:: with ( |_cs| {
169+ // Clear any pending alarm interrupt
170+ r. ctrl ( ) . modify ( |_r, w| w. alarm1hz ( ) . set_bit ( ) ) ;
171+
172+ // Set the match register with the new time
173+ r. match_ ( ) . write ( |w| unsafe { w. bits ( secs) } ) ;
174+
175+ // Enable the 1Hz timer alarm for deep power down
176+ r. ctrl ( ) . modify ( |_r, w| w. alarmdpd_en ( ) . set_bit ( ) ) ;
177+
178+ // Enable RTC interrupt
179+ interrupt:: RTC . unpend ( ) ;
180+ unsafe {
181+ interrupt:: RTC . enable ( ) ;
182+ }
183+ } ) ;
184+
185+ Ok ( ( ) )
186+ }
187+
188+ /// Clears the RTC 1Hz alarm by resetting related registers
189+ pub fn clear_alarm ( & mut self ) {
190+ // SAFETY: We have sole ownership of the RTC peripheral and we enforce that there is only one instance of RtcDatetime,
191+ // so we can safely access it as long as it's always from an object that has the handle-to-RTC.
192+ let r = unsafe { rtc ( ) } ;
193+
194+ critical_section:: with ( |_cs| {
195+ // Disable the 1Hz timer alarm for deep power down
196+ r. ctrl ( ) . modify ( |_r, w| w. alarmdpd_en ( ) . clear_bit ( ) ) ;
197+
198+ // Clear the Alarm1Hz match status register
199+ // Note: "Writing a 1 clears this bit"
200+ r. ctrl ( ) . modify ( |_r, w| w. alarm1hz ( ) . set_bit ( ) ) ;
201+
202+ // Resets the match register to its default
203+ r. match_ ( ) . write ( |w| unsafe { w. bits ( u32:: MAX ) } ) ;
204+
205+ // Disable RTC interrupt
206+ interrupt:: RTC . disable ( ) ;
207+ } ) ;
208+ }
209+
210+ /// Registers a waker to be notified when the RTC alarm fires.
211+ ///
212+ /// This method forwards the waker to the internal static [`AtomicWaker`] used
213+ /// by the RTC interrupt handler. When the alarm interrupt fires, the registered
214+ /// waker will be called.
215+ ///
216+ /// This is typically called from a `Future::poll` implementation when waiting
217+ /// for an RTC alarm to expire. Only one waker can be registered at a time;
218+ /// calling this method replaces any previously registered waker.
219+ ///
220+ /// # Parameters
221+ ///
222+ /// * `waker` - The waker to be notified when the alarm fires
223+ pub fn register_alarm_waker ( & self , waker : & core:: task:: Waker ) {
224+ RTC_ALARM_WAKER . register ( waker) ;
225+ }
90226}
91227
92228impl DatetimeClock for RtcDatetimeClock < ' _ > {
@@ -186,3 +322,31 @@ impl<'r> Nvram<'r, RtcNvramStorage<'r>, u32, IMXRT_GPREG_COUNT> for RtcNvram<'r>
186322 self . storage . each_ref ( ) . map ( |storage| storage. read ( ) )
187323 }
188324}
325+
326+ /// RTC interrupt handler
327+ /// This is called when the RTC alarm fires
328+ #[ cfg( feature = "rt" ) ]
329+ #[ interrupt]
330+ fn RTC ( ) {
331+ // SAFETY: This is called from an interrupt context, but we only read/write the CTRL register
332+ // which is safe to access from multiple contexts with proper atomic operations
333+ let r = unsafe { rtc ( ) } ;
334+
335+ // Check if this is an alarm interrupt
336+ if r. ctrl ( ) . read ( ) . alarm1hz ( ) . bit_is_set ( ) {
337+ // Clear any pending RTC interrupt before waking tasks to avoid spurious retriggers
338+ interrupt:: RTC . unpend ( ) ;
339+
340+ // Disable RTC interrupt
341+ interrupt:: RTC . disable ( ) ;
342+
343+ // Disable the 1Hz timer alarm for deep power down
344+ r. ctrl ( ) . modify ( |_r, w| w. alarmdpd_en ( ) . clear_bit ( ) ) ;
345+
346+ // Clear the alarm interrupt flag by writing 1 to it
347+ r. ctrl ( ) . modify ( |_r, w| w. alarm1hz ( ) . set_bit ( ) ) ;
348+
349+ // Wake any task waiting on the alarm
350+ RTC_ALARM_WAKER . wake ( ) ;
351+ }
352+ }
0 commit comments