diff options
| author | Joonas Javanainen <[email protected]> | 2022-04-26 20:33:57 +0300 |
|---|---|---|
| committer | Joonas Javanainen <[email protected]> | 2022-04-29 18:21:40 +0300 |
| commit | 07ad52162ba05cec358c35375f1f5e91d0b398e9 (patch) | |
| tree | 097b71dd0deee3065f889ee288d77ed1646f68d4 | |
| parent | 0cfe1dc9df9966ed36021869e9bf247aedfabc27 (diff) | |
Add PLL config support for F2
| -rw-r--r-- | embassy-stm32/src/rcc/f2.rs | 246 | ||||
| -rw-r--r-- | embassy-stm32/src/rcc/mod.rs | 2 |
2 files changed, 227 insertions, 21 deletions
diff --git a/embassy-stm32/src/rcc/f2.rs b/embassy-stm32/src/rcc/f2.rs index e44613926..7074d7c3a 100644 --- a/embassy-stm32/src/rcc/f2.rs +++ b/embassy-stm32/src/rcc/f2.rs | |||
| @@ -1,7 +1,8 @@ | |||
| 1 | use core::ops::Div; | 1 | use core::convert::TryFrom; |
| 2 | use core::ops::{Div, Mul}; | ||
| 2 | 3 | ||
| 3 | use crate::pac::flash::vals::Latency; | 4 | use crate::pac::flash::vals::Latency; |
| 4 | use crate::pac::rcc::vals::{Hpre, Ppre, Sw}; | 5 | use crate::pac::rcc::vals::{Hpre, Pllp, Pllsrc, Ppre, Sw}; |
| 5 | use crate::pac::{FLASH, RCC}; | 6 | use crate::pac::{FLASH, RCC}; |
| 6 | use crate::rcc::{set_freqs, Clocks}; | 7 | use crate::rcc::{set_freqs, Clocks}; |
| 7 | use crate::time::Hertz; | 8 | use crate::time::Hertz; |
| @@ -20,6 +21,7 @@ pub struct HSEConfig { | |||
| 20 | pub enum ClockSrc { | 21 | pub enum ClockSrc { |
| 21 | HSE, | 22 | HSE, |
| 22 | HSI, | 23 | HSI, |
| 24 | PLL, | ||
| 23 | } | 25 | } |
| 24 | 26 | ||
| 25 | /// HSE clock source | 27 | /// HSE clock source |
| @@ -31,6 +33,170 @@ pub enum HSESrc { | |||
| 31 | Bypass, | 33 | Bypass, |
| 32 | } | 34 | } |
| 33 | 35 | ||
| 36 | #[derive(Clone, Copy)] | ||
| 37 | pub struct PLLConfig { | ||
| 38 | pub pre_div: PLLPreDiv, | ||
| 39 | pub mul: PLLMul, | ||
| 40 | pub main_div: PLLMainDiv, | ||
| 41 | pub pll48_div: PLL48Div, | ||
| 42 | } | ||
| 43 | |||
| 44 | impl Default for PLLConfig { | ||
| 45 | fn default() -> Self { | ||
| 46 | PLLConfig { | ||
| 47 | pre_div: PLLPreDiv(16), | ||
| 48 | mul: PLLMul(192), | ||
| 49 | main_div: PLLMainDiv::Div2, | ||
| 50 | pll48_div: PLL48Div(4), | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | impl PLLConfig { | ||
| 56 | pub fn clocks(&self, src_freq: Hertz) -> PLLClocks { | ||
| 57 | let in_freq = src_freq / self.pre_div; | ||
| 58 | let vco_freq = src_freq * self.mul / self.pre_div; | ||
| 59 | let main_freq = vco_freq / self.main_div; | ||
| 60 | let pll48_freq = vco_freq / self.pll48_div; | ||
| 61 | PLLClocks { | ||
| 62 | in_freq, | ||
| 63 | vco_freq, | ||
| 64 | main_freq, | ||
| 65 | pll48_freq, | ||
| 66 | } | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | /// Clock source for both main PLL and PLLI2S | ||
| 71 | #[derive(Clone, Copy, PartialEq)] | ||
| 72 | pub enum PLLSrc { | ||
| 73 | HSE, | ||
| 74 | HSI, | ||
| 75 | } | ||
| 76 | |||
| 77 | impl Into<Pllsrc> for PLLSrc { | ||
| 78 | fn into(self) -> Pllsrc { | ||
| 79 | match self { | ||
| 80 | PLLSrc::HSE => Pllsrc::HSE, | ||
| 81 | PLLSrc::HSI => Pllsrc::HSI, | ||
| 82 | } | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | /// Division factor for both main PLL and PLLI2S | ||
| 87 | #[derive(Clone, Copy, PartialEq)] | ||
| 88 | #[repr(transparent)] | ||
| 89 | pub struct PLLPreDiv(u8); | ||
| 90 | |||
| 91 | impl TryFrom<u8> for PLLPreDiv { | ||
| 92 | type Error = &'static str; | ||
| 93 | |||
| 94 | fn try_from(value: u8) -> Result<Self, Self::Error> { | ||
| 95 | match value { | ||
| 96 | 2..=63 => Ok(PLLPreDiv(value)), | ||
| 97 | _ => Err("PLLPreDiv must be within range 2..=63"), | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | impl Div<PLLPreDiv> for Hertz { | ||
| 103 | type Output = Hertz; | ||
| 104 | |||
| 105 | fn div(self, rhs: PLLPreDiv) -> Self::Output { | ||
| 106 | Hertz(self.0 / u32::from(rhs.0)) | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | /// Multiplication factor for main PLL | ||
| 111 | #[derive(Clone, Copy, PartialEq)] | ||
| 112 | #[repr(transparent)] | ||
| 113 | pub struct PLLMul(u16); | ||
| 114 | |||
| 115 | impl Mul<PLLMul> for Hertz { | ||
| 116 | type Output = Hertz; | ||
| 117 | |||
| 118 | fn mul(self, rhs: PLLMul) -> Self::Output { | ||
| 119 | Hertz(self.0 * u32::from(rhs.0)) | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | impl TryFrom<u16> for PLLMul { | ||
| 124 | type Error = &'static str; | ||
| 125 | |||
| 126 | fn try_from(value: u16) -> Result<Self, Self::Error> { | ||
| 127 | match value { | ||
| 128 | 192..=432 => Ok(PLLMul(value)), | ||
| 129 | _ => Err("PLLMul must be within range 192..=432"), | ||
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | /// PLL division factor for the main system clock | ||
| 135 | #[derive(Clone, Copy, PartialEq)] | ||
| 136 | pub enum PLLMainDiv { | ||
| 137 | Div2, | ||
| 138 | Div4, | ||
| 139 | Div6, | ||
| 140 | Div8, | ||
| 141 | } | ||
| 142 | |||
| 143 | impl Into<Pllp> for PLLMainDiv { | ||
| 144 | fn into(self) -> Pllp { | ||
| 145 | match self { | ||
| 146 | PLLMainDiv::Div2 => Pllp::DIV2, | ||
| 147 | PLLMainDiv::Div4 => Pllp::DIV4, | ||
| 148 | PLLMainDiv::Div6 => Pllp::DIV8, | ||
| 149 | PLLMainDiv::Div8 => Pllp::DIV8, | ||
| 150 | } | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | impl Div<PLLMainDiv> for Hertz { | ||
| 155 | type Output = Hertz; | ||
| 156 | |||
| 157 | fn div(self, rhs: PLLMainDiv) -> Self::Output { | ||
| 158 | let divisor = match rhs { | ||
| 159 | PLLMainDiv::Div2 => 2, | ||
| 160 | PLLMainDiv::Div4 => 4, | ||
| 161 | PLLMainDiv::Div6 => 6, | ||
| 162 | PLLMainDiv::Div8 => 8, | ||
| 163 | }; | ||
| 164 | Hertz(self.0 / divisor) | ||
| 165 | } | ||
| 166 | } | ||
| 167 | |||
| 168 | /// PLL division factor for USB OTG FS / SDIO / RNG | ||
| 169 | #[derive(Clone, Copy, PartialEq)] | ||
| 170 | #[repr(transparent)] | ||
| 171 | pub struct PLL48Div(u8); | ||
| 172 | |||
| 173 | impl Div<PLL48Div> for Hertz { | ||
| 174 | type Output = Hertz; | ||
| 175 | |||
| 176 | fn div(self, rhs: PLL48Div) -> Self::Output { | ||
| 177 | Hertz(self.0 / u32::from(rhs.0)) | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | impl TryFrom<u8> for PLL48Div { | ||
| 182 | type Error = &'static str; | ||
| 183 | |||
| 184 | fn try_from(value: u8) -> Result<Self, Self::Error> { | ||
| 185 | match value { | ||
| 186 | 2..=15 => Ok(PLL48Div(value)), | ||
| 187 | _ => Err("PLL48Div must be within range 2..=15"), | ||
| 188 | } | ||
| 189 | } | ||
| 190 | } | ||
| 191 | |||
| 192 | #[derive(Clone, Copy, PartialEq)] | ||
| 193 | pub struct PLLClocks { | ||
| 194 | pub in_freq: Hertz, | ||
| 195 | pub vco_freq: Hertz, | ||
| 196 | pub main_freq: Hertz, | ||
| 197 | pub pll48_freq: Hertz, | ||
| 198 | } | ||
| 199 | |||
| 34 | /// AHB prescaler | 200 | /// AHB prescaler |
| 35 | #[derive(Clone, Copy, PartialEq)] | 201 | #[derive(Clone, Copy, PartialEq)] |
| 36 | pub enum AHBPrescaler { | 202 | pub enum AHBPrescaler { |
| @@ -213,6 +379,9 @@ impl VoltageRange { | |||
| 213 | /// Clocks configuration | 379 | /// Clocks configuration |
| 214 | pub struct Config { | 380 | pub struct Config { |
| 215 | pub hse: Option<HSEConfig>, | 381 | pub hse: Option<HSEConfig>, |
| 382 | pub hsi: bool, | ||
| 383 | pub pll_mux: PLLSrc, | ||
| 384 | pub pll: PLLConfig, | ||
| 216 | pub mux: ClockSrc, | 385 | pub mux: ClockSrc, |
| 217 | pub voltage: VoltageRange, | 386 | pub voltage: VoltageRange, |
| 218 | pub ahb_pre: AHBPrescaler, | 387 | pub ahb_pre: AHBPrescaler, |
| @@ -225,6 +394,9 @@ impl Default for Config { | |||
| 225 | fn default() -> Config { | 394 | fn default() -> Config { |
| 226 | Config { | 395 | Config { |
| 227 | hse: None, | 396 | hse: None, |
| 397 | hsi: true, | ||
| 398 | pll_mux: PLLSrc::HSI, | ||
| 399 | pll: PLLConfig::default(), | ||
| 228 | voltage: VoltageRange::Min1V8, | 400 | voltage: VoltageRange::Min1V8, |
| 229 | mux: ClockSrc::HSI, | 401 | mux: ClockSrc::HSI, |
| 230 | ahb_pre: AHBPrescaler::NotDivided, | 402 | ahb_pre: AHBPrescaler::NotDivided, |
| @@ -234,31 +406,53 @@ impl Default for Config { | |||
| 234 | } | 406 | } |
| 235 | } | 407 | } |
| 236 | 408 | ||
| 237 | #[inline] | 409 | pub(crate) unsafe fn init(config: Config) { |
| 238 | unsafe fn enable_hse(source: HSESrc) { | 410 | // Make sure HSI is enabled |
| 239 | RCC.cr().write(|w| { | ||
| 240 | w.set_hsebyp(match source { | ||
| 241 | HSESrc::Bypass => true, | ||
| 242 | HSESrc::Crystal => false, | ||
| 243 | }); | ||
| 244 | w.set_hseon(true) | ||
| 245 | }); | ||
| 246 | while !RCC.cr().read().hserdy() {} | ||
| 247 | } | ||
| 248 | |||
| 249 | #[inline] | ||
| 250 | unsafe fn enable_hsi() { | ||
| 251 | RCC.cr().write(|w| w.set_hsion(true)); | 411 | RCC.cr().write(|w| w.set_hsion(true)); |
| 252 | while !RCC.cr().read().hsirdy() {} | 412 | while !RCC.cr().read().hsirdy() {} |
| 253 | } | ||
| 254 | 413 | ||
| 255 | pub(crate) unsafe fn init(config: Config) { | ||
| 256 | if let Some(hse_config) = config.hse { | 414 | if let Some(hse_config) = config.hse { |
| 257 | enable_hse(hse_config.source); | 415 | RCC.cr().modify(|w| { |
| 416 | w.set_hsebyp(match hse_config.source { | ||
| 417 | HSESrc::Bypass => true, | ||
| 418 | HSESrc::Crystal => false, | ||
| 419 | }); | ||
| 420 | w.set_hseon(true) | ||
| 421 | }); | ||
| 422 | while !RCC.cr().read().hserdy() {} | ||
| 258 | } | 423 | } |
| 424 | |||
| 425 | let pll_src_freq = match config.pll_mux { | ||
| 426 | PLLSrc::HSE => { | ||
| 427 | config | ||
| 428 | .hse | ||
| 429 | .expect("HSE must be configured to be used as PLL input") | ||
| 430 | .frequency | ||
| 431 | } | ||
| 432 | PLLSrc::HSI => HSI, | ||
| 433 | }; | ||
| 434 | |||
| 435 | // Reference: STM32F215xx/217xx datasheet Table 33. Main PLL characteristics | ||
| 436 | let pll_clocks = config.pll.clocks(pll_src_freq); | ||
| 437 | assert!(Hertz(950_000) <= pll_clocks.in_freq && pll_clocks.in_freq <= Hertz(2_100_000)); | ||
| 438 | assert!(Hertz(192_000_000) <= pll_clocks.vco_freq && pll_clocks.vco_freq <= Hertz(432_000_000)); | ||
| 439 | assert!( | ||
| 440 | Hertz(24_000_000) <= pll_clocks.main_freq && pll_clocks.main_freq <= Hertz(120_000_000) | ||
| 441 | ); | ||
| 442 | // USB actually requires == 48 MHz, but other PLL48 peripherals are fine with <= 48MHz | ||
| 443 | assert!(pll_clocks.pll48_freq <= Hertz(48_000_000)); | ||
| 444 | |||
| 445 | RCC.pllcfgr().write(|w| { | ||
| 446 | w.set_pllsrc(config.pll_mux.into()); | ||
| 447 | w.set_pllm(config.pll.pre_div.0); | ||
| 448 | w.set_plln(config.pll.mul.0); | ||
| 449 | w.set_pllp(config.pll.main_div.into()); | ||
| 450 | w.set_pllq(config.pll.pll48_div.0); | ||
| 451 | }); | ||
| 452 | |||
| 259 | let (sys_clk, sw) = match config.mux { | 453 | let (sys_clk, sw) = match config.mux { |
| 260 | ClockSrc::HSI => { | 454 | ClockSrc::HSI => { |
| 261 | enable_hsi(); | 455 | assert!(config.hsi, "HSI must be enabled to be used as system clock"); |
| 262 | (HSI, Sw::HSI) | 456 | (HSI, Sw::HSI) |
| 263 | } | 457 | } |
| 264 | ClockSrc::HSE => { | 458 | ClockSrc::HSE => { |
| @@ -267,6 +461,11 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 267 | .expect("HSE must be configured to be used as system clock"); | 461 | .expect("HSE must be configured to be used as system clock"); |
| 268 | (hse_config.frequency, Sw::HSE) | 462 | (hse_config.frequency, Sw::HSE) |
| 269 | } | 463 | } |
| 464 | ClockSrc::PLL => { | ||
| 465 | RCC.cr().modify(|w| w.set_pllon(true)); | ||
| 466 | while !RCC.cr().read().pllrdy() {} | ||
| 467 | (pll_clocks.main_freq, Sw::PLL) | ||
| 468 | } | ||
| 270 | }; | 469 | }; |
| 271 | // RM0033 Figure 9. Clock tree suggests max SYSCLK/HCLK is 168 MHz, but datasheet specifies PLL | 470 | // RM0033 Figure 9. Clock tree suggests max SYSCLK/HCLK is 168 MHz, but datasheet specifies PLL |
| 272 | // max output to be 120 MHz, so there's no way to get higher frequencies | 471 | // max output to be 120 MHz, so there's no way to get higher frequencies |
| @@ -285,6 +484,12 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 285 | w.set_ppre1(config.apb1_pre.into()); | 484 | w.set_ppre1(config.apb1_pre.into()); |
| 286 | w.set_ppre2(config.apb2_pre.into()); | 485 | w.set_ppre2(config.apb2_pre.into()); |
| 287 | }); | 486 | }); |
| 487 | while RCC.cfgr().read().sws() != sw.0 {} | ||
| 488 | |||
| 489 | // Turn off HSI to save power if we don't need it | ||
| 490 | if !config.hsi { | ||
| 491 | RCC.cr().modify(|w| w.set_hsion(false)); | ||
| 492 | } | ||
| 288 | 493 | ||
| 289 | let (apb1_freq, apb1_tim_freq) = match config.apb1_pre { | 494 | let (apb1_freq, apb1_tim_freq) = match config.apb1_pre { |
| 290 | APBPrescaler::NotDivided => (ahb_freq, ahb_freq), | 495 | APBPrescaler::NotDivided => (ahb_freq, ahb_freq), |
| @@ -315,5 +520,6 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 315 | apb1_tim: apb1_tim_freq, | 520 | apb1_tim: apb1_tim_freq, |
| 316 | apb2: apb2_freq, | 521 | apb2: apb2_freq, |
| 317 | apb2_tim: apb2_tim_freq, | 522 | apb2_tim: apb2_tim_freq, |
| 523 | pll48: Some(pll_clocks.pll48_freq), | ||
| 318 | }); | 524 | }); |
| 319 | } | 525 | } |
diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index 9a95836a6..d3710b8c3 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs | |||
| @@ -53,7 +53,7 @@ pub struct Clocks { | |||
| 53 | #[cfg(any(rcc_h7, rcc_h7ab))] | 53 | #[cfg(any(rcc_h7, rcc_h7ab))] |
| 54 | pub ahb4: Hertz, | 54 | pub ahb4: Hertz, |
| 55 | 55 | ||
| 56 | #[cfg(any(rcc_f4, rcc_f410, rcc_f7))] | 56 | #[cfg(any(rcc_f2, rcc_f4, rcc_f410, rcc_f7))] |
| 57 | pub pll48: Option<Hertz>, | 57 | pub pll48: Option<Hertz>, |
| 58 | 58 | ||
| 59 | #[cfg(rcc_f1)] | 59 | #[cfg(rcc_f1)] |
