mirror of
https://github.com/embassy-rs/embassy.git
synced 2025-09-28 04:40:39 +00:00
feat: add RTC time driver
This commit is contained in:
parent
a8cb8a7fe1
commit
517714c98e
@ -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.
|
||||
|
@ -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
|
||||
|
172
embassy-nxp/src/time_driver/rtc.rs
Normal file
172
embassy-nxp/src/time_driver/rtc.rs
Normal file
@ -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<u64>,
|
||||
}
|
||||
|
||||
unsafe impl Send for AlarmState {}
|
||||
|
||||
impl AlarmState {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
timestamp: Cell::new(u64::MAX),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RtcDriver {
|
||||
alarms: Mutex<AlarmState>,
|
||||
queue: Mutex<RefCell<Queue>>,
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
@ -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"}
|
||||
|
12
examples/lpc55s69/README.md
Normal file
12
examples/lpc55s69/README.md
Normal file
@ -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.
|
26
examples/lpc55s69/src/bin/blinky_embassy_time.rs
Normal file
26
examples/lpc55s69/src/bin/blinky_embassy_time.rs
Normal file
@ -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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user