diff options
| author | Felipe Balbi <[email protected]> | 2025-05-07 10:40:43 -0700 |
|---|---|---|
| committer | Felipe Balbi <[email protected]> | 2025-05-07 16:25:06 -0700 |
| commit | 42c62ba8999df08ad34d566f30f0a7199dbae083 (patch) | |
| tree | ae416839a2eef772a6c4e7979a912cbac70a8c94 | |
| parent | 297ff3d03229bedb2582c171be23b488ecc4e520 (diff) | |
Add OS Event timer support
Allow for the use of the OS Event timer as a time source.
Signed-off-by: Felipe Balbi <[email protected]>
| -rw-r--r-- | embassy-imxrt/Cargo.toml | 9 | ||||
| -rw-r--r-- | embassy-imxrt/src/lib.rs | 4 | ||||
| -rw-r--r-- | embassy-imxrt/src/time_driver.rs | 196 | ||||
| -rw-r--r-- | examples/mimxrt6/Cargo.toml | 4 |
4 files changed, 184 insertions, 29 deletions
diff --git a/embassy-imxrt/Cargo.toml b/embassy-imxrt/Cargo.toml index d58de6353..f16002a8d 100644 --- a/embassy-imxrt/Cargo.toml +++ b/embassy-imxrt/Cargo.toml | |||
| @@ -12,7 +12,7 @@ documentation = "https://docs.embassy.dev/embassy-imxrt" | |||
| 12 | [package.metadata.embassy_docs] | 12 | [package.metadata.embassy_docs] |
| 13 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-imxrt-v$VERSION/embassy-imxrt/src/" | 13 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-imxrt-v$VERSION/embassy-imxrt/src/" |
| 14 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-imxrt/src/" | 14 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-imxrt/src/" |
| 15 | features = ["defmt", "unstable-pac", "time", "time-driver"] | 15 | features = ["defmt", "unstable-pac", "time", "time-driver-os-timer"] |
| 16 | flavors = [ | 16 | flavors = [ |
| 17 | { regex_feature = "mimxrt6.*", target = "thumbv8m.main-none-eabihf" } | 17 | { regex_feature = "mimxrt6.*", target = "thumbv8m.main-none-eabihf" } |
| 18 | ] | 18 | ] |
| @@ -37,9 +37,12 @@ defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt", "mimxr | |||
| 37 | time = ["dep:embassy-time", "embassy-embedded-hal/time"] | 37 | time = ["dep:embassy-time", "embassy-embedded-hal/time"] |
| 38 | 38 | ||
| 39 | ## Enable custom embassy time-driver implementation, using 32KHz RTC | 39 | ## Enable custom embassy time-driver implementation, using 32KHz RTC |
| 40 | time-driver-rtc = ["_time-driver"] | 40 | time-driver-rtc = ["_time-driver", "embassy-time-driver?/tick-hz-1_000"] |
| 41 | 41 | ||
| 42 | _time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-1_000", "dep:embassy-time-queue-utils", "embassy-embedded-hal/time"] | 42 | ## Enable custom embassy time-driver implementation, using 1MHz OS Timer |
| 43 | time-driver-os-timer = ["_time-driver", "embassy-time-driver?/tick-hz-1_000_000"] | ||
| 44 | |||
| 45 | _time-driver = ["dep:embassy-time-driver", "dep:embassy-time-queue-utils", "embassy-embedded-hal/time"] | ||
| 43 | 46 | ||
| 44 | ## Reexport the PAC for the currently enabled chip at `embassy_imxrt::pac` (unstable) | 47 | ## Reexport the PAC for the currently enabled chip at `embassy_imxrt::pac` (unstable) |
| 45 | unstable-pac = [] | 48 | unstable-pac = [] |
diff --git a/embassy-imxrt/src/lib.rs b/embassy-imxrt/src/lib.rs index fdf07b433..ad9f81e88 100644 --- a/embassy-imxrt/src/lib.rs +++ b/embassy-imxrt/src/lib.rs | |||
| @@ -132,12 +132,10 @@ pub fn init(config: config::Config) -> Peripherals { | |||
| 132 | error!("unable to initialize Clocks for reason: {:?}", e); | 132 | error!("unable to initialize Clocks for reason: {:?}", e); |
| 133 | // Panic here? | 133 | // Panic here? |
| 134 | } | 134 | } |
| 135 | gpio::init(); | ||
| 136 | } | 135 | } |
| 137 | |||
| 138 | // init RTC time driver | ||
| 139 | #[cfg(feature = "_time-driver")] | 136 | #[cfg(feature = "_time-driver")] |
| 140 | time_driver::init(config.time_interrupt_priority); | 137 | time_driver::init(config.time_interrupt_priority); |
| 138 | gpio::init(); | ||
| 141 | 139 | ||
| 142 | peripherals | 140 | peripherals |
| 143 | } | 141 | } |
diff --git a/embassy-imxrt/src/time_driver.rs b/embassy-imxrt/src/time_driver.rs index 56a8f7397..c68679d3e 100644 --- a/embassy-imxrt/src/time_driver.rs +++ b/embassy-imxrt/src/time_driver.rs | |||
| @@ -1,5 +1,6 @@ | |||
| 1 | //! RTC Time Driver. | 1 | //! Time Driver. |
| 2 | use core::cell::{Cell, RefCell}; | 2 | use core::cell::{Cell, RefCell}; |
| 3 | #[cfg(feature = "time-driver-rtc")] | ||
| 3 | use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; | 4 | use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; |
| 4 | 5 | ||
| 5 | use critical_section::CriticalSection; | 6 | use critical_section::CriticalSection; |
| @@ -8,9 +9,26 @@ use embassy_sync::blocking_mutex::Mutex; | |||
| 8 | use embassy_time_driver::Driver; | 9 | use embassy_time_driver::Driver; |
| 9 | use embassy_time_queue_utils::Queue; | 10 | use embassy_time_queue_utils::Queue; |
| 10 | 11 | ||
| 12 | #[cfg(feature = "time-driver-os-timer")] | ||
| 13 | use crate::clocks::enable; | ||
| 11 | use crate::interrupt::InterruptExt; | 14 | use crate::interrupt::InterruptExt; |
| 12 | use crate::{interrupt, pac}; | 15 | use crate::{interrupt, pac}; |
| 13 | 16 | ||
| 17 | struct AlarmState { | ||
| 18 | timestamp: Cell<u64>, | ||
| 19 | } | ||
| 20 | |||
| 21 | unsafe impl Send for AlarmState {} | ||
| 22 | |||
| 23 | impl AlarmState { | ||
| 24 | const fn new() -> Self { | ||
| 25 | Self { | ||
| 26 | timestamp: Cell::new(u64::MAX), | ||
| 27 | } | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | #[cfg(feature = "time-driver-rtc")] | ||
| 14 | fn rtc() -> &'static pac::rtc::RegisterBlock { | 32 | fn rtc() -> &'static pac::rtc::RegisterBlock { |
| 15 | unsafe { &*pac::Rtc::ptr() } | 33 | unsafe { &*pac::Rtc::ptr() } |
| 16 | } | 34 | } |
| @@ -25,24 +43,19 @@ fn rtc() -> &'static pac::rtc::RegisterBlock { | |||
| 25 | /// the 1kHz RTC counter is 16 bits and RTC doesn't have separate compare channels, | 43 | /// 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 | 44 | /// 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 | 45 | /// `period` is a 32bit integer, gpreg 'counter' is 31 bits plus the parity bit for overflow detection |
| 46 | #[cfg(feature = "time-driver-rtc")] | ||
| 28 | fn calc_now(period: u32, counter: u32) -> u64 { | 47 | fn calc_now(period: u32, counter: u32) -> u64 { |
| 29 | ((period as u64) << 31) + ((counter ^ ((period & 1) << 31)) as u64) | 48 | ((period as u64) << 31) + ((counter ^ ((period & 1) << 31)) as u64) |
| 30 | } | 49 | } |
| 31 | 50 | ||
| 32 | struct AlarmState { | 51 | #[cfg(feature = "time-driver-rtc")] |
| 33 | timestamp: Cell<u64>, | 52 | embassy_time_driver::time_driver_impl!(static DRIVER: Rtc = Rtc { |
| 34 | } | 53 | period: AtomicU32::new(0), |
| 35 | 54 | alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), | |
| 36 | unsafe impl Send for AlarmState {} | 55 | queue: Mutex::new(RefCell::new(Queue::new())), |
| 37 | 56 | }); | |
| 38 | impl AlarmState { | ||
| 39 | const fn new() -> Self { | ||
| 40 | Self { | ||
| 41 | timestamp: Cell::new(u64::MAX), | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } | ||
| 45 | 57 | ||
| 58 | #[cfg(feature = "time-driver-rtc")] | ||
| 46 | struct Rtc { | 59 | struct Rtc { |
| 47 | /// Number of 2^31 periods elapsed since boot. | 60 | /// Number of 2^31 periods elapsed since boot. |
| 48 | period: AtomicU32, | 61 | period: AtomicU32, |
| @@ -51,12 +64,7 @@ struct Rtc { | |||
| 51 | queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>, | 64 | queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>, |
| 52 | } | 65 | } |
| 53 | 66 | ||
| 54 | embassy_time_driver::time_driver_impl!(static DRIVER: Rtc = Rtc { | 67 | #[cfg(feature = "time-driver-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 | |||
| 60 | impl Rtc { | 68 | impl Rtc { |
| 61 | /// Access the GPREG0 register to use it as a 31-bit counter. | 69 | /// Access the GPREG0 register to use it as a 31-bit counter. |
| 62 | #[inline] | 70 | #[inline] |
| @@ -219,6 +227,7 @@ impl Rtc { | |||
| 219 | } | 227 | } |
| 220 | } | 228 | } |
| 221 | 229 | ||
| 230 | #[cfg(feature = "time-driver-rtc")] | ||
| 222 | impl Driver for Rtc { | 231 | impl Driver for Rtc { |
| 223 | fn now(&self) -> u64 { | 232 | fn now(&self) -> u64 { |
| 224 | // `period` MUST be read before `counter`, see comment at the top for details. | 233 | // `period` MUST be read before `counter`, see comment at the top for details. |
| @@ -242,13 +251,158 @@ impl Driver for Rtc { | |||
| 242 | } | 251 | } |
| 243 | } | 252 | } |
| 244 | 253 | ||
| 245 | #[cfg(feature = "rt")] | 254 | #[cfg(all(feature = "rt", feature = "time-driver-rtc"))] |
| 246 | #[allow(non_snake_case)] | 255 | #[allow(non_snake_case)] |
| 247 | #[interrupt] | 256 | #[interrupt] |
| 248 | fn RTC() { | 257 | fn RTC() { |
| 249 | DRIVER.on_interrupt() | 258 | DRIVER.on_interrupt() |
| 250 | } | 259 | } |
| 251 | 260 | ||
| 261 | #[cfg(feature = "time-driver-os-timer")] | ||
| 262 | fn os() -> &'static pac::ostimer0::RegisterBlock { | ||
| 263 | unsafe { &*pac::Ostimer0::ptr() } | ||
| 264 | } | ||
| 265 | |||
| 266 | /// Convert gray to decimal | ||
| 267 | /// | ||
| 268 | /// Os Event provides a 64-bit timestamp gray-encoded. All we have to | ||
| 269 | /// do here is read both 32-bit halves of the register and convert | ||
| 270 | /// from gray to regular binary. | ||
| 271 | #[cfg(feature = "time-driver-os-timer")] | ||
| 272 | fn gray_to_dec(gray: u64) -> u64 { | ||
| 273 | let mut dec = gray; | ||
| 274 | |||
| 275 | dec ^= dec >> 1; | ||
| 276 | dec ^= dec >> 2; | ||
| 277 | dec ^= dec >> 4; | ||
| 278 | dec ^= dec >> 8; | ||
| 279 | dec ^= dec >> 16; | ||
| 280 | dec ^= dec >> 32; | ||
| 281 | |||
| 282 | dec | ||
| 283 | } | ||
| 284 | |||
| 285 | /// Convert decimal to gray | ||
| 286 | /// | ||
| 287 | /// Before writing match value to the target register, we must convert | ||
| 288 | /// it back into gray code. | ||
| 289 | #[cfg(feature = "time-driver-os-timer")] | ||
| 290 | fn dec_to_gray(dec: u64) -> u64 { | ||
| 291 | let gray = dec; | ||
| 292 | gray ^ (gray >> 1) | ||
| 293 | } | ||
| 294 | |||
| 295 | #[cfg(feature = "time-driver-os-timer")] | ||
| 296 | embassy_time_driver::time_driver_impl!(static DRIVER: OsTimer = OsTimer { | ||
| 297 | alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), | ||
| 298 | queue: Mutex::new(RefCell::new(Queue::new())), | ||
| 299 | }); | ||
| 300 | |||
| 301 | #[cfg(feature = "time-driver-os-timer")] | ||
| 302 | struct OsTimer { | ||
| 303 | /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. | ||
| 304 | alarms: Mutex<CriticalSectionRawMutex, AlarmState>, | ||
| 305 | queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>, | ||
| 306 | } | ||
| 307 | |||
| 308 | #[cfg(feature = "time-driver-os-timer")] | ||
| 309 | impl OsTimer { | ||
| 310 | fn init(&'static self, irq_prio: crate::interrupt::Priority) { | ||
| 311 | // init alarms | ||
| 312 | critical_section::with(|cs| { | ||
| 313 | let alarm = DRIVER.alarms.borrow(cs); | ||
| 314 | alarm.timestamp.set(u64::MAX); | ||
| 315 | }); | ||
| 316 | |||
| 317 | // Enable clocks. Documentation advises AGAINST resetting this | ||
| 318 | // peripheral. | ||
| 319 | enable::<crate::peripherals::OS_EVENT>(); | ||
| 320 | |||
| 321 | interrupt::OS_EVENT.disable(); | ||
| 322 | |||
| 323 | // Make sure interrupt is masked | ||
| 324 | os().osevent_ctrl().modify(|_, w| w.ostimer_intena().clear_bit()); | ||
| 325 | |||
| 326 | // Default to the end of time | ||
| 327 | os().match_l().write(|w| unsafe { w.bits(0xffff_ffff) }); | ||
| 328 | os().match_h().write(|w| unsafe { w.bits(0xffff_ffff) }); | ||
| 329 | |||
| 330 | interrupt::OS_EVENT.unpend(); | ||
| 331 | interrupt::OS_EVENT.set_priority(irq_prio); | ||
| 332 | unsafe { interrupt::OS_EVENT.enable() }; | ||
| 333 | } | ||
| 334 | |||
| 335 | fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { | ||
| 336 | let alarm = self.alarms.borrow(cs); | ||
| 337 | alarm.timestamp.set(timestamp); | ||
| 338 | |||
| 339 | let t = self.now(); | ||
| 340 | if timestamp <= t { | ||
| 341 | os().osevent_ctrl().modify(|_, w| w.ostimer_intena().clear_bit()); | ||
| 342 | alarm.timestamp.set(u64::MAX); | ||
| 343 | return false; | ||
| 344 | } | ||
| 345 | |||
| 346 | let gray_timestamp = dec_to_gray(timestamp); | ||
| 347 | |||
| 348 | os().match_l() | ||
| 349 | .write(|w| unsafe { w.bits(gray_timestamp as u32 & 0xffff_ffff) }); | ||
| 350 | os().match_h() | ||
| 351 | .write(|w| unsafe { w.bits((gray_timestamp >> 32) as u32) }); | ||
| 352 | os().osevent_ctrl().modify(|_, w| w.ostimer_intena().set_bit()); | ||
| 353 | |||
| 354 | true | ||
| 355 | } | ||
| 356 | |||
| 357 | #[cfg(feature = "rt")] | ||
| 358 | fn trigger_alarm(&self, cs: CriticalSection) { | ||
| 359 | let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); | ||
| 360 | while !self.set_alarm(cs, next) { | ||
| 361 | next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); | ||
| 362 | } | ||
| 363 | } | ||
| 364 | |||
| 365 | #[cfg(feature = "rt")] | ||
| 366 | fn on_interrupt(&self) { | ||
| 367 | critical_section::with(|cs| { | ||
| 368 | if os().osevent_ctrl().read().ostimer_intrflag().bit_is_set() { | ||
| 369 | os().osevent_ctrl().modify(|_, w| w.ostimer_intena().clear_bit()); | ||
| 370 | self.trigger_alarm(cs); | ||
| 371 | } | ||
| 372 | }); | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | #[cfg(feature = "time-driver-os-timer")] | ||
| 377 | impl Driver for OsTimer { | ||
| 378 | fn now(&self) -> u64 { | ||
| 379 | let mut t = os().evtimerh().read().bits() as u64; | ||
| 380 | t <<= 32; | ||
| 381 | t |= os().evtimerl().read().bits() as u64; | ||
| 382 | gray_to_dec(t) | ||
| 383 | } | ||
| 384 | |||
| 385 | fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { | ||
| 386 | critical_section::with(|cs| { | ||
| 387 | let mut queue = self.queue.borrow(cs).borrow_mut(); | ||
| 388 | |||
| 389 | if queue.schedule_wake(at, waker) { | ||
| 390 | let mut next = queue.next_expiration(self.now()); | ||
| 391 | while !self.set_alarm(cs, next) { | ||
| 392 | next = queue.next_expiration(self.now()); | ||
| 393 | } | ||
| 394 | } | ||
| 395 | }) | ||
| 396 | } | ||
| 397 | } | ||
| 398 | |||
| 399 | #[cfg(all(feature = "rt", feature = "time-driver-os-timer"))] | ||
| 400 | #[allow(non_snake_case)] | ||
| 401 | #[interrupt] | ||
| 402 | fn OS_EVENT() { | ||
| 403 | DRIVER.on_interrupt() | ||
| 404 | } | ||
| 405 | |||
| 252 | pub(crate) fn init(irq_prio: crate::interrupt::Priority) { | 406 | pub(crate) fn init(irq_prio: crate::interrupt::Priority) { |
| 253 | DRIVER.init(irq_prio) | 407 | DRIVER.init(irq_prio) |
| 254 | } | 408 | } |
diff --git a/examples/mimxrt6/Cargo.toml b/examples/mimxrt6/Cargo.toml index 8fc510c47..b0c56f003 100644 --- a/examples/mimxrt6/Cargo.toml +++ b/examples/mimxrt6/Cargo.toml | |||
| @@ -12,8 +12,8 @@ defmt-rtt = "1.0" | |||
| 12 | 12 | ||
| 13 | embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } | 13 | embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } |
| 14 | embassy-futures = { version = "0.1.1", path = "../../embassy-futures" } | 14 | embassy-futures = { version = "0.1.1", path = "../../embassy-futures" } |
| 15 | embassy-imxrt = { version = "0.1.0", path = "../../embassy-imxrt", features = ["defmt", "mimxrt685s", "unstable-pac", "time", "time-driver-rtc"] } | 15 | embassy-imxrt = { version = "0.1.0", path = "../../embassy-imxrt", features = ["defmt", "mimxrt685s", "unstable-pac", "time", "time-driver-os-timer"] } |
| 16 | embassy-time = { version = "0.4", path = "../../embassy-time" } | 16 | embassy-time = { version = "0.4", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } |
| 17 | embassy-sync = { version = "0.6.2", path = "../../embassy-sync", features = ["defmt"] } | 17 | embassy-sync = { version = "0.6.2", path = "../../embassy-sync", features = ["defmt"] } |
| 18 | embedded-hal-1 = { package = "embedded-hal", version = "1.0" } | 18 | embedded-hal-1 = { package = "embedded-hal", version = "1.0" } |
| 19 | embedded-hal-async = "1.0.0" | 19 | embedded-hal-async = "1.0.0" |
