aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIrina Chiorean <[email protected]>2025-07-25 18:00:49 +0300
committerIrina Chiorean <[email protected]>2025-08-04 12:58:41 +0300
commit517714c98e4b5dc4c7ee844527f5d33fdc342125 (patch)
tree7dde24bb8bb16a14995483bb71450801bbf0675d
parenta8cb8a7fe1f594b765dee4cfc6ff3065842c7c6e (diff)
feat: add RTC time driver
-rw-r--r--embassy-nxp/Cargo.toml3
-rw-r--r--embassy-nxp/src/lib.rs1
-rw-r--r--embassy-nxp/src/time_driver/rtc.rs172
-rw-r--r--examples/lpc55s69/Cargo.toml4
-rw-r--r--examples/lpc55s69/README.md12
-rw-r--r--examples/lpc55s69/src/bin/blinky_embassy_time.rs26
6 files changed, 216 insertions, 2 deletions
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"]
50## Use Periodic Interrupt Timer (PIT) as the time driver for `embassy-time`, with a tick rate of 1 MHz 50## Use Periodic Interrupt Timer (PIT) as the time driver for `embassy-time`, with a tick rate of 1 MHz
51time-driver-pit = ["_time_driver", "embassy-time?/tick-hz-1_000_000"] 51time-driver-pit = ["_time_driver", "embassy-time?/tick-hz-1_000_000"]
52 52
53## Use Real Time Clock (RTC) as the time driver for `embassy-time`, with a tick rate of 32768 Hz
54time-driver-rtc = ["_time_driver", "embassy-time?/tick-hz-32_768"]
55
53## Reexport the PAC for the currently enabled chip at `embassy_nxp::pac` (unstable) 56## Reexport the PAC for the currently enabled chip at `embassy_nxp::pac` (unstable)
54unstable-pac = [] 57unstable-pac = []
55# This is unstable because semver-minor (non-breaking) releases of embassy-nxp may major-bump (breaking) the PAC version. 58# 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;
9 9
10#[cfg(feature = "_time_driver")] 10#[cfg(feature = "_time_driver")]
11#[cfg_attr(feature = "time-driver-pit", path = "time_driver/pit.rs")] 11#[cfg_attr(feature = "time-driver-pit", path = "time_driver/pit.rs")]
12#[cfg_attr(feature = "time-driver-rtc", path = "time_driver/rtc.rs")]
12mod time_driver; 13mod time_driver;
13 14
14// This mod MUST go last, so that it sees all the `impl_foo!` macros 15// 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 @@
1use core::cell::{Cell, RefCell};
2use core::task::Waker;
3
4use critical_section::CriticalSection;
5use embassy_hal_internal::interrupt::{InterruptExt, Priority};
6use embassy_sync::blocking_mutex::CriticalSectionMutex as Mutex;
7use embassy_time_driver::{time_driver_impl, Driver};
8use embassy_time_queue_utils::Queue;
9use lpc55_pac::{interrupt, PMC, RTC, SYSCON};
10struct AlarmState {
11 timestamp: Cell<u64>,
12}
13
14unsafe impl Send for AlarmState {}
15
16impl AlarmState {
17 const fn new() -> Self {
18 Self {
19 timestamp: Cell::new(u64::MAX),
20 }
21 }
22}
23
24pub struct RtcDriver {
25 alarms: Mutex<AlarmState>,
26 queue: Mutex<RefCell<Queue>>,
27}
28
29time_driver_impl!(static DRIVER: RtcDriver = RtcDriver {
30 alarms: Mutex::new(AlarmState::new()),
31 queue: Mutex::new(RefCell::new(Queue::new())),
32});
33impl RtcDriver {
34 fn init(&'static self) {
35 let syscon = unsafe { &*SYSCON::ptr() };
36 let pmc = unsafe { &*PMC::ptr() };
37 let rtc = unsafe { &*RTC::ptr() };
38
39 syscon.ahbclkctrl0.modify(|_, w| w.rtc().enable());
40
41 // By default the RTC enters software reset. If for some reason it is
42 // not in reset, we enter and them promptly leave.q
43 rtc.ctrl.modify(|_, w| w.swreset().set_bit());
44 rtc.ctrl.modify(|_, w| w.swreset().clear_bit());
45
46 // Select clock source - either XTAL or FRO
47 // pmc.rtcosc32k.write(|w| w.sel().xtal32k());
48 pmc.rtcosc32k.write(|w| w.sel().fro32k());
49
50 // Start the RTC peripheral
51 rtc.ctrl.modify(|_, w| w.rtc_osc_pd().power_up());
52
53 // rtc.ctrl.modify(|_, w| w.rtc_en().clear_bit()); // EXTRA
54
55 //reset/clear(?) counter
56 rtc.count.reset();
57 //en rtc main counter
58 rtc.ctrl.modify(|_, w| w.rtc_en().set_bit());
59 rtc.ctrl.modify(|_, w| w.rtc1khz_en().set_bit());
60 // subsec counter enable
61 rtc.ctrl.modify(|_, w| w.rtc_subsec_ena().set_bit());
62
63 // enable irq
64 unsafe {
65 interrupt::RTC.set_priority(Priority::from(3));
66 interrupt::RTC.enable();
67 }
68 }
69
70 fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
71 let rtc = unsafe { &*RTC::ptr() };
72 let alarm = &self.alarms.borrow(cs);
73 alarm.timestamp.set(timestamp);
74 let now = self.now();
75
76 if timestamp <= now {
77 alarm.timestamp.set(u64::MAX);
78 return false;
79 }
80
81 //time diff in sub-sec not ticks (32kHz)
82 let diff = timestamp - now;
83 let sec = (diff / 32768) as u32;
84 let subsec = (diff % 32768) as u32;
85
86 let current_sec = rtc.count.read().val().bits();
87 let target_sec = current_sec.wrapping_add(sec as u32);
88
89 rtc.match_.write(|w| unsafe { w.matval().bits(target_sec) });
90 rtc.wake.write(|w| unsafe {
91 let ms = (subsec * 1000) / 32768;
92 w.val().bits(ms as u16)
93 });
94 if subsec > 0 {
95 let ms = (subsec * 1000) / 32768;
96 rtc.wake.write(|w| unsafe { w.val().bits(ms as u16) });
97 }
98 rtc.ctrl.modify(|_, w| w.alarm1hz().clear_bit().wake1khz().clear_bit());
99 true
100 }
101
102 fn on_interrupt(&self) {
103 critical_section::with(|cs| {
104 let rtc = unsafe { &*RTC::ptr() };
105 let flags = rtc.ctrl.read();
106 if flags.alarm1hz().bit_is_clear() {
107 rtc.ctrl.modify(|_, w| w.alarm1hz().set_bit());
108 self.trigger_alarm(cs);
109 }
110
111 if flags.wake1khz().bit_is_clear() {
112 rtc.ctrl.modify(|_, w| w.wake1khz().set_bit());
113 self.trigger_alarm(cs);
114 }
115 });
116 }
117
118 fn trigger_alarm(&self, cs: CriticalSection) {
119 let alarm = &self.alarms.borrow(cs);
120 alarm.timestamp.set(u64::MAX);
121 let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
122 if next == u64::MAX {
123 // no scheduled events, skipping
124 return;
125 }
126 while !self.set_alarm(cs, next) {
127 next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
128 if next == u64::MAX {
129 //no next event found after retry
130 return;
131 }
132 }
133 }
134}
135
136impl Driver for RtcDriver {
137 fn now(&self) -> u64 {
138 let rtc = unsafe { &*RTC::ptr() };
139
140 loop {
141 let sec1 = rtc.count.read().val().bits() as u64;
142 let sub1 = rtc.subsec.read().subsec().bits() as u64;
143 let sec2 = rtc.count.read().val().bits() as u64;
144 let sub2 = rtc.subsec.read().subsec().bits() as u64;
145
146 if sec1 == sec2 && sub1 == sub2 {
147 return sec1 * 32768 + sub1;
148 }
149 }
150 }
151
152 fn schedule_wake(&self, at: u64, waker: &Waker) {
153 critical_section::with(|cs| {
154 let mut queue = self.queue.borrow(cs).borrow_mut();
155
156 if queue.schedule_wake(at, waker) {
157 let mut next = queue.next_expiration(self.now());
158 while !self.set_alarm(cs, next) {
159 next = queue.next_expiration(self.now());
160 }
161 }
162 })
163 }
164}
165#[cortex_m_rt::interrupt]
166fn RTC() {
167 DRIVER.on_interrupt();
168}
169
170pub fn init() {
171 DRIVER.init();
172}
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"
6 6
7 7
8[dependencies] 8[dependencies]
9embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["lpc55", "rt", "defmt"] } 9embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["lpc55", "rt", "defmt", "time-driver-rtc"] }
10embassy-executor = { version = "0.8.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] } 10embassy-executor = { version = "0.8.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] }
11embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } 11embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] }
12embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt"] } 12embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "tick-hz-32_768"] }
13panic-halt = "1.0.0" 13panic-halt = "1.0.0"
14cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } 14cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
15cortex-m-rt = { version = "0.7.0"} 15cortex-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 @@
1# LPC55S69 Examples
2
3## Available examples:
4- blinky_nop: Blink the integrated RED LED using nops as delay. Useful for flashing simple and known-good software on board.
5- button_executor: Turn on/off an LED by pressing the USER button. Demonstrates how to use the PINT and GPIO drivers.
6- blinky_embassy_time: Blink the integrated RED LED using `embassy-time`. Demonstrates how to use the time-driver that uses RTC.
7
8## Important Notes
9
10On 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.
11
12When 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 @@
1#![no_std]
2#![no_main]
3
4use defmt::*;
5use embassy_executor::Spawner;
6use embassy_nxp::gpio::{Level, Output};
7use embassy_time::Timer;
8use {defmt_rtt as _, panic_halt as _};
9
10#[embassy_executor::main]
11async fn main(_spawner: Spawner) {
12 let p = embassy_nxp::init(Default::default());
13 info!("Initialization complete");
14 let mut led = Output::new(p.PIO1_6, Level::Low);
15
16 info!("Entering main loop");
17 loop {
18 info!("led off!");
19 led.set_high();
20 Timer::after_millis(500).await;
21
22 info!("led on!");
23 led.set_low();
24 Timer::after_millis(500).await;
25 }
26}