Electronics Design AU
STM32

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:

SourceTypeTypical frequencyAccuracyPrimary use
HSIInternal RC16 MHz (F4/F7/G4), 8 MHz (F1), 64 MHz (G0/G4 on newer revisions)±1% factory-trimmed; thermally variableDefault startup clock; no external components required
HSEExternal crystal or oscillator input4–26 MHz (device-dependent; 8 MHz or 25 MHz are common board choices)PPM-level; depends on crystal quality and load capacitorsRecommended for USB, UART at high baud rates, audio, and any timing-critical peripheral
LSIInternal RC~32 kHz±10–20% — unreliable for precision timingIWDG watchdog timer, RTC when accuracy is not required
LSEExternal 32.768 kHz crystal32.768 kHzPPM-levelRTC, 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:

FamilyMax SYSCLKNotes
F048 MHzSimpler PLL, no PLLP divider
F172 MHzPLLMUL multiplier only
F4180 MHzThree-factor PLL (M/N/P); a separate PLLQ feeds USB/SDIO
F7216 MHzSame PLL architecture as F4
G064 MHzHSI can reach 64 MHz directly on some variants
G4170 MHzFractional PLLN for precise frequency synthesis
H5250 MHz
H7480 MHzThree 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 rangeFlash wait states (WS)
0–30 MHz0 WS
30–60 MHz1 WS
60–90 MHz2 WS
90–120 MHz3 WS
120–150 MHz4 WS
150–180 MHz5 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:

  1. Enable HSE (or HSI); poll ready flag.
  2. Configure PLL parameters.
  3. Enable PLL; poll PLL lock flag.
  4. Set flash wait states for the target frequency.
  5. Switch SYSCLK source to PLL.
  6. 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/APB2ENR register map) lists which bus each peripheral is clocked from. Hardcoding SystemCoreClock into a UART baud rate calculation, rather than reading the correct APBx clock via HAL_RCC_GetPCLK1Freq() or HAL_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 HSERDY will freeze. The HAL's default startup uses a timeout and falls back to the HSI if HSE fails — check that your SystemClock_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 SystemCoreClock in baud rate formulas produces wrong rates on APB1 UARTs.
  • Missing the timer clock doubling rule: expecting TIM2 to tick at PCLK1 when it actually ticks at 2 × PCLK1 whenever 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

Related Forum Discussions