STM32 Clock Configuration Guide: Understanding the Clock System Step-by-Step

Clock configuration is the first thing I verify when bringing up a new STM32-based board. It governs far more than the CPU frequency. UART baud rates, SPI clock speeds, timer periods, ADC conversion timing, and RTOS tick intervals all depend on the clock configuration.

A microcontroller may appear to work correctly even with an incorrect clock configuration. However, problems often surface when peripherals are enabled. UART communication may use an incorrect baud rate, SPI timing may violate device requirements, and timer-based operations may not behave as expected. This is why understanding the clock tree is essential before configuring individual peripherals.

In this article, we will explore the STM32 clock architecture and walk through its configuration step by step. We will use the STM32H5 series for our examples. Here, I will cover oscillator sources, PLL configuration, system clock selection, bus prescalers, and peripheral clock distribution.

Although the implementation focuses on STM32H5, the underlying concepts apply to many STM32 families. However, clock sources, PLL architecture, and peripheral clock selection can vary between devices, so always refer to the reference manual and datasheet for your specific microcontroller.

 

Microcontroller System Clock:

The system clock is one of the fundamental building blocks of a microcontroller. It determines how fast the processor executes instructions and provides the timing reference used by the processor and various on-chip peripherals. However, a modern microcontroller does not necessarily operate from a single clock. Its processor, bus interfaces, timers, and communication peripherals may operate using different clock sources and frequencies.

In the STM32H5 series, clock configuration is managed by the Reset and Clock Control (RCC) peripheral. The RCC controls the available clock sources, configures the PLLs, selects clock paths, and manages clock distribution throughout the microcontroller. These sources include internal and external oscillators, as well as PLL-generated clocks.

Clock configuration is more than simply selecting a CPU frequency. It directly affects peripheral timing, communication baud rates, timer calculations, flash access, and overall power consumption. An incorrect configuration can result in communication failures, inaccurate timing, or operation outside the device’s specified limits.

Understanding the STM32H5 clock tree is essential before configuring the system clock. It allows firmware developers to understand how clock sources are selected, how frequencies are distributed across different bus and peripheral domains, and how to configure the system to meet performance, timing, and power requirements.

 

Reset and Clock Control (RCC):

The Reset and Clock Control (RCC) peripheral is responsible for managing the clock system and reset functionality of an STM32 microcontroller. It controls the clock sources, selects clock paths, configures prescalers, and distributes clocks to the CPU, buses, and peripheral modules.

When an STM32 microcontroller starts, its clock system operates using a default clock configuration. The RCC allows firmware to change this configuration according to the application’s requirements. For example, a developer can select an external oscillator, configure a PLL to achieve a higher system clock frequency, and enable the clocks required by individual peripherals.

RCC also provides peripheral reset control, allowing firmware to reset individual peripherals or groups of peripherals when required.

The RCC architecture is not identical across all STM32 families. Devices such as STM32F0, STM32F1, STM32G4, and STM32H5 have different clock sources, PLL configurations, clock multiplexers, and peripheral clock selection mechanisms. However, the fundamental purpose remains the same: to provide controlled clock distribution and reset management throughout the microcontroller.

 

Clock Sources:

The STM32 Reset and Clock Control (RCC) peripheral manages the clock sources and distributes clock signals to the CPU, buses, and peripherals.

The STM32H5 provides four possible clock sources for generating the system clock (SYSCLK):

  • HSI (High-Speed Internal): A 64 MHz internal RC oscillator. It provides a convenient clock source without requiring an external crystal.
  • CSI (Clock Source Internal): A nominal 4 MHz low-power internal RC oscillator. It can be used directly as the system clock or as an input to the PLLs.
  • HSE (High-Speed External): An external crystal, ceramic resonator, or external clock source operating from 4 MHz to 50 MHz.
  • PLL1 (Phase-Locked Loop 1): Generates a higher-frequency clock using HSI, CSI, or HSE as its input. Its P output can be selected as the system clock source.

The following diagram illustrates the available system clock sources and their relationship with SYSCLK.

STM32 Clock Configuration

 

Additional Clock Sources:

Apart from the system clock sources, the STM32H5 provides several additional clock sources for low-power operation and peripheral clock generation.

  • LSI (Low-Speed Internal): A nominal 32 kHz internal RC oscillator. It can provide the clock for the independent watchdog (IWDG) and RTC.
  • LSE (Low-Speed External): A 32.768 kHz external crystal or clock source, commonly used to provide an accurate clock for the real-time clock (RTC).
  • HSI48 (High-Speed Internal 48 MHz): A dedicated 48 MHz internal RC oscillator that can provide the clock for peripherals such as USB Full Speed and RNG.
  • PLL2 and PLL3: Additional phase-locked loops that generate clock signals for selected peripherals. Their input can be selected from HSI, CSI, or HSE.

The following diagram illustrates these additional clock sources and their relationship with the peripheral clock system.

 

Note: The RCC peripheral allows individual clock sources to be enabled or disabled according to application requirements. Disabling unused oscillators can help reduce power consumption. However, make sure that no active peripheral or clock configuration depends on the oscillator before disabling it.

 

Phase-Locked Loops (PLLs):

The STM32H5 integrates three Phase-Locked Loops (PLL1, PLL2, and PLL3) within its Reset and Clock Control (RCC) unit. These PLLs generate different clock frequencies for the CPU and peripherals.

Each PLL provides three possible output clocks: P, Q, and R. These outputs can operate at different frequencies using independent output dividers.

  • PLL1: Primarily used to generate the system clock (SYSCLK). It can also provide clocks to supported peripherals.
  • PLL2 and PLL3: Provide additional clock-generation resources for peripheral kernel clocks.

PLL Architecture and Operation:

Each PLL contains a Voltage-Controlled Oscillator (VCO) that generates a high-frequency clock signal. The STM32H5 supports different VCO operating ranges to accommodate different frequency and power requirements.

The PLL generates its output frequency through three main stages:

1. Input Clock Division (M):

  • The selected reference clock (HSI, CSI, or HSE) is divided by the input divider M to obtain the PLL reference frequency.

2. Frequency Multiplication (N)

  • The PLL multiplies the reference frequency using the multiplication factor N. It also supports fractional multiplication through the FRACN configuration.

3. Output Clock Division (P, Q, R)

  • The generated VCO frequency is divided by the corresponding output dividers to produce the P, Q, and R clock signals.

 

Fractional PLL Mode:

The STM32H5 supports both integer and fractional PLL operation.

In integer mode, the PLL uses an integer multiplication factor (N) to generate the VCO frequency.

In fractional mode, an additional fractional component (FRACN) is used to provide finer frequency adjustment. The fractional component is implemented using a 13-bit sigma-delta modulator.

This feature is useful when an application requires a more precise frequency adjustment than integer multiplication alone can provide.

However, frequency resolution, frequency accuracy, and jitter are different characteristics. Fractional mode does not guarantee a particular absolute frequency accuracy or eliminate clock jitter.

 

RCC Registers Used for PLL Configuration

The RCC peripheral provides dedicated registers to configure and control the PLLs. These registers define the PLL input source, frequency multiplication and division factors, operating range, fractional component, and enable status.

Register Purpose
RCC_PLLCKSELR Selects the PLL input clock source and configures input dividers (M).
RCC_PLLxDIVR Configures the multiplication factor (N) and output dividers (P, Q, R).
RCC_PLLxFRACR Configures the fractional multiplication component (FRACN).
RCC_PLLCFGR Configures PLL operating modes, VCO range, and output enables.
RCC_CR Controls PLL enable states and provides PLL ready status flags.

Here, x represents the PLL instance (1, 2, or 3).

Activating a PLL:

The general sequence for configuring and enabling a PLL is:

  • Enable the required PLL input oscillator (HSI, CSI, or HSE).
  • Wait until the selected oscillator is ready.
  • Ensure the target PLL is disabled before configuring its parameters.
  • Configure the PLL input divider (M), multiplication factor (N), and output dividers (P, Q, R).
  • Configure the VCO operating range and fractional settings, if required.
  • Enable the required PLL output clocks.
  • Set the corresponding PLL enable bit (PLLxON) in RCC_CR.
  • Wait until the corresponding PLL ready flag (PLLxRDY) is set.

⚠️ Note: Do not modify PLL configuration parameters that require the PLL to be disabled while that PLL is running. Before changing its configuration, follow the reference manual’s recommended sequence to disable the PLL, wait for it to stop, update the configuration, and enable it again. Also consider the clock-switching requirements if the PLL currently supplies SYSCLK.

 

Example: Configuring PLL1 to Generate 400 MHz SYSCLK from 8 MHz HSE

The PLL can multiply the frequency of HSI, HSE or CSI to achieve higher clock frequencies. It involves:

  • Selecting the input source (HSI, HSE or CSI).
  • Setting the multiplication factor
  • Configuring dividers.

In this example we are using an external 8 MHz crystal oscillator (HSE) and want to configure the system clock (SYSCLK) to 400 MHz using PLL1.

Step 1: PLL Frequency Formula

Each PLL in STM32H5 follows this equation:

FVCO ​= (FIN/M)​​×N

FOUTx​= FVCO​​/ DIVx

Where:

  • FIN = input frequency (HSE, HSI, or CSI)
  • M = input prescaler (1–63)
  • N = multiplication factor (4–512)
  • DIVx = output divider (2–128) for P, Q, or R outputs

The SYSCLK is derived from PLL1_P, PLL1_Q, or PLL1_R depending on selection.

 

Note: DIVPx ranges from 2–128 for PLL1 and 1–128 for PLL2/PLL3, while DIVQx and DIVRx always range from 1–128, defining the output dividers for the respective P, Q, and R clocks.

 

Step 2: Choose PLL Parameters

We want SYSCLK = 400 MHz using HSE = 8 MHz.

Choose:

  • PLL1_M = 2 (Input pre scalar)
  • PLL1_N = 100 (multiplication factor)
  • PLL1_P = 2 (Post divider)

Now:

  • FVCO​ = (8MHz​/2) × 100=400MHz
  • FPLL1_P​=(400MHz/2)​ = 200MHz
  • FPLL1_R​=(400MHz/1)​ = 400MHz

We can select PLL1_R output as the SYSCLK source to achieve 400 MHz CPU clock.

 

Step 3: PLL Configuration using HAL

void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};   // Structure for oscillator configuration
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};   // Structure for bus clock configuration

    /*----------------------------------------------------------*/
    /* 0. Enable PWR Clock and Configure Voltage Scaling        */
    /*----------------------------------------------------------*/

    // Enable power control clock
    __HAL_RCC_PWR_CLK_ENABLE();

    // Set regulator output voltage to Scale 0 (highest performance for 400 MHz SYSCLK)
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);

    // Wait until voltage scaling is ready
    while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}

    /*----------------------------------------------------------*/
    /* 1. Configure HSE (External Oscillator) and Enable PLL1   */
    /*----------------------------------------------------------*/

    // Select external crystal (HSE)
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;

    // Turn ON the HSE oscillator
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;

    // Enable PLL1
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;

    // Select HSE as PLL input source
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;

    // PLL input divider: 8 MHz / 2 = 4 MHz
    RCC_OscInitStruct.PLL.PLLM = 2;

    // PLL multiplier: 4 MHz × 100 = 400 MHz (VCO frequency)
    RCC_OscInitStruct.PLL.PLLN = 100;

    // PLL1_P output = 400 / 2 = 200 MHz (optional peripheral clock)
    RCC_OscInitStruct.PLL.PLLP = 2;

    // PLL1_Q output = 400 / 4 = 100 MHz (optional peripheral clock)
    RCC_OscInitStruct.PLL.PLLQ = 4;

    // PLL1_R output = 400 / 1 = 400 MHz → used as SYSCLK
    RCC_OscInitStruct.PLL.PLLR = 1;

    // PLL input frequency range: 1–2 MHz
    RCC_OscInitStruct.PLL.PLLRGE = RCC_PLLVCIRANGE_1;

    // Select medium VCO range (for ~400 MHz operation)
    RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLLVCOMEDIUM;

    // Disable fractional mode (integer multiplication)
    RCC_OscInitStruct.PLL.PLLFRACN = 0;

    // Apply oscillator and PLL configuration
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
        Error_Handler();  // Handle configuration error
    }

    /*----------------------------------------------------------*/
    /* 2. Configure CPU, AHB, and APB Bus Clocks                */
    /*----------------------------------------------------------*/

    // Configure SYSCLK, HCLK, PCLK1, and PCLK2
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK |
                                  RCC_CLOCKTYPE_HCLK   |
                                  RCC_CLOCKTYPE_PCLK1  |
                                  RCC_CLOCKTYPE_PCLK2;

    // Select PLL1_R as system clock source (400 MHz)
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLL1;

    // AHB clock = SYSCLK / 1 → 400 MHz
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;

    // APB1 clock = HCLK / 2 → 200 MHz
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;

    // APB2 clock = HCLK / 2 → 200 MHz
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

    // Apply bus clock configuration and set Flash latency
    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
    {
        Error_Handler();  // Handle configuration error
    }

    /*----------------------------------------------------------*/
    /* 3. Frequency Summary (for reference & verification)      */
    /*----------------------------------------------------------*/
    /*
        HSE Frequency      = 8  MHz
        PLL Input (M)      = 4  MHz  (8 / 2)
        PLL VCO            = 400 MHz (4 × 100)
        PLL1_P Output      = 200 MHz
        PLL1_Q Output      = 100 MHz
        PLL1_R Output      = 400 MHz (used as SYSCLK)
        AHB Clock (HCLK)   = 400 MHz
        APB1 Clock (PCLK1) = 200 MHz
        APB2 Clock (PCLK2) = 200 MHz
        Flash Latency      = 5 WS
        Voltage Scaling    = Scale 0 (highest performance)
    */
}

 

Note: When operating at high CPU frequencies, Flash memory access may require wait states to ensure timing integrity. Always configure Flash latency appropriately before increasing the system clock speed. Failing to do so can lead to incorrect instruction fetches or data corruption.

 

Clock Output Generation (MCO1 / MCO2 / LSCO):

The STM32H5 series provides two microcontroller clock output pins (MCO1 and MCO2), which allow selected internal clocks to be routed to external pins. These outputs are useful for debugging, measuring, or providing clocks to other components.

  • Clock source: Each MCO pin can output a selectable clock from internal sources (SYSCLK, HSI, HSE, PLLs, etc.).
  • Prescaler: The output can be divided using a configurable prescaler to reduce the frequency.

Configuration registers:

  • MCO1 → MCO1[2:0] and MCO1PRE[3:0] (RCC_CFGR1)
  • MCO2 → MCO2[2:0] and MCO2PRE[3:0] (RCC_CFGR1)
  • GPIO: MCO pins must be configured in Alternate Function mode.

Limitations: Output frequency must not exceed the pin’s maximum rated speed (see datasheet).

Example:

LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_SYSCLK, LL_RCC_MCO1_DIV_4); // SYSCLK/4 on MCO1

LSCO (Low-Speed Clock Output)

  • Purpose: Outputs a low-speed clock (LSI or LSE) on the external LSCO pin.
  • Availability: Works in Stop mode, but not in Standby or VBAT modes.
  • Control:
    • LSCOSEL → selects LSI or LSE
    • LSCOEN → enables LSCO (RCC_BDCR)
  • Pin requirement: LSCO pin remains in default state, no alternate function needed.

 

Bus Architecture and Clock Domains:

The STM32H5 series microcontrollers implement the AMBA (Advanced Microcontroller Bus Architecture) defined by ARM, which standardizes communication between the processor core, memory, and peripherals.

This series use the two bus interfaces, Advanced High-performance Bus (AHB) and the Advanced Peripheral Bus (APB).

STM32 BUS CLOCK

1. Advanced High-performance Bus (AHB)

The Advanced High-performance Bus (AHB) provides the high-speed communication infrastructure in the STM32H5 architecture. It connects the Cortex-M33 processor, memory interfaces, DMA controllers, and high-performance peripherals through an AHB bus matrix.

The STM32H5 uses multiple AHB bus segments (AHB1, AHB2, AHB3, and AHB4) to organize communication between different bus masters and peripherals.

The AHB clock, known as HCLK, is derived from the system clock (SYSCLK) through a programmable AHB prescaler.

Key characteristics:

  • Provides high-speed communication between the processor, memory, and peripherals.
  • Supports efficient data transfers through the bus matrix.
  • Allows multiple bus masters, such as the CPU and DMA controllers, to access different resources concurrently.
  • Provides the clock source for APB buses through dedicated prescalers.

Clock relationship:

HCLK = SYSCLK / AHB_Prescaler

 

2. Advanced Peripheral Bus (APB)

The APB is designed for low-power and low-speed peripherals. In STM32H5, this bus type is split into three domains — APB1, APB2, and APB3, each clocked independently through prescalers derived from HCLK. This division allows selective power control and performance optimization for different groups of peripherals.

APB1 Bus:
  • Clock: PCLK1
  • Source: Derived from HCLK via a prescaler.
  • Used by: Low-speed peripherals such as TIM2–TIM7, USART2–USART5, I2C1–I2C3, SPI2, and LPUART1.
  • Purpose: Provides slower clocking for general-purpose and communication peripherals to reduce power consumption.
APB2 Bus:
  • Clock: PCLK2
  • Source: Derived from HCLK via a prescaler.
  • Used by: High-speed peripherals like TIM1, SPI1, USART1, and ADC.
  • Purpose: Supports peripherals that require faster data exchange or tighter timing.
APB3 Bus:
  • Clock: PCLK3
  • Source: Derived from HCLK via a prescaler.
  • Used by: System-level peripherals such as SYSCFG, PWR, EXTI, and RTC.
  • Purpose: Dedicated for system configuration and control peripherals, separating them from the main data and peripheral traffic.

 

Peripheral Clock Gating and Autonomous Mode:

Autonomous mode allows selected peripherals to continue operating, exchanging data, and even waking up the CPU — all while the rest of the system remains in low-power mode. This ensures energy efficiency without compromising responsiveness or real-time performance.

In STM32H5 microcontrollers, each peripheral’s clock is managed using enable (EN) bits located in the RCC_AHBxENR and RCC_APBxENR registers.

  • When an EN bit is set, the corresponding peripheral receives its clock and becomes fully operational.
  • When the EN bit is cleared, the clock to that peripheral is disabled, and its registers become inaccessible.

⚠️ Important: If a peripheral’s clock is not active, any attempt to read from or write to its registers will fail. The peripheral must be clocked to allow register access.

 

Caution: Just after enabling the clock for a peripheral, the software must wait for these 2 clock cycles before accessing the peripheral registers.

 

How to Check the Current Clock Frequencies:

It is essential to ensure your STM32 clock configuration is correctly set up to avoid timing issues and peripheral malfunctions. You can use HAL-provided functions such as HAL_RCC_GetSysClockFreq(), HAL_RCC_GetHCLKFreq(), HAL_RCC_GetPCLK1Freq(), and HAL_RCC_GetPCLK2Freq() to read and verify the current system and bus clock frequencies during runtime.

These functions are especially useful for debugging, performance tuning, and validating dynamic clock changes.

Consider the below example code,

void printClockFrequencies()
{
    uint32_t sysclk = HAL_RCC_GetSysClockFreq();
    uint32_t hclk   = HAL_RCC_GetHCLKFreq();
    uint32_t pclk1  = HAL_RCC_GetPCLK1Freq();
    uint32_t pclk2  = HAL_RCC_GetPCLK2Freq();

    printf("System Clock (SYSCLK): %lu Hz\r\n", sysclk);
    printf("AHB Clock     (HCLK):   %lu Hz\r\n", hclk);
    printf("APB1 Clock    (PCLK1):  %lu Hz\r\n", pclk1);
    printf("APB2 Clock    (PCLK2):  %lu Hz\r\n", pclk2);
}

📘 Related Articles: