Implement RealTimeClock for embassy-rp
This commit is contained in:
parent
9794bc59cc
commit
feead3ae89
@ -44,6 +44,7 @@ cortex-m-rt = ">=0.6.15,<0.8"
|
||||
cortex-m = "0.7.6"
|
||||
critical-section = "1.1"
|
||||
futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
|
||||
chrono = { version = "0.4", default-features = false, optional = true }
|
||||
|
||||
rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="017e3c9007b2d3b6965f0d85b5bf8ce3fa6d7364", features = ["rt"] }
|
||||
#rp2040-pac2 = { path = "../../rp2040-pac2", features = ["rt"] }
|
||||
|
@ -122,7 +122,7 @@ pub(crate) fn clk_peri_freq() -> u32 {
|
||||
125_000_000
|
||||
}
|
||||
|
||||
pub(crate) fn _clk_rtc_freq() -> u32 {
|
||||
pub(crate) fn clk_rtc_freq() -> u32 {
|
||||
46875
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ pub(crate) mod fmt;
|
||||
pub mod dma;
|
||||
pub mod gpio;
|
||||
pub mod interrupt;
|
||||
pub mod rtc;
|
||||
pub mod spi;
|
||||
pub mod timer;
|
||||
pub mod uart;
|
||||
@ -84,6 +85,8 @@ embassy_hal_common::peripherals! {
|
||||
DMA_CH11,
|
||||
|
||||
USB,
|
||||
|
||||
RTC,
|
||||
}
|
||||
|
||||
#[link_section = ".boot2"]
|
||||
|
62
embassy-rp/src/rtc/datetime_chrono.rs
Normal file
62
embassy-rp/src/rtc/datetime_chrono.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use chrono::{Datelike, Timelike};
|
||||
|
||||
use crate::pac::rtc::regs::{Rtc0, Rtc1, Setup0, Setup1};
|
||||
|
||||
/// Alias for [`chrono::NaiveDateTime`]
|
||||
pub type DateTime = chrono::NaiveDateTime;
|
||||
/// Alias for [`chrono::Weekday`]
|
||||
pub type DayOfWeek = chrono::Weekday;
|
||||
|
||||
/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs.
|
||||
///
|
||||
/// [`DateTimeFilter`]: struct.DateTimeFilter.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
/// The [DateTime] has an invalid year. The year must be between 0 and 4095.
|
||||
InvalidYear,
|
||||
/// The [DateTime] contains an invalid date.
|
||||
InvalidDate,
|
||||
/// The [DateTime] contains an invalid time.
|
||||
InvalidTime,
|
||||
}
|
||||
|
||||
pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 {
|
||||
dotw.num_days_from_sunday() as u8
|
||||
}
|
||||
|
||||
pub(crate) fn validate_datetime(dt: &DateTime) -> Result<(), Error> {
|
||||
if dt.year() < 0 || dt.year() > 4095 {
|
||||
// rp2040 can't hold these years
|
||||
Err(Error::InvalidYear)
|
||||
} else {
|
||||
// The rest of the chrono date is assumed to be valid
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn write_setup_0(dt: &DateTime, w: &mut Setup0) {
|
||||
w.set_year(dt.year() as u16);
|
||||
w.set_month(dt.month() as u8);
|
||||
w.set_day(dt.day() as u8);
|
||||
}
|
||||
|
||||
pub(super) fn write_setup_1(dt: &DateTime, w: &mut Setup1) {
|
||||
w.set_dotw(dt.weekday().num_days_from_sunday() as u8);
|
||||
w.set_hour(dt.hour() as u8);
|
||||
w.set_min(dt.minute() as u8);
|
||||
w.set_sec(dt.second() as u8);
|
||||
}
|
||||
|
||||
pub(super) fn datetime_from_registers(rtc_0: Rtc0, rtc_1: Rtc1) -> Result<DateTime, Error> {
|
||||
let year = rtc_1.year() as i32;
|
||||
let month = rtc_1.month() as u32;
|
||||
let day = rtc_1.day() as u32;
|
||||
|
||||
let hour = rtc_0.hour() as u32;
|
||||
let minute = rtc_0.min() as u32;
|
||||
let second = rtc_0.sec() as u32;
|
||||
|
||||
let date = chrono::NaiveDate::from_ymd_opt(year, month, day).ok_or(Error::InvalidDate)?;
|
||||
let time = chrono::NaiveTime::from_hms_opt(hour, minute, second).ok_or(Error::InvalidTime)?;
|
||||
Ok(DateTime::new(date, time))
|
||||
}
|
127
embassy-rp/src/rtc/datetime_no_deps.rs
Normal file
127
embassy-rp/src/rtc/datetime_no_deps.rs
Normal file
@ -0,0 +1,127 @@
|
||||
use crate::pac::rtc::regs::{Rtc0, Rtc1, Setup0, Setup1};
|
||||
|
||||
/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs.
|
||||
///
|
||||
/// [`DateTimeFilter`]: struct.DateTimeFilter.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
/// The [DateTime] contains an invalid year value. Must be between `0..=4095`.
|
||||
InvalidYear,
|
||||
/// The [DateTime] contains an invalid month value. Must be between `1..=12`.
|
||||
InvalidMonth,
|
||||
/// The [DateTime] contains an invalid day value. Must be between `1..=31`.
|
||||
InvalidDay,
|
||||
/// The [DateTime] contains an invalid day of week. Must be between `0..=6` where 0 is Sunday.
|
||||
InvalidDayOfWeek(
|
||||
/// The value of the DayOfWeek that was given.
|
||||
u8,
|
||||
),
|
||||
/// The [DateTime] contains an invalid hour value. Must be between `0..=23`.
|
||||
InvalidHour,
|
||||
/// The [DateTime] contains an invalid minute value. Must be between `0..=59`.
|
||||
InvalidMinute,
|
||||
/// The [DateTime] contains an invalid second value. Must be between `0..=59`.
|
||||
InvalidSecond,
|
||||
}
|
||||
|
||||
/// Structure containing date and time information
|
||||
pub struct DateTime {
|
||||
/// 0..4095
|
||||
pub year: u16,
|
||||
/// 1..12, 1 is January
|
||||
pub month: u8,
|
||||
/// 1..28,29,30,31 depending on month
|
||||
pub day: u8,
|
||||
///
|
||||
pub day_of_week: DayOfWeek,
|
||||
/// 0..23
|
||||
pub hour: u8,
|
||||
/// 0..59
|
||||
pub minute: u8,
|
||||
/// 0..59
|
||||
pub second: u8,
|
||||
}
|
||||
|
||||
/// A day of the week
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum DayOfWeek {
|
||||
Sunday = 0,
|
||||
Monday = 1,
|
||||
Tuesday = 2,
|
||||
Wednesday = 3,
|
||||
Thursday = 4,
|
||||
Friday = 5,
|
||||
Saturday = 6,
|
||||
}
|
||||
|
||||
fn day_of_week_from_u8(v: u8) -> Result<DayOfWeek, Error> {
|
||||
Ok(match v {
|
||||
0 => DayOfWeek::Sunday,
|
||||
1 => DayOfWeek::Monday,
|
||||
2 => DayOfWeek::Tuesday,
|
||||
3 => DayOfWeek::Wednesday,
|
||||
4 => DayOfWeek::Thursday,
|
||||
5 => DayOfWeek::Friday,
|
||||
6 => DayOfWeek::Saturday,
|
||||
x => return Err(Error::InvalidDayOfWeek(x)),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 {
|
||||
dotw as u8
|
||||
}
|
||||
|
||||
pub(super) fn validate_datetime(dt: &DateTime) -> Result<(), Error> {
|
||||
if dt.year > 4095 {
|
||||
Err(Error::InvalidYear)
|
||||
} else if dt.month < 1 || dt.month > 12 {
|
||||
Err(Error::InvalidMonth)
|
||||
} else if dt.day < 1 || dt.day > 31 {
|
||||
Err(Error::InvalidDay)
|
||||
} else if dt.hour > 23 {
|
||||
Err(Error::InvalidHour)
|
||||
} else if dt.minute > 59 {
|
||||
Err(Error::InvalidMinute)
|
||||
} else if dt.second > 59 {
|
||||
Err(Error::InvalidSecond)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn write_setup_0(dt: &DateTime, w: &mut Setup0) {
|
||||
w.set_year(dt.year);
|
||||
w.set_month(dt.month);
|
||||
w.set_day(dt.day);
|
||||
}
|
||||
|
||||
pub(super) fn write_setup_1(dt: &DateTime, w: &mut Setup1) {
|
||||
w.set_dotw(dt.day_of_week as u8);
|
||||
w.set_hour(dt.hour);
|
||||
w.set_min(dt.minute);
|
||||
w.set_sec(dt.second);
|
||||
}
|
||||
|
||||
pub(super) fn datetime_from_registers(rtc_0: Rtc0, rtc_1: Rtc1) -> Result<DateTime, Error> {
|
||||
let year = rtc_1.year();
|
||||
let month = rtc_1.month();
|
||||
let day = rtc_1.day();
|
||||
|
||||
let day_of_week = rtc_0.dotw();
|
||||
let hour = rtc_0.hour();
|
||||
let minute = rtc_0.min();
|
||||
let second = rtc_0.sec();
|
||||
|
||||
let day_of_week = day_of_week_from_u8(day_of_week)?;
|
||||
Ok(DateTime {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
day_of_week,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
})
|
||||
}
|
100
embassy-rp/src/rtc/filter.rs
Normal file
100
embassy-rp/src/rtc/filter.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use super::DayOfWeek;
|
||||
use crate::pac::rtc::regs::{IrqSetup0, IrqSetup1};
|
||||
|
||||
/// A filter used for [`RealTimeClock::schedule_alarm`].
|
||||
///
|
||||
/// [`RealTimeClock::schedule_alarm`]: struct.RealTimeClock.html#method.schedule_alarm
|
||||
#[derive(Default)]
|
||||
pub struct DateTimeFilter {
|
||||
/// The year that this alarm should trigger on, `None` if the RTC alarm should not trigger on a year value.
|
||||
pub year: Option<u16>,
|
||||
/// The month that this alarm should trigger on, `None` if the RTC alarm should not trigger on a month value.
|
||||
pub month: Option<u8>,
|
||||
/// The day that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day value.
|
||||
pub day: Option<u8>,
|
||||
/// The day of week that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day of week value.
|
||||
pub day_of_week: Option<DayOfWeek>,
|
||||
/// The hour that this alarm should trigger on, `None` if the RTC alarm should not trigger on a hour value.
|
||||
pub hour: Option<u8>,
|
||||
/// The minute that this alarm should trigger on, `None` if the RTC alarm should not trigger on a minute value.
|
||||
pub minute: Option<u8>,
|
||||
/// The second that this alarm should trigger on, `None` if the RTC alarm should not trigger on a second value.
|
||||
pub second: Option<u8>,
|
||||
}
|
||||
|
||||
impl DateTimeFilter {
|
||||
/// Set a filter on the given year
|
||||
pub fn year(mut self, year: u16) -> Self {
|
||||
self.year = Some(year);
|
||||
self
|
||||
}
|
||||
/// Set a filter on the given month
|
||||
pub fn month(mut self, month: u8) -> Self {
|
||||
self.month = Some(month);
|
||||
self
|
||||
}
|
||||
/// Set a filter on the given day
|
||||
pub fn day(mut self, day: u8) -> Self {
|
||||
self.day = Some(day);
|
||||
self
|
||||
}
|
||||
/// Set a filter on the given day of the week
|
||||
pub fn day_of_week(mut self, day_of_week: DayOfWeek) -> Self {
|
||||
self.day_of_week = Some(day_of_week);
|
||||
self
|
||||
}
|
||||
/// Set a filter on the given hour
|
||||
pub fn hour(mut self, hour: u8) -> Self {
|
||||
self.hour = Some(hour);
|
||||
self
|
||||
}
|
||||
/// Set a filter on the given minute
|
||||
pub fn minute(mut self, minute: u8) -> Self {
|
||||
self.minute = Some(minute);
|
||||
self
|
||||
}
|
||||
/// Set a filter on the given second
|
||||
pub fn second(mut self, second: u8) -> Self {
|
||||
self.second = Some(second);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// register helper functions
|
||||
impl DateTimeFilter {
|
||||
pub(super) fn write_setup_0(&self, w: &mut IrqSetup0) {
|
||||
if let Some(year) = self.year {
|
||||
w.set_year_ena(true);
|
||||
|
||||
w.set_year(year);
|
||||
}
|
||||
if let Some(month) = self.month {
|
||||
w.set_month_ena(true);
|
||||
w.set_month(month);
|
||||
}
|
||||
if let Some(day) = self.day {
|
||||
w.set_day_ena(true);
|
||||
w.set_day(day);
|
||||
}
|
||||
}
|
||||
pub(super) fn write_setup_1(&self, w: &mut IrqSetup1) {
|
||||
if let Some(day_of_week) = self.day_of_week {
|
||||
w.set_dotw_ena(true);
|
||||
let bits = super::datetime::day_of_week_to_u8(day_of_week);
|
||||
|
||||
w.set_dotw(bits);
|
||||
}
|
||||
if let Some(hour) = self.hour {
|
||||
w.set_hour_ena(true);
|
||||
w.set_hour(hour);
|
||||
}
|
||||
if let Some(minute) = self.minute {
|
||||
w.set_min_ena(true);
|
||||
w.set_min(minute);
|
||||
}
|
||||
if let Some(second) = self.second {
|
||||
w.set_sec_ena(true);
|
||||
w.set_sec(second);
|
||||
}
|
||||
}
|
||||
}
|
188
embassy-rp/src/rtc/mod.rs
Normal file
188
embassy-rp/src/rtc/mod.rs
Normal file
@ -0,0 +1,188 @@
|
||||
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() as u16 - 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);
|
||||
});
|
||||
|
||||
// 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 [RtcClock]
|
||||
#[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 {}
|
Loading…
Reference in New Issue
Block a user