aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xci.sh4
-rw-r--r--embassy-imxrt/Cargo.toml15
-rw-r--r--embassy-imxrt/src/lib.rs19
-rw-r--r--embassy-imxrt/src/rtc.rs254
-rw-r--r--examples/mimxrt6/Cargo.toml3
-rw-r--r--examples/mimxrt6/src/bin/blinky.rs3
6 files changed, 291 insertions, 7 deletions
diff --git a/ci.sh b/ci.sh
index f08c38b7c..edc9bd617 100755
--- a/ci.sh
+++ b/ci.sh
@@ -53,8 +53,8 @@ cargo batch \
53 --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip \ 53 --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip \
54 --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet \ 54 --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet \
55 --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,medium-ieee802154 \ 55 --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,medium-ieee802154 \
56 --- build --release --manifest-path embassy-imxrt/Cargo.toml --target thumbv8m.main-none-eabihf --features mimxrt633s,defmt,unstable-pac \ 56 --- build --release --manifest-path embassy-imxrt/Cargo.toml --target thumbv8m.main-none-eabihf --features mimxrt633s,defmt,unstable-pac,time,time-driver-rtc \
57 --- build --release --manifest-path embassy-imxrt/Cargo.toml --target thumbv8m.main-none-eabihf --features mimxrt685s,defmt,unstable-pac \ 57 --- build --release --manifest-path embassy-imxrt/Cargo.toml --target thumbv8m.main-none-eabihf --features mimxrt685s,defmt,unstable-pac,time,time-driver-rtc \
58 --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,gpiote,time,time-driver-rtc1 \ 58 --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,gpiote,time,time-driver-rtc1 \
59 --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time,time-driver-rtc1 \ 59 --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time,time-driver-rtc1 \
60 --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time,time-driver-rtc1 \ 60 --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time,time-driver-rtc1 \
diff --git a/embassy-imxrt/Cargo.toml b/embassy-imxrt/Cargo.toml
index 38087bf77..d58de6353 100644
--- a/embassy-imxrt/Cargo.toml
+++ b/embassy-imxrt/Cargo.toml
@@ -12,13 +12,13 @@ documentation = "https://docs.embassy.dev/embassy-imxrt"
12[package.metadata.embassy_docs] 12[package.metadata.embassy_docs]
13src_base = "https://github.com/embassy-rs/embassy/blob/embassy-imxrt-v$VERSION/embassy-imxrt/src/" 13src_base = "https://github.com/embassy-rs/embassy/blob/embassy-imxrt-v$VERSION/embassy-imxrt/src/"
14src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-imxrt/src/" 14src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-imxrt/src/"
15features = ["defmt", "unstable-pac"] 15features = ["defmt", "unstable-pac", "time", "time-driver"]
16flavors = [ 16flavors = [
17 { regex_feature = "mimxrt6.*", target = "thumbv8m.main-none-eabihf" } 17 { regex_feature = "mimxrt6.*", target = "thumbv8m.main-none-eabihf" }
18] 18]
19 19
20[package.metadata.docs.rs] 20[package.metadata.docs.rs]
21features = ["mimxrt685s", "defmt", "unstable-pac"] 21features = ["mimxrt685s", "defmt", "unstable-pac", "time", "time-driver"]
22rustdoc-args = ["--cfg", "docsrs"] 22rustdoc-args = ["--cfg", "docsrs"]
23 23
24[features] 24[features]
@@ -33,6 +33,14 @@ rt = [
33## Enable defmt 33## Enable defmt
34defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt", "mimxrt685s-pac?/defmt", "mimxrt633s-pac?/defmt"] 34defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt", "mimxrt685s-pac?/defmt", "mimxrt633s-pac?/defmt"]
35 35
36## Enable features requiring `embassy-time`
37time = ["dep:embassy-time", "embassy-embedded-hal/time"]
38
39## Enable custom embassy time-driver implementation, using 32KHz RTC
40time-driver-rtc = ["_time-driver"]
41
42_time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-1_000", "dep:embassy-time-queue-utils", "embassy-embedded-hal/time"]
43
36## Reexport the PAC for the currently enabled chip at `embassy_imxrt::pac` (unstable) 44## Reexport the PAC for the currently enabled chip at `embassy_imxrt::pac` (unstable)
37unstable-pac = [] 45unstable-pac = []
38 46
@@ -53,6 +61,9 @@ mimxrt633s = ["mimxrt633s-pac", "_mimxrt633s"]
53 61
54[dependencies] 62[dependencies]
55embassy-sync = { version = "0.6.2", path = "../embassy-sync" } 63embassy-sync = { version = "0.6.2", path = "../embassy-sync" }
64embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true }
65embassy-time-queue-utils = { version = "0.1", path = "../embassy-time-queue-utils", optional = true }
66embassy-time = { version = "0.4", path = "../embassy-time", optional = true }
56embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } 67embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] }
57embassy-embedded-hal = { version = "0.3.0", path = "../embassy-embedded-hal", default-features = false } 68embassy-embedded-hal = { version = "0.3.0", path = "../embassy-embedded-hal", default-features = false }
58embassy-futures = { version = "0.1.1", path = "../embassy-futures" } 69embassy-futures = { version = "0.1.1", path = "../embassy-futures" }
diff --git a/embassy-imxrt/src/lib.rs b/embassy-imxrt/src/lib.rs
index d56d993c3..5fbf3244b 100644
--- a/embassy-imxrt/src/lib.rs
+++ b/embassy-imxrt/src/lib.rs
@@ -21,6 +21,9 @@ pub mod clocks;
21pub mod gpio; 21pub mod gpio;
22pub mod iopctl; 22pub mod iopctl;
23 23
24#[cfg(feature = "_time-driver")]
25pub mod rtc;
26
24// This mod MUST go last, so that it sees all the `impl_foo!' macros 27// This mod MUST go last, so that it sees all the `impl_foo!' macros
25#[cfg_attr(feature = "mimxrt633s", path = "chips/mimxrt633s.rs")] 28#[cfg_attr(feature = "mimxrt633s", path = "chips/mimxrt633s.rs")]
26#[cfg_attr(feature = "mimxrt685s", path = "chips/mimxrt685s.rs")] 29#[cfg_attr(feature = "mimxrt685s", path = "chips/mimxrt685s.rs")]
@@ -86,12 +89,18 @@ pub mod config {
86 pub struct Config { 89 pub struct Config {
87 /// Clock configuration. 90 /// Clock configuration.
88 pub clocks: ClockConfig, 91 pub clocks: ClockConfig,
92
93 /// RTC Time driver interrupt priority.
94 #[cfg(feature = "_time-driver")]
95 pub time_interrupt_priority: crate::interrupt::Priority,
89 } 96 }
90 97
91 impl Default for Config { 98 impl Default for Config {
92 fn default() -> Self { 99 fn default() -> Self {
93 Self { 100 Self {
94 clocks: ClockConfig::crystal(), 101 clocks: ClockConfig::crystal(),
102 #[cfg(feature = "_time-driver")]
103 time_interrupt_priority: crate::interrupt::Priority::P0,
95 } 104 }
96 } 105 }
97 } 106 }
@@ -99,7 +108,11 @@ pub mod config {
99 impl Config { 108 impl Config {
100 /// Create a new configuration with the provided clock config. 109 /// Create a new configuration with the provided clock config.
101 pub fn new(clocks: ClockConfig) -> Self { 110 pub fn new(clocks: ClockConfig) -> Self {
102 Self { clocks } 111 Self {
112 clocks,
113 #[cfg(feature = "_time-driver")]
114 time_interrupt_priority: crate::interrupt::Priority::P0,
115 }
103 } 116 }
104 } 117 }
105} 118}
@@ -122,6 +135,10 @@ pub fn init(config: config::Config) -> Peripherals {
122 gpio::init(); 135 gpio::init();
123 } 136 }
124 137
138 // init RTC time driver
139 #[cfg(feature = "_time-driver")]
140 rtc::init(config.time_interrupt_priority);
141
125 peripherals 142 peripherals
126} 143}
127 144
diff --git a/embassy-imxrt/src/rtc.rs b/embassy-imxrt/src/rtc.rs
new file mode 100644
index 000000000..56a8f7397
--- /dev/null
+++ b/embassy-imxrt/src/rtc.rs
@@ -0,0 +1,254 @@
1//! RTC Time Driver.
2use core::cell::{Cell, RefCell};
3use core::sync::atomic::{compiler_fence, AtomicU32, Ordering};
4
5use critical_section::CriticalSection;
6use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
7use embassy_sync::blocking_mutex::Mutex;
8use embassy_time_driver::Driver;
9use embassy_time_queue_utils::Queue;
10
11use crate::interrupt::InterruptExt;
12use crate::{interrupt, pac};
13
14fn rtc() -> &'static pac::rtc::RegisterBlock {
15 unsafe { &*pac::Rtc::ptr() }
16}
17
18/// Calculate the timestamp from the period count and the tick count.
19///
20/// To get `now()`, `period` is read first, then `counter` is read. If the counter value matches
21/// the expected range for the `period` parity, we're done. If it doesn't, this means that
22/// a new period start has raced us between reading `period` and `counter`, so we assume the `counter` value
23/// corresponds to the next period.
24///
25/// the 1kHz RTC counter is 16 bits and RTC doesn't have separate compare channels,
26/// so using a 32 bit GPREG0-2 as counter, compare, and int_en
27/// `period` is a 32bit integer, gpreg 'counter' is 31 bits plus the parity bit for overflow detection
28fn calc_now(period: u32, counter: u32) -> u64 {
29 ((period as u64) << 31) + ((counter ^ ((period & 1) << 31)) as u64)
30}
31
32struct AlarmState {
33 timestamp: Cell<u64>,
34}
35
36unsafe impl Send for AlarmState {}
37
38impl AlarmState {
39 const fn new() -> Self {
40 Self {
41 timestamp: Cell::new(u64::MAX),
42 }
43 }
44}
45
46struct Rtc {
47 /// Number of 2^31 periods elapsed since boot.
48 period: AtomicU32,
49 /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
50 alarms: Mutex<CriticalSectionRawMutex, AlarmState>,
51 queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>,
52}
53
54embassy_time_driver::time_driver_impl!(static DRIVER: Rtc = Rtc {
55 period: AtomicU32::new(0),
56 alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()),
57 queue: Mutex::new(RefCell::new(Queue::new())),
58});
59
60impl Rtc {
61 /// Access the GPREG0 register to use it as a 31-bit counter.
62 #[inline]
63 fn counter_reg(&self) -> &pac::rtc::Gpreg {
64 rtc().gpreg(0)
65 }
66
67 /// Access the GPREG1 register to use it as a compare register for triggering alarms.
68 #[inline]
69 fn compare_reg(&self) -> &pac::rtc::Gpreg {
70 rtc().gpreg(1)
71 }
72
73 /// Access the GPREG2 register to use it to enable or disable interrupts (int_en).
74 #[inline]
75 fn int_en_reg(&self) -> &pac::rtc::Gpreg {
76 rtc().gpreg(2)
77 }
78
79 fn init(&'static self, irq_prio: crate::interrupt::Priority) {
80 let r = rtc();
81 // enable RTC int (1kHz since subsecond doesn't generate an int)
82 r.ctrl().modify(|_r, w| w.rtc1khz_en().set_bit());
83 // TODO: low power support. line above is leaving out write to .wakedpd_en().set_bit())
84 // which enables wake from deep power down
85
86 // safety: Writing to the gregs is always considered unsafe, gpreg1 is used
87 // as a compare register for triggering an alarm so to avoid unnecessary triggers
88 // after initialization, this is set to 0x:FFFF_FFFF
89 self.compare_reg().write(|w| unsafe { w.gpdata().bits(u32::MAX) });
90 // safety: writing a value to the 1kHz RTC wake counter is always considered unsafe.
91 // The following loads 10 into the count-down timer.
92 r.wake().write(|w| unsafe { w.bits(0xA) });
93 interrupt::RTC.set_priority(irq_prio);
94 unsafe { interrupt::RTC.enable() };
95 }
96
97 #[cfg(feature = "rt")]
98 fn on_interrupt(&self) {
99 let r = rtc();
100 // This interrupt fires every 10 ticks of the 1kHz RTC high res clk and adds
101 // 10 to the 31 bit counter gpreg0. The 32nd bit is used for parity detection
102 // This is done to avoid needing to calculate # of ticks spent on interrupt
103 // handlers to recalibrate the clock between interrupts
104 //
105 // TODO: this is admittedly not great for power that we're generating this
106 // many interrupts, will probably get updated in future iterations.
107 if r.ctrl().read().wake1khz().bit_is_set() {
108 r.ctrl().modify(|_r, w| w.wake1khz().set_bit());
109 // safety: writing a value to the 1kHz RTC wake counter is always considered unsafe.
110 // The following reloads 10 into the count-down timer after it triggers an int.
111 // The countdown begins anew after the write so time can continue to be measured.
112 r.wake().write(|w| unsafe { w.bits(0xA) });
113 if (self.counter_reg().read().bits() + 0xA) > 0x8000_0000 {
114 // if we're going to "overflow", increase the period
115 self.next_period();
116 let rollover_diff = 0x8000_0000 - (self.counter_reg().read().bits() + 0xA);
117 // safety: writing to gpregs is always considered unsafe. In order to
118 // not "lose" time when incrementing the period, gpreg0, the extended
119 // counter, is restarted at the # of ticks it would overflow by
120 self.counter_reg().write(|w| unsafe { w.bits(rollover_diff) });
121 } else {
122 self.counter_reg().modify(|r, w| unsafe { w.bits(r.bits() + 0xA) });
123 }
124 }
125
126 critical_section::with(|cs| {
127 // gpreg2 as an "int_en" set by next_period(). This is
128 // 1 when the timestamp for the alarm deadline expires
129 // before the counter register overflows again.
130 if self.int_en_reg().read().gpdata().bits() == 1 {
131 // gpreg0 is our extended counter register, check if
132 // our counter is larger than the compare value
133 if self.counter_reg().read().bits() > self.compare_reg().read().bits() {
134 self.trigger_alarm(cs);
135 }
136 }
137 })
138 }
139
140 #[cfg(feature = "rt")]
141 fn next_period(&self) {
142 critical_section::with(|cs| {
143 let period = self
144 .period
145 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |p| Some(p + 1))
146 .unwrap_or_else(|p| {
147 trace!("Unable to increment period. Time is now inaccurate");
148 // TODO: additional error handling beyond logging
149
150 p
151 });
152 let t = (period as u64) << 31;
153
154 let alarm = &self.alarms.borrow(cs);
155 let at = alarm.timestamp.get();
156 if at < t + 0xc000_0000 {
157 // safety: writing to gpregs is always unsafe, gpreg2 is an alarm
158 // enable. If the alarm must trigger within the next period, then
159 // just enable it. `set_alarm` has already set the correct CC val.
160 self.int_en_reg().write(|w| unsafe { w.gpdata().bits(1) });
161 }
162 })
163 }
164
165 #[must_use]
166 fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
167 let alarm = self.alarms.borrow(cs);
168 alarm.timestamp.set(timestamp);
169
170 let t = self.now();
171 if timestamp <= t {
172 // safety: Writing to the gpregs is always unsafe, gpreg2 is
173 // always just used as the alarm enable for the timer driver.
174 // If alarm timestamp has passed the alarm will not fire.
175 // Disarm the alarm and return `false` to indicate that.
176 self.int_en_reg().write(|w| unsafe { w.gpdata().bits(0) });
177
178 alarm.timestamp.set(u64::MAX);
179
180 return false;
181 }
182
183 // If it hasn't triggered yet, setup it by writing to the compare field
184 // An alarm can be delayed, but this is allowed by the Alarm trait contract.
185 // What's not allowed is triggering alarms *before* their scheduled time,
186 let safe_timestamp = timestamp.max(t + 10); //t+3 was done for nrf chip, choosing 10
187
188 // safety: writing to the gregs is always unsafe. When a new alarm is set,
189 // the compare register, gpreg1, is set to the last 31 bits of the timestamp
190 // as the 32nd and final bit is used for the parity check in `next_period`
191 // `period` will be used for the upper bits in a timestamp comparison.
192 self.compare_reg()
193 .modify(|_r, w| unsafe { w.bits(safe_timestamp as u32 & 0x7FFF_FFFF) });
194
195 // The following checks that the difference in timestamp is less than the overflow period
196 let diff = timestamp - t;
197 if diff < 0xc000_0000 {
198 // this is 0b11 << (30). NRF chip used 23 bit periods and checked against 0b11<<22
199
200 // safety: writing to the gpregs is always unsafe. If the alarm
201 // must trigger within the next period, set the "int enable"
202 self.int_en_reg().write(|w| unsafe { w.gpdata().bits(1) });
203 } else {
204 // safety: writing to the gpregs is always unsafe. If alarm must trigger
205 // some time after the current period, too far in the future, don't setup
206 // the alarm enable, gpreg2, yet. It will be setup later by `next_period`.
207 self.int_en_reg().write(|w| unsafe { w.gpdata().bits(0) });
208 }
209
210 true
211 }
212
213 #[cfg(feature = "rt")]
214 fn trigger_alarm(&self, cs: CriticalSection) {
215 let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
216 while !self.set_alarm(cs, next) {
217 next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
218 }
219 }
220}
221
222impl Driver for Rtc {
223 fn now(&self) -> u64 {
224 // `period` MUST be read before `counter`, see comment at the top for details.
225 let period = self.period.load(Ordering::Acquire);
226 compiler_fence(Ordering::Acquire);
227 let counter = self.counter_reg().read().bits();
228 calc_now(period, counter)
229 }
230
231 fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
232 critical_section::with(|cs| {
233 let mut queue = self.queue.borrow(cs).borrow_mut();
234
235 if queue.schedule_wake(at, waker) {
236 let mut next = queue.next_expiration(self.now());
237 while !self.set_alarm(cs, next) {
238 next = queue.next_expiration(self.now());
239 }
240 }
241 })
242 }
243}
244
245#[cfg(feature = "rt")]
246#[allow(non_snake_case)]
247#[interrupt]
248fn RTC() {
249 DRIVER.on_interrupt()
250}
251
252pub(crate) fn init(irq_prio: crate::interrupt::Priority) {
253 DRIVER.init(irq_prio)
254}
diff --git a/examples/mimxrt6/Cargo.toml b/examples/mimxrt6/Cargo.toml
index 894ce174c..0e4a1ee36 100644
--- a/examples/mimxrt6/Cargo.toml
+++ b/examples/mimxrt6/Cargo.toml
@@ -12,7 +12,8 @@ defmt-rtt = "1.0"
12 12
13embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } 13embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] }
14embassy-futures = { version = "0.1.1", path = "../../embassy-futures" } 14embassy-futures = { version = "0.1.1", path = "../../embassy-futures" }
15embassy-imxrt = { version = "0.1.0", path = "../../embassy-imxrt", features = ["defmt", "mimxrt685s", "unstable-pac"] } 15embassy-imxrt = { version = "0.1.0", path = "../../embassy-imxrt", features = ["defmt", "mimxrt685s", "unstable-pac", "time", "time-driver-rtc"] }
16embassy-time = { version = "0.4", path = "../../embassy-time" }
16embassy-sync = { version = "0.6.2", path = "../../embassy-sync", features = ["defmt"] } 17embassy-sync = { version = "0.6.2", path = "../../embassy-sync", features = ["defmt"] }
17embedded-hal-1 = { package = "embedded-hal", version = "1.0" } 18embedded-hal-1 = { package = "embedded-hal", version = "1.0" }
18embedded-hal-async = "1.0.0" 19embedded-hal-async = "1.0.0"
diff --git a/examples/mimxrt6/src/bin/blinky.rs b/examples/mimxrt6/src/bin/blinky.rs
index e40e71e6f..de079d505 100644
--- a/examples/mimxrt6/src/bin/blinky.rs
+++ b/examples/mimxrt6/src/bin/blinky.rs
@@ -6,6 +6,7 @@ extern crate embassy_imxrt_examples;
6use defmt::info; 6use defmt::info;
7use embassy_executor::Spawner; 7use embassy_executor::Spawner;
8use embassy_imxrt::gpio; 8use embassy_imxrt::gpio;
9use embassy_time::Timer;
9 10
10#[embassy_executor::main] 11#[embassy_executor::main]
11async fn main(_spawner: Spawner) { 12async fn main(_spawner: Spawner) {
@@ -24,6 +25,6 @@ async fn main(_spawner: Spawner) {
24 loop { 25 loop {
25 info!("Toggling LED"); 26 info!("Toggling LED");
26 led.toggle(); 27 led.toggle();
27 cortex_m::asm::delay(5_000_000); 28 Timer::after_secs(1).await;
28 } 29 }
29} 30}