diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index a4a232f5..a3ce9d99 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -66,6 +66,7 @@ stm32-fmc = "0.2.4" seq-macro = "0.3.0" cfg-if = "1.0.0" embedded-io = { version = "0.3.0", features = ["async"], optional = true } +chrono = { version = "^0.4", default-features = false, optional = true} [build-dependencies] proc-macro2 = "1.0.36" diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 0392e808..906980ac 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -46,6 +46,8 @@ pub mod flash; pub mod pwm; #[cfg(rng)] pub mod rng; +#[cfg(all(rtc, not(rtc_v1)))] +pub mod rtc; #[cfg(sdmmc)] pub mod sdmmc; #[cfg(spi)] diff --git a/embassy-stm32/src/rtc/datetime_chrono.rs b/embassy-stm32/src/rtc/datetime_chrono.rs new file mode 100644 index 00000000..b46316cc --- /dev/null +++ b/embassy-stm32/src/rtc/datetime_chrono.rs @@ -0,0 +1,85 @@ +use chrono::{Datelike, Timelike}; + +use super::byte_to_bcd2; +use crate::pac::rtc::Rtc; + +/// Alias for [`chrono::NaiveDateTime`] +pub type DateTime = chrono::NaiveDateTime; +/// Alias for [`chrono::Weekday`] +pub type DayOfWeek = chrono::Weekday; + +/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs. +/// +/// [`DateTimeFilter`]: struct.DateTimeFilter.html +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Error { + /// The [DateTime] has an invalid year. The year must be between 0 and 4095. + InvalidYear, + /// The [DateTime] contains an invalid date. + InvalidDate, + /// The [DateTime] contains an invalid time. + InvalidTime, +} + +pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 { + dotw.num_days_from_monday() as u8 +} + +pub(crate) fn validate_datetime(dt: &DateTime) -> Result<(), Error> { + if dt.year() < 0 || dt.year() > 4095 { + // rp2040 can't hold these years + Err(Error::InvalidYear) + } else { + // The rest of the chrono date is assumed to be valid + Ok(()) + } +} + +pub(super) fn write_date_time(rtc: &Rtc, t: DateTime) { + let (ht, hu) = byte_to_bcd2(t.hour() as u8); + let (mnt, mnu) = byte_to_bcd2(t.minute() as u8); + let (st, su) = byte_to_bcd2(t.second() as u8); + + let (dt, du) = byte_to_bcd2(t.day() as u8); + let (mt, mu) = byte_to_bcd2(t.month() as u8); + let yr = t.year() as u16; + let yr_offset = (yr - 1970_u16) as u8; + let (yt, yu) = byte_to_bcd2(yr_offset); + + unsafe { + rtc.tr().write(|w| { + w.set_ht(ht); + w.set_hu(hu); + w.set_mnt(mnt); + w.set_mnu(mnu); + w.set_st(st); + w.set_su(su); + w.set_pm(stm32_metapac::rtc::vals::Ampm::AM); + }); + + rtc.dr().write(|w| { + w.set_dt(dt); + w.set_du(du); + w.set_mt(mt > 0); + w.set_mu(mu); + w.set_yt(yt); + w.set_yu(yu); + w.set_wdu(day_of_week_to_u8(t.weekday())); + }); + } +} + +pub(super) fn datetime( + year: u16, + month: u8, + day: u8, + _day_of_week: u8, + hour: u8, + minute: u8, + second: u8, +) -> Result { + let date = chrono::NaiveDate::from_ymd_opt(year.into(), month.try_into().unwrap(), day.into()) + .ok_or(Error::InvalidDate)?; + let time = chrono::NaiveTime::from_hms_opt(hour.into(), minute.into(), second.into()).ok_or(Error::InvalidTime)?; + Ok(DateTime::new(date, time)) +} diff --git a/embassy-stm32/src/rtc/datetime_no_deps.rs b/embassy-stm32/src/rtc/datetime_no_deps.rs new file mode 100644 index 00000000..173f3837 --- /dev/null +++ b/embassy-stm32/src/rtc/datetime_no_deps.rs @@ -0,0 +1,146 @@ +use super::byte_to_bcd2; +use crate::pac::rtc::Rtc; + +/// Errors regarding the [`DateTime`] struct. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Error { + /// The [DateTime] contains an invalid year value. Must be between `0..=4095`. + InvalidYear, + /// The [DateTime] contains an invalid month value. Must be between `1..=12`. + InvalidMonth, + /// The [DateTime] contains an invalid day value. Must be between `1..=31`. + InvalidDay, + /// The [DateTime] contains an invalid day of week. Must be between `0..=6` where 0 is Sunday. + InvalidDayOfWeek( + /// The value of the DayOfWeek that was given. + u8, + ), + /// The [DateTime] contains an invalid hour value. Must be between `0..=23`. + InvalidHour, + /// The [DateTime] contains an invalid minute value. Must be between `0..=59`. + InvalidMinute, + /// The [DateTime] contains an invalid second value. Must be between `0..=59`. + InvalidSecond, +} + +/// Structure containing date and time information +pub struct DateTime { + /// 0..4095 + pub year: u16, + /// 1..12, 1 is January + pub month: u8, + /// 1..28,29,30,31 depending on month + pub day: u8, + /// + pub day_of_week: DayOfWeek, + /// 0..23 + pub hour: u8, + /// 0..59 + pub minute: u8, + /// 0..59 + pub second: u8, +} + +/// A day of the week +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[allow(missing_docs)] +pub enum DayOfWeek { + Monday = 0, + Tuesday = 1, + Wednesday = 2, + Thursday = 3, + Friday = 4, + Saturday = 5, + Sunday = 6, +} + +fn day_of_week_from_u8(v: u8) -> Result { + Ok(match v { + 0 => DayOfWeek::Monday, + 1 => DayOfWeek::Tuesday, + 2 => DayOfWeek::Wednesday, + 3 => DayOfWeek::Thursday, + 4 => DayOfWeek::Friday, + 5 => DayOfWeek::Saturday, + 6 => DayOfWeek::Sunday, + x => return Err(Error::InvalidDayOfWeek(x)), + }) +} + +pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 { + dotw as u8 +} + +pub(super) fn validate_datetime(dt: &DateTime) -> Result<(), Error> { + if dt.year > 4095 { + Err(Error::InvalidYear) + } else if dt.month < 1 || dt.month > 12 { + Err(Error::InvalidMonth) + } else if dt.day < 1 || dt.day > 31 { + Err(Error::InvalidDay) + } else if dt.hour > 23 { + Err(Error::InvalidHour) + } else if dt.minute > 59 { + Err(Error::InvalidMinute) + } else if dt.second > 59 { + Err(Error::InvalidSecond) + } else { + Ok(()) + } +} + +pub(super) fn write_date_time(rtc: &Rtc, t: DateTime) { + let (ht, hu) = byte_to_bcd2(t.hour as u8); + let (mnt, mnu) = byte_to_bcd2(t.minute as u8); + let (st, su) = byte_to_bcd2(t.second as u8); + + let (dt, du) = byte_to_bcd2(t.day as u8); + let (mt, mu) = byte_to_bcd2(t.month as u8); + let yr = t.year as u16; + let yr_offset = (yr - 1970_u16) as u8; + let (yt, yu) = byte_to_bcd2(yr_offset); + + unsafe { + rtc.tr().write(|w| { + w.set_ht(ht); + w.set_hu(hu); + w.set_mnt(mnt); + w.set_mnu(mnu); + w.set_st(st); + w.set_su(su); + w.set_pm(stm32_metapac::rtc::vals::Ampm::AM); + }); + + rtc.dr().write(|w| { + w.set_dt(dt); + w.set_du(du); + w.set_mt(mt > 0); + w.set_mu(mu); + w.set_yt(yt); + w.set_yu(yu); + w.set_wdu(day_of_week_to_u8(t.day_of_week)); + }); + } +} + +pub(super) fn datetime( + year: u16, + month: u8, + day: u8, + day_of_week: u8, + hour: u8, + minute: u8, + second: u8, +) -> Result { + let day_of_week = day_of_week_from_u8(day_of_week)?; + Ok(DateTime { + year, + month, + day, + day_of_week, + hour, + minute, + second, + }) +} diff --git a/embassy-stm32/src/rtc/mod.rs b/embassy-stm32/src/rtc/mod.rs new file mode 100644 index 00000000..ee3349b2 --- /dev/null +++ b/embassy-stm32/src/rtc/mod.rs @@ -0,0 +1,238 @@ +//! RTC peripheral abstraction +use core::marker::PhantomData; + +#[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}; + +/// refer to AN4759 to compare features of RTC2 and RTC3 +#[cfg_attr(any(rtc_v1), path = "v1.rs")] +#[cfg_attr( + any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb + ), + path = "v2/mod.rs" +)] +#[cfg_attr(any(rtc_v3, rtc_v3u5), path = "v3.rs")] +mod versions; +use embassy_hal_common::Peripheral; +pub use versions::*; + +/// 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, +} + +/// RTC Abstraction +pub struct Rtc<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + rtc_config: RtcConfig, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum RtcClockSource { + /// 00: No clock + NoClock = 0b00, + /// 01: LSE oscillator clock used as RTC clock + LSE = 0b01, + /// 10: LSI oscillator clock used as RTC clock + LSI = 0b10, + /// 11: HSE oscillator clock divided by 32 used as RTC clock + HSE = 0b11, +} + +#[derive(Copy, Clone, PartialEq)] +pub struct RtcConfig { + /// RTC clock source + clock_config: RtcClockSource, + /// Asynchronous prescaler factor + /// This is the asynchronous division factor: + /// ck_apre frequency = RTCCLK frequency/(PREDIV_A+1) + /// ck_apre drives the subsecond register + async_prescaler: u8, + /// Synchronous prescaler factor + /// This is the synchronous division factor: + /// ck_spre frequency = ck_apre frequency/(PREDIV_S+1) + /// ck_spre must be 1Hz + sync_prescaler: u16, +} + +impl Default for RtcConfig { + /// LSI with prescalers assuming 32.768 kHz. + /// Raw sub-seconds in 1/256. + fn default() -> Self { + RtcConfig { + clock_config: RtcClockSource::LSI, + async_prescaler: 127, + sync_prescaler: 255, + } + } +} + +impl RtcConfig { + /// Sets the clock source of RTC config + pub fn clock_config(mut self, cfg: RtcClockSource) -> Self { + self.clock_config = cfg; + self + } + + /// Set the asynchronous prescaler of RTC config + pub fn async_prescaler(mut self, prescaler: u8) -> Self { + self.async_prescaler = prescaler; + self + } + + /// Set the synchronous prescaler of RTC config + pub fn sync_prescaler(mut self, prescaler: u16) -> Self { + self.sync_prescaler = prescaler; + self + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum RtcCalibrationCyclePeriod { + /// 8-second calibration period + Seconds8, + /// 16-second calibration period + Seconds16, + /// 32-second calibration period + Seconds32, +} + +impl Default for RtcCalibrationCyclePeriod { + fn default() -> Self { + RtcCalibrationCyclePeriod::Seconds32 + } +} + +impl<'d, T: Instance> Rtc<'d, T> { + pub fn new(_rtc: impl Peripheral

+ 'd, rtc_config: RtcConfig) -> Self { + unsafe { enable_peripheral_clk() }; + + let mut rtc_struct = Self { + phantom: PhantomData, + rtc_config, + }; + + rtc_struct.apply_config(rtc_config); + + rtc_struct + } + + /// 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)?; + self.write(true, |rtc| self::datetime::write_date_time(rtc, t)); + + 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 { + let r = T::regs(); + unsafe { + let tr = r.tr().read(); + let second = bcd2_to_byte((tr.st(), tr.su())); + let minute = bcd2_to_byte((tr.mnt(), tr.mnu())); + let hour = bcd2_to_byte((tr.ht(), tr.hu())); + // Reading either RTC_SSR or RTC_TR locks the values in the higher-order + // calendar shadow registers until RTC_DR is read. + let dr = r.dr().read(); + + let weekday = dr.wdu(); + let day = bcd2_to_byte((dr.dt(), dr.du())); + let month = bcd2_to_byte((dr.mt() as u8, dr.mu())); + let year = bcd2_to_byte((dr.yt(), dr.yu())) as u16 + 1970_u16; + + self::datetime::datetime(year, month, day, weekday, hour, minute, second).map_err(RtcError::InvalidDateTime) + } + } + + /// Check if daylight savings time is active. + pub fn get_daylight_savings(&self) -> bool { + let cr = unsafe { T::regs().cr().read() }; + cr.bkp() + } + + /// Enable/disable daylight savings time. + pub fn set_daylight_savings(&mut self, daylight_savings: bool) { + self.write(true, |rtc| { + unsafe { rtc.cr().modify(|w| w.set_bkp(daylight_savings)) }; + }) + } + + pub fn get_config(&self) -> RtcConfig { + self.rtc_config + } + + pub const BACKUP_REGISTER_COUNT: usize = BACKUP_REGISTER_COUNT; + + /// Read content of the backup register. + /// + /// The registers retain their values during wakes from standby mode or system resets. They also + /// retain their value when Vdd is switched off as long as V_BAT is powered. + pub fn read_backup_register(&self, register: usize) -> Option { + read_backup_register(&T::regs(), register) + } + + /// Set content of the backup register. + /// + /// The registers retain their values during wakes from standby mode or system resets. They also + /// retain their value when Vdd is switched off as long as V_BAT is powered. + pub fn write_backup_register(&self, register: usize, value: u32) { + write_backup_register(&T::regs(), register, value) + } +} + +pub(crate) fn byte_to_bcd2(byte: u8) -> (u8, u8) { + let mut bcd_high: u8 = 0; + let mut value = byte; + + while value >= 10 { + bcd_high += 1; + value -= 10; + } + + (bcd_high, ((bcd_high << 4) | value) as u8) +} + +pub(crate) fn bcd2_to_byte(bcd: (u8, u8)) -> u8 { + let value = bcd.1 | bcd.0 << 4; + + let tmp = ((value & 0xF0) >> 0x4) * 10; + + tmp + (value & 0x0F) +} + +pub(crate) mod sealed { + pub trait Instance { + fn regs() -> crate::pac::rtc::Rtc; + } +} + +pub trait Instance: sealed::Instance + 'static {} + +impl sealed::Instance for crate::peripherals::RTC { + fn regs() -> crate::pac::rtc::Rtc { + crate::pac::RTC + } +} + +impl Instance for crate::peripherals::RTC {} diff --git a/embassy-stm32/src/rtc/v2/mod.rs b/embassy-stm32/src/rtc/v2/mod.rs new file mode 100644 index 00000000..296adae8 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/mod.rs @@ -0,0 +1,171 @@ +use stm32_metapac::rtc::vals::{Init, Osel, Pol}; + +use super::{Instance, RtcConfig}; +use crate::pac::rtc::Rtc; + +#[cfg_attr(rtc_v2f0, path = "v2f0.rs")] +#[cfg_attr(rtc_v2f2, path = "v2f2.rs")] +#[cfg_attr(rtc_v2f3, path = "v2f3.rs")] +#[cfg_attr(rtc_v2f4, path = "v2f4.rs")] +#[cfg_attr(rtc_v2f7, path = "v2f7.rs")] +#[cfg_attr(rtc_v2h7, path = "v2h7.rs")] +#[cfg_attr(rtc_v2l0, path = "v2l0.rs")] +#[cfg_attr(rtc_v2l1, path = "v2l1.rs")] +#[cfg_attr(rtc_v2l4, path = "v2l4.rs")] +#[cfg_attr(rtc_v2wb, path = "v2wb.rs")] +mod family; + +pub use family::*; + +impl<'d, T: Instance> super::Rtc<'d, T> { + /// Applies the RTC config + /// It this changes the RTC clock source the time will be reset + pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) { + // Unlock the backup domain + unsafe { + unlock_backup_domain(rtc_config.clock_config as u8); + } + + self.write(true, |rtc| unsafe { + rtc.cr().modify(|w| { + #[cfg(rtc_v2f2)] + w.set_fmt(false); + #[cfg(not(rtc_v2f2))] + w.set_fmt(stm32_metapac::rtc::vals::Fmt::TWENTY_FOUR_HOUR); + w.set_osel(Osel::DISABLED); + w.set_pol(Pol::HIGH); + }); + + rtc.prer().modify(|w| { + w.set_prediv_s(rtc_config.sync_prescaler); + w.set_prediv_a(rtc_config.async_prescaler); + }); + }); + + self.rtc_config = rtc_config; + } + + /// Calibrate the clock drift. + /// + /// `clock_drift` can be adjusted from -487.1 ppm to 488.5 ppm and is clamped to this range. + /// + /// ### Note + /// + /// To perform a calibration when `async_prescaler` is less then 3, `sync_prescaler` + /// has to be reduced accordingly (see RM0351 Rev 9, sec 38.3.12). + #[cfg(not(rtc_v2f2))] + pub fn calibrate(&mut self, mut clock_drift: f32, period: super::RtcCalibrationCyclePeriod) { + const RTC_CALR_MIN_PPM: f32 = -487.1; + const RTC_CALR_MAX_PPM: f32 = 488.5; + const RTC_CALR_RESOLUTION_PPM: f32 = 0.9537; + + if clock_drift < RTC_CALR_MIN_PPM { + clock_drift = RTC_CALR_MIN_PPM; + } else if clock_drift > RTC_CALR_MAX_PPM { + clock_drift = RTC_CALR_MAX_PPM; + } + + clock_drift = clock_drift / RTC_CALR_RESOLUTION_PPM; + + self.write(false, |rtc| { + unsafe { + rtc.calr().write(|w| { + match period { + super::RtcCalibrationCyclePeriod::Seconds8 => { + w.set_calw8(stm32_metapac::rtc::vals::Calw8::EIGHT_SECOND); + } + super::RtcCalibrationCyclePeriod::Seconds16 => { + w.set_calw16(stm32_metapac::rtc::vals::Calw16::SIXTEEN_SECOND); + } + super::RtcCalibrationCyclePeriod::Seconds32 => { + // Set neither `calw8` nor `calw16` to use 32 seconds + } + } + + // Extra pulses during calibration cycle period: CALP * 512 - CALM + // + // CALP sets whether pulses are added or omitted. + // + // CALM contains how many pulses (out of 512) are masked in a + // given calibration cycle period. + if clock_drift > 0.0 { + // Maximum (about 512.2) rounds to 512. + clock_drift += 0.5; + + // When the offset is positive (0 to 512), the opposite of + // the offset (512 - offset) is masked, i.e. for the + // maximum offset (512), 0 pulses are masked. + w.set_calp(stm32_metapac::rtc::vals::Calp::INCREASEFREQ); + w.set_calm(512 - clock_drift as u16); + } else { + // Minimum (about -510.7) rounds to -511. + clock_drift -= 0.5; + + // When the offset is negative or zero (-511 to 0), + // the absolute offset is masked, i.e. for the minimum + // offset (-511), 511 pulses are masked. + w.set_calp(stm32_metapac::rtc::vals::Calp::NOCHANGE); + w.set_calm((clock_drift * -1.0) as u16); + } + }); + } + }) + } + + pub(super) fn write(&mut self, init_mode: bool, f: F) -> R + where + F: FnOnce(&crate::pac::rtc::Rtc) -> R, + { + let r = T::regs(); + // Disable write protection. + // This is safe, as we're only writin the correct and expected values. + unsafe { + r.wpr().write(|w| w.set_key(0xca)); + r.wpr().write(|w| w.set_key(0x53)); + + // true if initf bit indicates RTC peripheral is in init mode + if init_mode && !r.isr().read().initf() { + // to update calendar date/time, time format, and prescaler configuration, RTC must be in init mode + r.isr().modify(|w| w.set_init(Init::INITMODE)); + // wait till init state entered + // ~2 RTCCLK cycles + while !r.isr().read().initf() {} + } + } + + let result = f(&r); + + unsafe { + if init_mode { + r.isr().modify(|w| w.set_init(Init::FREERUNNINGMODE)); // Exits init mode + } + + // Re-enable write protection. + // This is safe, as the field accepts the full range of 8-bit values. + r.wpr().write(|w| w.set_key(0xff)); + } + result + } +} + +/// Read content of the backup register. +/// +/// The registers retain their values during wakes from standby mode or system resets. They also +/// retain their value when Vdd is switched off as long as V_BAT is powered. +pub fn read_backup_register(rtc: &Rtc, register: usize) -> Option { + if register < BACKUP_REGISTER_COUNT { + Some(unsafe { rtc.bkpr(register).read().bkp() }) + } else { + None + } +} + +/// Set content of the backup register. +/// +/// The registers retain their values during wakes from standby mode or system resets. They also +/// retain their value when Vdd is switched off as long as V_BAT is powered. +pub fn write_backup_register(rtc: &Rtc, register: usize, value: u32) { + if register < BACKUP_REGISTER_COUNT { + unsafe { rtc.bkpr(register).write(|w| w.set_bkp(value)) } + } +} diff --git a/embassy-stm32/src/rtc/v2/v2f0.rs b/embassy-stm32/src/rtc/v2/v2f0.rs new file mode 100644 index 00000000..d6871d91 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2f0.rs @@ -0,0 +1,41 @@ +use stm32_metapac::rcc::vals::Rtcsel; + +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(Rtcsel(clock_config)); + w.set_rtcen(true); + + // Restore bcdr + w.set_lscosel(reg.lscosel()); + w.set_lscoen(reg.lscoen()); + + w.set_lseon(reg.lseon()); + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // enable peripheral clock for communication + crate::pac::RCC.apb1enr1().modify(|w| w.set_rtcapben(true)); + + // read to allow the pwr clock to enable + crate::pac::PWR.cr1().read(); +} diff --git a/embassy-stm32/src/rtc/v2/v2f2.rs b/embassy-stm32/src/rtc/v2/v2f2.rs new file mode 100644 index 00000000..e041f3f4 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2f2.rs @@ -0,0 +1,31 @@ +use stm32_metapac::rcc::vals::Rtcsel; + +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(Rtcsel(clock_config)); + w.set_rtcen(true); + + w.set_lseon(reg.lseon()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // Nothing to do +} diff --git a/embassy-stm32/src/rtc/v2/v2f3.rs b/embassy-stm32/src/rtc/v2/v2f3.rs new file mode 100644 index 00000000..e041f3f4 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2f3.rs @@ -0,0 +1,31 @@ +use stm32_metapac::rcc::vals::Rtcsel; + +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(Rtcsel(clock_config)); + w.set_rtcen(true); + + w.set_lseon(reg.lseon()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // Nothing to do +} diff --git a/embassy-stm32/src/rtc/v2/v2f4.rs b/embassy-stm32/src/rtc/v2/v2f4.rs new file mode 100644 index 00000000..4dd21cae --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2f4.rs @@ -0,0 +1,31 @@ +use stm32_metapac::rcc::vals::Rtcsel; + +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(Rtcsel(clock_config)); + w.set_rtcen(true); + + w.set_lseon(reg.lseon()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // Nothing to do +} diff --git a/embassy-stm32/src/rtc/v2/v2f7.rs b/embassy-stm32/src/rtc/v2/v2f7.rs new file mode 100644 index 00000000..d6871d91 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2f7.rs @@ -0,0 +1,41 @@ +use stm32_metapac::rcc::vals::Rtcsel; + +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(Rtcsel(clock_config)); + w.set_rtcen(true); + + // Restore bcdr + w.set_lscosel(reg.lscosel()); + w.set_lscoen(reg.lscoen()); + + w.set_lseon(reg.lseon()); + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // enable peripheral clock for communication + crate::pac::RCC.apb1enr1().modify(|w| w.set_rtcapben(true)); + + // read to allow the pwr clock to enable + crate::pac::PWR.cr1().read(); +} diff --git a/embassy-stm32/src/rtc/v2/v2h7.rs b/embassy-stm32/src/rtc/v2/v2h7.rs new file mode 100644 index 00000000..f3b18068 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2h7.rs @@ -0,0 +1,33 @@ +use stm32_metapac::rcc::vals::Rtcsel; + +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(Rtcsel(clock_config)); + w.set_rtcen(true); + + w.set_lseon(reg.lseon()); + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // Nothing to do +} diff --git a/embassy-stm32/src/rtc/v2/v2l0.rs b/embassy-stm32/src/rtc/v2/v2l0.rs new file mode 100644 index 00000000..8d800588 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2l0.rs @@ -0,0 +1,40 @@ +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(_clock_config: u8) { + // FIXME: + // crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + // while !crate::pac::PWR.cr1().read().dbp() {} + + // let reg = crate::pac::RCC.bdcr().read(); + // assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + // if !reg.rtcen() || reg.rtcsel().0 != clock_config { + // crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + // crate::pac::RCC.bdcr().modify(|w| { + // // Reset + // w.set_bdrst(false); + + // // Select RTC source + // w.set_rtcsel(Rtcsel(clock_config)); + // w.set_rtcen(true); + + // // Restore bcdr + // w.set_lscosel(reg.lscosel()); + // w.set_lscoen(reg.lscoen()); + + // w.set_lseon(reg.lseon()); + // w.set_lsedrv(reg.lsedrv()); + // w.set_lsebyp(reg.lsebyp()); + // }); + // } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // // enable peripheral clock for communication + // crate::pac::rcc.apb1enr1().modify(|w| w.set_rtcapben(true)); + + // // read to allow the pwr clock to enable + // crate::pac::PWR.cr1().read(); +} diff --git a/embassy-stm32/src/rtc/v2/v2l1.rs b/embassy-stm32/src/rtc/v2/v2l1.rs new file mode 100644 index 00000000..8d800588 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2l1.rs @@ -0,0 +1,40 @@ +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(_clock_config: u8) { + // FIXME: + // crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + // while !crate::pac::PWR.cr1().read().dbp() {} + + // let reg = crate::pac::RCC.bdcr().read(); + // assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + // if !reg.rtcen() || reg.rtcsel().0 != clock_config { + // crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + // crate::pac::RCC.bdcr().modify(|w| { + // // Reset + // w.set_bdrst(false); + + // // Select RTC source + // w.set_rtcsel(Rtcsel(clock_config)); + // w.set_rtcen(true); + + // // Restore bcdr + // w.set_lscosel(reg.lscosel()); + // w.set_lscoen(reg.lscoen()); + + // w.set_lseon(reg.lseon()); + // w.set_lsedrv(reg.lsedrv()); + // w.set_lsebyp(reg.lsebyp()); + // }); + // } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // // enable peripheral clock for communication + // crate::pac::rcc.apb1enr1().modify(|w| w.set_rtcapben(true)); + + // // read to allow the pwr clock to enable + // crate::pac::PWR.cr1().read(); +} diff --git a/embassy-stm32/src/rtc/v2/v2l4.rs b/embassy-stm32/src/rtc/v2/v2l4.rs new file mode 100644 index 00000000..d6871d91 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2l4.rs @@ -0,0 +1,41 @@ +use stm32_metapac::rcc::vals::Rtcsel; + +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(Rtcsel(clock_config)); + w.set_rtcen(true); + + // Restore bcdr + w.set_lscosel(reg.lscosel()); + w.set_lscoen(reg.lscoen()); + + w.set_lseon(reg.lseon()); + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // enable peripheral clock for communication + crate::pac::RCC.apb1enr1().modify(|w| w.set_rtcapben(true)); + + // read to allow the pwr clock to enable + crate::pac::PWR.cr1().read(); +} diff --git a/embassy-stm32/src/rtc/v2/v2wb.rs b/embassy-stm32/src/rtc/v2/v2wb.rs new file mode 100644 index 00000000..98761fa6 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2wb.rs @@ -0,0 +1,39 @@ +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + if !reg.rtcen() || reg.rtcsel() != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(clock_config); + w.set_rtcen(true); + + // Restore bcdr + w.set_lscosel(reg.lscosel()); + w.set_lscoen(reg.lscoen()); + + w.set_lseon(reg.lseon()); + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // enable peripheral clock for communication + crate::pac::RCC.apb1enr1().modify(|w| w.set_rtcapben(true)); + + // read to allow the pwr clock to enable + crate::pac::PWR.cr1().read(); +} diff --git a/embassy-stm32/src/rtc/v3.rs b/embassy-stm32/src/rtc/v3.rs new file mode 100644 index 00000000..7255e97e --- /dev/null +++ b/embassy-stm32/src/rtc/v3.rs @@ -0,0 +1,212 @@ +use stm32_metapac::rtc::vals::{Calp, Calw16, Calw8, Fmt, Init, Key, Osel, Pol, TampalrmPu, TampalrmType}; + +use super::{Instance, RtcCalibrationCyclePeriod, RtcConfig}; +use crate::pac::rtc::Rtc; + +impl<'d, T: Instance> super::Rtc<'d, T> { + /// Applies the RTC config + /// It this changes the RTC clock source the time will be reset + pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) { + // Unlock the backup domain + unsafe { + #[cfg(feature = "stm32g0c1ve")] + { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + } + + #[cfg(not(feature = "stm32g0c1ve"))] + { + crate::pac::PWR + .cr1() + .modify(|w| w.set_dbp(stm32_metapac::pwr::vals::Dbp::ENABLED)); + while crate::pac::PWR.cr1().read().dbp() != stm32_metapac::pwr::vals::Dbp::DISABLED {} + } + + let reg = crate::pac::RCC.bdcr().read(); + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + let config_rtcsel = rtc_config.clock_config as u8; + #[cfg(rtc_v3)] + #[cfg(not(any(feature = "stm32wl54jc-cm0p", feature = "stm32wle5ub", feature = "stm32g0c1ve")))] + let config_rtcsel = stm32_metapac::rtc::vals::Rtcsel(config_rtcsel); + #[cfg(feature = "stm32g0c1ve")] + let config_rtcsel = stm32_metapac::rcc::vals::Rtcsel(config_rtcsel); + + if !reg.rtcen() || reg.rtcsel() != config_rtcsel { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(config_rtcsel); + + w.set_rtcen(true); + + // Restore bcdr + w.set_lscosel(reg.lscosel()); + w.set_lscoen(reg.lscoen()); + + w.set_lseon(reg.lseon()); + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } + } + + self.write(true, |rtc| { + unsafe { + rtc.cr().modify(|w| { + w.set_fmt(Fmt::TWENTYFOURHOUR); + w.set_osel(Osel::DISABLED); + w.set_pol(Pol::HIGH); + }); + + rtc.prer().modify(|w| { + w.set_prediv_s(rtc_config.sync_prescaler); + w.set_prediv_a(rtc_config.async_prescaler); + }); + + // TODO: configuration for output pins + rtc.cr().modify(|w| { + w.set_out2en(false); + w.set_tampalrm_type(TampalrmType::PUSHPULL); + w.set_tampalrm_pu(TampalrmPu::NOPULLUP); + }); + } + }); + + self.rtc_config = rtc_config; + } + + const RTC_CALR_MIN_PPM: f32 = -487.1; + const RTC_CALR_MAX_PPM: f32 = 488.5; + const RTC_CALR_RESOLUTION_PPM: f32 = 0.9537; + + /// Calibrate the clock drift. + /// + /// `clock_drift` can be adjusted from -487.1 ppm to 488.5 ppm and is clamped to this range. + /// + /// ### Note + /// + /// To perform a calibration when `async_prescaler` is less then 3, `sync_prescaler` + /// has to be reduced accordingly (see RM0351 Rev 9, sec 38.3.12). + pub fn calibrate(&mut self, mut clock_drift: f32, period: RtcCalibrationCyclePeriod) { + if clock_drift < Self::RTC_CALR_MIN_PPM { + clock_drift = Self::RTC_CALR_MIN_PPM; + } else if clock_drift > Self::RTC_CALR_MAX_PPM { + clock_drift = Self::RTC_CALR_MAX_PPM; + } + + clock_drift = clock_drift / Self::RTC_CALR_RESOLUTION_PPM; + + self.write(false, |rtc| { + unsafe { + rtc.calr().write(|w| { + match period { + RtcCalibrationCyclePeriod::Seconds8 => { + w.set_calw8(Calw8::EIGHTSECONDS); + } + RtcCalibrationCyclePeriod::Seconds16 => { + w.set_calw16(Calw16::SIXTEENSECONDS); + } + RtcCalibrationCyclePeriod::Seconds32 => { + // Set neither `calw8` nor `calw16` to use 32 seconds + } + } + + // Extra pulses during calibration cycle period: CALP * 512 - CALM + // + // CALP sets whether pulses are added or omitted. + // + // CALM contains how many pulses (out of 512) are masked in a + // given calibration cycle period. + if clock_drift > 0.0 { + // Maximum (about 512.2) rounds to 512. + clock_drift += 0.5; + + // When the offset is positive (0 to 512), the opposite of + // the offset (512 - offset) is masked, i.e. for the + // maximum offset (512), 0 pulses are masked. + w.set_calp(Calp::INCREASEFREQ); + w.set_calm(512 - clock_drift as u16); + } else { + // Minimum (about -510.7) rounds to -511. + clock_drift -= 0.5; + + // When the offset is negative or zero (-511 to 0), + // the absolute offset is masked, i.e. for the minimum + // offset (-511), 511 pulses are masked. + w.set_calp(Calp::NOCHANGE); + w.set_calm((clock_drift * -1.0) as u16); + } + }); + } + }) + } + + pub(super) fn write(&mut self, init_mode: bool, f: F) -> R + where + F: FnOnce(&crate::pac::rtc::Rtc) -> R, + { + let r = T::regs(); + // Disable write protection. + // This is safe, as we're only writin the correct and expected values. + unsafe { + r.wpr().write(|w| w.set_key(Key::DEACTIVATE1)); + r.wpr().write(|w| w.set_key(Key::DEACTIVATE2)); + + if init_mode && !r.icsr().read().initf() { + r.icsr().modify(|w| w.set_init(Init::INITMODE)); + // wait till init state entered + // ~2 RTCCLK cycles + while !r.icsr().read().initf() {} + } + } + + let result = f(&r); + + unsafe { + if init_mode { + r.icsr().modify(|w| w.set_init(Init::FREERUNNINGMODE)); // Exits init mode + } + + // Re-enable write protection. + // This is safe, as the field accepts the full range of 8-bit values. + r.wpr().write(|w| w.set_key(Key::ACTIVATE)); + } + result + } +} + +pub(super) unsafe fn enable_peripheral_clk() { + // Nothing to do +} + +pub const BACKUP_REGISTER_COUNT: usize = 32; + +/// Read content of the backup register. +/// +/// The registers retain their values during wakes from standby mode or system resets. They also +/// retain their value when Vdd is switched off as long as V_BAT is powered. +pub fn read_backup_register(_rtc: &Rtc, register: usize) -> Option { + if register < BACKUP_REGISTER_COUNT { + //Some(rtc.bkpr()[register].read().bits()) + None // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC + } else { + None + } +} + +/// Set content of the backup register. +/// +/// The registers retain their values during wakes from standby mode or system resets. They also +/// retain their value when Vdd is switched off as long as V_BAT is powered. +pub fn write_backup_register(_rtc: &Rtc, register: usize, _value: u32) { + if register < BACKUP_REGISTER_COUNT { + // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC + //unsafe { self.rtc.bkpr()[register].write(|w| w.bits(value)) } + } +}