diff --git a/embassy-stm32/src/rcc/f2.rs b/embassy-stm32/src/rcc/f2.rs index bece046f..7e5992bb 100644 --- a/embassy-stm32/src/rcc/f2.rs +++ b/embassy-stm32/src/rcc/f2.rs @@ -1,7 +1,8 @@ -use core::ops::Div; +use core::convert::TryFrom; +use core::ops::{Div, Mul}; use crate::pac::flash::vals::Latency; -use crate::pac::rcc::vals::{Hpre, Ppre, Sw}; +use crate::pac::rcc::vals::{Hpre, Pllp, Pllsrc, Ppre, Sw}; use crate::pac::{FLASH, RCC}; use crate::rcc::{set_freqs, Clocks}; use crate::time::Hertz; @@ -9,11 +10,18 @@ use crate::time::Hertz; /// HSI speed pub const HSI: Hertz = Hertz(16_000_000); +#[derive(Clone, Copy)] +pub struct HSEConfig { + pub frequency: Hertz, + pub source: HSESrc, +} + /// System clock mux source #[derive(Clone, Copy)] pub enum ClockSrc { - HSE(Hertz, HSESrc), + HSE, HSI, + PLL, } /// HSE clock source @@ -25,6 +33,170 @@ pub enum HSESrc { Bypass, } +#[derive(Clone, Copy)] +pub struct PLLConfig { + pub pre_div: PLLPreDiv, + pub mul: PLLMul, + pub main_div: PLLMainDiv, + pub pll48_div: PLL48Div, +} + +impl Default for PLLConfig { + fn default() -> Self { + PLLConfig { + pre_div: PLLPreDiv(16), + mul: PLLMul(192), + main_div: PLLMainDiv::Div2, + pll48_div: PLL48Div(4), + } + } +} + +impl PLLConfig { + pub fn clocks(&self, src_freq: Hertz) -> PLLClocks { + let in_freq = src_freq / self.pre_div; + let vco_freq = src_freq * self.mul / self.pre_div; + let main_freq = vco_freq / self.main_div; + let pll48_freq = vco_freq / self.pll48_div; + PLLClocks { + in_freq, + vco_freq, + main_freq, + pll48_freq, + } + } +} + +/// Clock source for both main PLL and PLLI2S +#[derive(Clone, Copy, PartialEq)] +pub enum PLLSrc { + HSE, + HSI, +} + +impl Into for PLLSrc { + fn into(self) -> Pllsrc { + match self { + PLLSrc::HSE => Pllsrc::HSE, + PLLSrc::HSI => Pllsrc::HSI, + } + } +} + +/// Division factor for both main PLL and PLLI2S +#[derive(Clone, Copy, PartialEq)] +#[repr(transparent)] +pub struct PLLPreDiv(u8); + +impl TryFrom for PLLPreDiv { + type Error = &'static str; + + fn try_from(value: u8) -> Result { + match value { + 2..=63 => Ok(PLLPreDiv(value)), + _ => Err("PLLPreDiv must be within range 2..=63"), + } + } +} + +impl Div for Hertz { + type Output = Hertz; + + fn div(self, rhs: PLLPreDiv) -> Self::Output { + Hertz(self.0 / u32::from(rhs.0)) + } +} + +/// Multiplication factor for main PLL +#[derive(Clone, Copy, PartialEq)] +#[repr(transparent)] +pub struct PLLMul(u16); + +impl Mul for Hertz { + type Output = Hertz; + + fn mul(self, rhs: PLLMul) -> Self::Output { + Hertz(self.0 * u32::from(rhs.0)) + } +} + +impl TryFrom for PLLMul { + type Error = &'static str; + + fn try_from(value: u16) -> Result { + match value { + 192..=432 => Ok(PLLMul(value)), + _ => Err("PLLMul must be within range 192..=432"), + } + } +} + +/// PLL division factor for the main system clock +#[derive(Clone, Copy, PartialEq)] +pub enum PLLMainDiv { + Div2, + Div4, + Div6, + Div8, +} + +impl Into for PLLMainDiv { + fn into(self) -> Pllp { + match self { + PLLMainDiv::Div2 => Pllp::DIV2, + PLLMainDiv::Div4 => Pllp::DIV4, + PLLMainDiv::Div6 => Pllp::DIV8, + PLLMainDiv::Div8 => Pllp::DIV8, + } + } +} + +impl Div for Hertz { + type Output = Hertz; + + fn div(self, rhs: PLLMainDiv) -> Self::Output { + let divisor = match rhs { + PLLMainDiv::Div2 => 2, + PLLMainDiv::Div4 => 4, + PLLMainDiv::Div6 => 6, + PLLMainDiv::Div8 => 8, + }; + Hertz(self.0 / divisor) + } +} + +/// PLL division factor for USB OTG FS / SDIO / RNG +#[derive(Clone, Copy, PartialEq)] +#[repr(transparent)] +pub struct PLL48Div(u8); + +impl Div for Hertz { + type Output = Hertz; + + fn div(self, rhs: PLL48Div) -> Self::Output { + Hertz(self.0 / u32::from(rhs.0)) + } +} + +impl TryFrom for PLL48Div { + type Error = &'static str; + + fn try_from(value: u8) -> Result { + match value { + 2..=15 => Ok(PLL48Div(value)), + _ => Err("PLL48Div must be within range 2..=15"), + } + } +} + +#[derive(Clone, Copy, PartialEq)] +pub struct PLLClocks { + pub in_freq: Hertz, + pub vco_freq: Hertz, + pub main_freq: Hertz, + pub pll48_freq: Hertz, +} + /// AHB prescaler #[derive(Clone, Copy, PartialEq)] pub enum AHBPrescaler { @@ -206,6 +378,10 @@ impl VoltageRange { /// Clocks configuration pub struct Config { + pub hse: Option, + pub hsi: bool, + pub pll_mux: PLLSrc, + pub pll: PLLConfig, pub mux: ClockSrc, pub voltage: VoltageRange, pub ahb_pre: AHBPrescaler, @@ -217,6 +393,10 @@ impl Default for Config { #[inline] fn default() -> Config { Config { + hse: None, + hsi: true, + pll_mux: PLLSrc::HSI, + pll: PLLConfig::default(), voltage: VoltageRange::Min1V8, mux: ClockSrc::HSI, ahb_pre: AHBPrescaler::NotDivided, @@ -226,30 +406,65 @@ impl Default for Config { } } -#[inline] -unsafe fn enable_hse(source: HSESrc) { - RCC.cr().write(|w| { - w.set_hsebyp(match source { - HSESrc::Bypass => true, - HSESrc::Crystal => false, - }); - w.set_hseon(true) - }); - while !RCC.cr().read().hserdy() {} -} - pub(crate) unsafe fn init(config: Config) { + // Make sure HSI is enabled + RCC.cr().write(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + + if let Some(hse_config) = config.hse { + RCC.cr().modify(|w| { + w.set_hsebyp(match hse_config.source { + HSESrc::Bypass => true, + HSESrc::Crystal => false, + }); + w.set_hseon(true) + }); + while !RCC.cr().read().hserdy() {} + } + + let pll_src_freq = match config.pll_mux { + PLLSrc::HSE => { + let hse_config = config + .hse + .unwrap_or_else(|| panic!("HSE must be configured to be used as PLL input")); + hse_config.frequency + } + PLLSrc::HSI => HSI, + }; + + // Reference: STM32F215xx/217xx datasheet Table 33. Main PLL characteristics + let pll_clocks = config.pll.clocks(pll_src_freq); + assert!(Hertz(950_000) <= pll_clocks.in_freq && pll_clocks.in_freq <= Hertz(2_100_000)); + assert!(Hertz(192_000_000) <= pll_clocks.vco_freq && pll_clocks.vco_freq <= Hertz(432_000_000)); + assert!( + Hertz(24_000_000) <= pll_clocks.main_freq && pll_clocks.main_freq <= Hertz(120_000_000) + ); + // USB actually requires == 48 MHz, but other PLL48 peripherals are fine with <= 48MHz + assert!(pll_clocks.pll48_freq <= Hertz(48_000_000)); + + RCC.pllcfgr().write(|w| { + w.set_pllsrc(config.pll_mux.into()); + w.set_pllm(config.pll.pre_div.0); + w.set_plln(config.pll.mul.0); + w.set_pllp(config.pll.main_div.into()); + w.set_pllq(config.pll.pll48_div.0); + }); + let (sys_clk, sw) = match config.mux { ClockSrc::HSI => { - // Enable HSI - RCC.cr().write(|w| w.set_hsion(true)); - while !RCC.cr().read().hsirdy() {} - + assert!(config.hsi, "HSI must be enabled to be used as system clock"); (HSI, Sw::HSI) } - ClockSrc::HSE(freq, source) => { - enable_hse(source); - (freq, Sw::HSE) + ClockSrc::HSE => { + let hse_config = config + .hse + .unwrap_or_else(|| panic!("HSE must be configured to be used as PLL input")); + (hse_config.frequency, Sw::HSE) + } + ClockSrc::PLL => { + RCC.cr().modify(|w| w.set_pllon(true)); + while !RCC.cr().read().pllrdy() {} + (pll_clocks.main_freq, Sw::PLL) } }; // RM0033 Figure 9. Clock tree suggests max SYSCLK/HCLK is 168 MHz, but datasheet specifies PLL @@ -260,7 +475,7 @@ pub(crate) unsafe fn init(config: Config) { // Reference: STM32F215xx/217xx datasheet Table 13. General operating conditions assert!(ahb_freq <= Hertz(120_000_000)); - let flash_ws = config.voltage.wait_states(ahb_freq).expect("Invalid HCLK"); + let flash_ws = unwrap!(config.voltage.wait_states(ahb_freq)); FLASH.acr().modify(|w| w.set_latency(flash_ws)); RCC.cfgr().modify(|w| { @@ -269,6 +484,12 @@ pub(crate) unsafe fn init(config: Config) { w.set_ppre1(config.apb1_pre.into()); w.set_ppre2(config.apb2_pre.into()); }); + while RCC.cfgr().read().sws() != sw.0 {} + + // Turn off HSI to save power if we don't need it + if !config.hsi { + RCC.cr().modify(|w| w.set_hsion(false)); + } let (apb1_freq, apb1_tim_freq) = match config.apb1_pre { APBPrescaler::NotDivided => (ahb_freq, ahb_freq), @@ -299,5 +520,6 @@ pub(crate) unsafe fn init(config: Config) { apb1_tim: apb1_tim_freq, apb2: apb2_freq, apb2_tim: apb2_tim_freq, + pll48: Some(pll_clocks.pll48_freq), }); } diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index 9a95836a..d3710b8c 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -53,7 +53,7 @@ pub struct Clocks { #[cfg(any(rcc_h7, rcc_h7ab))] pub ahb4: Hertz, - #[cfg(any(rcc_f4, rcc_f410, rcc_f7))] + #[cfg(any(rcc_f2, rcc_f4, rcc_f410, rcc_f7))] pub pll48: Option, #[cfg(rcc_f1)] diff --git a/examples/stm32f2/src/bin/pll.rs b/examples/stm32f2/src/bin/pll.rs new file mode 100644 index 00000000..4bd74f0b --- /dev/null +++ b/examples/stm32f2/src/bin/pll.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::convert::TryFrom; +use defmt::*; +use embassy::executor::Spawner; +use embassy::time::{Duration, Timer}; +use embassy_stm32::{ + rcc::{ + APBPrescaler, ClockSrc, HSEConfig, HSESrc, PLL48Div, PLLConfig, PLLMainDiv, PLLMul, + PLLPreDiv, PLLSrc, + }, + time::Hertz, + Config, Peripherals, +}; + +use defmt_rtt as _; // global logger +use panic_probe as _; + +// Example config for maximum performance on a NUCLEO-F207ZG board +fn config() -> Config { + let mut config = Config::default(); + // By default, HSE on the board comes from a 8 MHz clock signal (not a crystal) + config.rcc.hse = Some(HSEConfig { + frequency: Hertz(8_000_000), + source: HSESrc::Bypass, + }); + // PLL uses HSE as the clock source + config.rcc.pll_mux = PLLSrc::HSE; + config.rcc.pll = PLLConfig { + // 8 MHz clock source / 8 = 1 MHz PLL input + pre_div: unwrap!(PLLPreDiv::try_from(8)), + // 1 MHz PLL input * 240 = 240 MHz PLL VCO + mul: unwrap!(PLLMul::try_from(240)), + // 240 MHz PLL VCO / 2 = 120 MHz main PLL output + main_div: PLLMainDiv::Div2, + // 240 MHz PLL VCO / 5 = 48 MHz PLL48 output + pll48_div: unwrap!(PLL48Div::try_from(5)), + }; + // System clock comes from PLL (= the 120 MHz main PLL output) + config.rcc.mux = ClockSrc::PLL; + // 120 MHz / 4 = 30 MHz APB1 frequency + config.rcc.apb1_pre = APBPrescaler::Div4; + // 120 MHz / 2 = 60 MHz APB2 frequency + config.rcc.apb2_pre = APBPrescaler::Div2; + config +} + +#[embassy::main(config = "config()")] +async fn main(_spawner: Spawner, _p: Peripherals) { + loop { + Timer::after(Duration::from_millis(1000)).await; + info!("1s elapsed"); + } +}