How Does the STM32 Clock Tree Work?
Last updated 26 June 2026 · 10 min read
Direct Answer
The STM32 clock tree is the hardware path that takes a clock source — either the internal HSI RC oscillator or an external HSE crystal — multiplies it through the PLL to produce the system clock (SYSCLK), then divides SYSCLK through the AHB, APB1, and APB2 bus prescalers to supply the CPU core and each peripheral at an appropriate frequency. Every peripheral's input clock is derived from this tree, so understanding it is a prerequisite for correctly configuring UART baud rates, timer periods, ADC sample rates, and any other peripheral that depends on a specific clock.
Detailed Explanation
Every STM32 microcontroller has a clock tree — a hierarchy of oscillators, a PLL, and prescalers — that determines how fast the CPU runs and what clock frequency each peripheral receives. Getting this right matters because nearly every peripheral parameter that has "frequency" in it — baud rate, timer period, ADC conversion rate, I2S sample rate — is calculated as a ratio of the peripheral's input clock.
Clock Sources
Four hardware oscillators are available on most STM32 families:
| Source | Type | Typical frequency | Accuracy | Primary use |
|---|---|---|---|---|
| HSI | Internal RC | 16 MHz (F4/F7/G4), 8 MHz (F1), 64 MHz (G0/G4 on newer revisions) | ±1% factory-trimmed; thermally variable | Default startup clock; no external components required |
| HSE | External crystal or oscillator input | 4–26 MHz (device-dependent; 8 MHz or 25 MHz are common board choices) | PPM-level; depends on crystal quality and load capacitors | Recommended for USB, UART at high baud rates, audio, and any timing-critical peripheral |
| LSI | Internal RC | ~32 kHz | ±10–20% — unreliable for precision timing | IWDG watchdog timer, RTC when accuracy is not required |
| LSE | External 32.768 kHz crystal | 32.768 kHz | PPM-level | RTC, accurate real-time timekeeping, calendar |
HSI starts instantly on power-up, which is why STM32 boots from it by default. It is good enough to reach the target SYSCLK through the PLL, but its thermal variation makes it unsuitable for USB (which needs ±500 ppm) or high-baud UART links where baud rate accuracy below 1–2% is required.
HSE requires an external crystal and 1–5 ms to stabilise. Firmware must poll or wait on the HSE ready flag (RCC_CR_HSERDY) before attempting to switch SYSCLK to a PLL fed by HSE. If the crystal is missing, has wrong load capacitors, or has too long traces, HSE will never become ready — and the startup timeout expiry is one cause of unexpected STM32 resets in early hardware prototypes.
LSE is separate from the main clock tree. It feeds only the RTC and backup domain. The 32.768 kHz frequency is chosen because it divides cleanly by powers of 2 to produce 1 Hz for a real-time clock.
The PLL
The PLL multiplies the HSI or HSE input frequency up to the target SYSCLK. On the STM32F4 (a representative example), the PLL has three tunable parameters:
PLL input = HSE (or HSI) ÷ PLLM [must land in 1–2 MHz range]
VCO output = PLL input × PLLN [PLLN: 50–432; VCO must be 64–432 MHz]
SYSCLK = VCO output ÷ PLLP [PLLP: 2, 4, 6, or 8]
A typical STM32F4 at 168 MHz from a 8 MHz HSE:
PLLM = 4 → PLL input = 8 ÷ 4 = 2 MHz
PLLN = 168 → VCO output = 2 × 168 = 336 MHz
PLLP = 2 → SYSCLK = 336 ÷ 2 = 168 MHz
Maximum SYSCLK per STM32 family:
| Family | Max SYSCLK | Notes |
|---|---|---|
| F0 | 48 MHz | Simpler PLL, no PLLP divider |
| F1 | 72 MHz | PLLMUL multiplier only |
| F4 | 180 MHz | Three-factor PLL (M/N/P); a separate PLLQ feeds USB/SDIO |
| F7 | 216 MHz | Same PLL architecture as F4 |
| G0 | 64 MHz | HSI can reach 64 MHz directly on some variants |
| G4 | 170 MHz | Fractional PLLN for precise frequency synthesis |
| H5 | 250 MHz | |
| H7 | 480 MHz | Three independent PLLs (PLL1/PLL2/PLL3) |
Bus Clock Dividers: AHB, APB1, APB2
SYSCLK is not used directly by every peripheral. A prescaler tree distributes it to several buses, each of which serves a group of peripherals:
SYSCLK
└─ AHB prescaler (/1, /2, /4 … /512) → HCLK
├─ CPU core, SysTick, DMA, FSMC, SDIO
├─ APB1 prescaler (/1, /2, /4, /8, /16) → PCLK1 [low-speed peripherals]
│ └─ USART2/3/4/5, I2C1/2/3, SPI2/3, TIM2–7, DAC, CAN
└─ APB2 prescaler (/1, /2, /4, /8, /16) → PCLK2 [high-speed peripherals]
└─ USART1/6, SPI1/4, TIM1/8/9/10/11, ADC1/2/3
HCLK is the CPU clock. SysTick and the NVIC interrupts run off HCLK — this is what interrupt latency and SysTick tick rate are measured against.
On the STM32F4 at 180 MHz, the bus limits are:
- AHB: HCLK ≤ 180 MHz → prescaler /1
- APB1: PCLK1 ≤ 45 MHz → prescaler /4 (180/4 = 45 MHz)
- APB2: PCLK2 ≤ 90 MHz → prescaler /2 (180/2 = 90 MHz)
The timer clock doubling rule catches many developers: if an APB prescaler is not 1, the timers on that bus receive 2× their APBx clock as their timer clock (TIMxCLK). At 180 MHz with APB1/4:
- PCLK1 = 45 MHz
- TIM2 (APB1 peripheral) timer clock = 45 × 2 = 90 MHz
A developer who calculates timer reload values against 45 MHz will get periods twice as long as expected.
Flash Wait States
As SYSCLK rises, the CPU runs faster than the flash memory can supply instructions. The flash controller needs additional wait states inserted to avoid returning incorrect data. On the F4, the required wait states for VCC = 2.7–3.6 V are:
| HCLK range | Flash wait states (WS) |
|---|---|
| 0–30 MHz | 0 WS |
| 30–60 MHz | 1 WS |
| 60–90 MHz | 2 WS |
| 90–120 MHz | 3 WS |
| 120–150 MHz | 4 WS |
| 150–180 MHz | 5 WS |
The flash wait states must be configured before increasing SYSCLK — not after. Switching to a higher PLL frequency with insufficient wait states produces unpredictable instruction fetches and is one of the harder-to-diagnose causes of firmware crashes at startup.
Configuring with STM32CubeMX
The CubeMX "Clock Configuration" tab presents the entire clock tree as an interactive diagram. Entering the target HCLK value (e.g. 180) in the HCLK input box causes the resolver to automatically calculate PLL M/N/P and bus prescalers.
CubeMX generates a SystemClock_Config() function in main.c that programs the RCC registers in the correct order:
- Enable HSE (or HSI); poll ready flag.
- Configure PLL parameters.
- Enable PLL; poll PLL lock flag.
- Set flash wait states for the target frequency.
- Switch SYSCLK source to PLL.
- Set AHB/APB1/APB2 prescalers.
This sequence is important — CubeMX gets it right, but hand-written clock initialisation frequently misorders steps 4 and 5.
Practical Examples
An STM32F411 with an 8 MHz external crystal targeting 100 MHz:
PLLM = 4 → PLL input = 8 ÷ 4 = 2 MHz
PLLN = 200 → VCO = 2 × 200 = 400 MHz
PLLP = 4 → SYSCLK = 400 ÷ 4 = 100 MHz
Flash WS = 3 (100 MHz is in the 90–120 MHz band for 3.3 V supply)
APB1 = /2 → PCLK1 = 50 MHz; APB2 = /1 → PCLK2 = 100 MHz
A developer then configures USART1 (on APB2) at 115200 baud, expecting the baud rate divisor to be calculated from 100 MHz — correct. But if they had used USART2 (on APB1) and calculated against 100 MHz rather than 50 MHz, every baud divisor value would be wrong by a factor of 2.
Design Considerations
- Use HSE for anything timing-critical: UART at 115200 baud and above, USB, I2S audio codecs, and any sensor with a synchronous protocol running at the edge of its speed spec. The HSI's ±1% tolerance is adequate for slow GPIO toggling or I2C at 100 kHz, but insufficient for USB enumeration, which requires ±500 ppm. On F4 and F7 devices, the separate PLLQ divider must also produce exactly 48 MHz for USB FS to operate — a misconfigured PLLQ is one of the most common reasons the STM32 DFU bootloader fails to enumerate after asserting BOOT0 or jumping to system memory in software.
- Always increase flash wait states before raising SYSCLK: the correct sequence is (1) raise wait states, (2) switch PLL, (3) update SystemCoreClock. Reversing steps 1 and 2 causes the MCU to execute from flash that responds too slowly for the new clock — the resulting data corruption is non-deterministic and often intermittent, making it extremely hard to debug without an oscilloscope and knowledge of the failure mode.
- Check which APB bus each peripheral is on: the reference manual's RCC block diagram (or the
APB1ENR/APB2ENRregister map) lists which bus each peripheral is clocked from. HardcodingSystemCoreClockinto a UART baud rate calculation, rather than reading the correct APBx clock viaHAL_RCC_GetPCLK1Freq()orHAL_RCC_GetPCLK2Freq(), is the most common source of slightly-wrong baud rates. - Account for the timer clock doubling rule: timers on APBx buses with a prescaler other than 1 receive 2× their PCLK as their TIMxCLK. Always verify the actual timer input clock from the RCC section of the reference manual for the specific family being used — not from memory.
- HSE startup timeout handling: always implement a timeout on the HSE ready poll. If the HSE fails to start (missing crystal, bad load caps, wrong NRST behaviour), firmware that spins indefinitely waiting for
HSERDYwill freeze. The HAL's default startup uses a timeout and falls back to the HSI if HSE fails — check that yourSystemClock_Config()does the same. - Production STM32 firmware: correctly configuring and verifying the full clock tree — including USB PLL, low-power clock source selection, and runtime clock switching — is a common source of subtle bugs in commercial designs. Zeus Design's embedded firmware team handles this as a standard part of STM32 firmware development for products.
Common Mistakes
- Forgetting flash wait states before increasing SYSCLK: the most dangerous mistake. It works on some silicon and fails silently on others depending on temperature and process variation, making it hard to reproduce.
- Calculating baud rate divisors against SYSCLK rather than the correct PCLKx: USART1 and USART6 are on APB2; USART2, USART3, UART4, and UART5 are on APB1. When APB1 and APB2 have different prescalers — which they usually do at maximum clock — hardcoding
SystemCoreClockin baud rate formulas produces wrong rates on APB1 UARTs. - Missing the timer clock doubling rule: expecting
TIM2to tick atPCLK1when it actually ticks at2 × PCLK1whenever the APB1 prescaler is not 1. Timer-based delays, PWM frequencies, and input capture timing are all wrong by a factor of 2. - No HSE timeout fallback: if the PCB has no crystal or a marginal crystal, the firmware hangs indefinitely waiting for
HSERDY, producing a device that appears completely dead rather than booting at a reduced speed on HSI. - Reinitialising peripherals after a clock change without updating their divisors: changing SYSCLK at runtime invalidates every register that holds a clock-derived divisor — UART BRR, timer PSC/ARR, ADC prescaler, SPI BR. Always recalculate and rewrite these registers immediately after a clock switch.
Frequently Asked Questions
- What clock does SysTick run on in STM32?
- SysTick runs off HCLK (the AHB clock) by default when configured by the HAL. If the /8 option is selected in the SysTick control register, it runs at HCLK/8. The HAL_Init() function configures SysTick to fire every 1 ms — the reload value is calculated against HCLK, so if you reconfigure SYSCLK at any point you must call HAL_RCC_ClockConfig() with the correct HCLK value to update the SysTick reload register, or the HAL delay/tick functions will give wrong timing.
- Can I change the STM32 clock speed at runtime?
- Yes, but the sequence matters: always increase flash wait states BEFORE raising SYSCLK, and decrease them AFTER lowering it. Any peripheral that depends on the old peripheral clock frequency — UART baud rate divisor register, timer prescaler, ADC clock prescaler — must be reprogrammed after the clock change. The HAL function HAL_RCC_ClockConfig() handles the wait-state sequencing for you if used correctly.
References
Related Questions
Why Does My STM32 Keep Resetting Unexpectedly?
STM32 unexpected resets are caused by watchdog timeout, brown-out, hard fault, or power decoupling issues. Use the RCC reset flags to identify the root cause.
How Do You Use the STM32 DFU Bootloader to Flash Firmware?
The STM32 DFU bootloader lets you flash firmware over USB without a debug probe. Learn how to enter DFU mode, use dfu-util, and fix common detection issues.
How Do You Configure STM32 Peripherals with HAL and CubeMX?
STM32CubeMX generates HAL initialisation code for UART, SPI, and I2C from a GUI. This guide explains key settings and how generated code maps to the hardware.
How Do You Select a Crystal Oscillator for an Embedded System?
Covers crystal selection for embedded systems: load capacitance matching, ESR verification, frequency tolerance, TCXOs, and 32.768 kHz RTC crystals.
Which STM32 Family Should You Use?
Compare STM32 families for new designs: G0, G4, F4, H7, L4, U5, WB, and WL — performance tiers, power profiles, peripheral sets, and which to choose.
What Is a Microcontroller (MCU)?
A microcontroller (MCU) combines a CPU, flash, RAM, and peripherals on one chip. Learn how MCUs work and how they differ from microprocessors and FPGAs.
Related Forum Discussions
STM32F401 UART printing garbage after switching to 84 MHz PLL — same 115200 baud in CubeMX and PuTTY
Got a WeAct Black Pill (STM32F401CCU6) project that's been running happily on the default HSI clock at 16 MHz. Using USART1 on PA9/PA10 thro
STM32H743 HAL_UART_Receive_DMA fires error callback immediately — TEIF1 set, RxCplt never fires
Upgrading a project from STM32F4 to STM32H743. UART DMA receive worked on the F4 without any issues — standard CubeMX setup, call HAL_UART_R
STM32 GPIO interrupt configured but ISR never fires — what am I missing?
Trying to use a button on PA0 to trigger an interrupt on an STM32F411 Nucleo board. Using HAL, generated the init code with CubeMX. The GPIO
STM32 USB not detected by Windows after jumping to bootloader mode
Working on a custom STM32F411 board, trying to jump into the built-in USB DFU bootloader from application code instead of holding BOOT0 on p