stm32/rcc: port g0 to new api.

This commit is contained in:
Dario Nieuwenhuis 2024-03-03 23:19:54 +01:00
parent b4567bb8c5
commit c8c4b0b701
3 changed files with 264 additions and 276 deletions

View file

@ -1,7 +1,8 @@
use crate::pac::flash::vals::Latency; use crate::pac::flash::vals::Latency;
use crate::pac::rcc::vals::{self, Sw}; pub use crate::pac::pwr::vals::Vos as VoltageRange;
pub use crate::pac::rcc::vals::{ pub use crate::pac::rcc::vals::{
Hpre as AHBPrescaler, Hsidiv as HSIPrescaler, Pllm, Plln, Pllp, Pllq, Pllr, Ppre as APBPrescaler, Hpre as AHBPrescaler, Pllm as PllPreDiv, Plln as PllMul, Pllp as PllPDiv, Pllq as PllQDiv, Pllr as PllRDiv,
Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk,
}; };
use crate::pac::{FLASH, PWR, RCC}; use crate::pac::{FLASH, PWR, RCC};
use crate::time::Hertz; use crate::time::Hertz;
@ -9,6 +10,7 @@ use crate::time::Hertz;
/// HSI speed /// HSI speed
pub const HSI_FREQ: Hertz = Hertz(16_000_000); pub const HSI_FREQ: Hertz = Hertz(16_000_000);
/// HSE Mode
#[derive(Clone, Copy, Eq, PartialEq)] #[derive(Clone, Copy, Eq, PartialEq)]
pub enum HseMode { pub enum HseMode {
/// crystal/ceramic oscillator (HSEBYP=0) /// crystal/ceramic oscillator (HSEBYP=0)
@ -17,69 +19,71 @@ pub enum HseMode {
Bypass, Bypass,
} }
/// System clock mux source /// HSE Configuration
#[derive(Clone, Copy)]
pub enum Sysclk {
HSE(Hertz, HseMode),
HSI(HSIPrescaler),
PLL(PllConfig),
LSI,
}
/// The PLL configuration.
///
/// * `VCOCLK = source / m * n`
/// * `PLLRCLK = VCOCLK / r`
/// * `PLLQCLK = VCOCLK / q`
/// * `PLLPCLK = VCOCLK / p`
#[derive(Clone, Copy)]
pub struct PllConfig {
/// The source from which the PLL receives a clock signal
pub source: PllSource,
/// The initial divisor of that clock signal
pub m: Pllm,
/// The PLL VCO multiplier, which must be in the range `8..=86`.
pub n: Plln,
/// The final divisor for `PLLRCLK` output which drives the system clock
pub r: Pllr,
/// The divisor for the `PLLQCLK` output, if desired
pub q: Option<Pllq>,
/// The divisor for the `PLLPCLK` output, if desired
pub p: Option<Pllp>,
}
impl Default for PllConfig {
#[inline]
fn default() -> PllConfig {
// HSI / 1 * 8 / 2 = 64 MHz
PllConfig {
source: PllSource::HSI,
m: Pllm::DIV1,
n: Plln::MUL8,
r: Pllr::DIV2,
q: None,
p: None,
}
}
}
#[derive(Clone, Copy, Eq, PartialEq)] #[derive(Clone, Copy, Eq, PartialEq)]
pub enum PllSource { pub struct Hse {
HSI, /// HSE frequency.
HSE(Hertz, HseMode), pub freq: Hertz,
/// HSE mode.
pub mode: HseMode,
}
/// PLL Configuration
///
/// Use this struct to configure the PLL source, input frequency, multiplication factor, and output
/// dividers. Be sure to keep check the datasheet for your specific part for the appropriate
/// frequency ranges for each of these settings.
pub struct Pll {
/// PLL Source clock selection.
pub source: PllSource,
/// PLL pre-divider
pub prediv: PllPreDiv,
/// PLL multiplication factor for VCO
pub mul: PllMul,
/// PLL division factor for P clock (ADC Clock)
pub divp: Option<PllPDiv>,
/// PLL division factor for Q clock (USB, I2S23, SAI1, FDCAN, QSPI)
pub divq: Option<PllQDiv>,
/// PLL division factor for R clock (SYSCLK)
pub divr: Option<PllRDiv>,
} }
/// Clocks configutation /// Clocks configutation
#[non_exhaustive]
pub struct Config { pub struct Config {
/// HSI Enable
pub hsi: bool,
/// HSE Configuration
pub hse: Option<Hse>,
/// System Clock Configuration
pub sys: Sysclk, pub sys: Sysclk,
pub ahb_pre: AHBPrescaler,
pub apb_pre: APBPrescaler, /// HSI48 Configuration
pub low_power_run: bool,
pub ls: super::LsConfig,
#[cfg(crs)] #[cfg(crs)]
pub hsi48: Option<super::Hsi48Config>, pub hsi48: Option<super::Hsi48Config>,
/// PLL Configuration
pub pll: Option<Pll>,
/// If PLL is requested as the main clock source in the `sys` field then the PLL configuration
/// MUST turn on the PLLR output.
pub ahb_pre: AHBPrescaler,
pub apb1_pre: APBPrescaler,
/// Low-Speed Clock Configuration
pub ls: super::LsConfig,
pub low_power_run: bool,
pub voltage_range: VoltageRange,
/// Per-peripheral kernel clock selection muxes /// Per-peripheral kernel clock selection muxes
pub mux: super::mux::ClockMux, pub mux: super::mux::ClockMux,
} }
@ -88,248 +92,218 @@ impl Default for Config {
#[inline] #[inline]
fn default() -> Config { fn default() -> Config {
Config { Config {
sys: Sysclk::HSI(HSIPrescaler::DIV1), hsi: true,
ahb_pre: AHBPrescaler::DIV1, hse: None,
apb_pre: APBPrescaler::DIV1, sys: Sysclk::HSI,
low_power_run: false,
ls: Default::default(),
#[cfg(crs)] #[cfg(crs)]
hsi48: Some(Default::default()), hsi48: Some(Default::default()),
pll: None,
ahb_pre: AHBPrescaler::DIV1,
apb1_pre: APBPrescaler::DIV1,
low_power_run: false,
ls: Default::default(),
voltage_range: VoltageRange::RANGE1,
mux: Default::default(), mux: Default::default(),
} }
} }
} }
impl PllConfig { #[derive(Default)]
pub(crate) fn init(self) -> (Hertz, Option<Hertz>, Option<Hertz>) { pub struct PllFreq {
let (src, input_freq) = match self.source { pub pll_p: Option<Hertz>,
PllSource::HSI => (vals::Pllsrc::HSI, HSI_FREQ), pub pll_q: Option<Hertz>,
PllSource::HSE(freq, _) => (vals::Pllsrc::HSE, freq), pub pll_r: Option<Hertz>,
};
let m_freq = input_freq / self.m;
// RM0454 § 5.4.4:
// > Caution: The software must set these bits so that the PLL input frequency after the
// > /M divider is between 2.66 and 16 MHz.
debug_assert!(m_freq.0 >= 2_660_000 && m_freq.0 <= 16_000_000);
let n_freq = m_freq * self.n as u32;
// RM0454 § 5.4.4:
// > Caution: The software must set these bits so that the VCO output frequency is between
// > 64 and 344 MHz.
debug_assert!(n_freq.0 >= 64_000_000 && n_freq.0 <= 344_000_000);
let r_freq = n_freq / self.r;
// RM0454 § 5.4.4:
// > Caution: The software must set this bitfield so as not to exceed 64 MHz on this clock.
debug_assert!(r_freq.0 <= 64_000_000);
let q_freq = self.q.map(|q| n_freq / q);
let p_freq = self.p.map(|p| n_freq / p);
// RM0454 § 5.2.3:
// > To modify the PLL configuration, proceed as follows:
// > 1. Disable the PLL by setting PLLON to 0 in Clock control register (RCC_CR).
RCC.cr().modify(|w| w.set_pllon(false));
// > 2. Wait until PLLRDY is cleared. The PLL is now fully stopped.
while RCC.cr().read().pllrdy() {}
// > 3. Change the desired parameter.
// Enable whichever clock source we're using, and wait for it to become ready
match self.source {
PllSource::HSI => {
RCC.cr().write(|w| w.set_hsion(true));
while !RCC.cr().read().hsirdy() {}
}
PllSource::HSE(_, mode) => {
RCC.cr().write(|w| {
w.set_hsebyp(mode != HseMode::Oscillator);
w.set_hseon(true);
});
while !RCC.cr().read().hserdy() {}
}
}
// Configure PLLCFGR
RCC.pllcfgr().modify(|w| {
w.set_pllr(self.r);
w.set_pllren(false);
w.set_pllq(self.q.unwrap_or(Pllq::DIV2));
w.set_pllqen(false);
w.set_pllp(self.p.unwrap_or(Pllp::DIV2));
w.set_pllpen(false);
w.set_plln(self.n);
w.set_pllm(self.m);
w.set_pllsrc(src)
});
// > 4. Enable the PLL again by setting PLLON to 1.
RCC.cr().modify(|w| w.set_pllon(true));
// Wait for the PLL to become ready
while !RCC.cr().read().pllrdy() {}
// > 5. Enable the desired PLL outputs by configuring PLLPEN, PLLQEN, and PLLREN in PLL
// > configuration register (RCC_PLLCFGR).
RCC.pllcfgr().modify(|w| {
// We'll use R for system clock, so enable that unconditionally
w.set_pllren(true);
// We may also use Q or P
w.set_pllqen(self.q.is_some());
w.set_pllpen(self.p.is_some());
});
(r_freq, q_freq, p_freq)
}
} }
pub(crate) unsafe fn init(config: Config) { pub(crate) unsafe fn init(config: Config) {
let mut pll1_q_freq = None; // Configure HSI
let mut pll1_p_freq = None; let hsi = match config.hsi {
false => {
let (sys_clk, sw) = match config.sys { RCC.cr().modify(|w| w.set_hsion(false));
Sysclk::HSI(div) => { None
// Enable HSI }
RCC.cr().write(|w| { true => {
w.set_hsidiv(div); RCC.cr().modify(|w| w.set_hsion(true));
w.set_hsion(true)
});
while !RCC.cr().read().hsirdy() {} while !RCC.cr().read().hsirdy() {}
Some(HSI_FREQ)
(HSI_FREQ / div, Sw::HSI)
} }
Sysclk::HSE(freq, mode) => { };
// Enable HSE
RCC.cr().write(|w| { // Configure HSE
w.set_hseon(true); let hse = match config.hse {
w.set_hsebyp(mode != HseMode::Oscillator); 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() {} while !RCC.cr().read().hserdy() {}
Some(hse.freq)
(freq, Sw::HSE)
}
Sysclk::PLL(pll) => {
let (r_freq, q_freq, p_freq) = pll.init();
pll1_q_freq = q_freq;
pll1_p_freq = p_freq;
(r_freq, Sw::PLL1_R)
}
Sysclk::LSI => {
// Enable LSI
RCC.csr().write(|w| w.set_lsion(true));
while !RCC.csr().read().lsirdy() {}
(super::LSI_FREQ, Sw::LSI)
} }
}; };
// Determine the flash latency implied by the target clock speed // Configure HSI48 if required
// RM0454 § 3.3.4: #[cfg(crs)]
let target_flash_latency = if sys_clk.0 <= 24_000_000 { let hsi48 = config.hsi48.map(super::init_hsi48);
Latency::WS0
} else if sys_clk.0 <= 48_000_000 { let pll = config
Latency::WS1 .pll
} else { .map(|pll_config| {
Latency::WS2 let src_freq = match pll_config.source {
PllSource::HSI => unwrap!(hsi),
PllSource::HSE => unwrap!(hse),
_ => unreachable!(),
}; };
// Increase the number of cycles we wait for flash if the new value is higher // Disable PLL before configuration
// There's no harm in waiting a little too much before the clock change, but we'll RCC.cr().modify(|w| w.set_pllon(false));
// crash immediately if we don't wait enough after the clock change while RCC.cr().read().pllrdy() {}
let mut set_flash_latency_after = false;
let in_freq = src_freq / pll_config.prediv;
assert!(max::PLL_IN.contains(&in_freq));
let internal_freq = in_freq * pll_config.mul;
assert!(max::PLL_VCO.contains(&internal_freq));
RCC.pllcfgr().write(|w| {
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.divp.map(|div_p| {
RCC.pllcfgr().modify(|w| {
w.set_pllp(div_p);
w.set_pllpen(true);
});
let freq = internal_freq / div_p;
assert!(max::PLL_P.contains(&freq));
freq
});
let pll_q_freq = pll_config.divq.map(|div_q| {
RCC.pllcfgr().modify(|w| {
w.set_pllq(div_q);
w.set_pllqen(true);
});
let freq = internal_freq / div_q;
assert!(max::PLL_Q.contains(&freq));
freq
});
let pll_r_freq = pll_config.divr.map(|div_r| {
RCC.pllcfgr().modify(|w| {
w.set_pllr(div_r);
w.set_pllren(true);
});
let freq = internal_freq / div_r;
assert!(max::PLL_R.contains(&freq));
freq
});
// Enable the PLL
RCC.cr().modify(|w| w.set_pllon(true));
while !RCC.cr().read().pllrdy() {}
PllFreq {
pll_p: pll_p_freq,
pll_q: pll_q_freq,
pll_r: pll_r_freq,
}
})
.unwrap_or_default();
let sys = match config.sys {
Sysclk::HSI => unwrap!(hsi),
Sysclk::HSE => unwrap!(hse),
Sysclk::PLL1_R => unwrap!(pll.pll_r),
_ => unreachable!(),
};
assert!(max::SYSCLK.contains(&sys));
// Calculate the AHB frequency (HCLK), among other things so we can calculate the correct flash read latency.
let hclk = sys / config.ahb_pre;
assert!(max::HCLK.contains(&hclk));
let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre);
assert!(max::PCLK.contains(&pclk1));
let latency = match (config.voltage_range, hclk.0) {
(VoltageRange::RANGE1, ..=24_000_000) => Latency::WS0,
(VoltageRange::RANGE1, ..=48_000_000) => Latency::WS1,
(VoltageRange::RANGE1, _) => Latency::WS2,
(VoltageRange::RANGE2, ..=8_000_000) => Latency::WS0,
(VoltageRange::RANGE2, ..=16_000_000) => Latency::WS1,
(VoltageRange::RANGE2, _) => Latency::WS2,
_ => unreachable!(),
};
// Configure flash read access latency based on voltage scale and frequency (RM0444 3.3.4)
FLASH.acr().modify(|w| { FLASH.acr().modify(|w| {
// Is the current flash latency less than what we need at the new SYSCLK? w.set_latency(latency);
if w.latency().to_bits() <= target_flash_latency.to_bits() {
// We must increase the number of wait states now
w.set_latency(target_flash_latency)
} else {
// We may decrease the number of wait states later
set_flash_latency_after = true;
}
// RM0454 § 3.3.5:
// > Prefetch is enabled by setting the PRFTEN bit of the FLASH access control register
// > (FLASH_ACR). This feature is useful if at least one wait state is needed to access the
// > Flash memory.
//
// Enable flash prefetching if we have at least one wait state, and disable it otherwise.
w.set_prften(target_flash_latency.to_bits() > 0);
}); });
if !set_flash_latency_after { // Spin until the effective flash latency is set.
// Spin until the effective flash latency is compatible with the clock change while FLASH.acr().read().latency() != latency {}
while FLASH.acr().read().latency().to_bits() < target_flash_latency.to_bits() {}
}
// Configure SYSCLK source, HCLK divisor, and PCLK divisor all at once // Now that boost mode and flash read access latency are configured, set up SYSCLK
let (sw, hpre, ppre) = (sw.into(), config.ahb_pre, config.apb_pre);
RCC.cfgr().modify(|w| { RCC.cfgr().modify(|w| {
w.set_sw(sw); w.set_sw(config.sys);
w.set_hpre(hpre); w.set_hpre(config.ahb_pre);
w.set_ppre(ppre); w.set_ppre(config.apb1_pre);
}); });
if set_flash_latency_after {
// We can make the flash require fewer wait states
// Spin until the SYSCLK changes have taken effect
loop {
let cfgr = RCC.cfgr().read();
if cfgr.sw() == sw && cfgr.hpre() == hpre && cfgr.ppre() == ppre {
break;
}
}
// Set the flash latency to require fewer wait states
FLASH.acr().modify(|w| w.set_latency(target_flash_latency));
}
let ahb_freq = sys_clk / config.ahb_pre;
let (apb_freq, apb_tim_freq) = match config.apb_pre {
APBPrescaler::DIV1 => (ahb_freq, ahb_freq),
pre => {
let freq = ahb_freq / pre;
(freq, freq * 2u32)
}
};
if config.low_power_run { if config.low_power_run {
assert!(sys_clk.0 <= 2_000_000); assert!(sys <= Hertz(2_000_000));
PWR.cr1().modify(|w| w.set_lpr(true)); PWR.cr1().modify(|w| w.set_lpr(true));
} }
let rtc = config.ls.init(); let rtc = config.ls.init();
let lse_freq = config.ls.lse.map(|lse| lse.frequency);
let hsi_freq = (sw == Sw::HSI).then_some(HSI_FREQ);
let hsi_div_8_freq = hsi_freq.map(|f| f / 8u32);
let lsi_freq = (sw == Sw::LSI).then_some(super::LSI_FREQ);
let hse_freq = (sw == Sw::HSE).then_some(sys_clk);
#[cfg(crs)]
let hsi48 = config.hsi48.map(super::init_hsi48);
#[cfg(not(crs))]
let hsi48: Option<Hertz> = None;
config.mux.init(); config.mux.init();
set_clocks!( set_clocks!(
sys: Some(sys_clk), sys: Some(sys),
hclk1: Some(ahb_freq), hclk1: Some(hclk),
pclk1: Some(apb_freq), pclk1: Some(pclk1),
pclk1_tim: Some(apb_tim_freq), pclk1_tim: Some(pclk1_tim),
hsi: hsi_freq, pll1_p: pll.pll_p,
pll1_q: pll.pll_q,
pll1_r: pll.pll_r,
hsi: hsi,
hse: hse,
#[cfg(crs)]
hsi48: hsi48, hsi48: hsi48,
hsi_div_8: hsi_div_8_freq,
hse: hse_freq,
lse: lse_freq,
lsi: lsi_freq,
pll1_q: pll1_q_freq,
pll1_p: pll1_p_freq,
rtc: rtc, rtc: rtc,
hsi_div_488: None, hsi_div_8: hsi.map(|h| h / 8u32),
hsi_div_488: hsi.map(|h| h / 488u32),
// TODO
lsi: None,
lse: None,
); );
} }
mod max {
use core::ops::RangeInclusive;
use crate::time::Hertz;
pub(crate) const HSE_OSC: RangeInclusive<Hertz> = Hertz(4_000_000)..=Hertz(48_000_000);
pub(crate) const HSE_BYP: RangeInclusive<Hertz> = Hertz(0)..=Hertz(48_000_000);
pub(crate) const SYSCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(64_000_000);
pub(crate) const PCLK: RangeInclusive<Hertz> = Hertz(8)..=Hertz(64_000_000);
pub(crate) const HCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(64_000_000);
pub(crate) const PLL_IN: RangeInclusive<Hertz> = Hertz(2_660_000)..=Hertz(16_000_000);
pub(crate) const PLL_VCO: RangeInclusive<Hertz> = Hertz(96_000_000)..=Hertz(344_000_000);
pub(crate) const PLL_P: RangeInclusive<Hertz> = Hertz(3_090_000)..=Hertz(122_000_000);
pub(crate) const PLL_Q: RangeInclusive<Hertz> = Hertz(12_000_000)..=Hertz(128_000_000);
pub(crate) const PLL_R: RangeInclusive<Hertz> = Hertz(12_000_000)..=Hertz(64_000_000);
}

View file

@ -16,15 +16,16 @@ async fn main(_spawner: Spawner) {
let mut config = PeripheralConfig::default(); let mut config = PeripheralConfig::default();
{ {
use embassy_stm32::rcc::*; use embassy_stm32::rcc::*;
config.rcc.hsi = true;
config.rcc.sys = Sysclk::PLL(PllConfig { config.rcc.pll = Some(Pll {
source: PllSource::HSI, source: PllSource::HSI,
m: Pllm::DIV1, prediv: PllPreDiv::DIV1,
n: Plln::MUL16, mul: PllMul::MUL16,
r: Pllr::DIV4, // CPU clock comes from PLLR (HSI (16MHz) / 1 * 16 / 4 = 64MHz) divp: None,
q: Some(Pllq::DIV2), // TIM1 or TIM15 can be sourced from PLLQ (HSI (16MHz) / 1 * 16 / 2 = 128MHz) divq: Some(PllQDiv::DIV2), // 16 / 1 * 16 / 2 = 128 Mhz
p: None, divr: Some(PllRDiv::DIV4), // 16 / 1 * 16 / 4 = 64 Mhz
}); });
config.rcc.sys = Sysclk::PLL1_R;
// configure TIM1 mux to select PLLQ as clock source // configure TIM1 mux to select PLLQ as clock source
// https://www.st.com/resource/en/reference_manual/rm0444-stm32g0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf // https://www.st.com/resource/en/reference_manual/rm0444-stm32g0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf

View file

@ -260,6 +260,19 @@ pub fn config() -> Config {
#[allow(unused_mut)] #[allow(unused_mut)]
let mut config = Config::default(); let mut config = Config::default();
#[cfg(feature = "stm32g071rb")]
{
config.rcc.hsi = true;
config.rcc.pll = Some(Pll {
source: PllSource::HSI,
prediv: PllPreDiv::DIV1,
mul: PllMul::MUL16,
divp: None,
divq: None,
divr: Some(PllRDiv::DIV4), // 16 / 1 * 16 / 4 = 64 Mhz
});
config.rcc.sys = Sysclk::PLL1_R;
}
#[cfg(feature = "stm32wb55rg")] #[cfg(feature = "stm32wb55rg")]
{ {
config.rcc = embassy_stm32::rcc::WPAN_DEFAULT; config.rcc = embassy_stm32::rcc::WPAN_DEFAULT;