embassy-rs/embassy · error
USB clock should be 48Mhz but is
Error message
USB clock should be 48Mhz but is {} Hz. Please double-check your RCC settings. What it means
On all other STM32 USB parts (not H7RS/N6/U5-HS/WBA-HS), the USB clock must be 48 MHz with at most 0.25% deviation (±120 kHz), because the OTG core derives its timing directly from that clock. embassy-stm32 validates this in common_init and panics when abs_diff(freq, 48_000_000) > 120_000, since an off-spec clock breaks USB enumeration/timing.
Solutions
- Reconfigure RCC so the USB kernel clock is exactly 48 MHz (PLL Q divider, USBPRE/OTGFSPRE prescaler)
- On parts with HSI48, enable HSI48 with CRS sync to produce a USB-legal 48 MHz without a crystal
- Verify the actual frequency with rcc.usb().frequency() (or T::frequency()) before constructing the USB driver
- Start from the official embassy-stm32 USB example for your board, which has a known-good clock tree
Example fix
// before config.pll.q = 5; // -> usb 47.5 MHz, out of 0.25% tolerance // after config.pll.q = 4; // -> usb 48 MHz // sanity check before init: assert!(rcc.usb().frequency().0.abs_diff(48_000_000) <= 120_000);
Defensive patterns
Strategy: validation
Validate before calling
let freq = <periph>::frequency();
assert!(freq.0.abs_diff(48_000_000) <= 120_000, "USB clk {} Hz outside 48MHz ±0.25%", freq.0); Type guard
fn usb_clk_48mhz_ok(hz: u32) -> bool { hz.abs_diff(48_000_000) <= 120_000 } Prevention
- Enable HSI48+CRS on parts without a suitable PLL path
- Keep the USB prescaler/PLL-Q setting untouched when tuning other clocks
- Verify frequency at startup before constructing the USB device
- Compare against the 0.25% (±120 kHz) tolerance, not exact equality, if using CRS-trimmed sources
When it happens
Trigger: Initializing a USB driver on any non-exception part (e.g. F1/F4/L4/G4/H7-FS) while the USB peripheral's kernel clock is not within 47.88–48.12 MHz.
Common situations: RCC PLL tuned for 72/168 MHz sysclk where the USB prescaler yields 47 or 48.5 MHz; CRS/HSI48 not enabled on parts lacking a crystal; audio-oriented clock trees that intentionally detune USB; running on a board without an external 48 MHz-capable PLL path.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- USB clock should be one of 16, 19.2, 20, 24, 26, 32Mhz but…
- LSE frequency more than 5% off from 32.768 kHz, cannot use…
- MSIx auto-calibration is enabled for a source that has not…
- LSE frequency more than 5% off from 32.768 kHz, cannot use…
- cannot select OTG_HS reference clock with source frequency…
AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10).
Data as JSON: /api/errors/3130ee1659e68d6a.
Report an issue: GitHub.
Appendix: source
Thrown at embassy-stm32/src/usb/mod.rs:37
if ![16_000_000, 19_200_000, 20_000_000, 24_000_000, 26_000_000, 32_000_000].contains(&freq.0) {
panic!(
"USB clock should be one of 16, 19.2, 20, 24, 26, 32Mhz but is {} Hz. Please double-check your RCC settings.",
freq.0
)
}
// On the N6 the OTG core always runs off its integrated High-Speed PHY, whose reference
// clock must be 19.2, 20 or 24 MHz (RM0486 Rev 4, USBPHYC_CR.FSEL, p. 3929). Panics
// naming the offending frequency; the same mapping programs FSEL in `T::phy_init()`.
#[cfg(stm32n6)]
let _ = fsel_from_freq(freq);
// Check frequency is within the 0.25% tolerance allowed by the spec.
// Clock might not be exact 48Mhz due to rounding errors in PLL calculation, or if the user
// has tight clock restrictions due to something else (like audio).
#[cfg(not(any(stm32h7rs, stm32n6, all(stm32u5, peri_usb_otg_hs), all(stm32wba, peri_usb_otg_hs))))]
if freq.0.abs_diff(48_000_000) > 120_000 {
panic!(
"USB clock should be 48Mhz but is {} Hz. Please double-check your RCC settings.",
freq.0
)
}
#[cfg(any(stm32l4, stm32l5, stm32wb, stm32u0))]
critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true)));
#[cfg(pwr_h5)]
critical_section::with(|_| crate::pac::PWR.usbscr().modify(|w| w.set_usb33sv(true)));
#[cfg(stm32h7)]
{
// If true, VDD33USB is generated by internal regulator from VDD50USB
// If false, VDD33USB and VDD50USB must be suplied directly with 3.3V (default on nucleo)
// TODO: unhardcode
let internal_regulator = false;
View on GitHub (pinned to 463a07b963)