diff --git a/.vscode/settings.json b/.vscode/settings.json index 700804dc..9ef7fe1c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,10 +16,11 @@ // "embassy-executor/Cargo.toml", // "embassy-sync/Cargo.toml", "examples/nrf52840/Cargo.toml", - //"examples/nrf5340/Cargo.toml", + // "examples/nrf5340/Cargo.toml", // "examples/nrf-rtos-trace/Cargo.toml", // "examples/rp/Cargo.toml", // "examples/std/Cargo.toml", + // "examples/stm32c0/Cargo.toml", // "examples/stm32f0/Cargo.toml", // "examples/stm32f1/Cargo.toml", // "examples/stm32f2/Cargo.toml", @@ -28,6 +29,7 @@ // "examples/stm32f7/Cargo.toml", // "examples/stm32g0/Cargo.toml", // "examples/stm32g4/Cargo.toml", + // "examples/stm32h5/Cargo.toml", // "examples/stm32h7/Cargo.toml", // "examples/stm32l0/Cargo.toml", // "examples/stm32l1/Cargo.toml", @@ -35,9 +37,7 @@ // "examples/stm32l5/Cargo.toml", // "examples/stm32u5/Cargo.toml", // "examples/stm32wb/Cargo.toml", - // "examples/stm32wb55/Cargo.toml", // "examples/stm32wl/Cargo.toml", - // "examples/stm32wl55/Cargo.toml", // "examples/wasm/Cargo.toml", ], } \ No newline at end of file diff --git a/embassy-hal-common/src/macros.rs b/embassy-hal-common/src/macros.rs index 5e62e048..f06b4600 100644 --- a/embassy-hal-common/src/macros.rs +++ b/embassy-hal-common/src/macros.rs @@ -1,5 +1,5 @@ #[macro_export] -macro_rules! peripherals { +macro_rules! peripherals_definition { ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { /// Types for the peripheral singletons. pub mod peripherals { @@ -26,7 +26,12 @@ macro_rules! peripherals { $crate::impl_peripheral!($name); )* } + }; +} +#[macro_export] +macro_rules! peripherals_struct { + ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { /// Struct containing all the peripheral singletons. /// /// To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`]. @@ -76,6 +81,24 @@ macro_rules! peripherals { }; } +#[macro_export] +macro_rules! peripherals { + ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { + $crate::peripherals_definition!( + $( + $(#[$cfg])? + $name, + )* + ); + $crate::peripherals_struct!( + $( + $(#[$cfg])? + $name, + )* + ); + }; +} + #[macro_export] macro_rules! into_ref { ($($name:ident),*) => { diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 18b1d4d0..f27cf6a5 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.4.0", features = ["async"], optional = true } +chrono = { version = "^0.4", default-features = false, optional = true} [dev-dependencies] critical-section = { version = "1.1", features = ["std"] } diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 73bd29fc..c7d12e13 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -81,11 +81,74 @@ fn main() { singletons.push(c.name.to_string()); } + // ======== + // Handle time-driver-XXXX features. + + let time_driver = match env::vars() + .map(|(a, _)| a) + .filter(|x| x.starts_with("CARGO_FEATURE_TIME_DRIVER_")) + .get_one() + { + Ok(x) => Some( + x.strip_prefix("CARGO_FEATURE_TIME_DRIVER_") + .unwrap() + .to_ascii_lowercase(), + ), + Err(GetOneError::None) => None, + Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"), + }; + + let time_driver_singleton = match time_driver.as_ref().map(|x| x.as_ref()) { + None => "", + Some("tim2") => "TIM2", + Some("tim3") => "TIM3", + Some("tim4") => "TIM4", + Some("tim5") => "TIM5", + Some("tim12") => "TIM12", + Some("tim15") => "TIM15", + Some("any") => { + if singletons.contains(&"TIM2".to_string()) { + "TIM2" + } else if singletons.contains(&"TIM3".to_string()) { + "TIM3" + } else if singletons.contains(&"TIM4".to_string()) { + "TIM4" + } else if singletons.contains(&"TIM5".to_string()) { + "TIM5" + } else if singletons.contains(&"TIM12".to_string()) { + "TIM12" + } else if singletons.contains(&"TIM15".to_string()) { + "TIM15" + } else { + panic!("time-driver-any requested, but the chip doesn't have TIM2, TIM3, TIM4, TIM5, TIM12 or TIM15.") + } + } + _ => panic!("unknown time_driver {:?}", time_driver), + }; + + if time_driver_singleton != "" { + println!("cargo:rustc-cfg=time_driver_{}", time_driver_singleton.to_lowercase()); + } + + // ======== + // Write singletons + let mut g = TokenStream::new(); let singleton_tokens: Vec<_> = singletons.iter().map(|s| format_ident!("{}", s)).collect(); + g.extend(quote! { - embassy_hal_common::peripherals!(#(#singleton_tokens),*); + embassy_hal_common::peripherals_definition!(#(#singleton_tokens),*); + }); + + let singleton_tokens: Vec<_> = singletons + .iter() + .filter(|s| *s != &time_driver_singleton.to_string()) + .map(|s| format_ident!("{}", s)) + .collect(); + + g.extend(quote! { + embassy_hal_common::peripherals_struct!(#(#singleton_tokens),*); }); // ======== @@ -838,51 +901,6 @@ fn main() { println!("cargo:rustc-cfg={}x", &chip_name[..8]); // stm32f42x println!("cargo:rustc-cfg={}x{}", &chip_name[..7], &chip_name[8..9]); // stm32f4x9 - // ======== - // Handle time-driver-XXXX features. - - let time_driver = match env::vars() - .map(|(a, _)| a) - .filter(|x| x.starts_with("CARGO_FEATURE_TIME_DRIVER_")) - .get_one() - { - Ok(x) => Some( - x.strip_prefix("CARGO_FEATURE_TIME_DRIVER_") - .unwrap() - .to_ascii_lowercase(), - ), - Err(GetOneError::None) => None, - Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"), - }; - - match time_driver.as_ref().map(|x| x.as_ref()) { - None => {} - Some("tim2") => println!("cargo:rustc-cfg=time_driver_tim2"), - Some("tim3") => println!("cargo:rustc-cfg=time_driver_tim3"), - Some("tim4") => println!("cargo:rustc-cfg=time_driver_tim4"), - Some("tim5") => println!("cargo:rustc-cfg=time_driver_tim5"), - Some("tim12") => println!("cargo:rustc-cfg=time_driver_tim12"), - Some("tim15") => println!("cargo:rustc-cfg=time_driver_tim15"), - Some("any") => { - if singletons.contains(&"TIM2".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim2"); - } else if singletons.contains(&"TIM3".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim3"); - } else if singletons.contains(&"TIM4".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim4"); - } else if singletons.contains(&"TIM5".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim5"); - } else if singletons.contains(&"TIM12".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim12"); - } else if singletons.contains(&"TIM15".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim15"); - } else { - panic!("time-driver-any requested, but the chip doesn't have TIM2, TIM3, TIM4, TIM5, TIM12 or TIM15.") - } - } - _ => panic!("unknown time_driver {:?}", time_driver), - } - // Handle time-driver-XXXX features. if env::var("CARGO_FEATURE_TIME_DRIVER_ANY").is_ok() {} println!("cargo:rustc-cfg={}", &chip_name[..chip_name.len() - 2]); diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index d4d7155b..0dbc9e5c 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -49,6 +49,8 @@ pub mod pwm; pub mod qspi; #[cfg(rng)] pub mod rng; +#[cfg(all(rtc, not(any(rtc_v1, rtc_v2f0, rtc_v2f7, rtc_v3, rtc_v3u5))))] +pub mod rtc; #[cfg(sdmmc)] pub mod sdmmc; #[cfg(spi)] diff --git a/embassy-stm32/src/rtc/datetime.rs b/embassy-stm32/src/rtc/datetime.rs new file mode 100644 index 00000000..6274c1e0 --- /dev/null +++ b/embassy-stm32/src/rtc/datetime.rs @@ -0,0 +1,203 @@ +#[cfg(feature = "chrono")] +use core::convert::From; + +#[cfg(feature = "chrono")] +use chrono::{self, Datelike, NaiveDate, Timelike, Weekday}; + +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, +} + +#[cfg(feature = "chrono")] +impl From for DateTime { + fn from(date_time: chrono::NaiveDateTime) -> Self { + Self { + year: (date_time.year() - 1970) as u16, + month: date_time.month() as u8, + day: date_time.day() as u8, + day_of_week: date_time.weekday().into(), + hour: date_time.hour() as u8, + minute: date_time.minute() as u8, + second: date_time.second() as u8, + } + } +} + +#[cfg(feature = "chrono")] +impl From for chrono::NaiveDateTime { + fn from(date_time: DateTime) -> Self { + NaiveDate::from_ymd_opt( + (date_time.year + 1970) as i32, + date_time.month as u32, + date_time.day as u32, + ) + .unwrap() + .and_hms_opt(date_time.hour as u32, date_time.minute as u32, date_time.second as u32) + .unwrap() + } +} + +/// 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, +} + +#[cfg(feature = "chrono")] +impl From for DayOfWeek { + fn from(weekday: Weekday) -> Self { + day_of_week_from_u8(weekday.number_from_monday() as u8).unwrap() + } +} + +#[cfg(feature = "chrono")] +impl From for chrono::Weekday { + fn from(weekday: DayOfWeek) -> Self { + match weekday { + DayOfWeek::Monday => Weekday::Mon, + DayOfWeek::Tuesday => Weekday::Tue, + DayOfWeek::Wednesday => Weekday::Wed, + DayOfWeek::Thursday => Weekday::Thu, + DayOfWeek::Friday => Weekday::Fri, + DayOfWeek::Saturday => Weekday::Sat, + DayOfWeek::Sunday => Weekday::Sun, + } + } +} + +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/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/mod.rs b/embassy-stm32/src/rtc/mod.rs new file mode 100644 index 00000000..170783b2 --- /dev/null +++ b/embassy-stm32/src/rtc/mod.rs @@ -0,0 +1,235 @@ +//! RTC peripheral abstraction +use core::marker::PhantomData; +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..dbd3b088 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2l0.rs @@ -0,0 +1,26 @@ +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + // TODO: Missing from PAC? + // crate::pac::PWR.cr().modify(|w| w.set_dbp(true)); + // while !crate::pac::PWR.cr().read().dbp() {} + + let reg = crate::pac::RCC.csr().read(); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.csr().modify(|w| { + // Select RTC source + w.set_rtcsel(crate::pac::rcc::vals::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/v2l1.rs b/embassy-stm32/src/rtc/v2/v2l1.rs new file mode 100644 index 00000000..1ac78b31 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2l1.rs @@ -0,0 +1,24 @@ +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.csr().read(); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.csr().modify(|w| { + // Select RTC source + w.set_rtcsel(crate::pac::rcc::vals::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/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..6998c48c --- /dev/null +++ b/embassy-stm32/src/rtc/v3.rs @@ -0,0 +1,226 @@ +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(any( + feature = "stm32g0c1ve", + feature = "stm32g491re", + feature = "stm32u585zi", + feature = "stm32g473cc" + )))] + { + 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(not(any( + feature = "stm32wl54jc-cm0p", + feature = "stm32wle5ub", + feature = "stm32g0c1ve", + feature = "stm32wl55jc-cm4", + feature = "stm32wl55uc-cm4", + feature = "stm32g491re", + feature = "stm32g473cc", + feature = "stm32u585zi", + feature = "stm32wle5jb" + )))] + 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)) } + } +} diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index 1736769e..69dcab64 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers", "arch-cortex-m", "executor-thread", "executor-interrupt"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti", "embedded-sdmmc"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti", "embedded-sdmmc", "chrono"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "nightly"] } diff --git a/examples/stm32g4/src/bin/pwm.rs b/examples/stm32g4/src/bin/pwm.rs index 017e89e4..8f7842ed 100644 --- a/examples/stm32g4/src/bin/pwm.rs +++ b/examples/stm32g4/src/bin/pwm.rs @@ -15,8 +15,8 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let ch1 = PwmPin::new_ch1(p.PA5); - let mut pwm = SimplePwm::new(p.TIM2, Some(ch1), None, None, None, khz(10)); + let ch1 = PwmPin::new_ch1(p.PC0); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10)); let max = pwm.get_max_duty(); pwm.enable(Channel::Ch1);