Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 6

Setting STM32 System Clock with PLL and Clock Sources

Recommended resources

Introduction

In our previous lesson, we unpacked the role of the linker script, seeing how it acts as the architectural blueprint that maps our compiled code and data onto the microcontroller's Flash and SRAM. You now have a complete picture of the static setup of your program. Today, we transition from this static architecture to the dynamic runtime configuration of the hardware itself. We will begin with one of the most fundamental and critical tasks: setting the "heartbeat" of the microcontroller.

The goal of this lesson is to configure the system clock. The speed at which your microcontroller executes instructions, communicates with peripherals, and performs calculations is governed by its clock system. By default, an STM32 starts up using a conservative, internal clock source. To unlock its full performance potential, we must learn to configure its various clock sources and, most importantly, harness the power of the Phase-Locked Loop (PLL) to generate a high-speed system clock. This is a foundational skill for balancing performance, power consumption, and peripheral compatibility in any embedded project.


Understanding the STM32 Clock System

Before we can configure the clock, we need a mental map of the system. A microcontroller's clock system is not a single oscillator but a complex network of sources, multiplexers, and dividers. The video below provides an excellent introduction to the primary clock sources and the overall structure.

Tutorial 8: MCU Clocks configuration in STM32 using STM32CUBEMX

This video from Hexnbit gives a clear, conceptual overview of the different clock sources and introduces the clock tree diagram, which is our primary tool for visualizing the clock system.

Please watch the section from "three different clock sources" which explains the High-Speed Internal (HSI), High-Speed External (HSE), and Phase-Locked Loop (PLL). Then, watch the segment from "clock tree" which shows how the visual layout in STM32CubeMX corresponds to the official documentation.

The clock tree is the definitive diagram for understanding the flow of clock signals. Let's examine a typical clock tree for an STM32 microcontroller.

A typical clock tree for an STM32 microcontroller. It shows the paths from the initial sources (like HSE and HSI) through the PLL and multiplexers, ultimately feeding the CPU core (SYSCLK) and various peripheral buses (HCLK, PCLK1, PCLK2).

Let's break down the key components you see in this diagram:

  • Clock Sources:

    • HSI (High-Speed Internal): An RC oscillator factory-calibrated to a specific frequency (e.g., 16 MHz). It's convenient because it requires no external components and is available immediately on startup. However, it is less accurate than an external crystal and can drift with temperature and voltage changes.
    • HSE (High-Speed External): An input for an external crystal or oscillator. On most development boards like the Nucleo or Discovery series, an 8 MHz crystal is provided. The HSE is highly accurate and stable, making it the preferred source for high-performance and timing-critical applications (like USB or Ethernet).
  • The PLL (Phase-Locked Loop): This is the most important component for achieving high performance. The PLL is a feedback control system that acts as a frequency multiplier. It takes a stable reference clock (typically the HSE) and generates a much higher output frequency. For example, it can take an 8 MHz HSE input and produce a 168 MHz output for the system clock.

  • System Clock (SYSCLK) MUX: This multiplexer is a switch that allows you to select which source—HSI, HSE, or the PLL output—will be the main clock for the entire system.

  • Prescalers and Buses: The high-frequency SYSCLK is often too fast for peripherals. The clock tree divides the SYSCLK using prescalers before distributing it to different buses:

    • AHB (Advanced High-performance Bus): Connects the CPU core, memory, and high-speed peripherals like DMA. The clock for this bus is called HCLK.
    • APB (Advanced Peripheral Bus): Connects lower-speed peripherals. There are often two (APB1 and APB2), with different maximum frequency limits. Their clocks are PCLK1 and PCLK2.

The PLL Calculation

The PLL works by dividing the input frequency, multiplying it in a Voltage-Controlled Oscillator (VCO), and then dividing the result. The main formula for the system clock output from the PLL is:

Where:

  • is the frequency of the selected PLL source (e.g., 8 MHz from HSE).
  • M is the input prescaler.
  • N is the main multiplication factor.
  • P is the final output divider for the system clock.

For the VCO to operate correctly, its output frequency, , must be within a specific range defined in the datasheet (e.g., 100-432 MHz).

Example: To get a 180 MHz SYSCLK from an 8 MHz HSE on an STM32F4 series MCU:

  1. Choose M=4. This gives as the input to the VCO, which is within the typical required range (e.g., 1-2 MHz).
  2. To get a VCO output near the optimal range, we need to find N. Let's aim for a VCO output of 360 MHz. So, .
  3. Finally, we need to find P to get our desired 180 MHz system clock. .

So, our PLL parameters are M=4, N=180, P=2.


Configuring the Clock: A Step-by-Step Guide

Now that we understand the theory, let's walk through the practical steps of configuring the clock system using direct register access. This process is sequential and requires careful attention to detail. The following article provides an excellent roadmap. We will follow its structure to implement our configuration.

STM32 Clock Setup Using Registers

This article from ControllersTech outlines a clear, 7-step process for configuring the STM32 clock system using registers.

First, read the list of seven steps. This is the exact sequence we will follow. Then, skim the concluding paragraph which summarizes the outcome of this process.

Here is a detailed breakdown of that 7-step process, explaining the purpose and the registers involved at each stage.

Step 1: Enable HSE and Wait for it to Become Ready

We first need to turn on our external crystal oscillator and wait for it to stabilize.

  • Register: RCC->CR (Clock Control Register)
  • Action: Set the HSEON bit (bit 16).
  • Wait: Poll the HSERDY flag (bit 17) until it is set, indicating the HSE is stable and ready.
// 1. Enable HSE and wait for it to become ready
RCC->CR |= (1 << 16); // HSEON = 1
while (!(RCC->CR & (1 << 17))); // Wait until HSERDY = 1

Step 2: Configure Power and Voltage Scaling

To run the microcontroller at high frequencies, we must enable the power interface clock and set the main voltage regulator to a scale that supports the target speed.

  • Register: RCC->APB1ENR (APB1 Peripheral Clock Enable Register) and PWR->CR (Power Control Register).
  • Action: Set the PWREN bit in RCC->APB1ENR to enable the power controller. Then, set the VOS (Voltage Scaling) bits in PWR->CR according to the target frequency range specified in the datasheet.

Step 3: Configure Flash Latency

The CPU at high speed can request instructions faster than the Flash memory can provide them. We must insert "wait states" to compensate.

  • Register: FLASH->ACR (Flash Access Control Register)
  • Action: Write the required number of wait states to the LATENCY bits. The number of wait states depends on both the CPU frequency (HCLK) and the supply voltage. This value is critical and must be looked up in the device datasheet's "Flash memory read interface" section. For high performance, also enable the instruction cache, data cache, and prefetch buffer if available.
// Example for 180MHz operation on STM32F4
FLASH->ACR |= (1 << 8)  | // Prefetch Buffer Enable
              (1 << 9)  | // Instruction Cache Enable
              (1 << 10) | // Data Cache Enable
              (5 << 0);   // 5 Wait States (for VCC ~3.3V)

Step 4: Configure the Bus Prescalers

Now we set the dividers for the AHB and APB buses.

  • Register: RCC->CFGR (Clock Configuration Register)
  • Action: Set the HPRE, PPRE1, and PPRE2 bits.
    • HPRE (AHB prescaler) divides SYSCLK to get HCLK. For maximum performance, this is usually set to /1.
    • PPRE1 (APB1 prescaler) divides HCLK to get PCLK1. APB1 has a lower maximum frequency (e.g., 45 MHz), so a divider like /4 is common.
    • PPRE2 (APB2 prescaler) divides HCLK to get PCLK2. APB2 can run faster (e.g., 90 MHz), so a divider like /2 might be used.

Step 5: Configure the Main PLL

This is the core of our setup, where we apply the M, N, and P values we calculated.

  • Register: RCC->PLLCFGR (PLL Configuration Register)
  • Action:
    • First, ensure the PLL is off before configuring it.
    • Set the PLLSRC bit to select HSE as the PLL input.
    • Write the calculated values to the PLLM, PLLN, and PLLP bitfields.
// Example for 180MHz SYSCLK from 8MHz HSE
// PLLM = 4, PLLN = 180, PLLP = 2 (encoded as 0)
RCC->PLLCFGR = (4 << 0)   | // PLLM = 4
               (180 << 6) | // PLLN = 180
               (0 << 16)  | // PLLP = 2
               (1 << 22);   // PLLSRC = HSE

Step 6: Enable the PLL and Wait for it to Lock

With the PLL configured, we turn it on and wait for it to achieve a lock.

  • Register: RCC->CR
  • Action: Set the PLLON bit (bit 24).
  • Wait: Poll the PLLRDY flag (bit 25) until it is set.
// 6. Enable PLL and wait for it to become ready
RCC->CR |= (1 << 24); // PLLON = 1
while (!(RCC->CR & (1 << 25))); // Wait until PLLRDY = 1

Step 7: Select PLL as the System Clock

This is the final step. We switch the system clock multiplexer from HSI (the default) to our newly configured PLL.

  • Register: RCC->CFGR
  • Action: Set the SW (System Clock Switch) bits to select the PLL.
  • Wait: Poll the SWS (System Clock Switch Status) bits to confirm the switch has completed.
// 7. Select PLL as the system clock
RCC->CFGR |= (2 << 0); // SW = PLL
while ((RCC->CFGR & (3 << 2)) != (2 << 2)); // Wait until SWS indicates PLL

After this sequence, the microcontroller will be running at the target frequency.


From Registers to Abstraction

While working directly with registers gives you complete control, it's also helpful to see how this process is represented by higher-level tools like STM32CubeMX and the HAL library. The following article shows the code generated by CubeMX for a clock configuration.

Part 2: Introduction to the STM32 microcontroller clock system

This ST Community article shows a SystemClock_Config function generated by STM32CubeMX and explains the structures used by the HAL library.

Read through the code in Snippet 1. Notice how parameters like RCC_OscInitStruct.PLL.PLLM and RCC_ClkInitStruct.SYSCLKSource correspond directly to the values we just wrote into registers. Then, look at the tables for PLL factors and Clock parameters. These tables provide a clear description of what each parameter controls.

Seeing the HAL code reinforces that even when using an abstraction layer, the same underlying hardware registers are being configured. Understanding this register-level process empowers you to debug issues, optimize performance, and work without being entirely dependent on code generation tools.


Conclusion

In this lesson, we have moved from the static world of memory maps to the dynamic configuration of the microcontroller's core. You now have a comprehensive understanding of the STM32 clock system and the precise steps required to configure it for high-performance operation.

Here are the key takeaways:

  • The clock tree is the essential map showing how clock signals flow from sources (HSI, HSE) through the PLL and prescalers to the CPU (SYSCLK) and peripheral buses (HCLK, PCLKx).
  • The PLL is a frequency multiplier that is critical for achieving high system clock speeds from a stable, lower-frequency external crystal (HSE).
  • Clock configuration is a strict, sequential process. You must enable sources, configure dividers and multipliers, and wait for stability flags (HSERDY, PLLRDY) at each stage.
  • Running the CPU at high speeds requires configuring Flash memory latency (FLASH_ACR) to prevent instruction fetching errors.
  • Understanding direct register manipulation gives you ultimate control and a deeper understanding of the hardware, even when using abstraction libraries like HAL.

In our next lesson, we will make immediate use of our newly configured system clock. We will explore the SysTick timer, a core Cortex-M peripheral used for creating time bases and delays, whose timing is directly derived from the system clock frequency we learned to set today.