aboutsummaryrefslogtreecommitdiff
path: root/embassy-rp/src
diff options
context:
space:
mode:
authorMathias <[email protected]>2022-09-16 06:45:27 +0200
committerMathias <[email protected]>2022-09-16 13:18:23 +0200
commitfeead3ae89d57e9f0ff7d7a264136d3e89aaebcf (patch)
tree5553b485edc241ee2a72c76a08ee955e9cf0bd57 /embassy-rp/src
parent9794bc59cc74598f5131f502153d4288c3261274 (diff)
Implement RealTimeClock for embassy-rp
Diffstat (limited to 'embassy-rp/src')
-rw-r--r--embassy-rp/src/clocks.rs2
-rw-r--r--embassy-rp/src/lib.rs3
-rw-r--r--embassy-rp/src/rtc/datetime_chrono.rs62
-rw-r--r--embassy-rp/src/rtc/datetime_no_deps.rs127
-rw-r--r--embassy-rp/src/rtc/filter.rs100
-rw-r--r--embassy-rp/src/rtc/mod.rs188
6 files changed, 481 insertions, 1 deletions
diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs
index 3ad1e5d82..1c446f389 100644
--- a/embassy-rp/src/clocks.rs
+++ b/embassy-rp/src/clocks.rs
@@ -122,7 +122,7 @@ pub(crate) fn clk_peri_freq() -> u32 {
122 125_000_000 122 125_000_000
123} 123}
124 124
125pub(crate) fn _clk_rtc_freq() -> u32 { 125pub(crate) fn clk_rtc_freq() -> u32 {
126 46875 126 46875
127} 127}
128 128
diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs
index aebbbf567..730354557 100644
--- a/embassy-rp/src/lib.rs
+++ b/embassy-rp/src/lib.rs
@@ -7,6 +7,7 @@ pub(crate) mod fmt;
7pub mod dma; 7pub mod dma;
8pub mod gpio; 8pub mod gpio;
9pub mod interrupt; 9pub mod interrupt;
10pub mod rtc;
10pub mod spi; 11pub mod spi;
11pub mod timer; 12pub mod timer;
12pub mod uart; 13pub mod uart;
@@ -84,6 +85,8 @@ embassy_hal_common::peripherals! {
84 DMA_CH11, 85 DMA_CH11,
85 86
86 USB, 87 USB,
88
89 RTC,
87} 90}
88 91
89#[link_section = ".boot2"] 92#[link_section = ".boot2"]
diff --git a/embassy-rp/src/rtc/datetime_chrono.rs b/embassy-rp/src/rtc/datetime_chrono.rs
new file mode 100644
index 000000000..b3c78dd47
--- /dev/null
+++ b/embassy-rp/src/rtc/datetime_chrono.rs
@@ -0,0 +1,62 @@
1use chrono::{Datelike, Timelike};
2
3use crate::pac::rtc::regs::{Rtc0, Rtc1, Setup0, Setup1};
4
5/// Alias for [`chrono::NaiveDateTime`]
6pub type DateTime = chrono::NaiveDateTime;
7/// Alias for [`chrono::Weekday`]
8pub type DayOfWeek = chrono::Weekday;
9
10/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs.
11///
12/// [`DateTimeFilter`]: struct.DateTimeFilter.html
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub enum Error {
15 /// The [DateTime] has an invalid year. The year must be between 0 and 4095.
16 InvalidYear,
17 /// The [DateTime] contains an invalid date.
18 InvalidDate,
19 /// The [DateTime] contains an invalid time.
20 InvalidTime,
21}
22
23pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 {
24 dotw.num_days_from_sunday() as u8
25}
26
27pub(crate) fn validate_datetime(dt: &DateTime) -> Result<(), Error> {
28 if dt.year() < 0 || dt.year() > 4095 {
29 // rp2040 can't hold these years
30 Err(Error::InvalidYear)
31 } else {
32 // The rest of the chrono date is assumed to be valid
33 Ok(())
34 }
35}
36
37pub(super) fn write_setup_0(dt: &DateTime, w: &mut Setup0) {
38 w.set_year(dt.year() as u16);
39 w.set_month(dt.month() as u8);
40 w.set_day(dt.day() as u8);
41}
42
43pub(super) fn write_setup_1(dt: &DateTime, w: &mut Setup1) {
44 w.set_dotw(dt.weekday().num_days_from_sunday() as u8);
45 w.set_hour(dt.hour() as u8);
46 w.set_min(dt.minute() as u8);
47 w.set_sec(dt.second() as u8);
48}
49
50pub(super) fn datetime_from_registers(rtc_0: Rtc0, rtc_1: Rtc1) -> Result<DateTime, Error> {
51 let year = rtc_1.year() as i32;
52 let month = rtc_1.month() as u32;
53 let day = rtc_1.day() as u32;
54
55 let hour = rtc_0.hour() as u32;
56 let minute = rtc_0.min() as u32;
57 let second = rtc_0.sec() as u32;
58
59 let date = chrono::NaiveDate::from_ymd_opt(year, month, day).ok_or(Error::InvalidDate)?;
60 let time = chrono::NaiveTime::from_hms_opt(hour, minute, second).ok_or(Error::InvalidTime)?;
61 Ok(DateTime::new(date, time))
62}
diff --git a/embassy-rp/src/rtc/datetime_no_deps.rs b/embassy-rp/src/rtc/datetime_no_deps.rs
new file mode 100644
index 000000000..92770e984
--- /dev/null
+++ b/embassy-rp/src/rtc/datetime_no_deps.rs
@@ -0,0 +1,127 @@
1use crate::pac::rtc::regs::{Rtc0, Rtc1, Setup0, Setup1};
2
3/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs.
4///
5/// [`DateTimeFilter`]: struct.DateTimeFilter.html
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub enum Error {
8 /// The [DateTime] contains an invalid year value. Must be between `0..=4095`.
9 InvalidYear,
10 /// The [DateTime] contains an invalid month value. Must be between `1..=12`.
11 InvalidMonth,
12 /// The [DateTime] contains an invalid day value. Must be between `1..=31`.
13 InvalidDay,
14 /// The [DateTime] contains an invalid day of week. Must be between `0..=6` where 0 is Sunday.
15 InvalidDayOfWeek(
16 /// The value of the DayOfWeek that was given.
17 u8,
18 ),
19 /// The [DateTime] contains an invalid hour value. Must be between `0..=23`.
20 InvalidHour,
21 /// The [DateTime] contains an invalid minute value. Must be between `0..=59`.
22 InvalidMinute,
23 /// The [DateTime] contains an invalid second value. Must be between `0..=59`.
24 InvalidSecond,
25}
26
27/// Structure containing date and time information
28pub struct DateTime {
29 /// 0..4095
30 pub year: u16,
31 /// 1..12, 1 is January
32 pub month: u8,
33 /// 1..28,29,30,31 depending on month
34 pub day: u8,
35 ///
36 pub day_of_week: DayOfWeek,
37 /// 0..23
38 pub hour: u8,
39 /// 0..59
40 pub minute: u8,
41 /// 0..59
42 pub second: u8,
43}
44
45/// A day of the week
46#[repr(u8)]
47#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
48#[allow(missing_docs)]
49pub enum DayOfWeek {
50 Sunday = 0,
51 Monday = 1,
52 Tuesday = 2,
53 Wednesday = 3,
54 Thursday = 4,
55 Friday = 5,
56 Saturday = 6,
57}
58
59fn day_of_week_from_u8(v: u8) -> Result<DayOfWeek, Error> {
60 Ok(match v {
61 0 => DayOfWeek::Sunday,
62 1 => DayOfWeek::Monday,
63 2 => DayOfWeek::Tuesday,
64 3 => DayOfWeek::Wednesday,
65 4 => DayOfWeek::Thursday,
66 5 => DayOfWeek::Friday,
67 6 => DayOfWeek::Saturday,
68 x => return Err(Error::InvalidDayOfWeek(x)),
69 })
70}
71
72pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 {
73 dotw as u8
74}
75
76pub(super) fn validate_datetime(dt: &DateTime) -> Result<(), Error> {
77 if dt.year > 4095 {
78 Err(Error::InvalidYear)
79 } else if dt.month < 1 || dt.month > 12 {
80 Err(Error::InvalidMonth)
81 } else if dt.day < 1 || dt.day > 31 {
82 Err(Error::InvalidDay)
83 } else if dt.hour > 23 {
84 Err(Error::InvalidHour)
85 } else if dt.minute > 59 {
86 Err(Error::InvalidMinute)
87 } else if dt.second > 59 {
88 Err(Error::InvalidSecond)
89 } else {
90 Ok(())
91 }
92}
93
94pub(super) fn write_setup_0(dt: &DateTime, w: &mut Setup0) {
95 w.set_year(dt.year);
96 w.set_month(dt.month);
97 w.set_day(dt.day);
98}
99
100pub(super) fn write_setup_1(dt: &DateTime, w: &mut Setup1) {
101 w.set_dotw(dt.day_of_week as u8);
102 w.set_hour(dt.hour);
103 w.set_min(dt.minute);
104 w.set_sec(dt.second);
105}
106
107pub(super) fn datetime_from_registers(rtc_0: Rtc0, rtc_1: Rtc1) -> Result<DateTime, Error> {
108 let year = rtc_1.year();
109 let month = rtc_1.month();
110 let day = rtc_1.day();
111
112 let day_of_week = rtc_0.dotw();
113 let hour = rtc_0.hour();
114 let minute = rtc_0.min();
115 let second = rtc_0.sec();
116
117 let day_of_week = day_of_week_from_u8(day_of_week)?;
118 Ok(DateTime {
119 year,
120 month,
121 day,
122 day_of_week,
123 hour,
124 minute,
125 second,
126 })
127}
diff --git a/embassy-rp/src/rtc/filter.rs b/embassy-rp/src/rtc/filter.rs
new file mode 100644
index 000000000..d4a3bab2f
--- /dev/null
+++ b/embassy-rp/src/rtc/filter.rs
@@ -0,0 +1,100 @@
1use super::DayOfWeek;
2use crate::pac::rtc::regs::{IrqSetup0, IrqSetup1};
3
4/// A filter used for [`RealTimeClock::schedule_alarm`].
5///
6/// [`RealTimeClock::schedule_alarm`]: struct.RealTimeClock.html#method.schedule_alarm
7#[derive(Default)]
8pub struct DateTimeFilter {
9 /// The year that this alarm should trigger on, `None` if the RTC alarm should not trigger on a year value.
10 pub year: Option<u16>,
11 /// The month that this alarm should trigger on, `None` if the RTC alarm should not trigger on a month value.
12 pub month: Option<u8>,
13 /// The day that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day value.
14 pub day: Option<u8>,
15 /// The day of week that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day of week value.
16 pub day_of_week: Option<DayOfWeek>,
17 /// The hour that this alarm should trigger on, `None` if the RTC alarm should not trigger on a hour value.
18 pub hour: Option<u8>,
19 /// The minute that this alarm should trigger on, `None` if the RTC alarm should not trigger on a minute value.
20 pub minute: Option<u8>,
21 /// The second that this alarm should trigger on, `None` if the RTC alarm should not trigger on a second value.
22 pub second: Option<u8>,
23}
24
25impl DateTimeFilter {
26 /// Set a filter on the given year
27 pub fn year(mut self, year: u16) -> Self {
28 self.year = Some(year);
29 self
30 }
31 /// Set a filter on the given month
32 pub fn month(mut self, month: u8) -> Self {
33 self.month = Some(month);
34 self
35 }
36 /// Set a filter on the given day
37 pub fn day(mut self, day: u8) -> Self {
38 self.day = Some(day);
39 self
40 }
41 /// Set a filter on the given day of the week
42 pub fn day_of_week(mut self, day_of_week: DayOfWeek) -> Self {
43 self.day_of_week = Some(day_of_week);
44 self
45 }
46 /// Set a filter on the given hour
47 pub fn hour(mut self, hour: u8) -> Self {
48 self.hour = Some(hour);
49 self
50 }
51 /// Set a filter on the given minute
52 pub fn minute(mut self, minute: u8) -> Self {
53 self.minute = Some(minute);
54 self
55 }
56 /// Set a filter on the given second
57 pub fn second(mut self, second: u8) -> Self {
58 self.second = Some(second);
59 self
60 }
61}
62
63// register helper functions
64impl DateTimeFilter {
65 pub(super) fn write_setup_0(&self, w: &mut IrqSetup0) {
66 if let Some(year) = self.year {
67 w.set_year_ena(true);
68
69 w.set_year(year);
70 }
71 if let Some(month) = self.month {
72 w.set_month_ena(true);
73 w.set_month(month);
74 }
75 if let Some(day) = self.day {
76 w.set_day_ena(true);
77 w.set_day(day);
78 }
79 }
80 pub(super) fn write_setup_1(&self, w: &mut IrqSetup1) {
81 if let Some(day_of_week) = self.day_of_week {
82 w.set_dotw_ena(true);
83 let bits = super::datetime::day_of_week_to_u8(day_of_week);
84
85 w.set_dotw(bits);
86 }
87 if let Some(hour) = self.hour {
88 w.set_hour_ena(true);
89 w.set_hour(hour);
90 }
91 if let Some(minute) = self.minute {
92 w.set_min_ena(true);
93 w.set_min(minute);
94 }
95 if let Some(second) = self.second {
96 w.set_sec_ena(true);
97 w.set_sec(second);
98 }
99 }
100}
diff --git a/embassy-rp/src/rtc/mod.rs b/embassy-rp/src/rtc/mod.rs
new file mode 100644
index 000000000..7f3bbbe73
--- /dev/null
+++ b/embassy-rp/src/rtc/mod.rs
@@ -0,0 +1,188 @@
1mod filter;
2
3use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
4
5pub use self::filter::DateTimeFilter;
6
7#[cfg_attr(feature = "chrono", path = "datetime_chrono.rs")]
8#[cfg_attr(not(feature = "chrono"), path = "datetime_no_deps.rs")]
9mod datetime;
10
11pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError};
12use crate::clocks::clk_rtc_freq;
13
14/// A reference to the real time clock of the system
15pub struct RealTimeClock<'d, T: Instance> {
16 inner: PeripheralRef<'d, T>,
17}
18
19impl<'d, T: Instance> RealTimeClock<'d, T> {
20 /// Create a new instance of the real time clock, with the given date as an initial value.
21 ///
22 /// # Errors
23 ///
24 /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range.
25 pub fn new(inner: impl Peripheral<P = T> + 'd, initial_date: DateTime) -> Result<Self, RtcError> {
26 into_ref!(inner);
27
28 // Set the RTC divider
29 unsafe {
30 inner
31 .regs()
32 .clkdiv_m1()
33 .write(|w| w.set_clkdiv_m1(clk_rtc_freq() as u16 - 1))
34 };
35
36 let mut result = Self { inner };
37 result.set_leap_year_check(true); // should be on by default, make sure this is the case.
38 result.set_datetime(initial_date)?;
39 Ok(result)
40 }
41
42 /// 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.
43 ///
44 /// Leap year checking is enabled by default.
45 pub fn set_leap_year_check(&mut self, leap_year_check_enabled: bool) {
46 unsafe {
47 self.inner
48 .regs()
49 .ctrl()
50 .modify(|w| w.set_force_notleapyear(!leap_year_check_enabled))
51 };
52 }
53
54 /// Checks to see if this RealTimeClock is running
55 pub fn is_running(&self) -> bool {
56 unsafe { self.inner.regs().ctrl().read().rtc_active() }
57 }
58
59 /// Set the datetime to a new value.
60 ///
61 /// # Errors
62 ///
63 /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range.
64 pub fn set_datetime(&mut self, t: DateTime) -> Result<(), RtcError> {
65 self::datetime::validate_datetime(&t).map_err(RtcError::InvalidDateTime)?;
66
67 // disable RTC while we configure it
68 unsafe {
69 self.inner.regs().ctrl().modify(|w| w.set_rtc_enable(false));
70 while self.inner.regs().ctrl().read().rtc_active() {
71 core::hint::spin_loop();
72 }
73
74 self.inner.regs().setup_0().write(|w| {
75 self::datetime::write_setup_0(&t, w);
76 });
77 self.inner.regs().setup_1().write(|w| {
78 self::datetime::write_setup_1(&t, w);
79 });
80
81 // Load the new datetime and re-enable RTC
82 self.inner.regs().ctrl().write(|w| w.set_load(true));
83 self.inner.regs().ctrl().write(|w| w.set_rtc_enable(true));
84 while !self.inner.regs().ctrl().read().rtc_active() {
85 core::hint::spin_loop();
86 }
87 }
88 Ok(())
89 }
90
91 /// Return the current datetime.
92 ///
93 /// # Errors
94 ///
95 /// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`].
96 pub fn now(&self) -> Result<DateTime, RtcError> {
97 if !self.is_running() {
98 return Err(RtcError::NotRunning);
99 }
100
101 let rtc_0 = unsafe { self.inner.regs().rtc_0().read() };
102 let rtc_1 = unsafe { self.inner.regs().rtc_1().read() };
103
104 self::datetime::datetime_from_registers(rtc_0, rtc_1).map_err(RtcError::InvalidDateTime)
105 }
106
107 /// Disable the alarm that was scheduled with [`schedule_alarm`].
108 ///
109 /// [`schedule_alarm`]: #method.schedule_alarm
110 pub fn disable_alarm(&mut self) {
111 unsafe {
112 self.inner.regs().irq_setup_0().modify(|s| s.set_match_ena(false));
113
114 while self.inner.regs().irq_setup_0().read().match_active() {
115 core::hint::spin_loop();
116 }
117 }
118 }
119
120 /// Schedule an alarm. The `filter` determines at which point in time this alarm is set.
121 ///
122 /// 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:
123 /// ```no_run
124 /// # #[cfg(feature = "chrono")]
125 /// # fn main() { }
126 /// # #[cfg(not(feature = "chrono"))]
127 /// # fn main() {
128 /// # use embassy_rp::rtc::{RealTimeClock, DateTimeFilter};
129 /// # let mut real_time_clock: RealTimeClock = unsafe { core::mem::zeroed() };
130 /// let now = real_time_clock.now().unwrap();
131 /// real_time_clock.schedule_alarm(
132 /// DateTimeFilter::default()
133 /// .minute(if now.minute == 59 { 0 } else { now.minute + 1 })
134 /// );
135 /// # }
136 /// ```
137 pub fn schedule_alarm(&mut self, filter: DateTimeFilter) {
138 self.disable_alarm();
139
140 unsafe {
141 self.inner.regs().irq_setup_0().write(|w| {
142 filter.write_setup_0(w);
143 });
144 self.inner.regs().irq_setup_1().write(|w| {
145 filter.write_setup_1(w);
146 });
147
148 // Set the enable bit and check if it is set
149 self.inner.regs().irq_setup_0().modify(|w| w.set_match_ena(true));
150 while !self.inner.regs().irq_setup_0().read().match_active() {
151 core::hint::spin_loop();
152 }
153 }
154 }
155
156 /// Clear the interrupt. This should be called every time the `RTC_IRQ` interrupt is triggered,
157 /// or the next [`schedule_alarm`] will never fire.
158 ///
159 /// [`schedule_alarm`]: #method.schedule_alarm
160 pub fn clear_interrupt(&mut self) {
161 self.disable_alarm();
162 }
163}
164
165/// Errors that can occur on methods on [RtcClock]
166#[derive(Clone, Debug, PartialEq, Eq)]
167pub enum RtcError {
168 /// An invalid DateTime was given or stored on the hardware.
169 InvalidDateTime(DateTimeError),
170
171 /// The RTC clock is not running
172 NotRunning,
173}
174
175mod sealed {
176 pub trait Instance {
177 fn regs(&self) -> crate::pac::rtc::Rtc;
178 }
179}
180
181pub trait Instance: sealed::Instance {}
182
183impl sealed::Instance for crate::peripherals::RTC {
184 fn regs(&self) -> crate::pac::rtc::Rtc {
185 crate::pac::RTC
186 }
187}
188impl Instance for crate::peripherals::RTC {}