Skip to content

embassy-rp (rp2040): Rtc wait_for_alarm #4216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions embassy-rp/src/rtc/datetime_no_deps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion embassy-rp/src/rtc/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u16>,
Expand Down
122 changes: 121 additions & 1 deletion embassy-rp/src/rtc/mod.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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> {
Expand All @@ -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<interrupt::typelevel::RTC_IRQ, InterruptHandler>) -> 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 }
}

Expand Down Expand Up @@ -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<DateTimeFilter> {
// 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<crate::interrupt::typelevel::RTC_IRQ> 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]
Expand Down
8 changes: 7 additions & 1 deletion examples/rp/src/bin/rtc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
66 changes: 66 additions & 0 deletions examples/rp/src/bin/rtc_alarm.rs
Original file line number Diff line number Diff line change
@@ -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
match rtc.alarm_scheduled() {
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());
}
Some(_) => {}
}
}
// Alarm triggered
Either::Second(_) => {
info!("ALARM TRIGGERED!");
}
}
}
}