Merge #743
743: Add PLL config support for F2 r=Dirbaio a=Gekkio
- minor changes to make the F2 RCC API a bit more flexible
- low-level PLL config with assertions based on datasheet specs. It shouldn't be very difficult to later add a "reverse API" where you pass the clocks you want to a function and it generates a `PLLConfig` struct for you
- PLL API tested on my custom board with 12 MHz HSE as source for PLL to generate max clocks for SYSCLK/AHB/APB/APB1/PLL48
- the example *should* work but is untested since I don't have the Nucleo board 😞
Co-authored-by: Joonas Javanainen <joonas.javanainen@gmail.com>
This commit is contained in:
commit
d600f39260
@ -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::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::pac::{FLASH, RCC};
|
||||||
use crate::rcc::{set_freqs, Clocks};
|
use crate::rcc::{set_freqs, Clocks};
|
||||||
use crate::time::Hertz;
|
use crate::time::Hertz;
|
||||||
@ -9,11 +10,18 @@ use crate::time::Hertz;
|
|||||||
/// HSI speed
|
/// HSI speed
|
||||||
pub const HSI: Hertz = Hertz(16_000_000);
|
pub const HSI: Hertz = Hertz(16_000_000);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct HSEConfig {
|
||||||
|
pub frequency: Hertz,
|
||||||
|
pub source: HSESrc,
|
||||||
|
}
|
||||||
|
|
||||||
/// System clock mux source
|
/// System clock mux source
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum ClockSrc {
|
pub enum ClockSrc {
|
||||||
HSE(Hertz, HSESrc),
|
HSE,
|
||||||
HSI,
|
HSI,
|
||||||
|
PLL,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// HSE clock source
|
/// HSE clock source
|
||||||
@ -25,6 +33,170 @@ pub enum HSESrc {
|
|||||||
Bypass,
|
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<Pllsrc> 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<u8> for PLLPreDiv {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
2..=63 => Ok(PLLPreDiv(value)),
|
||||||
|
_ => Err("PLLPreDiv must be within range 2..=63"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div<PLLPreDiv> 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<PLLMul> for Hertz {
|
||||||
|
type Output = Hertz;
|
||||||
|
|
||||||
|
fn mul(self, rhs: PLLMul) -> Self::Output {
|
||||||
|
Hertz(self.0 * u32::from(rhs.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u16> for PLLMul {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||||
|
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<Pllp> 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<PLLMainDiv> 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<PLL48Div> for Hertz {
|
||||||
|
type Output = Hertz;
|
||||||
|
|
||||||
|
fn div(self, rhs: PLL48Div) -> Self::Output {
|
||||||
|
Hertz(self.0 / u32::from(rhs.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for PLL48Div {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
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
|
/// AHB prescaler
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum AHBPrescaler {
|
pub enum AHBPrescaler {
|
||||||
@ -206,6 +378,10 @@ impl VoltageRange {
|
|||||||
|
|
||||||
/// Clocks configuration
|
/// Clocks configuration
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
pub hse: Option<HSEConfig>,
|
||||||
|
pub hsi: bool,
|
||||||
|
pub pll_mux: PLLSrc,
|
||||||
|
pub pll: PLLConfig,
|
||||||
pub mux: ClockSrc,
|
pub mux: ClockSrc,
|
||||||
pub voltage: VoltageRange,
|
pub voltage: VoltageRange,
|
||||||
pub ahb_pre: AHBPrescaler,
|
pub ahb_pre: AHBPrescaler,
|
||||||
@ -217,6 +393,10 @@ impl Default for Config {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn default() -> Config {
|
fn default() -> Config {
|
||||||
Config {
|
Config {
|
||||||
|
hse: None,
|
||||||
|
hsi: true,
|
||||||
|
pll_mux: PLLSrc::HSI,
|
||||||
|
pll: PLLConfig::default(),
|
||||||
voltage: VoltageRange::Min1V8,
|
voltage: VoltageRange::Min1V8,
|
||||||
mux: ClockSrc::HSI,
|
mux: ClockSrc::HSI,
|
||||||
ahb_pre: AHBPrescaler::NotDivided,
|
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) {
|
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 {
|
let (sys_clk, sw) = match config.mux {
|
||||||
ClockSrc::HSI => {
|
ClockSrc::HSI => {
|
||||||
// Enable HSI
|
assert!(config.hsi, "HSI must be enabled to be used as system clock");
|
||||||
RCC.cr().write(|w| w.set_hsion(true));
|
|
||||||
while !RCC.cr().read().hsirdy() {}
|
|
||||||
|
|
||||||
(HSI, Sw::HSI)
|
(HSI, Sw::HSI)
|
||||||
}
|
}
|
||||||
ClockSrc::HSE(freq, source) => {
|
ClockSrc::HSE => {
|
||||||
enable_hse(source);
|
let hse_config = config
|
||||||
(freq, Sw::HSE)
|
.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
|
// 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
|
// Reference: STM32F215xx/217xx datasheet Table 13. General operating conditions
|
||||||
assert!(ahb_freq <= Hertz(120_000_000));
|
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));
|
FLASH.acr().modify(|w| w.set_latency(flash_ws));
|
||||||
|
|
||||||
RCC.cfgr().modify(|w| {
|
RCC.cfgr().modify(|w| {
|
||||||
@ -269,6 +484,12 @@ pub(crate) unsafe fn init(config: Config) {
|
|||||||
w.set_ppre1(config.apb1_pre.into());
|
w.set_ppre1(config.apb1_pre.into());
|
||||||
w.set_ppre2(config.apb2_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 {
|
let (apb1_freq, apb1_tim_freq) = match config.apb1_pre {
|
||||||
APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
|
APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
|
||||||
@ -299,5 +520,6 @@ pub(crate) unsafe fn init(config: Config) {
|
|||||||
apb1_tim: apb1_tim_freq,
|
apb1_tim: apb1_tim_freq,
|
||||||
apb2: apb2_freq,
|
apb2: apb2_freq,
|
||||||
apb2_tim: apb2_tim_freq,
|
apb2_tim: apb2_tim_freq,
|
||||||
|
pll48: Some(pll_clocks.pll48_freq),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ pub struct Clocks {
|
|||||||
#[cfg(any(rcc_h7, rcc_h7ab))]
|
#[cfg(any(rcc_h7, rcc_h7ab))]
|
||||||
pub ahb4: Hertz,
|
pub ahb4: Hertz,
|
||||||
|
|
||||||
#[cfg(any(rcc_f4, rcc_f410, rcc_f7))]
|
#[cfg(any(rcc_f2, rcc_f4, rcc_f410, rcc_f7))]
|
||||||
pub pll48: Option<Hertz>,
|
pub pll48: Option<Hertz>,
|
||||||
|
|
||||||
#[cfg(rcc_f1)]
|
#[cfg(rcc_f1)]
|
||||||
|
56
examples/stm32f2/src/bin/pll.rs
Normal file
56
examples/stm32f2/src/bin/pll.rs
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user