diff --git a/CHANGELOG.md b/CHANGELOG.md index d357e1f7..6ca9b699 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased -No changes. +- Added RtcBuilder for rtc module ## [v0.10.0] - 2023-11-30 diff --git a/src/rtc.rs b/src/rtc.rs index d6d41a97..19cbae98 100644 --- a/src/rtc.rs +++ b/src/rtc.rs @@ -5,7 +5,7 @@ //! //! [ST AN4759]: https:/www.st.com%2Fresource%2Fen%2Fapplication_note%2Fdm00226326-using-the-hardware-realtime-clock-rtc-and-the-tamper-management-unit-tamp-with-stm32-microcontrollers-stmicroelectronics.pdf&usg=AOvVaw3PzvL2TfYtwS32fw-Uv37h -use crate::pac::{PWR, RTC}; +use crate::pac::{PWR, RCC, RTC}; use crate::rcc::{Enable, APB1, BDCR}; use core::convert::TryInto; use core::fmt; @@ -22,6 +22,120 @@ pub enum Error { InvalidRtcData, } +/// RTC clock source +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum RtcClockSource { + /// Low Speed Internal Clock (LSI) + LSI, + /// Low Speed External Clock (LSE) - To turn off/on bypass for LSE, please use inner bool parameter LSE(false/true) + LSE(bool), +} + +/// Builder struct for RTC +pub struct RtcBuilder { + pub(crate) rtc: RTC, + prediv_s: u16, + prediv_a: u8, + clock_source: RtcClockSource, + default: bool, +} + +/// Builder for RTC using both LSI and LSE clock sources, +/// by default LSE is used with configuration of 1Hz calendar clock. +impl RtcBuilder { + /// Builder for RTC using both LSI and LSE clock sources, + /// by default LSE is used with configuration of 1Hz calendar clock. + /// + /// ### Example for LSE: + /// ``` + /// use stm32f3xx_hal::{pac, rtc::{RtcBuilder, RtcClockSource}}; + /// ... + /// let dp = pac::Peripherals::take().unwrap(); + /// let rcc = pac.RCC.constrain(); + /// let mut pwr = dp.PWR; + /// + /// let rtc = RtcBuilder::new(pac.RTC).build(&mut pwr, &mut rcc.apb1, &mut rcc.bdcr); + /// /// or + /// let rtc = RtcBuilder::new(pac.RTC) + /// .set_clock_source(RtcClockSource::LSE(true)).build(&mut pwr, &mut rcc.apb1, &mut rcc.bdcr); + /// ``` + /// ### Example for LSI: + /// ```` + /// let rtc = RtcBuilder::new(pac.RTC) + /// .set_clock_source(RtcClockSource::LSI).build(&mut pwr, &mut rcc.apb1, &mut rcc.bdcr); + /// ```` + /// **This examples shows how to run RTC with LSI and LSE with 1Hz frequency. This means + /// that RTC will increase by 1 second every second.** + /// + /// If you want to change your clock source or prescalers please use + /// correct functions. + pub fn new(rtc: RTC) -> Self { + Self { + prediv_s: 255, + prediv_a: 127, + clock_source: RtcClockSource::LSE(false), + default: true, + rtc, + } + } + + /// Set your prescaler "perediv_s" + /// + /// **Note:** Using deferent values than default will affect your RTC clock, + /// it can slow down or speed up RTC. + pub fn set_perediv_s(mut self, prediv_s: u16) -> Self { + self.default = false; + self.prediv_s = prediv_s; + self + } + + /// Set your prescaler "perediv_a" + /// + /// **Note:** Using deferent values than default will affect your RTC clock, + /// it can slow down or speed up RTC. + pub fn set_perediv_a(mut self, prediv_a: u8) -> Self { + self.default = false; + self.prediv_a = prediv_a; + self + } + + /// Please select your clock source. Selecting source of RTC clock will impact on + /// clock accuracy. + /// + /// If your using: + /// + /// - ***LSE*** - your clock will be accurate, but it is external peripheral, so you might not have it. + /// + /// - ***LSI*** - your clock might be not accurate, but if you don't need super accurate clock you can use LSI. + /// It is build in microcontroller clock source. + pub fn set_clock_source(mut self, clock_source: RtcClockSource) -> Self { + self.clock_source = clock_source; + self + } + + /// Build RTC ready for use + pub fn build(self, pwr: &mut PWR, apb1: &mut APB1, bdcr: &mut BDCR) -> Rtc { + match self.clock_source { + RtcClockSource::LSI => { + let cfg = match self.default { + true => Self { + prediv_s: 319, + prediv_a: 127, + clock_source: RtcClockSource::LSI, + default: true, + rtc: self.rtc, + }, + false => self, + }; + Rtc::new_for_builder(cfg, apb1, bdcr, pwr) + } + RtcClockSource::LSE(_bypass) => Rtc::new_for_builder(self, apb1, bdcr, pwr), + } + } +} + /// Real Time Clock peripheral pub struct Rtc { /// RTC Peripheral register definition @@ -72,6 +186,36 @@ impl Rtc { result } + /// Constructor for RTC builder + pub(crate) fn new_for_builder( + cfg: RtcBuilder, + apb1: &mut APB1, + bdcr: &mut BDCR, + pwr: &mut PWR, + ) -> Self { + let mut result = Self { rtc: cfg.rtc }; + + unlock(apb1, pwr); + match cfg.clock_source { + RtcClockSource::LSI => { + enable_lsi(); + enable_rtc_with_lsi(bdcr); + } + RtcClockSource::LSE(bypass) => { + enable_lse(bdcr, bypass); + enable(bdcr); + } + } + result.set_24h_fmt(); + + result.rtc.prer.modify(|_, w| { + w.prediv_s().bits(cfg.prediv_s); + w.prediv_a().bits(cfg.prediv_a) + }); + + result + } + /// Sets calendar clock to 24 hr format pub fn set_24h_fmt(&mut self) { self.rtc.cr.modify(|_, w| w.fmt().set_bit()); @@ -435,19 +579,37 @@ fn enable_lse(bdcr: &mut BDCR, bypass: bool) { while bdcr.bdcr().read().lserdy().bit_is_clear() {} } +/// Enable the low frequency internal oscillator (LSI) - potentially unsafe +/// +/// # Safety +/// Function potentially unsafe because of writing into CSR +fn enable_lsi() { + // SAFETY: + // potentially unsafe because of writing into CSR + let rcc = unsafe { &*RCC::ptr() }; + rcc.csr.modify(|_, w| w.lsion().set_bit()); + while rcc.csr.read().lsirdy().bit_is_clear() {} +} + +/// Enable DBP fn unlock(apb1: &mut APB1, pwr: &mut PWR) { // Enable the backup interface by setting PWREN PWR::enable(apb1); - pwr.cr.modify(|_, w| { - w - // Enable access to the backup registers - .dbp() - .set_bit() - }); + pwr.cr.modify(|_, w| w.dbp().set_bit()); while pwr.cr.read().dbp().bit_is_clear() {} } +/// Enable RTC with Low Speed Internal clock (LSI) as clock source. +fn enable_rtc_with_lsi(bdcr: &mut BDCR) { + bdcr.bdcr().modify(|_, w| w.bdrst().enabled()); + bdcr.bdcr().modify(|_, w| { + w.rtcsel().lsi(); + w.rtcen().enabled(); + w.bdrst().disabled() + }); +} + fn enable(bdcr: &mut BDCR) { bdcr.bdcr().modify(|_, w| w.bdrst().enabled()); bdcr.bdcr().modify(|_, w| {