diff options
| -rw-r--r-- | embassy-rp/CHANGELOG.md | 5 | ||||
| -rw-r--r-- | embassy-rp/src/rtc/datetime_no_deps.rs | 1 | ||||
| -rw-r--r-- | embassy-rp/src/rtc/filter.rs | 3 | ||||
| -rw-r--r-- | embassy-rp/src/rtc/mod.rs | 123 | ||||
| -rw-r--r-- | examples/rp/src/bin/rtc.rs | 8 | ||||
| -rw-r--r-- | examples/rp/src/bin/rtc_alarm.rs | 66 | ||||
| -rw-r--r-- | tests/rp/Cargo.toml | 6 | ||||
| -rw-r--r-- | tests/rp/src/bin/rtc.rs | 125 |
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)] |
| 50 | pub enum DayOfWeek { | 51 | pub 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))] | ||
| 8 | pub struct DateTimeFilter { | 9 | pub 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. |
| 2 | mod filter; | 2 | mod filter; |
| 3 | 3 | ||
| 4 | use core::future::poll_fn; | ||
| 5 | use core::sync::atomic::{compiler_fence, AtomicBool, Ordering}; | ||
| 6 | use core::task::Poll; | ||
| 7 | |||
| 4 | use embassy_hal_internal::{Peri, PeripheralType}; | 8 | use embassy_hal_internal::{Peri, PeripheralType}; |
| 9 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 5 | 10 | ||
| 6 | pub use self::filter::DateTimeFilter; | 11 | pub use self::filter::DateTimeFilter; |
| 7 | 12 | ||
| @@ -11,6 +16,13 @@ mod datetime; | |||
| 11 | 16 | ||
| 12 | pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError}; | 17 | pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError}; |
| 13 | use crate::clocks::clk_rtc_freq; | 18 | 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); | ||
| 14 | 26 | ||
| 15 | /// A reference to the real time clock of the system | 27 | /// A reference to the real time clock of the system |
| 16 | pub struct Rtc<'d, T: Instance> { | 28 | pub 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. | ||
| 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 | } | ||
| 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 | ||
| 6 | use defmt::*; | 6 | use defmt::*; |
| 7 | use embassy_executor::Spawner; | 7 | use embassy_executor::Spawner; |
| 8 | use embassy_rp::bind_interrupts; | ||
| 8 | use embassy_rp::rtc::{DateTime, DayOfWeek, Rtc}; | 9 | use embassy_rp::rtc::{DateTime, DayOfWeek, Rtc}; |
| 9 | use embassy_time::Timer; | 10 | use embassy_time::Timer; |
| 10 | use {defmt_rtt as _, panic_probe as _}; | 11 | use {defmt_rtt as _, panic_probe as _}; |
| 11 | 12 | ||
| 13 | // Bind the RTC interrupt to the handler | ||
| 14 | bind_interrupts!(struct Irqs { | ||
| 15 | RTC_IRQ => embassy_rp::rtc::InterruptHandler; | ||
| 16 | }); | ||
| 17 | |||
| 12 | #[embassy_executor::main] | 18 | #[embassy_executor::main] |
| 13 | async fn main(_spawner: Spawner) { | 19 | async 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 | |||
| 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 | } | ||
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" | |||
| 64 | path = "src/bin/float.rs" | 64 | path = "src/bin/float.rs" |
| 65 | required-features = [ "rp2040",] | 65 | required-features = [ "rp2040",] |
| 66 | 66 | ||
| 67 | # RTC is only available on RP2040 | ||
| 68 | [[bin]] | ||
| 69 | name = "rtc" | ||
| 70 | path = "src/bin/rtc.rs" | ||
| 71 | required-features = [ "rp2040",] | ||
| 72 | |||
| 67 | [profile.dev] | 73 | [profile.dev] |
| 68 | debug = 2 | 74 | debug = 2 |
| 69 | debug-assertions = true | 75 | debug-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")] | ||
| 4 | teleprobe_meta::target!(b"rpi-pico"); | ||
| 5 | |||
| 6 | use defmt::{assert, *}; | ||
| 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::{Duration, Instant, 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 | 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 | } | ||
