aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlf Lilleengen <[email protected]>2025-09-17 09:19:38 +0000
committerGitHub <[email protected]>2025-09-17 09:19:38 +0000
commit90f6497959adf6f0f0a65f1c53be0bd6b0e3f1a7 (patch)
tree0cc2100d2e2cf93954d0033d210afcace25cf471
parentbbc93851fb9fb31342cc44ba096bed84134427f6 (diff)
parenta54996d8d76d7168ec458597cee276de975bd699 (diff)
Merge pull request #4216 from 1-rafael-1/rp2040-rtc-alarm
embassy-rp (rp2040): Rtc wait_for_alarm
-rw-r--r--embassy-rp/CHANGELOG.md5
-rw-r--r--embassy-rp/src/rtc/datetime_no_deps.rs1
-rw-r--r--embassy-rp/src/rtc/filter.rs3
-rw-r--r--embassy-rp/src/rtc/mod.rs123
-rw-r--r--examples/rp/src/bin/rtc.rs8
-rw-r--r--examples/rp/src/bin/rtc_alarm.rs66
-rw-r--r--tests/rp/Cargo.toml6
-rw-r--r--tests/rp/src/bin/rtc.rs125
8 files changed, 332 insertions, 5 deletions
diff --git a/embassy-rp/CHANGELOG.md b/embassy-rp/CHANGELOG.md
index 841c9f068..d1265ffc4 100644
--- a/embassy-rp/CHANGELOG.md
+++ b/embassy-rp/CHANGELOG.md
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11- Add PIO SPI 11- Add PIO SPI
12- Add PIO I2S input 12- Add PIO I2S input
13- Add PIO onewire parasite power strong pullup 13- 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))
14 15
15## 0.8.0 - 2025-08-26 16## 0.8.0 - 2025-08-26
16 17
@@ -55,7 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
55 56
56## 0.4.0 - 2025-03-09 57## 0.4.0 - 2025-03-09
57 58
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))
59 The functions added in this change are `get_addr` `get_tx_threshold`, `set_tx_threshold`, `get_rx_threshold`, `set_rx_threshold`, `set_thresholds`. 60 The functions added in this change are `get_addr` `get_tx_threshold`, `set_tx_threshold`, `get_rx_threshold`, `set_rx_threshold`, `set_thresholds`.
60- Expose the watchdog reset reason. ([#3877](https://github.com/embassy-rs/embassy/pull/3877)) 61- Expose the watchdog reset reason. ([#3877](https://github.com/embassy-rs/embassy/pull/3877))
61- Update pio-rs, reexport, move instr methods to SM. ([#3865](https://github.com/embassy-rs/embassy/pull/3865)) 62- 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.
96- Add Clone and Copy to Error types 97- Add Clone and Copy to Error types
97- fix spinlocks staying locked after reset. 98- fix spinlocks staying locked after reset.
98- wait until read matches for PSM accesses. 99- wait until read matches for PSM accesses.
99- Remove generics 100- Remove generics
100- fix drop implementation of BufferedUartRx and BufferedUartTx 101- fix drop implementation of BufferedUartRx and BufferedUartTx
101- implement `embedded_storage_async::nor_flash::MultiwriteNorFlash` 102- implement `embedded_storage_async::nor_flash::MultiwriteNorFlash`
102- rp usb: wake ep-wakers after stalling 103- rp usb: wake ep-wakers after stalling
diff --git a/embassy-rp/src/rtc/datetime_no_deps.rs b/embassy-rp/src/rtc/datetime_no_deps.rs
index 5de00e6b4..77d4a3055 100644
--- a/embassy-rp/src/rtc/datetime_no_deps.rs
+++ b/embassy-rp/src/rtc/datetime_no_deps.rs
@@ -46,6 +46,7 @@ pub struct DateTime {
46/// A day of the week 46/// A day of the week
47#[repr(u8)] 47#[repr(u8)]
48#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] 48#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
49#[cfg_attr(feature = "defmt", derive(defmt::Format))]
49#[allow(missing_docs)] 50#[allow(missing_docs)]
50pub enum DayOfWeek { 51pub enum DayOfWeek {
51 Sunday = 0, 52 Sunday = 0,
diff --git a/embassy-rp/src/rtc/filter.rs b/embassy-rp/src/rtc/filter.rs
index d4a3bab2f..433053613 100644
--- a/embassy-rp/src/rtc/filter.rs
+++ b/embassy-rp/src/rtc/filter.rs
@@ -4,7 +4,8 @@ use crate::pac::rtc::regs::{IrqSetup0, IrqSetup1};
4/// A filter used for [`RealTimeClock::schedule_alarm`]. 4/// A filter used for [`RealTimeClock::schedule_alarm`].
5/// 5///
6/// [`RealTimeClock::schedule_alarm`]: struct.RealTimeClock.html#method.schedule_alarm 6/// [`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))]
8pub struct DateTimeFilter { 9pub struct DateTimeFilter {
9 /// The year that this alarm should trigger on, `None` if the RTC alarm should not trigger on a year value. 10 /// The year that this alarm should trigger on, `None` if the RTC alarm should not trigger on a year value.
10 pub year: Option<u16>, 11 pub year: Option<u16>,
diff --git a/embassy-rp/src/rtc/mod.rs b/embassy-rp/src/rtc/mod.rs
index 63cf91d28..8b0deed21 100644
--- a/embassy-rp/src/rtc/mod.rs
+++ b/embassy-rp/src/rtc/mod.rs
@@ -1,7 +1,12 @@
1//! RTC driver. 1//! RTC driver.
2mod filter; 2mod filter;
3 3
4use core::future::poll_fn;
5use core::sync::atomic::{compiler_fence, AtomicBool, Ordering};
6use core::task::Poll;
7
4use embassy_hal_internal::{Peri, PeripheralType}; 8use embassy_hal_internal::{Peri, PeripheralType};
9use embassy_sync::waitqueue::AtomicWaker;
5 10
6pub use self::filter::DateTimeFilter; 11pub use self::filter::DateTimeFilter;
7 12
@@ -11,6 +16,13 @@ mod datetime;
11 16
12pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError}; 17pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError};
13use crate::clocks::clk_rtc_freq; 18use crate::clocks::clk_rtc_freq;
19use crate::interrupt::typelevel::Binding;
20use crate::interrupt::{self, InterruptExt};
21
22// Static waker for the interrupt handler
23static WAKER: AtomicWaker = AtomicWaker::new();
24// Static flag to indicate if an alarm has occurred
25static ALARM_OCCURRED: AtomicBool = AtomicBool::new(false);
14 26
15/// A reference to the real time clock of the system 27/// A reference to the real time clock of the system
16pub struct Rtc<'d, T: Instance> { 28pub struct Rtc<'d, T: Instance> {
@@ -23,10 +35,15 @@ impl<'d, T: Instance> Rtc<'d, T> {
23 /// # Errors 35 /// # Errors
24 /// 36 ///
25 /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. 37 /// 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 {
27 // Set the RTC divider 39 // Set the RTC divider
28 inner.regs().clkdiv_m1().write(|w| w.set_clkdiv_m1(clk_rtc_freq() - 1)); 40 inner.regs().clkdiv_m1().write(|w| w.set_clkdiv_m1(clk_rtc_freq() - 1));
29 41
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
30 Self { inner } 47 Self { inner }
31 } 48 }
32 49
@@ -174,6 +191,110 @@ impl<'d, T: Instance> Rtc<'d, T> {
174 pub fn clear_interrupt(&mut self) { 191 pub fn clear_interrupt(&mut self) {
175 self.disable_alarm(); 192 self.disable_alarm();
176 } 193 }
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.
284pub struct InterruptHandler {
285 _empty: (),
286}
287
288impl 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 }
177} 298}
178 299
179/// Errors that can occur on methods on [Rtc] 300/// Errors that can occur on methods on [Rtc]
diff --git a/examples/rp/src/bin/rtc.rs b/examples/rp/src/bin/rtc.rs
index e9a5e43a8..1692bdf36 100644
--- a/examples/rp/src/bin/rtc.rs
+++ b/examples/rp/src/bin/rtc.rs
@@ -5,16 +5,22 @@
5 5
6use defmt::*; 6use defmt::*;
7use embassy_executor::Spawner; 7use embassy_executor::Spawner;
8use embassy_rp::bind_interrupts;
8use embassy_rp::rtc::{DateTime, DayOfWeek, Rtc}; 9use embassy_rp::rtc::{DateTime, DayOfWeek, Rtc};
9use embassy_time::Timer; 10use embassy_time::Timer;
10use {defmt_rtt as _, panic_probe as _}; 11use {defmt_rtt as _, panic_probe as _};
11 12
13// Bind the RTC interrupt to the handler
14bind_interrupts!(struct Irqs {
15 RTC_IRQ => embassy_rp::rtc::InterruptHandler;
16});
17
12#[embassy_executor::main] 18#[embassy_executor::main]
13async fn main(_spawner: Spawner) { 19async fn main(_spawner: Spawner) {
14 let p = embassy_rp::init(Default::default()); 20 let p = embassy_rp::init(Default::default());
15 info!("Wait for 20s"); 21 info!("Wait for 20s");
16 22
17 let mut rtc = Rtc::new(p.RTC); 23 let mut rtc = Rtc::new(p.RTC, Irqs);
18 24
19 if !rtc.is_running() { 25 if !rtc.is_running() {
20 info!("Start RTC"); 26 info!("Start RTC");
diff --git a/examples/rp/src/bin/rtc_alarm.rs b/examples/rp/src/bin/rtc_alarm.rs
new file mode 100644
index 000000000..94b5fbd27
--- /dev/null
+++ b/examples/rp/src/bin/rtc_alarm.rs
@@ -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
6use defmt::*;
7use embassy_executor::Spawner;
8use embassy_futures::select::{select, Either};
9use embassy_rp::bind_interrupts;
10use embassy_rp::rtc::{DateTime, DateTimeFilter, DayOfWeek, Rtc};
11use embassy_time::Timer;
12use {defmt_rtt as _, panic_probe as _};
13
14// Bind the RTC interrupt to the handler
15bind_interrupts!(struct Irqs {
16 RTC_IRQ => embassy_rp::rtc::InterruptHandler;
17});
18
19#[embassy_executor::main]
20async 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}
diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml
index 809346bed..19461520a 100644
--- a/tests/rp/Cargo.toml
+++ b/tests/rp/Cargo.toml
@@ -64,6 +64,12 @@ name = "float"
64path = "src/bin/float.rs" 64path = "src/bin/float.rs"
65required-features = [ "rp2040",] 65required-features = [ "rp2040",]
66 66
67# RTC is only available on RP2040
68[[bin]]
69name = "rtc"
70path = "src/bin/rtc.rs"
71required-features = [ "rp2040",]
72
67[profile.dev] 73[profile.dev]
68debug = 2 74debug = 2
69debug-assertions = true 75debug-assertions = true
diff --git a/tests/rp/src/bin/rtc.rs b/tests/rp/src/bin/rtc.rs
new file mode 100644
index 000000000..c66981d95
--- /dev/null
+++ b/tests/rp/src/bin/rtc.rs
@@ -0,0 +1,125 @@
1#![no_std]
2#![no_main]
3#[cfg(feature = "rp2040")]
4teleprobe_meta::target!(b"rpi-pico");
5
6use defmt::{assert, *};
7use embassy_executor::Spawner;
8use embassy_futures::select::{select, Either};
9use embassy_rp::bind_interrupts;
10use embassy_rp::rtc::{DateTime, DateTimeFilter, DayOfWeek, Rtc};
11use embassy_time::{Duration, Instant, Timer};
12use {defmt_rtt as _, panic_probe as _};
13
14// Bind the RTC interrupt to the handler
15bind_interrupts!(struct Irqs {
16 RTC_IRQ => embassy_rp::rtc::InterruptHandler;
17});
18
19#[embassy_executor::main]
20async fn main(_spawner: Spawner) {
21 let p = embassy_rp::init(Default::default());
22 let mut rtc = Rtc::new(p.RTC, Irqs);
23
24 info!("RTC test started");
25
26 // Initialize RTC if not running
27 if !rtc.is_running() {
28 info!("Starting RTC");
29 let now = DateTime {
30 year: 2000,
31 month: 1,
32 day: 1,
33 day_of_week: DayOfWeek::Saturday,
34 hour: 0,
35 minute: 0,
36 second: 0,
37 };
38 rtc.set_datetime(now).unwrap();
39 Timer::after_millis(100).await;
40 }
41
42 // Test 1: Basic RTC functionality - read current time
43 let initial_time = rtc.now().unwrap();
44 info!(
45 "Initial time: {}-{:02}-{:02} {}:{:02}:{:02}",
46 initial_time.year,
47 initial_time.month,
48 initial_time.day,
49 initial_time.hour,
50 initial_time.minute,
51 initial_time.second
52 );
53
54 // Test 2: Schedule and wait for alarm
55 info!("Testing alarm scheduling");
56
57 // Wait until we're at a predictable second, then schedule for a future second
58 loop {
59 let current = rtc.now().unwrap();
60 if current.second <= 55 {
61 break;
62 }
63 Timer::after_millis(100).await;
64 }
65
66 // Now schedule alarm for 3 seconds from current time
67 let current_time = rtc.now().unwrap();
68 let alarm_second = (current_time.second + 3) % 60;
69 let alarm_filter = DateTimeFilter::default().second(alarm_second);
70
71 info!("Scheduling alarm for second: {}", alarm_second);
72 rtc.schedule_alarm(alarm_filter);
73
74 // Verify alarm is scheduled
75 let scheduled = rtc.alarm_scheduled();
76 assert!(scheduled.is_some(), "Alarm should be scheduled");
77 info!("Alarm scheduled successfully: {}", scheduled.unwrap());
78
79 // Wait for alarm with timeout
80 let alarm_start = Instant::now();
81 match select(Timer::after_secs(5), rtc.wait_for_alarm()).await {
82 Either::First(_) => {
83 core::panic!("Alarm timeout - alarm should have triggered by now");
84 }
85 Either::Second(_) => {
86 let alarm_duration = Instant::now() - alarm_start;
87 info!("ALARM TRIGGERED after {:?}", alarm_duration);
88
89 // Verify timing is reasonable (should be around 3 seconds)
90 assert!(
91 alarm_duration >= Duration::from_secs(2) && alarm_duration <= Duration::from_secs(4),
92 "Alarm timing incorrect: {:?}",
93 alarm_duration
94 );
95 }
96 }
97
98 // Test 3: Verify RTC is still running and time has advanced
99 let final_time = rtc.now().unwrap();
100 info!(
101 "Final time: {}-{:02}-{:02} {}:{:02}:{:02}",
102 final_time.year, final_time.month, final_time.day, final_time.hour, final_time.minute, final_time.second
103 );
104
105 // Verify time has advanced (allowing for minute/hour rollover)
106 let time_diff = if final_time.second >= initial_time.second {
107 final_time.second - initial_time.second
108 } else {
109 60 - initial_time.second + final_time.second
110 };
111
112 assert!(time_diff >= 3, "RTC should have advanced by at least 3 seconds");
113 info!("Time advanced by {} seconds", time_diff);
114
115 // Test 4: Verify alarm is no longer scheduled after triggering
116 let post_alarm_scheduled = rtc.alarm_scheduled();
117 assert!(
118 post_alarm_scheduled.is_none(),
119 "Alarm should not be scheduled after triggering"
120 );
121 info!("Alarm correctly cleared after triggering");
122
123 info!("Test OK");
124 cortex_m::asm::bkpt();
125}