Skip to main content

core_s3/
rtc.rs

1//! BM8563 RTC driver and `no_std` date/time types.
2
3use embedded_hal::i2c::I2c;
4
5use crate::devices;
6
7const REG_CONTROL_STATUS1: u8 = 0x00;
8const REG_SECONDS: u8 = 0x02;
9const REG_MINUTE_ALARM: u8 = 0x09;
10const REG_TIMER_CONTROL: u8 = 0x0E;
11const REG_TIMER: u8 = 0x0F;
12
13#[cfg_attr(feature = "defmt", derive(defmt::Format))]
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub struct Date {
16    pub year: u16,
17    pub month: u8,
18    pub day: u8,
19    pub weekday: u8,
20}
21
22#[cfg_attr(feature = "defmt", derive(defmt::Format))]
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub struct Time {
25    pub hour: u8,
26    pub minute: u8,
27    pub second: u8,
28}
29
30#[cfg_attr(feature = "defmt", derive(defmt::Format))]
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub struct DateTime {
33    pub date: Date,
34    pub time: Time,
35}
36
37#[cfg_attr(feature = "defmt", derive(defmt::Format))]
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub struct Alarm {
40    pub minute: Option<u8>,
41    pub hour: Option<u8>,
42    pub day: Option<u8>,
43    pub weekday: Option<u8>,
44}
45
46#[cfg_attr(feature = "defmt", derive(defmt::Format))]
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48pub enum TimerClock {
49    Hz4096,
50    Hz64,
51    Hz1,
52    OneOver60Hz,
53}
54
55#[cfg_attr(feature = "defmt", derive(defmt::Format))]
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub struct TimerConfig {
58    pub clock: TimerClock,
59    pub count: u8,
60}
61
62#[cfg_attr(feature = "defmt", derive(defmt::Format))]
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
64pub struct TimedWakeMetadata {
65    pub alarm: Option<Alarm>,
66    pub timer: Option<TimerConfig>,
67}
68
69pub struct Bm8563<I2C> {
70    i2c: I2C,
71    address: u8,
72}
73
74impl<I2C> Bm8563<I2C> {
75    pub const fn new(i2c: I2C) -> Self {
76        Self {
77            i2c,
78            address: devices::i2c::BM8563_RTC,
79        }
80    }
81
82    pub fn release(self) -> I2C {
83        self.i2c
84    }
85}
86
87impl<I2C, Error> Bm8563<I2C>
88where
89    I2C: I2c<Error = Error>,
90{
91    pub fn init(&mut self) -> Result<(), Error> {
92        self.write_register(REG_CONTROL_STATUS1, 0x00)
93    }
94
95    pub fn datetime(&mut self) -> Result<DateTime, Error> {
96        let data = self.datetime_registers()?;
97        Ok(DateTime {
98            time: Time {
99                second: bcd_to_bin(data[0] & 0x7F),
100                minute: bcd_to_bin(data[1] & 0x7F),
101                hour: bcd_to_bin(data[2] & 0x3F),
102            },
103            date: Date {
104                day: bcd_to_bin(data[3] & 0x3F),
105                weekday: data[4] & 0x07,
106                month: bcd_to_bin(data[5] & 0x1F),
107                year: 2000 + u16::from(bcd_to_bin(data[6])),
108            },
109        })
110    }
111
112    /// Returns `true` when BM8563 reports low voltage or clock-integrity loss.
113    ///
114    /// BM8563 stores this in bit 7 of the seconds register. When set, the date
115    /// and time should be treated as potentially invalid until application code
116    /// sets a known-good time.
117    pub fn clock_integrity_lost(&mut self) -> Result<bool, Error> {
118        Ok(self.datetime_registers()?[0] & 0x80 != 0)
119    }
120
121    pub fn set_datetime(&mut self, datetime: DateTime) -> Result<(), Error> {
122        let data = [
123            REG_SECONDS,
124            bin_to_bcd(datetime.time.second),
125            bin_to_bcd(datetime.time.minute),
126            bin_to_bcd(datetime.time.hour),
127            bin_to_bcd(datetime.date.day),
128            datetime.date.weekday & 0x07,
129            bin_to_bcd(datetime.date.month),
130            bin_to_bcd((datetime.date.year % 100) as u8),
131        ];
132        self.i2c.write(self.address, &data)
133    }
134
135    pub fn set_alarm(&mut self, alarm: Alarm) -> Result<(), Error> {
136        let disabled = 0x80;
137        let data = [
138            REG_MINUTE_ALARM,
139            alarm.minute.map(bin_to_bcd).unwrap_or(disabled),
140            alarm.hour.map(bin_to_bcd).unwrap_or(disabled),
141            alarm.day.map(bin_to_bcd).unwrap_or(disabled),
142            alarm.weekday.map(bin_to_bcd).unwrap_or(disabled),
143        ];
144        self.i2c.write(self.address, &data)
145    }
146
147    pub fn set_timer(&mut self, timer: TimerConfig) -> Result<(), Error> {
148        self.write_register(REG_TIMER, timer.count)?;
149        self.write_register(REG_TIMER_CONTROL, 0x80 | timer_clock_code(timer.clock))
150    }
151
152    fn datetime_registers(&mut self) -> Result<[u8; 7], Error> {
153        let mut data = [0u8; 7];
154        self.i2c
155            .write_read(self.address, &[REG_SECONDS], &mut data)?;
156        Ok(data)
157    }
158
159    fn write_register(&mut self, register: u8, value: u8) -> Result<(), Error> {
160        self.i2c.write(self.address, &[register, value])
161    }
162}
163
164pub const fn bcd_to_bin(value: u8) -> u8 {
165    ((value >> 4) * 10) + (value & 0x0F)
166}
167
168pub const fn bin_to_bcd(value: u8) -> u8 {
169    ((value / 10) << 4) | (value % 10)
170}
171
172const fn timer_clock_code(clock: TimerClock) -> u8 {
173    match clock {
174        TimerClock::Hz4096 => 0,
175        TimerClock::Hz64 => 1,
176        TimerClock::Hz1 => 2,
177        TimerClock::OneOver60Hz => 3,
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn bcd_round_trips() {
187        assert_eq!(bcd_to_bin(0x59), 59);
188        assert_eq!(bin_to_bcd(42), 0x42);
189    }
190}