feat: add RTC time driver

This commit is contained in:
Irina Chiorean 2025-07-25 18:00:49 +03:00
parent a8cb8a7fe1
commit 517714c98e
6 changed files with 216 additions and 2 deletions

View File

@ -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.

View File

@ -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

View 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();
}

View File

@ -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"}

View 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.

View 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;
}
}