pennae f79d8cb2d3 rp/clocks: store clock frequencies in ram
don't recalculate clock frequencies every time they are asked for. while
this is not very often in practice it does consume a bunch of flash
space that cannot be optimized away, and was pulled in unconditionally
previously. while we technically only need the configured rosc, xosc and
gpin frequencies it is easier to store all frequencies (and much cheaper
at runtime too).
2023-05-17 21:36:19 +02:00

186 lines
6.3 KiB
Rust

mod filter;
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
pub use self::filter::DateTimeFilter;
#[cfg_attr(feature = "chrono", path = "datetime_chrono.rs")]
#[cfg_attr(not(feature = "chrono"), path = "datetime_no_deps.rs")]
mod datetime;
pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError};
use crate::clocks::clk_rtc_freq;
/// A reference to the real time clock of the system
pub struct RealTimeClock<'d, T: Instance> {
inner: PeripheralRef<'d, T>,
}
impl<'d, T: Instance> RealTimeClock<'d, T> {
/// Create a new instance of the real time clock, with the given date as an initial value.
///
/// # Errors
///
/// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range.
pub fn new(inner: impl Peripheral<P = T> + 'd, initial_date: DateTime) -> Result<Self, RtcError> {
into_ref!(inner);
// Set the RTC divider
unsafe { inner.regs().clkdiv_m1().write(|w| w.set_clkdiv_m1(clk_rtc_freq() - 1)) };
let mut result = Self { inner };
result.set_leap_year_check(true); // should be on by default, make sure this is the case.
result.set_datetime(initial_date)?;
Ok(result)
}
/// Enable or disable the leap year check. The rp2040 chip will always add a Feb 29th on every year that is divisable by 4, but this may be incorrect (e.g. on century years). This function allows you to disable this check.
///
/// Leap year checking is enabled by default.
pub fn set_leap_year_check(&mut self, leap_year_check_enabled: bool) {
unsafe {
self.inner
.regs()
.ctrl()
.modify(|w| w.set_force_notleapyear(!leap_year_check_enabled))
};
}
/// Checks to see if this RealTimeClock is running
pub fn is_running(&self) -> bool {
unsafe { self.inner.regs().ctrl().read().rtc_active() }
}
/// Set the datetime to a new value.
///
/// # Errors
///
/// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range.
pub fn set_datetime(&mut self, t: DateTime) -> Result<(), RtcError> {
self::datetime::validate_datetime(&t).map_err(RtcError::InvalidDateTime)?;
// disable RTC while we configure it
unsafe {
self.inner.regs().ctrl().modify(|w| w.set_rtc_enable(false));
while self.inner.regs().ctrl().read().rtc_active() {
core::hint::spin_loop();
}
self.inner.regs().setup_0().write(|w| {
self::datetime::write_setup_0(&t, w);
});
self.inner.regs().setup_1().write(|w| {
self::datetime::write_setup_1(&t, w);
});
// Load the new datetime and re-enable RTC
self.inner.regs().ctrl().write(|w| w.set_load(true));
self.inner.regs().ctrl().write(|w| w.set_rtc_enable(true));
while !self.inner.regs().ctrl().read().rtc_active() {
core::hint::spin_loop();
}
}
Ok(())
}
/// Return the current datetime.
///
/// # Errors
///
/// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`].
pub fn now(&self) -> Result<DateTime, RtcError> {
if !self.is_running() {
return Err(RtcError::NotRunning);
}
let rtc_0 = unsafe { self.inner.regs().rtc_0().read() };
let rtc_1 = unsafe { self.inner.regs().rtc_1().read() };
self::datetime::datetime_from_registers(rtc_0, rtc_1).map_err(RtcError::InvalidDateTime)
}
/// Disable the alarm that was scheduled with [`schedule_alarm`].
///
/// [`schedule_alarm`]: #method.schedule_alarm
pub fn disable_alarm(&mut self) {
unsafe {
self.inner.regs().irq_setup_0().modify(|s| s.set_match_ena(false));
while self.inner.regs().irq_setup_0().read().match_active() {
core::hint::spin_loop();
}
}
}
/// Schedule an alarm. The `filter` determines at which point in time this alarm is set.
///
/// Keep in mind that the filter only triggers on the specified time. If you want to schedule this alarm every minute, you have to call:
/// ```no_run
/// # #[cfg(feature = "chrono")]
/// # fn main() { }
/// # #[cfg(not(feature = "chrono"))]
/// # fn main() {
/// # use embassy_rp::rtc::{RealTimeClock, DateTimeFilter};
/// # let mut real_time_clock: RealTimeClock = unsafe { core::mem::zeroed() };
/// let now = real_time_clock.now().unwrap();
/// real_time_clock.schedule_alarm(
/// DateTimeFilter::default()
/// .minute(if now.minute == 59 { 0 } else { now.minute + 1 })
/// );
/// # }
/// ```
pub fn schedule_alarm(&mut self, filter: DateTimeFilter) {
self.disable_alarm();
unsafe {
self.inner.regs().irq_setup_0().write(|w| {
filter.write_setup_0(w);
});
self.inner.regs().irq_setup_1().write(|w| {
filter.write_setup_1(w);
});
self.inner.regs().inte().modify(|w| w.set_rtc(true));
// Set the enable bit and check if it is set
self.inner.regs().irq_setup_0().modify(|w| w.set_match_ena(true));
while !self.inner.regs().irq_setup_0().read().match_active() {
core::hint::spin_loop();
}
}
}
/// Clear the interrupt. This should be called every time the `RTC_IRQ` interrupt is triggered,
/// or the next [`schedule_alarm`] will never fire.
///
/// [`schedule_alarm`]: #method.schedule_alarm
pub fn clear_interrupt(&mut self) {
self.disable_alarm();
}
}
/// Errors that can occur on methods on [RealTimeClock]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RtcError {
/// An invalid DateTime was given or stored on the hardware.
InvalidDateTime(DateTimeError),
/// The RTC clock is not running
NotRunning,
}
mod sealed {
pub trait Instance {
fn regs(&self) -> crate::pac::rtc::Rtc;
}
}
pub trait Instance: sealed::Instance {}
impl sealed::Instance for crate::peripherals::RTC {
fn regs(&self) -> crate::pac::rtc::Rtc {
crate::pac::RTC
}
}
impl Instance for crate::peripherals::RTC {}