Skip to content

Commit 90f6497

Browse files
authored
Merge pull request #4216 from 1-rafael-1/rp2040-rtc-alarm
embassy-rp (rp2040): Rtc wait_for_alarm
2 parents bbc9385 + a54996d commit 90f6497

File tree

8 files changed

+332
-5
lines changed

8 files changed

+332
-5
lines changed

embassy-rp/CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- Add PIO SPI
1212
- Add PIO I2S input
1313
- Add PIO onewire parasite power strong pullup
14+
- add `wait_for_alarm` and `alarm_scheduled` methods to rtc module ([#4216](https://github.com/embassy-rs/embassy/pull/4216))
1415

1516
## 0.8.0 - 2025-08-26
1617

@@ -55,7 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5556

5657
## 0.4.0 - 2025-03-09
5758

58-
- Add PIO functions. ([#3857](https://github.com/embassy-rs/embassy/pull/3857))
59+
- Add PIO functions. ([#3857](https://github.com/embassy-rs/embassy/pull/3857))
5960
The functions added in this change are `get_addr` `get_tx_threshold`, `set_tx_threshold`, `get_rx_threshold`, `set_rx_threshold`, `set_thresholds`.
6061
- Expose the watchdog reset reason. ([#3877](https://github.com/embassy-rs/embassy/pull/3877))
6162
- 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.
9697
- Add Clone and Copy to Error types
9798
- fix spinlocks staying locked after reset.
9899
- wait until read matches for PSM accesses.
99-
- Remove generics
100+
- Remove generics
100101
- fix drop implementation of BufferedUartRx and BufferedUartTx
101102
- implement `embedded_storage_async::nor_flash::MultiwriteNorFlash`
102103
- rp usb: wake ep-wakers after stalling

embassy-rp/src/rtc/datetime_no_deps.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub struct DateTime {
4646
/// A day of the week
4747
#[repr(u8)]
4848
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
49+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
4950
#[allow(missing_docs)]
5051
pub enum DayOfWeek {
5152
Sunday = 0,

embassy-rp/src/rtc/filter.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use crate::pac::rtc::regs::{IrqSetup0, IrqSetup1};
44
/// A filter used for [`RealTimeClock::schedule_alarm`].
55
///
66
/// [`RealTimeClock::schedule_alarm`]: struct.RealTimeClock.html#method.schedule_alarm
7-
#[derive(Default)]
7+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
8+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
89
pub struct DateTimeFilter {
910
/// The year that this alarm should trigger on, `None` if the RTC alarm should not trigger on a year value.
1011
pub year: Option<u16>,

embassy-rp/src/rtc/mod.rs

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
//! RTC driver.
22
mod filter;
33

4+
use core::future::poll_fn;
5+
use core::sync::atomic::{compiler_fence, AtomicBool, Ordering};
6+
use core::task::Poll;
7+
48
use embassy_hal_internal::{Peri, PeripheralType};
9+
use embassy_sync::waitqueue::AtomicWaker;
510

611
pub use self::filter::DateTimeFilter;
712

@@ -11,6 +16,13 @@ mod datetime;
1116

1217
pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError};
1318
use crate::clocks::clk_rtc_freq;
19+
use crate::interrupt::typelevel::Binding;
20+
use crate::interrupt::{self, InterruptExt};
21+
22+
// Static waker for the interrupt handler
23+
static WAKER: AtomicWaker = AtomicWaker::new();
24+
// Static flag to indicate if an alarm has occurred
25+
static ALARM_OCCURRED: AtomicBool = AtomicBool::new(false);
1426

1527
/// A reference to the real time clock of the system
1628
pub struct Rtc<'d, T: Instance> {
@@ -23,10 +35,15 @@ impl<'d, T: Instance> Rtc<'d, T> {
2335
/// # Errors
2436
///
2537
/// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range.
26-
pub fn new(inner: Peri<'d, T>) -> Self {
38+
pub fn new(inner: Peri<'d, T>, _irq: impl Binding<interrupt::typelevel::RTC_IRQ, InterruptHandler>) -> Self {
2739
// Set the RTC divider
2840
inner.regs().clkdiv_m1().write(|w| w.set_clkdiv_m1(clk_rtc_freq() - 1));
2941

42+
// Setup the IRQ
43+
// Clear any pending interrupts from the RTC_IRQ interrupt and enable it, so we do not have unexpected interrupts after initialization
44+
interrupt::RTC_IRQ.unpend();
45+
unsafe { interrupt::RTC_IRQ.enable() };
46+
3047
Self { inner }
3148
}
3249

@@ -174,6 +191,110 @@ impl<'d, T: Instance> Rtc<'d, T> {
174191
pub fn clear_interrupt(&mut self) {
175192
self.disable_alarm();
176193
}
194+
195+
/// Check if an alarm is scheduled.
196+
///
197+
/// This function checks if the RTC alarm is currently active. If it is, it returns the alarm configuration
198+
/// as a [`DateTimeFilter`]. Otherwise, it returns `None`.
199+
pub fn alarm_scheduled(&self) -> Option<DateTimeFilter> {
200+
// Check if alarm is active
201+
if !self.inner.regs().irq_setup_0().read().match_active() {
202+
return None;
203+
}
204+
205+
// Get values from both alarm registers
206+
let irq_0 = self.inner.regs().irq_setup_0().read();
207+
let irq_1 = self.inner.regs().irq_setup_1().read();
208+
209+
// Create a DateTimeFilter and populate it based on which fields are enabled
210+
let mut filter = DateTimeFilter::default();
211+
212+
if irq_0.year_ena() {
213+
filter.year = Some(irq_0.year());
214+
}
215+
216+
if irq_0.month_ena() {
217+
filter.month = Some(irq_0.month());
218+
}
219+
220+
if irq_0.day_ena() {
221+
filter.day = Some(irq_0.day());
222+
}
223+
224+
if irq_1.dotw_ena() {
225+
// Convert day of week value to DayOfWeek enum
226+
let day_of_week = match irq_1.dotw() {
227+
0 => DayOfWeek::Sunday,
228+
1 => DayOfWeek::Monday,
229+
2 => DayOfWeek::Tuesday,
230+
3 => DayOfWeek::Wednesday,
231+
4 => DayOfWeek::Thursday,
232+
5 => DayOfWeek::Friday,
233+
6 => DayOfWeek::Saturday,
234+
_ => return None, // Invalid day of week
235+
};
236+
filter.day_of_week = Some(day_of_week);
237+
}
238+
239+
if irq_1.hour_ena() {
240+
filter.hour = Some(irq_1.hour());
241+
}
242+
243+
if irq_1.min_ena() {
244+
filter.minute = Some(irq_1.min());
245+
}
246+
247+
if irq_1.sec_ena() {
248+
filter.second = Some(irq_1.sec());
249+
}
250+
251+
Some(filter)
252+
}
253+
254+
/// Wait for an RTC alarm to trigger.
255+
///
256+
/// This function will wait until the RTC alarm is triggered. If the alarm is already triggered, it will return immediately.
257+
/// If no alarm is scheduled, it will wait indefinitely until one is scheduled and triggered.
258+
pub async fn wait_for_alarm(&mut self) {
259+
poll_fn(|cx| {
260+
WAKER.register(cx.waker());
261+
262+
// Atomically check and clear the alarm occurred flag to prevent race conditions
263+
if critical_section::with(|_| {
264+
let occurred = ALARM_OCCURRED.load(Ordering::SeqCst);
265+
if occurred {
266+
ALARM_OCCURRED.store(false, Ordering::SeqCst);
267+
}
268+
occurred
269+
}) {
270+
// Clear the interrupt and disable the alarm
271+
self.clear_interrupt();
272+
273+
compiler_fence(Ordering::SeqCst);
274+
return Poll::Ready(());
275+
} else {
276+
return Poll::Pending;
277+
}
278+
})
279+
.await;
280+
}
281+
}
282+
283+
/// Interrupt handler.
284+
pub struct InterruptHandler {
285+
_empty: (),
286+
}
287+
288+
impl crate::interrupt::typelevel::Handler<crate::interrupt::typelevel::RTC_IRQ> for InterruptHandler {
289+
unsafe fn on_interrupt() {
290+
// Disable the alarm first thing, to prevent unexpected re-entry
291+
let rtc = crate::pac::RTC;
292+
rtc.irq_setup_0().modify(|w| w.set_match_ena(false));
293+
294+
// Set the alarm occurred flag and wake the waker
295+
ALARM_OCCURRED.store(true, Ordering::SeqCst);
296+
WAKER.wake();
297+
}
177298
}
178299

179300
/// Errors that can occur on methods on [Rtc]

examples/rp/src/bin/rtc.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,22 @@
55

66
use defmt::*;
77
use embassy_executor::Spawner;
8+
use embassy_rp::bind_interrupts;
89
use embassy_rp::rtc::{DateTime, DayOfWeek, Rtc};
910
use embassy_time::Timer;
1011
use {defmt_rtt as _, panic_probe as _};
1112

13+
// Bind the RTC interrupt to the handler
14+
bind_interrupts!(struct Irqs {
15+
RTC_IRQ => embassy_rp::rtc::InterruptHandler;
16+
});
17+
1218
#[embassy_executor::main]
1319
async fn main(_spawner: Spawner) {
1420
let p = embassy_rp::init(Default::default());
1521
info!("Wait for 20s");
1622

17-
let mut rtc = Rtc::new(p.RTC);
23+
let mut rtc = Rtc::new(p.RTC, Irqs);
1824

1925
if !rtc.is_running() {
2026
info!("Start RTC");

examples/rp/src/bin/rtc_alarm.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//! This example shows how to use RTC (Real Time Clock) for scheduling alarms and reacting to them.
2+
3+
#![no_std]
4+
#![no_main]
5+
6+
use defmt::*;
7+
use embassy_executor::Spawner;
8+
use embassy_futures::select::{select, Either};
9+
use embassy_rp::bind_interrupts;
10+
use embassy_rp::rtc::{DateTime, DateTimeFilter, DayOfWeek, Rtc};
11+
use embassy_time::Timer;
12+
use {defmt_rtt as _, panic_probe as _};
13+
14+
// Bind the RTC interrupt to the handler
15+
bind_interrupts!(struct Irqs {
16+
RTC_IRQ => embassy_rp::rtc::InterruptHandler;
17+
});
18+
19+
#[embassy_executor::main]
20+
async fn main(_spawner: Spawner) {
21+
let p = embassy_rp::init(Default::default());
22+
let mut rtc = Rtc::new(p.RTC, Irqs);
23+
24+
if !rtc.is_running() {
25+
info!("Start RTC");
26+
let now = DateTime {
27+
year: 2000,
28+
month: 1,
29+
day: 1,
30+
day_of_week: DayOfWeek::Saturday,
31+
hour: 0,
32+
minute: 0,
33+
second: 0,
34+
};
35+
rtc.set_datetime(now).unwrap();
36+
}
37+
38+
loop {
39+
// Wait for 5 seconds or until the alarm is triggered
40+
match select(Timer::after_secs(5), rtc.wait_for_alarm()).await {
41+
// Timer expired
42+
Either::First(_) => {
43+
let dt = rtc.now().unwrap();
44+
info!(
45+
"Now: {}-{:02}-{:02} {}:{:02}:{:02}",
46+
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
47+
);
48+
49+
// See if the alarm is already scheduled, if not, schedule it
50+
if rtc.alarm_scheduled().is_none() {
51+
info!("Scheduling alarm for 30 seconds from now");
52+
rtc.schedule_alarm(DateTimeFilter::default().second((dt.second + 30) % 60));
53+
info!("Alarm scheduled: {}", rtc.alarm_scheduled().unwrap());
54+
}
55+
}
56+
// Alarm triggered
57+
Either::Second(_) => {
58+
let dt = rtc.now().unwrap();
59+
info!(
60+
"ALARM TRIGGERED! Now: {}-{:02}-{:02} {}:{:02}:{:02}",
61+
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
62+
);
63+
}
64+
}
65+
}
66+
}

tests/rp/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ name = "float"
6464
path = "src/bin/float.rs"
6565
required-features = [ "rp2040",]
6666

67+
# RTC is only available on RP2040
68+
[[bin]]
69+
name = "rtc"
70+
path = "src/bin/rtc.rs"
71+
required-features = [ "rp2040",]
72+
6773
[profile.dev]
6874
debug = 2
6975
debug-assertions = true

0 commit comments

Comments
 (0)