Skip to main content

ambiq_hal/
rtc.rs

1//! Real Time Clock
2//!
3//! TODO:
4//!     * Add a Delay implementation that does deep sleep?
5//!     * Arbitrary time alarm (if possible?)
6
7use crate::clock::ClockCtrl;
8use pac::{CLKGEN, RTC};
9use pac::rtc::rtcctl::RPT_A;
10use rtcc::DateTimeAccess;
11
12use chrono::{Datelike, Timelike};
13
14#[allow(unused_imports)]
15use defmt::{debug, error, info, trace, warn};
16
17pub struct Rtc {
18    rtc: RTC,
19}
20
21fn bcd_to_dec(bcd: u8) -> u8 {
22    (((bcd & 0xf0) >> 4) * 10) + (bcd & 0x0f)
23}
24
25fn dec_to_bcd(dec: u8) -> u8 {
26    ((dec / 10) << 4) | (dec % 10)
27}
28
29impl Rtc {
30    pub fn new(rtc: RTC, clkgen: &mut CLKGEN) -> Rtc {
31        // Enable XT for RTC
32        let mut clk = ClockCtrl::new(clkgen);
33        // clk.enable_xt();
34        rtc.rtcctl.reset();
35        rtc.almup.reset();
36        rtc.almlow.reset();
37        rtc.inten.write(|w| w.alm().clear_bit());
38        rtc.intclr.write(|w| w.alm().set_bit());
39
40        // Select XT as source
41        clk.rtc_use_xt();
42
43        rtc.rtcctl.modify(|_, w| w.hr1224()._24hr());
44
45        Rtc { rtc }
46    }
47
48    pub fn enable(&mut self) {
49        self.rtc.rtcctl.modify(|_, w| w.rstop().run());
50    }
51
52    pub fn disable(&mut self) {
53        self.rtc.rtcctl.modify(|_, w| w.rstop().stop());
54    }
55
56    /// Set the current time and date (accuracy 1/100th).
57    ///
58    /// The century will always be the 21st (20xx).
59    pub fn set(&self, dt: &chrono::NaiveDateTime) {
60        let date = dt.date();
61        let time = dt.time();
62
63        let year = date.year();
64        let yr = year % 100;
65
66        debug!("set RTC to: {}-{}-{} {}:{}:{}.{}",
67            year,
68            date.month(),
69            date.day(),
70            time.hour(),
71            time.minute(),
72            time.second(),
73            time.nanosecond());
74
75        self.rtc.rtcctl.modify(|_, w| w.wrtc().en());
76
77        self.rtc.ctrlow.write(|w| unsafe {
78            w.ctrhr().bits(dec_to_bcd(time.hour() as u8))
79                .ctrmin().bits(dec_to_bcd(time.minute() as u8))
80                .ctrsec().bits(dec_to_bcd(time.second() as u8))
81                .ctr100().bits(dec_to_bcd((time.nanosecond() / 1_000_000 * 100) as u8))
82        });
83
84        self.rtc.ctrup.write(|w| unsafe {
85            w.ceb().dis() // TODO: support other centuries
86                .ctryr()
87                .bits(dec_to_bcd(yr as u8))
88                .ctrmo()
89                .bits(dec_to_bcd(date.month() as u8))
90                .ctrdate()
91                .bits(dec_to_bcd(date.day() as u8))
92                .ctrwkdy()
93                .bits(date.weekday() as u8)
94        });
95
96        self.rtc.rtcctl.modify(|_, w| w.wrtc().dis());
97    }
98
99    /// Get the current datetime (accurate to 1/100th second). Blocks untill no rollover
100    /// error.
101    pub fn now(&self) -> chrono::NaiveDateTime {
102        let (upper, lower) = loop {
103            let lower = self.rtc.ctrlow.read();
104
105            let no_err = self.rtc.ctrup.read().cterr().is_noerr(); // Set if upper read is done later than 1/100th sec after lower read.
106
107            let upper = self.rtc.ctrup.read(); // Resets error.
108
109            // Check for rollover between read of lower and upper.
110            // p. 554.
111            if no_err {
112                break (upper, lower);
113            }
114        };
115
116        let yr = bcd_to_dec(upper.ctryr().bits()) as i32;
117        const CE: i32 = 20;
118
119        chrono::NaiveDate::from_ymd_opt(
120            CE * 100 + yr,
121            bcd_to_dec(upper.ctrmo().bits()).into(),
122            bcd_to_dec(upper.ctrdate().bits()).into(),
123        ).and_then(|y| y.and_hms_milli_opt(
124            bcd_to_dec(lower.ctrhr().bits()).into(),
125            bcd_to_dec(lower.ctrmin().bits()).into(),
126            bcd_to_dec(lower.ctrsec().bits()).into(),
127            u32::from(bcd_to_dec(lower.ctr100().bits())) * 10u32,
128        )).unwrap()
129    }
130
131    /// Set the repeat alarm interval. Remember to enable the alarm as well.
132    pub fn set_alarm_repeat(&mut self, interval: AlarmRepeat) {
133        self.rtc.almup.reset();
134        self.rtc.almlow.reset();
135
136        self.rtc.rtcctl.modify(|_, w| w.rpt().variant(interval.into()));
137
138        match interval {
139            AlarmRepeat::DeciSecond => {
140                self.rtc.almlow.write(|w| unsafe { w.alm100().bits(0xf0) });
141            },
142            AlarmRepeat::CentiSecond => {
143                self.rtc.almlow.write(|w| unsafe { w.alm100().bits(0xff) });
144            },
145            _ => {
146            }
147        }
148    }
149
150    pub fn clear_interrupts(&mut self) {
151        self.rtc.intclr.write(|w| w.alm().set_bit());
152    }
153
154    pub fn disable_alarm_repeat(&mut self) {
155        self.rtc.rtcctl.modify(|_, w| w.rpt().dis());
156    }
157
158    pub fn enable_alarm(&mut self) {
159        cortex_m::interrupt::free(|_| {
160            self.clear_interrupts();
161            self.rtc.inten.write(|w| w.alm().set_bit());
162            unsafe {
163                pac::NVIC::unmask(pac::Interrupt::RTC);
164            }
165        });
166    }
167
168    pub fn disable_alarm(&mut self) {
169        pac::NVIC::mask(pac::Interrupt::RTC);
170        self.rtc.inten.write(|w| w.alm().clear_bit());
171        self.clear_interrupts();
172    }
173}
174
175impl DateTimeAccess for Rtc {
176    type Error = !;
177
178    fn datetime(&mut self) -> Result<chrono::NaiveDateTime, !> {
179        Ok(self.now())
180    }
181
182    fn set_datetime(&mut self, datetime: &chrono::NaiveDateTime) -> Result<(), !> {
183        self.set(datetime);
184
185        Ok(())
186    }
187}
188
189#[derive(Clone, Copy, Debug, PartialEq)]
190#[repr(u8)]
191pub enum AlarmRepeat {
192    Disabled,
193    Year,
194    Month,
195    Week,
196    Day,
197    Hour,
198    Minute,
199    Second,
200
201    /// Every 100th millisecond
202    DeciSecond,
203
204    /// Every 10th millisecond
205    CentiSecond,
206}
207
208impl Into<RPT_A> for AlarmRepeat {
209    fn into(self) -> RPT_A {
210        use AlarmRepeat::*;
211        use RPT_A::*;
212
213        match self {
214            Disabled => DIS,
215            Year => YEAR,
216            Month => MONTH,
217            Week => WEEK,
218            Day => DAY,
219            Hour => HR,
220            Minute => MIN,
221            Second => SEC,
222            DeciSecond => SEC,
223            CentiSecond => SEC,
224        }
225    }
226}
227