summaryrefslogtreecommitdiff
path: root/embassy-rp/src/rtc/mod.rs
blob: e4b6f0b1dea5f981735793f3088eb73122156e1e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
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);
            });

            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 [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 {}