embassy-rs/embassy · error
pll vco_clk out of range
Error message
pll vco_clk out of range: {} What it means
The computed VCO output frequency (ref_clk * mul, after the feedback divider) must fit inside VCO_RANGE (medium VCO) or, when the reference range permits it, VCO_WIDE_RANGE (wide VCO). If vco_clk fits neither window, init_pll panics with the offending value, preventing an impossible hardware configuration.
Solutions
- Adjust pll.mul so vco_clk lands inside VCO_RANGE (medium) — or inside VCO_WIDE_RANGE with a reference >= 2 MHz.
- Raise prediv above the <2 MHz boundary if you need WideVco, so wide_allowed becomes true.
- Recompute the output dividers (divp/divq/divr) to still hit your target peripheral clocks.
Example fix
// before
// ref_clk = 1.5 MHz (48MHz/32), mul = 300 -> vco 450 MHz; wide not allowed (Range1) -> panic
let pll = Pll { prediv: PllPreDiv::Div32, mul: Mul::Mul300, ..Default::default() };
// after
let pll = Pll { prediv: PllPreDiv::Div8, mul: Mul::Mul75, ..Default::default() }; // ref 6 MHz, wide allowed Defensive patterns
Strategy: validation
Validate before calling
const VCO_MIN: u32 = 150_000_000; const VCO_MAX: u32 = 420_000_000; const VCO_WIDE_MAX: u32 = 832_000_000;
let vco = ref_hz * pll.mul as u32;
let in_medium = (VCO_MIN..=VCO_MAX).contains(&vco);
let in_wide = ref_hz >= 2_000_000 && (VCO_MIN..=VCO_WIDE_MAX).contains(&vco);
assert!(in_medium || in_wide, "vco {} Hz out of range", vco); Prevention
- Keep ref_clk >= 2 MHz so wide-VCO mode is available when needed.
- Solve for mul from the target VCO value instead of tuning by hand.
- Re-derive output dividers whenever mul changes.
When it happens
Trigger: Configuring pll.mul so that ref_clk * mul falls between the two hardware VCO windows or above the wide-VCO max (e.g. a mid-range speed in the ~420 MHz gap, or beyond the wide limit). Also triggered by Range1 (ref_clk < 2 MHz), where wide mode is disallowed (wide_allowed == false).
Common situations: Tuning mul by hand for a specific target frequency and landing in the gap; using a very small prediv (<2 MHz reference) that forces medium VCO while mul requires wide range; miscomputed math after changing prediv.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- pll ref_clk out of range
- if PLL source is HSI, PLL prediv must be 2.
- if PLL source is PLL2, Config::pll2 must also be set.
- Source must be equal across all enabled PLLs.
- must not set PllSource::Disable
AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10).
Data as JSON: /api/errors/a5cf8fa6576de248.
Report an issue: GitHub.
Appendix: source
Thrown at embassy-stm32/src/rcc/h.rs:998
// be chosen when the reference clock frequency is lower than 2 MHz.
let wide_allowed = ref_range != Pllrge::Range1;
#[cfg(any(stm32h743, stm32h730))]
let vco_clk = match config.fracn {
Some(fracn) => {
Hertz::hz((ref_clk.0 as f32 * ((config.mul.to_bits() + 1) as f32 + (fracn as f32 / 8192.0))) as u32)
}
None => ref_clk * config.mul,
};
#[cfg(not(any(stm32h743, stm32h730)))]
let vco_clk = ref_clk * config.mul;
let vco_range = if VCO_RANGE.contains(&vco_clk) {
Pllvcosel::MediumVco
} else if wide_allowed && VCO_WIDE_RANGE.contains(&vco_clk) {
Pllvcosel::WideVco
} else {
panic!("pll vco_clk out of range: {}", vco_clk)
};
let p = config.divp.map(|div| {
if num == 0 {
// on PLL1, DIVP must be even for most series.
// The enum value is 1 less than the divider, so check it's odd.
#[cfg(not(any(pwr_h7rm0468, stm32h7rs)))]
assert!(div.to_bits() % 2 == 1);
#[cfg(pwr_h7rm0468)]
assert!(div.to_bits() % 2 == 1 || div.to_bits() == 0);
}
vco_clk / div
});
let q = config.divq.map(|div| vco_clk / div);
let r = config.divr.map(|div| vco_clk / div);
#[cfg(stm32h7rs)]
let s = config.divs.map(|div| vco_clk / div);View on GitHub (pinned to 463a07b963)