From 517714c98e4b5dc4c7ee844527f5d33fdc342125 Mon Sep 17 00:00:00 2001 From: Irina Chiorean Date: Fri, 25 Jul 2025 18:00:49 +0300 Subject: feat: add RTC time driver --- embassy-nxp/Cargo.toml | 3 + embassy-nxp/src/lib.rs | 1 + embassy-nxp/src/time_driver/rtc.rs | 172 +++++++++++++++++++++++ examples/lpc55s69/Cargo.toml | 4 +- examples/lpc55s69/README.md | 12 ++ examples/lpc55s69/src/bin/blinky_embassy_time.rs | 26 ++++ 6 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 embassy-nxp/src/time_driver/rtc.rs create mode 100644 examples/lpc55s69/README.md create mode 100644 examples/lpc55s69/src/bin/blinky_embassy_time.rs diff --git a/embassy-nxp/Cargo.toml b/embassy-nxp/Cargo.toml index 2644b0fa9..9fa48c4b9 100644 --- a/embassy-nxp/Cargo.toml +++ b/embassy-nxp/Cargo.toml @@ -50,6 +50,9 @@ log = ["dep:log"] ## Use Periodic Interrupt Timer (PIT) as the time driver for `embassy-time`, with a tick rate of 1 MHz time-driver-pit = ["_time_driver", "embassy-time?/tick-hz-1_000_000"] +## Use Real Time Clock (RTC) as the time driver for `embassy-time`, with a tick rate of 32768 Hz +time-driver-rtc = ["_time_driver", "embassy-time?/tick-hz-32_768"] + ## Reexport the PAC for the currently enabled chip at `embassy_nxp::pac` (unstable) unstable-pac = [] # This is unstable because semver-minor (non-breaking) releases of embassy-nxp may major-bump (breaking) the PAC version. diff --git a/embassy-nxp/src/lib.rs b/embassy-nxp/src/lib.rs index 5e77fc0db..b2e910f7e 100644 --- a/embassy-nxp/src/lib.rs +++ b/embassy-nxp/src/lib.rs @@ -9,6 +9,7 @@ pub mod pint; #[cfg(feature = "_time_driver")] #[cfg_attr(feature = "time-driver-pit", path = "time_driver/pit.rs")] +#[cfg_attr(feature = "time-driver-rtc", path = "time_driver/rtc.rs")] mod time_driver; // This mod MUST go last, so that it sees all the `impl_foo!` macros diff --git a/embassy-nxp/src/time_driver/rtc.rs b/embassy-nxp/src/time_driver/rtc.rs new file mode 100644 index 000000000..94272e9c2 --- /dev/null +++ b/embassy-nxp/src/time_driver/rtc.rs @@ -0,0 +1,172 @@ +use core::cell::{Cell, RefCell}; +use core::task::Waker; + +use critical_section::CriticalSection; +use embassy_hal_internal::interrupt::{InterruptExt, Priority}; +use embassy_sync::blocking_mutex::CriticalSectionMutex as Mutex; +use embassy_time_driver::{time_driver_impl, Driver}; +use embassy_time_queue_utils::Queue; +use lpc55_pac::{interrupt, PMC, RTC, SYSCON}; +struct AlarmState { + timestamp: Cell, +} + +unsafe impl Send for AlarmState {} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: Cell::new(u64::MAX), + } + } +} + +pub struct RtcDriver { + alarms: Mutex, + queue: Mutex>, +} + +time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { + alarms: Mutex::new(AlarmState::new()), + queue: Mutex::new(RefCell::new(Queue::new())), +}); +impl RtcDriver { + fn init(&'static self) { + let syscon = unsafe { &*SYSCON::ptr() }; + let pmc = unsafe { &*PMC::ptr() }; + let rtc = unsafe { &*RTC::ptr() }; + + syscon.ahbclkctrl0.modify(|_, w| w.rtc().enable()); + + // By default the RTC enters software reset. If for some reason it is + // not in reset, we enter and them promptly leave.q + rtc.ctrl.modify(|_, w| w.swreset().set_bit()); + rtc.ctrl.modify(|_, w| w.swreset().clear_bit()); + + // Select clock source - either XTAL or FRO + // pmc.rtcosc32k.write(|w| w.sel().xtal32k()); + pmc.rtcosc32k.write(|w| w.sel().fro32k()); + + // Start the RTC peripheral + rtc.ctrl.modify(|_, w| w.rtc_osc_pd().power_up()); + + // rtc.ctrl.modify(|_, w| w.rtc_en().clear_bit()); // EXTRA + + //reset/clear(?) counter + rtc.count.reset(); + //en rtc main counter + rtc.ctrl.modify(|_, w| w.rtc_en().set_bit()); + rtc.ctrl.modify(|_, w| w.rtc1khz_en().set_bit()); + // subsec counter enable + rtc.ctrl.modify(|_, w| w.rtc_subsec_ena().set_bit()); + + // enable irq + unsafe { + interrupt::RTC.set_priority(Priority::from(3)); + interrupt::RTC.enable(); + } + } + + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let rtc = unsafe { &*RTC::ptr() }; + let alarm = &self.alarms.borrow(cs); + alarm.timestamp.set(timestamp); + let now = self.now(); + + if timestamp <= now { + alarm.timestamp.set(u64::MAX); + return false; + } + + //time diff in sub-sec not ticks (32kHz) + let diff = timestamp - now; + let sec = (diff / 32768) as u32; + let subsec = (diff % 32768) as u32; + + let current_sec = rtc.count.read().val().bits(); + let target_sec = current_sec.wrapping_add(sec as u32); + + rtc.match_.write(|w| unsafe { w.matval().bits(target_sec) }); + rtc.wake.write(|w| unsafe { + let ms = (subsec * 1000) / 32768; + w.val().bits(ms as u16) + }); + if subsec > 0 { + let ms = (subsec * 1000) / 32768; + rtc.wake.write(|w| unsafe { w.val().bits(ms as u16) }); + } + rtc.ctrl.modify(|_, w| w.alarm1hz().clear_bit().wake1khz().clear_bit()); + true + } + + fn on_interrupt(&self) { + critical_section::with(|cs| { + let rtc = unsafe { &*RTC::ptr() }; + let flags = rtc.ctrl.read(); + if flags.alarm1hz().bit_is_clear() { + rtc.ctrl.modify(|_, w| w.alarm1hz().set_bit()); + self.trigger_alarm(cs); + } + + if flags.wake1khz().bit_is_clear() { + rtc.ctrl.modify(|_, w| w.wake1khz().set_bit()); + self.trigger_alarm(cs); + } + }); + } + + fn trigger_alarm(&self, cs: CriticalSection) { + let alarm = &self.alarms.borrow(cs); + alarm.timestamp.set(u64::MAX); + let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + if next == u64::MAX { + // no scheduled events, skipping + return; + } + while !self.set_alarm(cs, next) { + next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + if next == u64::MAX { + //no next event found after retry + return; + } + } + } +} + +impl Driver for RtcDriver { + fn now(&self) -> u64 { + let rtc = unsafe { &*RTC::ptr() }; + + loop { + let sec1 = rtc.count.read().val().bits() as u64; + let sub1 = rtc.subsec.read().subsec().bits() as u64; + let sec2 = rtc.count.read().val().bits() as u64; + let sub2 = rtc.subsec.read().subsec().bits() as u64; + + if sec1 == sec2 && sub1 == sub2 { + return sec1 * 32768 + sub1; + } + } + } + + fn schedule_wake(&self, at: u64, waker: &Waker) { + critical_section::with(|cs| { + let mut queue = self.queue.borrow(cs).borrow_mut(); + + if queue.schedule_wake(at, waker) { + let mut next = queue.next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = queue.next_expiration(self.now()); + } + } + }) + } +} +#[cortex_m_rt::interrupt] +fn RTC() { + DRIVER.on_interrupt(); +} + +pub fn init() { + DRIVER.init(); +} diff --git a/examples/lpc55s69/Cargo.toml b/examples/lpc55s69/Cargo.toml index 5faec13da..f9bd409e2 100644 --- a/examples/lpc55s69/Cargo.toml +++ b/examples/lpc55s69/Cargo.toml @@ -6,10 +6,10 @@ license = "MIT OR Apache-2.0" [dependencies] -embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["lpc55", "rt", "defmt"] } +embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["lpc55", "rt", "defmt", "time-driver-rtc"] } embassy-executor = { version = "0.8.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] } embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "tick-hz-32_768"] } panic-halt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = { version = "0.7.0"} diff --git a/examples/lpc55s69/README.md b/examples/lpc55s69/README.md new file mode 100644 index 000000000..d200f4f99 --- /dev/null +++ b/examples/lpc55s69/README.md @@ -0,0 +1,12 @@ +# LPC55S69 Examples + +## Available examples: +- blinky_nop: Blink the integrated RED LED using nops as delay. Useful for flashing simple and known-good software on board. +- button_executor: Turn on/off an LED by pressing the USER button. Demonstrates how to use the PINT and GPIO drivers. +- blinky_embassy_time: Blink the integrated RED LED using `embassy-time`. Demonstrates how to use the time-driver that uses RTC. + +## Important Notes + +On older version of probe-rs, some examples (such as `blinky_embassy_time`) do not work directly after flashing and the board must be reset after flashing. It is reccomended to update the version of probe-rs to the latest one. + +When developing drivers for this board, probe-rs might not be able to flash the board after entering a fault. Either reset the board to clear the fault, or use NXP's proprietary software `LinkServer`/`LinkFlash` to bring the board back to a known-good state. \ No newline at end of file diff --git a/examples/lpc55s69/src/bin/blinky_embassy_time.rs b/examples/lpc55s69/src/bin/blinky_embassy_time.rs new file mode 100644 index 000000000..adc3d8bd3 --- /dev/null +++ b/examples/lpc55s69/src/bin/blinky_embassy_time.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Level, Output}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nxp::init(Default::default()); + info!("Initialization complete"); + let mut led = Output::new(p.PIO1_6, Level::Low); + + info!("Entering main loop"); + loop { + info!("led off!"); + led.set_high(); + Timer::after_millis(500).await; + + info!("led on!"); + led.set_low(); + Timer::after_millis(500).await; + } +} -- cgit