aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2022-04-30 21:08:43 +0000
committerGitHub <[email protected]>2022-04-30 21:08:43 +0000
commitd600f392608ebebe97d1e7461789d0a724afc9ef (patch)
treef57df320554eb534c7aba15e4d09e5929deb8e96
parentc474682ea97add6ae8d448e8472ee0444c3cc366 (diff)
parente88559c5ca2450bbcfd6fe65e73fe0fe47465680 (diff)
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 :disappointed: Co-authored-by: Joonas Javanainen <[email protected]>
-rw-r--r--embassy-stm32/src/rcc/f2.rs264
-rw-r--r--embassy-stm32/src/rcc/mod.rs2
-rw-r--r--examples/stm32f2/src/bin/pll.rs56
3 files changed, 300 insertions, 22 deletions
diff --git a/embassy-stm32/src/rcc/f2.rs b/embassy-stm32/src/rcc/f2.rs
index bece046f8..7e5992bba 100644
--- a/embassy-stm32/src/rcc/f2.rs
+++ b/embassy-stm32/src/rcc/f2.rs
@@ -1,7 +1,8 @@
1use core::ops::Div; 1use core::convert::TryFrom;
2use core::ops::{Div, Mul};
2 3
3use crate::pac::flash::vals::Latency; 4use crate::pac::flash::vals::Latency;
4use crate::pac::rcc::vals::{Hpre, Ppre, Sw}; 5use crate::pac::rcc::vals::{Hpre, Pllp, Pllsrc, Ppre, Sw};
5use crate::pac::{FLASH, RCC}; 6use crate::pac::{FLASH, RCC};
6use crate::rcc::{set_freqs, Clocks}; 7use crate::rcc::{set_freqs, Clocks};
7use crate::time::Hertz; 8use crate::time::Hertz;
@@ -9,11 +10,18 @@ use crate::time::Hertz;
9/// HSI speed 10/// HSI speed
10pub const HSI: Hertz = Hertz(16_000_000); 11pub const HSI: Hertz = Hertz(16_000_000);
11 12
13#[derive(Clone, Copy)]
14pub struct HSEConfig {
15 pub frequency: Hertz,
16 pub source: HSESrc,
17}
18
12/// System clock mux source 19/// System clock mux source
13#[derive(Clone, Copy)] 20#[derive(Clone, Copy)]
14pub enum ClockSrc { 21pub enum ClockSrc {
15 HSE(Hertz, HSESrc), 22 HSE,
16 HSI, 23 HSI,
24 PLL,
17} 25}
18 26
19/// HSE clock source 27/// HSE clock source
@@ -25,6 +33,170 @@ pub enum HSESrc {
25 Bypass, 33 Bypass,
26} 34}
27 35
36#[derive(Clone, Copy)]
37pub struct PLLConfig {
38 pub pre_div: PLLPreDiv,
39 pub mul: PLLMul,
40 pub main_div: PLLMainDiv,
41 pub pll48_div: PLL48Div,
42}
43
44impl 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
55impl 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)]
72pub enum PLLSrc {
73 HSE,
74 HSI,
75}
76
77impl 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)]
89pub struct PLLPreDiv(u8);
90
91impl 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
102impl 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)]
113pub struct PLLMul(u16);
114
115impl 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
123impl 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)]
136pub enum PLLMainDiv {
137 Div2,
138 Div4,
139 Div6,
140 Div8,
141}
142
143impl 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
154impl 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)]
171pub struct PLL48Div(u8);
172
173impl 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
181impl 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)]
193pub struct PLLClocks {
194 pub in_freq: Hertz,
195 pub vco_freq: Hertz,
196 pub main_freq: Hertz,
197 pub pll48_freq: Hertz,
198}
199
28/// AHB prescaler 200/// AHB prescaler
29#[derive(Clone, Copy, PartialEq)] 201#[derive(Clone, Copy, PartialEq)]
30pub enum AHBPrescaler { 202pub enum AHBPrescaler {
@@ -206,6 +378,10 @@ impl VoltageRange {
206 378
207/// Clocks configuration 379/// Clocks configuration
208pub struct Config { 380pub struct Config {
381 pub hse: Option<HSEConfig>,
382 pub hsi: bool,
383 pub pll_mux: PLLSrc,
384 pub pll: PLLConfig,
209 pub mux: ClockSrc, 385 pub mux: ClockSrc,
210 pub voltage: VoltageRange, 386 pub voltage: VoltageRange,
211 pub ahb_pre: AHBPrescaler, 387 pub ahb_pre: AHBPrescaler,
@@ -217,6 +393,10 @@ impl Default for Config {
217 #[inline] 393 #[inline]
218 fn default() -> Config { 394 fn default() -> Config {
219 Config { 395 Config {
396 hse: None,
397 hsi: true,
398 pll_mux: PLLSrc::HSI,
399 pll: PLLConfig::default(),
220 voltage: VoltageRange::Min1V8, 400 voltage: VoltageRange::Min1V8,
221 mux: ClockSrc::HSI, 401 mux: ClockSrc::HSI,
222 ahb_pre: AHBPrescaler::NotDivided, 402 ahb_pre: AHBPrescaler::NotDivided,
@@ -226,30 +406,65 @@ impl Default for Config {
226 } 406 }
227} 407}
228 408
229#[inline] 409pub(crate) unsafe fn init(config: Config) {
230unsafe fn enable_hse(source: HSESrc) { 410 // Make sure HSI is enabled
231 RCC.cr().write(|w| { 411 RCC.cr().write(|w| w.set_hsion(true));
232 w.set_hsebyp(match source { 412 while !RCC.cr().read().hsirdy() {}
233 HSESrc::Bypass => true, 413
234 HSESrc::Crystal => false, 414 if let Some(hse_config) = config.hse {
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)
235 }); 421 });
236 w.set_hseon(true) 422 while !RCC.cr().read().hserdy() {}
423 }
424
425 let pll_src_freq = match config.pll_mux {
426 PLLSrc::HSE => {
427 let hse_config = config
428 .hse
429 .unwrap_or_else(|| panic!("HSE must be configured to be used as PLL input"));
430 hse_config.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);
237 }); 451 });
238 while !RCC.cr().read().hserdy() {}
239}
240 452
241pub(crate) unsafe fn init(config: Config) {
242 let (sys_clk, sw) = match config.mux { 453 let (sys_clk, sw) = match config.mux {
243 ClockSrc::HSI => { 454 ClockSrc::HSI => {
244 // Enable HSI 455 assert!(config.hsi, "HSI must be enabled to be used as system clock");
245 RCC.cr().write(|w| w.set_hsion(true));
246 while !RCC.cr().read().hsirdy() {}
247
248 (HSI, Sw::HSI) 456 (HSI, Sw::HSI)
249 } 457 }
250 ClockSrc::HSE(freq, source) => { 458 ClockSrc::HSE => {
251 enable_hse(source); 459 let hse_config = config
252 (freq, Sw::HSE) 460 .hse
461 .unwrap_or_else(|| panic!("HSE must be configured to be used as PLL input"));
462 (hse_config.frequency, Sw::HSE)
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)
253 } 468 }
254 }; 469 };
255 // 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
@@ -260,7 +475,7 @@ pub(crate) unsafe fn init(config: Config) {
260 // Reference: STM32F215xx/217xx datasheet Table 13. General operating conditions 475 // Reference: STM32F215xx/217xx datasheet Table 13. General operating conditions
261 assert!(ahb_freq <= Hertz(120_000_000)); 476 assert!(ahb_freq <= Hertz(120_000_000));
262 477
263 let flash_ws = config.voltage.wait_states(ahb_freq).expect("Invalid HCLK"); 478 let flash_ws = unwrap!(config.voltage.wait_states(ahb_freq));
264 FLASH.acr().modify(|w| w.set_latency(flash_ws)); 479 FLASH.acr().modify(|w| w.set_latency(flash_ws));
265 480
266 RCC.cfgr().modify(|w| { 481 RCC.cfgr().modify(|w| {
@@ -269,6 +484,12 @@ pub(crate) unsafe fn init(config: Config) {
269 w.set_ppre1(config.apb1_pre.into()); 484 w.set_ppre1(config.apb1_pre.into());
270 w.set_ppre2(config.apb2_pre.into()); 485 w.set_ppre2(config.apb2_pre.into());
271 }); 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 }
272 493
273 let (apb1_freq, apb1_tim_freq) = match config.apb1_pre { 494 let (apb1_freq, apb1_tim_freq) = match config.apb1_pre {
274 APBPrescaler::NotDivided => (ahb_freq, ahb_freq), 495 APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
@@ -299,5 +520,6 @@ pub(crate) unsafe fn init(config: Config) {
299 apb1_tim: apb1_tim_freq, 520 apb1_tim: apb1_tim_freq,
300 apb2: apb2_freq, 521 apb2: apb2_freq,
301 apb2_tim: apb2_tim_freq, 522 apb2_tim: apb2_tim_freq,
523 pll48: Some(pll_clocks.pll48_freq),
302 }); 524 });
303} 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)]
diff --git a/examples/stm32f2/src/bin/pll.rs b/examples/stm32f2/src/bin/pll.rs
new file mode 100644
index 000000000..4bd74f0bd
--- /dev/null
+++ b/examples/stm32f2/src/bin/pll.rs
@@ -0,0 +1,56 @@
1#![no_std]
2#![no_main]
3#![feature(type_alias_impl_trait)]
4
5use core::convert::TryFrom;
6use defmt::*;
7use embassy::executor::Spawner;
8use embassy::time::{Duration, Timer};
9use embassy_stm32::{
10 rcc::{
11 APBPrescaler, ClockSrc, HSEConfig, HSESrc, PLL48Div, PLLConfig, PLLMainDiv, PLLMul,
12 PLLPreDiv, PLLSrc,
13 },
14 time::Hertz,
15 Config, Peripherals,
16};
17
18use defmt_rtt as _; // global logger
19use panic_probe as _;
20
21// Example config for maximum performance on a NUCLEO-F207ZG board
22fn config() -> Config {
23 let mut config = Config::default();
24 // By default, HSE on the board comes from a 8 MHz clock signal (not a crystal)
25 config.rcc.hse = Some(HSEConfig {
26 frequency: Hertz(8_000_000),
27 source: HSESrc::Bypass,
28 });
29 // PLL uses HSE as the clock source
30 config.rcc.pll_mux = PLLSrc::HSE;
31 config.rcc.pll = PLLConfig {
32 // 8 MHz clock source / 8 = 1 MHz PLL input
33 pre_div: unwrap!(PLLPreDiv::try_from(8)),
34 // 1 MHz PLL input * 240 = 240 MHz PLL VCO
35 mul: unwrap!(PLLMul::try_from(240)),
36 // 240 MHz PLL VCO / 2 = 120 MHz main PLL output
37 main_div: PLLMainDiv::Div2,
38 // 240 MHz PLL VCO / 5 = 48 MHz PLL48 output
39 pll48_div: unwrap!(PLL48Div::try_from(5)),
40 };
41 // System clock comes from PLL (= the 120 MHz main PLL output)
42 config.rcc.mux = ClockSrc::PLL;
43 // 120 MHz / 4 = 30 MHz APB1 frequency
44 config.rcc.apb1_pre = APBPrescaler::Div4;
45 // 120 MHz / 2 = 60 MHz APB2 frequency
46 config.rcc.apb2_pre = APBPrescaler::Div2;
47 config
48}
49
50#[embassy::main(config = "config()")]
51async fn main(_spawner: Spawner, _p: Peripherals) {
52 loop {
53 Timer::after(Duration::from_millis(1000)).await;
54 info!("1s elapsed");
55 }
56}