Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
4 changes: 2 additions & 2 deletions examples/rt685s-evk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ These examples illustrates how to use the embassy-imxrt HAL.
Add uniquely named example to `src/bin` like `adc.rs`

## Build
`cd` to examples folder
`cd` to examples/[device] folder
`cargo build --bin <example_name>` for example, `cargo build --bin adc`

## Run
Assuming RT685 is powered and connected to Jlink debug probe and the latest probe-rs is installed via
`$ cargo install probe-rs-tools --git https://github.com/probe-rs/probe-rs --locked`
`cd` to examples folder
`cd` to examples/[device] folder
`cargo run --bin <example_name>` for example, `cargo run --bin adc`
93 changes: 93 additions & 0 deletions examples/rt685s-evk/src/bin/rtc-alarm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#![no_std]
#![no_main]

use core::task::Poll;

use defmt::info;
use embassy_executor::Spawner;
use embassy_imxrt::rtc::{Rtc, RtcDatetimeClock};
use embassy_time::Timer;
use embedded_mcu_hal::time::{Datetime, DatetimeClock, DatetimeClockError, Month, UncheckedDatetime};
use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _};

/// RTC alarm struct to await the time alarm wakeup location
/// This should be implemented by the user to handle RTC peripheral synchronization
struct RtcAlarm<'r> {
expires_at: u64,
rtc: &'r RtcDatetimeClock<'r>,
}

impl<'r> Future for RtcAlarm<'r> {
type Output = Result<(), DatetimeClockError>;
fn poll(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll<Self::Output> {
match self.rtc.get_current_datetime() {
Ok(now) => {
if self.expires_at <= now.to_unix_time_seconds() {
Poll::Ready(Ok(()))
} else {
info!("Alarm pending at time {}, expires at {}", now, self.expires_at);

// Register our waker to be called by the interrupt handler
self.rtc.register_alarm_waker(cx.waker());

Poll::Pending
}
}
Err(e) => Poll::Ready(Err(e)),
}
}
}
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
const ALARM_SECONDS: u64 = 10;

let p = embassy_imxrt::init(Default::default());
let mut r = Rtc::new(p.RTC);
let (dt_clock, _rtc_nvram) = r.split();

// Initialize the system RTC
let datetime = Datetime::new(UncheckedDatetime {
year: 2026,
month: Month::January,
day: 12,
hour: 16,
..Default::default()
})
.unwrap();
let ret = dt_clock.set_current_datetime(&datetime);
info!("RTC set time: {:?}", datetime);
assert!(ret.is_ok());

// Show RTC functioning: Display current time before setting alarm
let current_time = dt_clock.get_current_datetime().unwrap();
info!("Current time before alarm: {:?}", current_time);

info!("Waiting 5 seconds...");
Timer::after_secs(5).await; // This timer uses the OsTimer peripheral

info!(
"Current time after waiting: {:?}",
dt_clock.get_current_datetime().unwrap()
);

// Set an RTC alarm to trigger after ALARM_SECONDS
info!("Setting alarm to trigger in {} seconds...", ALARM_SECONDS);
let expires_at_secs = dt_clock.set_alarm_from_now(ALARM_SECONDS).expect("Failed to set alarm");

let alarm = RtcAlarm {
expires_at: expires_at_secs,
rtc: dt_clock,
};

// Wait for the alarm to trigger
info!("Waiting for alarm...");
alarm.await.expect("Alarm failed");

// Display time after alarm triggered
let wake_time = dt_clock.get_current_datetime().unwrap();
info!("Alarm triggered! Wake time: {:?}", wake_time);

// Clear the alarm
dt_clock.clear_alarm().expect("Failed to clear alarm");
info!("Alarm cleared");
}
172 changes: 169 additions & 3 deletions src/rtc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

use core::marker::PhantomData;

use embassy_hal_internal::interrupt::InterruptExt;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_mcu_hal::time::{Datetime, DatetimeClock, DatetimeClockError};
use embedded_mcu_hal::{Nvram, NvramStorage};

use crate::{Peri, pac, peripherals};
use crate::{Peri, interrupt, pac, peripherals};

/// Number of general-purpose registers in the RTC NVRAM
// If you need to consume some of these registers for internal HAL use, write a feature flag that reduces this count,
Expand All @@ -19,6 +21,27 @@ unsafe fn rtc() -> &'static pac::rtc::RegisterBlock {
unsafe { &*pac::Rtc::ptr() }
}

/// Static waker for RTC alarm interrupts.
///
/// This is a **global** `AtomicWaker` used by the RTC alarm interrupt handler to
/// wake an asynchronous task waiting for the next alarm.
///
/// # Concurrency and usage constraints
///
/// - Only **one** alarm can be pending at a time when using this waker. Registering
/// a new waker (e.g. by polling a different future that also uses this waker)
/// will replace the previously registered waker.
/// - This value is a low-level primitive and does **not** itself represent a
/// future. Consumers are expected to implement their own `Future` wrapper
/// (for example, a type like `RtcAlarm`) that:
/// - registers its task's waker with `RTC_ALARM_WAKER`,
/// - programs/configures the hardware alarm, and
/// - completes when the alarm interrupt fires and calls `wake()`.
///
/// Using this waker directly from multiple independent alarm abstractions at the
/// same time is not supported and may lead to lost wakeups.
static RTC_ALARM_WAKER: AtomicWaker = AtomicWaker::new();

/// Represents the real-time clock (RTC) peripheral and provides access to its datetime clock and NVRAM functionality.
pub struct Rtc<'r> {
_p: Peri<'r, peripherals::RTC>,
Expand Down Expand Up @@ -49,10 +72,10 @@ pub struct RtcDatetimeClock<'r> {
_phantom: PhantomData<&'r Peri<'r, peripherals::RTC>>,
}

/// Implementation for `RtcDatetime`.
/// Implementation for `RtcDatetimeClock`.
impl<'r> RtcDatetimeClock<'r> {
/// Set the datetime in seconds since the Unix time epoch (January 1, 1970).
fn set_datetime_in_secs(&self, secs: u64) -> Result<(), DatetimeClockError> {
fn set_datetime_in_secs(&mut self, secs: u64) -> Result<(), DatetimeClockError> {
// SAFETY: We have sole ownership of the RTC peripheral and we enforce that there is only one instance of RtcDatetime,
// so we can safely access it as long as it's always from an object that has the handle-to-RTC.
let r = unsafe { rtc() };
Expand Down Expand Up @@ -87,6 +110,125 @@ impl<'r> RtcDatetimeClock<'r> {

Ok(secs.into())
}

/// Sets the RTC wake alarm via the match register to wake after the given time in seconds.
///
/// WARNING:
/// * Only one RTC alarm can be present in the system at a time.
/// Setting a new alarm will cancel an existing one.
/// * After an alarm register is set, any call to set the RTC to a different time
/// will NOT update the alarm's end time, and the relative alarm time will
/// be different than when originally set.
///
/// # Parameters
///
/// * `secs_from_now` - A relative offset in seconds from the current RTC time
/// after which the alarm should fire.
///
/// # Returns
///
/// `u64` - The absolute RTC time in seconds at which the alarm is scheduled to fire.
pub fn set_alarm_from_now(&mut self, secs_from_now: u64) -> Result<u64, DatetimeClockError> {
let secs = self
.get_datetime_in_secs()?
.checked_add(secs_from_now)
.ok_or(DatetimeClockError::UnsupportedDatetime)?;
self.set_alarm_at(Datetime::from_unix_time_seconds(secs))
}

/// Sets the RTC wake alarm via the match register to wake at the given Datetime.
///
/// WARNING:
/// * Only one RTC alarm can be present in the system at a time.
/// Setting a new alarm will cancel an existing one.
/// * After an alarm register is set, any call to set the RTC to a different time
/// will NOT update the alarm's end time. Thus, absolute UTC time set in this
/// call will remain as the alarm time.
///
/// # Parameters
///
/// * `alarm_time` - An absolute Datetime at which the alarm should fire.
///
/// # Returns
///
/// `u64` - The absolute RTC time in seconds at which the alarm is scheduled to fire.
/// This is the `alarm_time` argument converted into u64
pub fn set_alarm_at(&mut self, alarm_time: Datetime) -> Result<u64, DatetimeClockError> {
let secs_u64 = alarm_time.to_unix_time_seconds();
let secs: u32 = secs_u64
.try_into()
.map_err(|_| DatetimeClockError::UnsupportedDatetime)?;

// Check that the alarm end is not in the past
if self.get_datetime_in_secs()? > secs_u64 {
return Err(DatetimeClockError::UnsupportedDatetime);
}

// SAFETY: We have sole ownership of the RTC peripheral and we enforce that there is only one instance of RtcDatetime,
// so we can safely access it as long as it's always from an object that has the handle-to-RTC.
// The handle was retrieved in get_datetime_in_secs(), which has since completed and gone out of scope.
let r = unsafe { rtc() };

critical_section::with(|_cs| {
// Clear any pending alarm interrupt
r.ctrl().modify(|_r, w| w.alarm1hz().set_bit());

// Set the match register with the new time
r.match_().write(|w| unsafe { w.bits(secs) });

// Enable the 1Hz timer alarm for deep power down
r.ctrl().modify(|_r, w| w.alarmdpd_en().set_bit());

// Enable RTC interrupt
interrupt::RTC.unpend();
unsafe {
interrupt::RTC.enable();
}
});

Ok(secs_u64)
}

/// Clears the RTC 1Hz alarm by resetting related registers
pub fn clear_alarm(&mut self) -> Result<(), DatetimeClockError> {
// SAFETY: We have sole ownership of the RTC peripheral and we enforce that there is only one instance of RtcDatetime,
// so we can safely access it as long as it's always from an object that has the handle-to-RTC.
let r = unsafe { rtc() };

critical_section::with(|_cs| {
// Disable the 1Hz timer alarm for deep power down
r.ctrl().modify(|_r, w| w.alarmdpd_en().clear_bit());

// Clear the Alarm1Hz match status register
// Note: "Writing a 1 clears this bit"
r.ctrl().modify(|_r, w| w.alarm1hz().set_bit());

// Resets the match register to its default
r.match_().write(|w| unsafe { w.bits(u32::MAX) });

// Disable RTC interrupt
interrupt::RTC.disable();
});

Ok(())
}

/// Registers a waker to be notified when the RTC alarm fires.
///
/// This method forwards the waker to the internal static [`AtomicWaker`] used
/// by the RTC interrupt handler. When the alarm interrupt fires, the registered
/// waker will be called.
///
/// This is typically called from a `Future::poll` implementation when waiting
/// for an RTC alarm to expire. Only one waker can be registered at a time;
/// calling this method replaces any previously registered waker.
///
/// # Parameters
///
/// * `waker` - The waker to be notified when the alarm fires
pub fn register_alarm_waker(&self, waker: &core::task::Waker) {
RTC_ALARM_WAKER.register(waker);
}
}

impl DatetimeClock for RtcDatetimeClock<'_> {
Expand Down Expand Up @@ -186,3 +328,27 @@ impl<'r> Nvram<'r, RtcNvramStorage<'r>, u32, IMXRT_GPREG_COUNT> for RtcNvram<'r>
self.storage.each_ref().map(|storage| storage.read())
}
}

/// RTC interrupt handler
/// This is called when the RTC alarm fires
#[cfg(feature = "rt")]
#[interrupt]
fn RTC() {
// SAFETY: This is called from an interrupt context, but we only read/write the CTRL register
// which is safe to access from multiple contexts with proper atomic operations
let r = unsafe { rtc() };

// Check if this is an alarm interrupt
if r.ctrl().read().alarm1hz().bit_is_set() {
critical_section::with(|_cs| {
// Clear any pending RTC interrupt before waking tasks to avoid spurious retriggers
interrupt::RTC.unpend();

// Clear the alarm interrupt flag by writing 1 to it
r.ctrl().modify(|_r, w| w.alarm1hz().set_bit());
});

// Wake any task waiting on the alarm
RTC_ALARM_WAKER.wake();
}
}