diff --git a/embassy-stm32/src/rcc/g4.rs b/embassy-stm32/src/rcc/g4.rs index 3e20bf6af..382ffbead 100644 --- a/embassy-stm32/src/rcc/g4.rs +++ b/embassy-stm32/src/rcc/g4.rs @@ -1,10 +1,11 @@ use stm32_metapac::flash::vals::Latency; -use stm32_metapac::rcc::vals::{Adcsel, Pllsrc, Sw}; +use stm32_metapac::rcc::vals::{Adcsel, Sw}; use stm32_metapac::FLASH; pub use crate::pac::rcc::vals::{ - Adcsel as AdcClockSource, Fdcansel as FdCanClockSource, Hpre as AHBPrescaler, Pllm as PllM, Plln as PllN, - Pllp as PllP, Pllq as PllQ, Pllr as PllR, Ppre as APBPrescaler, + Adcsel as AdcClockSource, Clk48sel as Clk48Src, Fdcansel as FdCanClockSource, Hpre as AHBPrescaler, + Pllm as PllPreDiv, Plln as PllMul, Pllp as PllPDiv, Pllq as PllQDiv, Pllr as PllRDiv, Pllsrc, Ppre as APBPrescaler, + Sw as Sysclk, }; use crate::pac::{PWR, RCC}; use crate::time::Hertz; @@ -12,28 +13,22 @@ use crate::time::Hertz; /// HSI speed pub const HSI_FREQ: Hertz = Hertz(16_000_000); -/// System clock mux source -#[derive(Clone, Copy)] -pub enum ClockSrc { - HSE(Hertz), - HSI, - PLL, +/// HSE Mode +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1) + Bypass, } -/// PLL clock input source -#[derive(Clone, Copy, Debug)] -pub enum PllSource { - HSI, - HSE(Hertz), -} - -impl Into for PllSource { - fn into(self) -> Pllsrc { - match self { - PllSource::HSE(..) => Pllsrc::HSE, - PllSource::HSI => Pllsrc::HSI, - } - } +/// HSE Configuration +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, } /// PLL Configuration @@ -43,69 +38,89 @@ impl Into for PllSource { /// frequency ranges for each of these settings. pub struct Pll { /// PLL Source clock selection. - pub source: PllSource, + pub source: Pllsrc, /// PLL pre-divider - pub prediv_m: PllM, + pub prediv: PllPreDiv, /// PLL multiplication factor for VCO - pub mul_n: PllN, + pub mul: PllMul, /// PLL division factor for P clock (ADC Clock) - pub div_p: Option, + pub divp: Option, /// PLL division factor for Q clock (USB, I2S23, SAI1, FDCAN, QSPI) - pub div_q: Option, + pub divq: Option, /// PLL division factor for R clock (SYSCLK) - pub div_r: Option, -} - -/// Sets the source for the 48MHz clock to the USB and RNG peripherals. -pub enum Clock48MhzSrc { - /// Use the High Speed Internal Oscillator. For USB usage, the CRS must be used to calibrate the - /// oscillator to comply with the USB specification for oscillator tolerance. - Hsi48(super::Hsi48Config), - /// Use the PLLQ output. The PLL must be configured to output a 48MHz clock. For USB usage the - /// PLL needs to be using the HSE source to comply with the USB specification for oscillator - /// tolerance. - PllQ, + pub divr: Option, } /// Clocks configutation +#[non_exhaustive] pub struct Config { - pub mux: ClockSrc, + /// HSI Enable + pub hsi: bool, + + /// HSE Configuration + pub hse: Option, + + /// System Clock Configuration + pub sys: Sysclk, + + /// HSI48 Configuration + pub hsi48: Option, + + /// PLL Configuration + pub pll: Option, + + /// Iff PLL is requested as the main clock source in the `mux` field then the PLL configuration + /// MUST turn on the PLLR output. pub ahb_pre: AHBPrescaler, pub apb1_pre: APBPrescaler, pub apb2_pre: APBPrescaler, + pub low_power_run: bool, - /// Iff PLL is requested as the main clock source in the `mux` field then the PLL configuration - /// MUST turn on the PLLR output. - pub pll: Option, + /// Sets the clock source for the 48MHz clock used by the USB and RNG peripherals. - pub clock_48mhz_src: Option, + pub clk48_src: Clk48Src, + + /// Low-Speed Clock Configuration + pub ls: super::LsConfig, + + /// Clock Source for ADCs 1 and 2 pub adc12_clock_source: AdcClockSource, + + /// Clock Source for ADCs 3, 4 and 5 pub adc345_clock_source: AdcClockSource, + + /// Clock Source for FDCAN pub fdcan_clock_source: FdCanClockSource, - pub ls: super::LsConfig, + /// Enable range1 boost mode + /// Recommended when the SYSCLK frequency is greater than 150MHz. + pub boost: bool, } impl Default for Config { #[inline] fn default() -> Config { Config { - mux: ClockSrc::HSI, + hsi: true, + hse: None, + sys: Sysclk::HSI, + hsi48: Some(Default::default()), + pll: None, ahb_pre: AHBPrescaler::DIV1, apb1_pre: APBPrescaler::DIV1, apb2_pre: APBPrescaler::DIV1, low_power_run: false, - pll: None, - clock_48mhz_src: Some(Clock48MhzSrc::Hsi48(Default::default())), + clk48_src: Clk48Src::HSI48, + ls: Default::default(), adc12_clock_source: Adcsel::DISABLE, adc345_clock_source: Adcsel::DISABLE, fdcan_clock_source: FdCanClockSource::PCLK1, - ls: Default::default(), + boost: false, } } } @@ -117,34 +132,65 @@ pub struct PllFreq { } pub(crate) unsafe fn init(config: Config) { + // Configure HSI + let hsi = match config.hsi { + false => { + RCC.cr().modify(|w| w.set_hsion(false)); + None + } + true => { + RCC.cr().modify(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + Some(HSI_FREQ) + } + }; + + // Configure HSE + let hse = match config.hse { + None => { + RCC.cr().modify(|w| w.set_hseon(false)); + None + } + Some(hse) => { + match hse.mode { + HseMode::Bypass => assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => assert!(max::HSE_OSC.contains(&hse.freq)), + } + + RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); + RCC.cr().modify(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + Some(hse.freq) + } + }; + + // Configure HSI48 if required + if let Some(hsi48_config) = config.hsi48 { + super::init_hsi48(hsi48_config); + } + let pll_freq = config.pll.map(|pll_config| { let src_freq = match pll_config.source { - PllSource::HSI => { - RCC.cr().write(|w| w.set_hsion(true)); - while !RCC.cr().read().hsirdy() {} - - HSI_FREQ - } - PllSource::HSE(freq) => { - RCC.cr().write(|w| w.set_hseon(true)); - while !RCC.cr().read().hserdy() {} - freq - } + Pllsrc::HSI => unwrap!(hsi), + Pllsrc::HSE => unwrap!(hse), + _ => unreachable!(), }; + // TODO: check PLL input, internal and output frequencies for validity + // Disable PLL before configuration RCC.cr().modify(|w| w.set_pllon(false)); while RCC.cr().read().pllrdy() {} - let internal_freq = src_freq / pll_config.prediv_m * pll_config.mul_n; + let internal_freq = src_freq / pll_config.prediv * pll_config.mul; RCC.pllcfgr().write(|w| { - w.set_plln(pll_config.mul_n); - w.set_pllm(pll_config.prediv_m); + w.set_plln(pll_config.mul); + w.set_pllm(pll_config.prediv); w.set_pllsrc(pll_config.source.into()); }); - let pll_p_freq = pll_config.div_p.map(|div_p| { + let pll_p_freq = pll_config.divp.map(|div_p| { RCC.pllcfgr().modify(|w| { w.set_pllp(div_p); w.set_pllpen(true); @@ -152,7 +198,7 @@ pub(crate) unsafe fn init(config: Config) { internal_freq / div_p }); - let pll_q_freq = pll_config.div_q.map(|div_q| { + let pll_q_freq = pll_config.divq.map(|div_q| { RCC.pllcfgr().modify(|w| { w.set_pllq(div_q); w.set_pllqen(true); @@ -160,7 +206,7 @@ pub(crate) unsafe fn init(config: Config) { internal_freq / div_q }); - let pll_r_freq = pll_config.div_r.map(|div_r| { + let pll_r_freq = pll_config.divr.map(|div_r| { RCC.pllcfgr().modify(|w| { w.set_pllr(div_r); w.set_pllren(true); @@ -179,22 +225,10 @@ pub(crate) unsafe fn init(config: Config) { } }); - let (sys_clk, sw) = match config.mux { - ClockSrc::HSI => { - // Enable HSI - RCC.cr().write(|w| w.set_hsion(true)); - while !RCC.cr().read().hsirdy() {} - - (HSI_FREQ, Sw::HSI) - } - ClockSrc::HSE(freq) => { - // Enable HSE - RCC.cr().write(|w| w.set_hseon(true)); - while !RCC.cr().read().hserdy() {} - - (freq, Sw::HSE) - } - ClockSrc::PLL => { + let (sys_clk, sw) = match config.sys { + Sysclk::HSI => (HSI_FREQ, Sw::HSI), + Sysclk::HSE => (unwrap!(hse), Sw::HSE), + Sysclk::PLL1_R => { assert!(pll_freq.is_some()); assert!(pll_freq.as_ref().unwrap().pll_r.is_some()); @@ -202,41 +236,51 @@ pub(crate) unsafe fn init(config: Config) { assert!(freq <= 170_000_000); - if freq >= 150_000_000 { - // Enable Core Boost mode on freq >= 150Mhz ([RM0440] p234) - PWR.cr5().modify(|w| w.set_r1mode(false)); - // Set flash wait state in boost mode based on frequency ([RM0440] p191) - if freq <= 36_000_000 { - FLASH.acr().modify(|w| w.set_latency(Latency::WS0)); - } else if freq <= 68_000_000 { - FLASH.acr().modify(|w| w.set_latency(Latency::WS1)); - } else if freq <= 102_000_000 { - FLASH.acr().modify(|w| w.set_latency(Latency::WS2)); - } else if freq <= 136_000_000 { - FLASH.acr().modify(|w| w.set_latency(Latency::WS3)); - } else { - FLASH.acr().modify(|w| w.set_latency(Latency::WS4)); - } - } else { - PWR.cr5().modify(|w| w.set_r1mode(true)); - // Set flash wait state in normal mode based on frequency ([RM0440] p191) - if freq <= 30_000_000 { - FLASH.acr().modify(|w| w.set_latency(Latency::WS0)); - } else if freq <= 60_000_000 { - FLASH.acr().modify(|w| w.set_latency(Latency::WS1)); - } else if freq <= 80_000_000 { - FLASH.acr().modify(|w| w.set_latency(Latency::WS2)); - } else if freq <= 120_000_000 { - FLASH.acr().modify(|w| w.set_latency(Latency::WS3)); - } else { - FLASH.acr().modify(|w| w.set_latency(Latency::WS4)); - } - } - (Hertz(freq), Sw::PLL1_R) } + _ => unreachable!(), }; + // Calculate the AHB frequency (HCLK), among other things so we can calculate the correct flash read latency. + let hclk = sys_clk / config.ahb_pre; + + // Configure Core Boost mode ([RM0440] p234 – inverted because setting r1mode to 0 enables boost mode!) + if config.boost { + // RM0440 p235 + // “The sequence to switch from Range1 normal mode to Range1 boost mode is: + // 1. The system clock must be divided by 2 using the AHB prescaler before switching to a higher system frequency. + RCC.cfgr().modify(|w| w.set_hpre(AHBPrescaler::DIV2)); + // 2. Clear the R1MODE bit in the PWR_CR5 register. (enables boost mode) + PWR.cr5().modify(|w| w.set_r1mode(false)); + + // Below: + // 3. Adjust wait states according to new freq target + // 4. Configure and switch to new frequency + } + + // Configure flash read access latency based on boost mode and frequency (RM0440 p98) + FLASH.acr().modify(|w| { + w.set_latency(match (config.boost, hclk.0) { + (true, ..=34_000_000) => Latency::WS0, + (true, ..=68_000_000) => Latency::WS1, + (true, ..=102_000_000) => Latency::WS2, + (true, ..=136_000_000) => Latency::WS3, + (true, _) => Latency::WS4, + + (false, ..=36_000_000) => Latency::WS0, + (false, ..=60_000_000) => Latency::WS1, + (false, ..=90_000_000) => Latency::WS2, + (false, ..=120_000_000) => Latency::WS3, + (false, _) => Latency::WS4, + }) + }); + + if config.boost { + // 5. Wait for at least 1us and then reconfigure the AHB prescaler to get the needed HCLK clock frequency. + cortex_m::asm::delay(16); + } + + // Now that boost mode and flash read access latency are configured, set up SYSCLK RCC.cfgr().modify(|w| { w.set_sw(sw); w.set_hpre(config.ahb_pre); @@ -244,42 +288,26 @@ pub(crate) unsafe fn init(config: Config) { w.set_ppre2(config.apb2_pre); }); - let ahb_freq = sys_clk / config.ahb_pre; - - let (apb1_freq, apb1_tim_freq) = match config.apb1_pre { - APBPrescaler::DIV1 => (ahb_freq, ahb_freq), - pre => { - let freq = ahb_freq / pre; - (freq, freq * 2u32) - } - }; - - let (apb2_freq, apb2_tim_freq) = match config.apb2_pre { - APBPrescaler::DIV1 => (ahb_freq, ahb_freq), - pre => { - let freq = ahb_freq / pre; - (freq, freq * 2u32) - } - }; - - // Setup the 48 MHz clock if needed - if let Some(clock_48mhz_src) = config.clock_48mhz_src { - let source = match clock_48mhz_src { - Clock48MhzSrc::PllQ => { - // Make sure the PLLQ is enabled and running at 48Mhz - let pllq_freq = pll_freq.as_ref().and_then(|f| f.pll_q); - assert!(pllq_freq.is_some() && pllq_freq.unwrap().0 == 48_000_000); + let (apb1_freq, apb1_tim_freq) = super::util::calc_pclk(hclk, config.apb1_pre); + let (apb2_freq, apb2_tim_freq) = super::util::calc_pclk(hclk, config.apb2_pre); + // Configure the 48MHz clock source for USB and RNG peripherals. + RCC.ccipr().modify(|w| { + w.set_clk48sel(match config.clk48_src { + Clk48Src::PLL1_Q => { + // Not checking that PLL1_Q is 48MHz here so as not to require the user to have a 48MHz clock. + // Peripherals which require one (USB, RNG) should check that they‘re driven by a valid 48MHz + // clock at init. crate::pac::rcc::vals::Clk48sel::PLL1_Q } - Clock48MhzSrc::Hsi48(config) => { - super::init_hsi48(config); + Clk48Src::HSI48 => { + // Make sure HSI48 is enabled + assert!(config.hsi48.is_some()); crate::pac::rcc::vals::Clk48sel::HSI48 } - }; - - RCC.ccipr().modify(|w| w.set_clk48sel(source)); - } + _ => unreachable!(), + }) + }); RCC.ccipr().modify(|w| w.set_adc12sel(config.adc12_clock_source)); RCC.ccipr().modify(|w| w.set_adc345sel(config.adc345_clock_source)); @@ -308,18 +336,42 @@ pub(crate) unsafe fn init(config: Config) { set_clocks!( sys: Some(sys_clk), - hclk1: Some(ahb_freq), - hclk2: Some(ahb_freq), - hclk3: Some(ahb_freq), + hclk1: Some(hclk), + hclk2: Some(hclk), + hclk3: Some(hclk), pclk1: Some(apb1_freq), pclk1_tim: Some(apb1_tim_freq), pclk2: Some(apb2_freq), pclk2_tim: Some(apb2_tim_freq), adc: adc12_ck, adc34: adc345_ck, - pll1_p: None, - pll1_q: None, // TODO - hse: None, // TODO + pll1_p: pll_freq.as_ref().and_then(|pll| pll.pll_p), + pll1_q: pll_freq.as_ref().and_then(|pll| pll.pll_p), + hse: hse, rtc: rtc, ); } + +// TODO: if necessary, make more of these, gated behind cfg attrs +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + /// HSE 4-48MHz (RM0440 p280) + pub(crate) const HSE_OSC: RangeInclusive = Hertz(4_000_000)..=Hertz(48_000_000); + + /// External Clock ?-48MHz (RM0440 p280) + pub(crate) const HSE_BYP: RangeInclusive = Hertz(0)..=Hertz(48_000_000); + + // SYSCLK ?-170MHz (RM0440 p282) + //pub(crate) const SYSCLK: RangeInclusive = Hertz(0)..=Hertz(170_000_000); + + // PLL Output frequency ?-170MHz (RM0440 p281) + //pub(crate) const PCLK: RangeInclusive = Hertz(0)..=Hertz(170_000_000); + + // Left over from f.rs, remove if not necessary + //pub(crate) const HCLK: RangeInclusive = Hertz(12_500_000)..=Hertz(216_000_000); + //pub(crate) const PLL_IN: RangeInclusive = Hertz(1_000_000)..=Hertz(2_100_000); + //pub(crate) const PLL_VCO: RangeInclusive = Hertz(100_000_000)..=Hertz(432_000_000); +} diff --git a/examples/stm32g4/src/bin/adc.rs b/examples/stm32g4/src/bin/adc.rs index 35324d931..99e3ef63b 100644 --- a/examples/stm32g4/src/bin/adc.rs +++ b/examples/stm32g4/src/bin/adc.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::adc::{Adc, SampleTime}; -use embassy_stm32::rcc::{AdcClockSource, ClockSrc, Pll, PllM, PllN, PllR, PllSource}; +use embassy_stm32::rcc::{AdcClockSource, Pll, PllMul, PllPreDiv, PllRDiv, Pllsrc, Sysclk}; use embassy_stm32::Config; use embassy_time::{Delay, Timer}; use {defmt_rtt as _, panic_probe as _}; @@ -14,17 +14,17 @@ async fn main(_spawner: Spawner) { let mut config = Config::default(); config.rcc.pll = Some(Pll { - source: PllSource::HSI, - prediv_m: PllM::DIV4, - mul_n: PllN::MUL85, - div_p: None, - div_q: None, + source: Pllsrc::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL85, + divp: None, + divq: None, // Main system clock at 170 MHz - div_r: Some(PllR::DIV2), + divr: Some(PllRDiv::DIV2), }); config.rcc.adc12_clock_source = AdcClockSource::SYS; - config.rcc.mux = ClockSrc::PLL; + config.rcc.sys = Sysclk::PLL1_R; let mut p = embassy_stm32::init(config); info!("Hello World!"); diff --git a/examples/stm32g4/src/bin/pll.rs b/examples/stm32g4/src/bin/pll.rs index 46ebe0b0d..5274de79d 100644 --- a/examples/stm32g4/src/bin/pll.rs +++ b/examples/stm32g4/src/bin/pll.rs @@ -3,7 +3,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::rcc::{ClockSrc, Pll, PllM, PllN, PllR, PllSource}; +use embassy_stm32::rcc::{Pll, PllMul, PllPreDiv, PllRDiv, Pllsrc, Sysclk}; use embassy_stm32::Config; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -13,16 +13,16 @@ async fn main(_spawner: Spawner) { let mut config = Config::default(); config.rcc.pll = Some(Pll { - source: PllSource::HSI, - prediv_m: PllM::DIV4, - mul_n: PllN::MUL85, - div_p: None, - div_q: None, + source: Pllsrc::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL85, + divp: None, + divq: None, // Main system clock at 170 MHz - div_r: Some(PllR::DIV2), + divr: Some(PllRDiv::DIV2), }); - config.rcc.mux = ClockSrc::PLL; + config.rcc.sys = Sysclk::PLL1_R; let _p = embassy_stm32::init(config); info!("Hello World!"); diff --git a/examples/stm32g4/src/bin/usb_serial.rs b/examples/stm32g4/src/bin/usb_serial.rs index c26fa76b7..989fef5b0 100644 --- a/examples/stm32g4/src/bin/usb_serial.rs +++ b/examples/stm32g4/src/bin/usb_serial.rs @@ -3,7 +3,9 @@ use defmt::{panic, *}; use embassy_executor::Spawner; -use embassy_stm32::rcc::{Clock48MhzSrc, ClockSrc, Hsi48Config, Pll, PllM, PllN, PllQ, PllR, PllSource}; +use embassy_stm32::rcc::{ + Clk48Src, Hse, HseMode, Hsi48Config, Pll, PllMul, PllPreDiv, PllQDiv, PllRDiv, Pllsrc, Sysclk, +}; use embassy_stm32::time::Hertz; use embassy_stm32::usb::{self, Driver, Instance}; use embassy_stm32::{bind_interrupts, peripherals, Config}; @@ -24,25 +26,32 @@ async fn main(_spawner: Spawner) { // Change this to `false` to use the HSE clock source for the USB. This example assumes an 8MHz HSE. const USE_HSI48: bool = true; - let plldivq = if USE_HSI48 { None } else { Some(PllQ::DIV6) }; + let plldivq = if USE_HSI48 { None } else { Some(PllQDiv::DIV6) }; - config.rcc.pll = Some(Pll { - source: PllSource::HSE(Hertz(8_000_000)), - prediv_m: PllM::DIV2, - mul_n: PllN::MUL72, - div_p: None, - div_q: plldivq, - // Main system clock at 144 MHz - div_r: Some(PllR::DIV2), + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Oscillator, }); - config.rcc.mux = ClockSrc::PLL; + config.rcc.pll = Some(Pll { + source: Pllsrc::HSE, + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL72, + divp: None, + divq: plldivq, + // Main system clock at 144 MHz + divr: Some(PllRDiv::DIV2), + }); + + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.boost = true; // BOOST! if USE_HSI48 { // Sets up the Clock Recovery System (CRS) to use the USB SOF to trim the HSI48 oscillator. - config.rcc.clock_48mhz_src = Some(Clock48MhzSrc::Hsi48(Hsi48Config { sync_from_usb: true })); + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); + config.rcc.clk48_src = Clk48Src::HSI48; } else { - config.rcc.clock_48mhz_src = Some(Clock48MhzSrc::PllQ); + config.rcc.clk48_src = Clk48Src::PLL1_Q; } let p = embassy_stm32::init(config);