From 82bed97a80b8d29d572f03d0821a115e287418a7 Mon Sep 17 00:00:00 2001 From: astapleton Date: Fri, 8 Sep 2023 16:41:09 -0700 Subject: [PATCH] rcc: Add MCO configuration --- src/rcc.rs | 57 +++++++++++++- src/rcc/mco.rs | 201 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 src/rcc/mco.rs diff --git a/src/rcc.rs b/src/rcc.rs index 107c6ea..edc3d0f 100644 --- a/src/rcc.rs +++ b/src/rcc.rs @@ -144,6 +144,7 @@ use crate::time::Hertz; use log::debug; mod core_clocks; +mod mco; mod pll; mod rec; mod reset_reason; @@ -153,6 +154,8 @@ pub use pll::{PllConfig, PllConfigStrategy}; pub use rec::{LowPowerMode, PeripheralREC, ResetEnable}; pub use reset_reason::ResetReason; +use mco::{MCO1Config, MCO2Config, MCO1, MCO2}; + /// Configuration of the core clocks pub struct Config { hse: Option, @@ -167,6 +170,8 @@ pub struct Config { rcc_pclk3: Option, #[cfg(feature = "rm0481")] rcc_pclk4: Option, + mco1: MCO1Config, + mco2: MCO2Config, pll1: PllConfig, pll2: PllConfig, #[cfg(feature = "rm0481")] @@ -196,6 +201,8 @@ impl RccExt for RCC { rcc_pclk3: None, #[cfg(feature = "rm0481")] rcc_pclk4: None, + mco1: MCO1Config::default(), + mco2: MCO2Config::default(), pll1: PllConfig::default(), pll2: PllConfig::default(), #[cfg(feature = "rm0481")] @@ -552,6 +559,13 @@ impl Rcc { // We do not reset RCC here. This routine must assert when // the previous state of the RCC peripheral is unacceptable. + // config modifications ---------------------------------------- + // (required for self-consistency and usability) + + // if needed for mco, set sys_ck / pll1_p / pll1_q / pll2_p + self.mco1_setup(); + self.mco2_setup(); + // sys_ck from PLL if needed, else HSE or HSI let (sys_ck, sys_use_pll1_p) = self.sys_ck_setup(); @@ -632,6 +646,30 @@ impl Rcc { (ppre3, ppre3_bits): (self, rcc_hclk, rcc_pclk3), } + // Calculate MCO dividers and real MCO frequencies + let mco1_in = match self.config.mco1.source { + // We set the required clock earlier, so can unwrap() here. + MCO1::Hsi => HSI, + MCO1::Lse => unimplemented!(), + MCO1::Hse => self.config.hse.unwrap(), + MCO1::Pll1Q => pll1_q_ck.unwrap().raw(), + MCO1::Hsi48 => HSI48, + }; + let (mco_1_pre, mco1_ck) = + self.config.mco1.calculate_prescaler(mco1_in); + + let mco2_in = match self.config.mco2.source { + // We set the required clock earlier, so can unwrap() here. + MCO2::Sysclk => sys_ck.raw(), + MCO2::Pll2P => pll2_p_ck.unwrap().raw(), + MCO2::Hse => self.config.hse.unwrap(), + MCO2::Pll1P => pll1_p_ck.unwrap().raw(), + MCO2::Csi => CSI, + MCO2::Lsi => LSI, + }; + let (mco_2_pre, mco2_ck) = + self.config.mco2.calculate_prescaler(mco2_in); + // Start switching clocks here! ---------------------------------------- // Flash setup @@ -645,6 +683,21 @@ impl Rcc { rcc.cr().modify(|_, w| w.hsi48on().on()); while rcc.cr().read().hsi48rdy().is_not_ready() {} + // Set the MCO outputs. + // + // It is highly recommended to configure these bits only after + // reset, before enabling the external oscillators and the PLLs. + rcc.cfgr1().modify(|_, w| { + w.mco1sel() + .variant(self.config.mco1.source) + .mco1pre() + .bits(mco_1_pre) + .mco2sel() + .variant(self.config.mco2.source) + .mco2pre() + .bits(mco_2_pre) + }); + // HSE let hse_ck = match self.config.hse { Some(hse) => { @@ -829,8 +882,8 @@ impl Rcc { hse_ck, lse_ck, audio_ck, - mco1_ck: None, - mco2_ck: None, + mco1_ck, + mco2_ck, pll1_p_ck, pll1_q_ck, pll1_r_ck, diff --git a/src/rcc/mco.rs b/src/rcc/mco.rs new file mode 100644 index 0000000..ba7545a --- /dev/null +++ b/src/rcc/mco.rs @@ -0,0 +1,201 @@ +//! Micro-Controller Out (MCO) pins + +use super::Rcc; +use crate::time::Hertz; + +pub use crate::stm32::rcc::cfgr1::MCO1SEL as MCO1; +pub use crate::stm32::rcc::cfgr1::MCO2SEL as MCO2; + +/// Clock settings for Micro-Controller Out 1 (MCO1) +pub struct MCO1Config { + pub(super) source: MCO1, + pub(super) frequency: Option, +} +impl Default for MCO1Config { + fn default() -> MCO1Config { + Self { + source: MCO1::Hsi, + frequency: None, + } + } +} + +/// Clock settings for Micro-Controller Out 2 (MCO2) +pub struct MCO2Config { + pub(super) source: MCO2, + frequency: Option, +} +impl Default for MCO2Config { + fn default() -> MCO2Config { + Self { + source: MCO2::Sysclk, + frequency: None, + } + } +} + +macro_rules! calculate_prescaler { + () => { + /// Calculates the prescaler and the resulting clock frequency + pub(super) fn calculate_prescaler( + &self, + in_ck: u32, + ) -> (u8, Option) { + // Running? + if let Some(freq) = self.frequency { + // Calculate prescaler + let prescaler = match (in_ck + freq - 1) / freq { + 0 => unreachable!(), + x @ 1..=15 => x, + _ => { + panic!("Clock is too fast to achieve {} Hz MCO!", freq) + } + }; + + (prescaler as u8, Some(Hertz::from_raw(in_ck / prescaler))) + } else { + // Disabled + (0, None) + } + } + }; +} +impl MCO1Config { + calculate_prescaler!(); +} +impl MCO2Config { + calculate_prescaler!(); +} + +impl Rcc { + /// Checks the MCO1 setup and sets further requirements in `config` if they + /// are currently set to `None` + /// + /// # Panics + /// + /// Panics if the MCO1 setup is invalid, or if it is inconsistent with the + /// rest of the `config` + pub(super) fn mco1_setup(&mut self) { + // HSI always runs + + // LSE unimplemented + + // HSE must be explicitly stated + if self.config.mco1.source == MCO1::Hse { + assert!( + self.config.hse.is_some(), + "HSE is required for MCO1. Explicitly state its frequency with `use_hse`" + ); + } + + // Set pll1_q_ck based on requirement + if self.config.mco1.source == MCO1::Pll1Q + && self.config.pll1.q_ck.is_none() + { + self.config.pll1.q_ck = self.config.mco1.frequency; + } + + // HSI48 always runs + } + + /// Checks the MCO2 setup and sets further requirements in `config` if they + /// are currently set to `None` + /// + /// # Panics + /// + /// Panics if the MCO2 setup is invalid, or if it is inconsistent with the + /// rest of the `config` + pub(super) fn mco2_setup(&mut self) { + // Set sysclk based on requirement + if self.config.mco2.source == MCO2::Sysclk + && self.config.sys_ck.is_none() + { + self.config.sys_ck = self.config.mco2.frequency; + } + + // Set pll2_p_ck based on requirement + if self.config.mco2.source == MCO2::Pll2P + && self.config.pll2.p_ck.is_none() + { + self.config.pll2.p_ck = self.config.mco2.frequency; + } + + // HSE must be explicitly stated + if self.config.mco2.source == MCO2::Hse { + assert!( + self.config.hse.is_some(), + "HSE is required for MCO2. Explicitly state its frequency with `use_hse`" + ); + } + + // Set pll1_p_ck based on requirement + if self.config.mco2.source == MCO2::Pll1P + && self.config.pll1.p_ck.is_none() + { + self.config.pll1.p_ck = self.config.mco2.frequency; + } + + // CSI always runs + + // LSI unimplemented + } +} + +macro_rules! mco1_setters { + ($($mco_setter:ident: $source:ident $doc:expr),+) => { + /// Setters for Micro-Controller Out 1 (MCO1) + impl Rcc { + $( + /// Set the MCO1 output frequency. The clock is sourced from + #[doc=$doc] + /// + /// This only enables the signal within the RCC block, it does + /// not enable the MCO1 output pin itself (use the GPIO for + /// that). + #[must_use] + pub fn $mco_setter(mut self, freq: Hertz) -> Self { + self.config.mco1.source = MCO1::$source; + self.config.mco1.frequency = Some(freq.raw()); + self + } + )+ + } + } +} +mco1_setters! { + mco1_from_hsi: Hsi "the HSI", + //mco1_from_lse: Lse "the LSE", UNIMPLEMENTED + mco1_from_hse: Hse "the HSE", + mco1_from_pll1_q_ck: Pll1Q "pll1_q_ck", + mco1_from_hsi48: Hsi48 "HSI48" +} + +macro_rules! mco2_setters { + ($($mco_setter:ident: $source:ident $doc:expr),+) => { + /// Setters for Micro-Controller Out 2 (MCO2) + impl Rcc { + $( + /// Set the MCO2 output frequency. The clock is sourced from + #[doc=$doc] + /// + /// This only enables the signal within the RCC block, it does + /// not enable the MCO2 output pin itself (use the GPIO for + /// that). + #[must_use] + pub fn $mco_setter(mut self, freq: Hertz) -> Self { + self.config.mco2.source = MCO2::$source; + self.config.mco2.frequency = Some(freq.raw()); + self + } + )+ + } + } +} +mco2_setters! { + mco2_from_sys_ck: Sysclk "sys_ck", + mco2_from_pll2_p_ck: Pll2P "pll2_p_ck", + mco2_from_hse: Hse "the HSE", + mco2_from_pll1_p_ck: Pll1P "pll1_p_ck", + mco2_from_csi: Csi "CSI", + mco2_from_lsi: Lsi "the LSI" +}