The puzzle
A chip’s datasheet says it runs at 125 MHz, yet the board carries a 12 MHz crystal, the chip boots at around 6 MHz, and the USB block insists on exactly 48 MHz. None of these numbers is wrong. Where do all these frequencies come from, and what happens to the UART when someone changes one of them?
STEP 1
Three kinds of source
| source | start-up | accuracy | typical use |
|---|---|---|---|
| internal RC or ring oscillator | microseconds | poor: the RP2040’s boot clock is 1.8–11.3 MHz, typically 6.5 | booting, low-power modes |
| crystal oscillator | milliseconds | parts per million (ppm) | the accurate reference |
| PLL from a reference | lock time, microseconds to milliseconds | that of its reference | high-speed clocks |
Chips boot on the internal oscillator because it starts at once (unit 5, lesson 1); firmware then starts the crystal and, from it, the phase-locked loop (PLL) that makes the fast clocks.
STEP 2
A PLL multiplies with integer dividers
A PLL contains a voltage-controlled oscillator (VCO) whose output, divided by a feedback divider FBDIV, is locked to the reference. The VCO therefore runs at reference × FBDIV, and post-dividers bring it down to the frequency you want:
with the reference divider REFDIV = 1 in the SDK’s default set-up.
↑ This step uses the figure at the top of the page.
Every PLL has limits: the RP2040’s VCO must stay between 750 and 1600 MHz, FBDIV between 16 and 320, each post-divider between 1 and 7. The STM32F4 writes the same idea with other names, f = f_in/PLLM × PLLN/PLLP, with its own ranges (the input to its VCO must be 1–2 MHz). Only frequencies that the integers can reach are available: 125 MHz is 12 × 125 ÷ 6 ÷ 2, and 48 MHz is 12 × 100 ÷ 5 ÷ 5.
STEP 3
The clock tree
The PLLs feed a clock tree: each group of peripherals gets its clock from a multiplexer and a divider. After the pico-sdk’s clocks_init(), the RP2040 looks like this:
After the SDK’s clocks_init(), the RP2040 runs clk_sys from the system PLL at 125 MHz, clk_usb, clk_adc and clk_rtc from the 48 MHz USB PLL, and clk_ref from the 12 MHz crystal (pico-sdk clocks.c). The dividers differ: clk_sys and clk_rtc have 24.8 fixed-point dividers, clk_ref, clk_usb and clk_adc divide only by 1, 2 or 3, and clk_peri has none (pico-sdk hardware_regs/clocks.h). A fractional setting gives the right average frequency with an uneven period.
Three consequences matter for firmware:
- A peripheral’s rate is set by its clock, not by the CPU’s. The UART’s baud rate is computed from clk_peri; change clk_sys, which feeds clk_peri, and every baud rate derived from it is wrong until reconfigured.
- Dividers differ from clock to clock. On the RP2040, clk_sys, clk_rtc and the GPOUT clocks have fixed-point dividers with 8 fractional bits; clk_ref, clk_usb and clk_adc divide only by 1, 2 or 3; clk_peri has no divider. A fractional setting gives the right average frequency, but individual periods alternate between two lengths, which is jitter.
- Clocks cost power. Gating off the clocks of unused blocks, and running the tree slower, is the first step of power saving (unit 14).
STEP 4
Accuracy in ppm
An oscillator’s error is quoted in parts per million. A clock that is off by e ppm gains or loses e microseconds every second:
STEP 5
Worked example: two clocks for one board
A board uses the 12 MHz crystal and needs 125 MHz for the cores and 48 MHz for USB.
- System PLL: FBDIV = 125 gives a VCO of 1500 MHz, inside 750–1600 MHz; 1500 ÷ 6 ÷ 2 = 125 MHz.
- USB PLL: FBDIV = 100 gives 1200 MHz; 1200 ÷ 5 ÷ 5 = 48 MHz.
If the crystal is specified at ±20 ppm (an illustrative figure; use your crystal’s datasheet), a clock kept by it drifts by at most
fine for timestamps, not for a wall clock left running for months without correction. The ring oscillator’s frequency is only known to lie somewhere in 1.8–11.3 MHz across parts, voltage and temperature, so it cannot keep time without calibration, which is why it is only used to boot.
MYTHS AND FACTS
Common misconceptions
The chip runs at the crystal’s frequency
The crystal is the reference; PLLs and dividers make the actual clocks.
A PLL can make any frequency
An integer-N PLL such as the RP2040’s or the STM32F4’s makes only ratios of integers within the divider ranges, with the VCO inside its range (fractional-N PLLs on some other chips relax this).
Changing the CPU clock only changes CPU speed
Everything downstream in the tree changes too: baud rates, timer ticks, PWM frequencies.
A fractional divider gives a clean clock
It gives the right average with alternating period lengths.
Check yourself
Answer in your head, then open the card.
With a 12 MHz reference, FBDIV = 110 and post-dividers 5 and 2, what is the VCO and the output? Is it valid?
VCO = 1320 MHz (within 750–1600); output = 1320 / 10 = 132 MHz. Valid for the PLL; whether the chip is rated to run there is a datasheet question.
The UART is set for 115 200 baud and then the firmware raises clk_sys from 125 to 150 MHz, with clk_peri following clk_sys. What happens to the baud rate?
It rises by the same factor, to 138 240 baud: 20 % off, far outside what a UART tolerates, until the baud-rate divisor is recomputed.
A clock with an 8-fractional-bit divider (on the RP2040, clk_rtc, clk_sys or a GPOUT clock fed from the USB PLL) makes 7 MHz from 48 MHz. What integer and fractional parts does it use, and is the output clean?
48 / 7 = 6.857…; integer 6, fraction round(0.857 × 256) = 219. The average is about 7.0 MHz, but periods alternate between 6 and 7 input cycles: jitter.
How far can a ±50 ppm oscillator drift in one hour?
50 × 10⁻⁶ × 3600 s = 0.18 s.
Sources (4)
- Raspberry Pi Ltd, pico-sdk 1.5.1, hardware_pll/pll.c, pll.h and hardware_clocks/scripts/vcocalc.py — fbdiv = vco_freq / ref_freq, asserted 16–320; post dividers 1–7, “post_div1 should be >= post_div2”; VCO between PICO_PLL_VCO_MIN_FREQ_KHZ 750 MHz and PICO_PLL_VCO_MAX_FREQ_KHZ 1600 MHz; vcocalc: “Use a lower VCO frequency when possible. This reduces power consumption, at the cost of increased jitter”
- Raspberry Pi Ltd, pico-sdk 1.5.1, hardware_clocks/clocks.c and clocks.h — PLL_SYS_VCO_FREQ 1500 MHz, POSTDIV1 6, POSTDIV2 2; PLL_USB_VCO_FREQ 1200 MHz, 5, 5; clocks_init: clk_ref = XOSC 12 MHz, clk_sys = PLL SYS 125 MHz, clk_usb and clk_adc = PLL USB 48 MHz, clk_rtc = 48 MHz / 1024 = 46 875 Hz, clk_peri = clk_sys; dividers take src_freq << 8 / freq; hardware_regs/clocks.h: CLK_SYS_DIV and CLK_RTC_DIV have 24 integer and 8 fractional bits, CLK_REF_DIV, CLK_USB_DIV and CLK_ADC_DIV a 2-bit integer only, and “CLK PERI … No dividers”; hardware_uart/uart.c computes baud_rate_div = 8 × clock_get_hz(clk_peri) / baudrate
- Raspberry Pi Ltd, pico-bootrom-rp2040, bootrom/bootrom_main.c, and pico-sdk 1.5.1 hardware_timer/timer.h — the ring-oscillator boot clock: “CLK_SYS FREQ ON STARTUP (in MHz) min 1.8, typ 6.5, max 11.3”; timer.h: the timer uses “a one microsecond reference that is generated in the Watchdog … derived from the clk_ref”
- STMicroelectronics, stm32f4xx-hal-driver, Inc/stm32f4xx_hal_rcc.h and stm32f4xx_hal_rcc_ex.h — PLLM 2–63, set “to ensure that the VCO input frequency ranges from 1 to 2 MHz. It is recommended to select a frequency of 2 MHz to limit PLL jitter”; PLLN 50–432 (192–432 on the STM32F411); PLLP 2, 4, 6 or 8 for the system clock