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

  1. Adjust pll.mul so vco_clk lands inside VCO_RANGE (medium) — or inside VCO_WIDE_RANGE with a reference >= 2 MHz.
  2. Raise prediv above the <2 MHz boundary if you need WideVco, so wide_allowed becomes true.
  3. 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

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


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)