Bare Metal STM32 · Part 2 of 7

Clock Configuration with RCC: From HSI to PLL

STM32 Clocks RCC Beginner Source Code
Bare Metal STM32 Part 2
Bare Metal STM32 — Part 2
Clock Configuration with RCC: From HSI to PLL
22:10
Part 2 / 7
22:10
Beginner
STM32F4 · 168 MHz
01

Overview

The clock tree is the heartbeat of your STM32. Get it wrong and every peripheral behaves unpredictably — UART baud rates drift, timers fire at the wrong frequency, and flash reads produce corrupt data. This part walks you through the full STM32F4 clock tree bare-metal: enabling HSE, configuring the PLL, setting bus prescalers, and handling flash wait states correctly to hit 168 MHz safely.

  • Understand the STM32F4 clock tree: HSI, HSE, PLL, SYSCLK, AHB, APB1, APB2
  • Configure the PLL from a 25 MHz HSE crystal to achieve 168 MHz SYSCLK
  • Set flash wait states correctly before switching the clock source
  • Verify your clock config with a MCO output pin and oscilloscope
02

The Clock Tree

🔵
HSI / HSE
HSI is the internal 16 MHz RC oscillator — always available, less accurate. HSE is your external crystal (typically 8–25 MHz) — more stable, required for USB and precise baud rates.
RCC_CR: HSION / HSEON
⚙️
PLL
Multiplies the source clock to reach high SYSCLK speeds. Configured with M (input divider), N (multiplier), P (output divider). Formula: SYSCLK = (HSE/M) × N / P.
RCC_PLLCFGR: M, N, P, Q
🚌
Bus Prescalers
AHB divides SYSCLK for the main bus. APB1 max is 42 MHz; APB2 max is 84 MHz on STM32F4. Timers on slow APB buses get an automatic ×2 multiplier.
RCC_CFGR: HPRE, PPRE1, PPRE2
03

Code

01
Full SystemClock_Config — HSE → PLL → 168 MHz bare metal
clock.c
C
void SystemClock_Config(void) {
    /* 1. Enable power controller, set VOS scale 1 (required for 168 MHz) */
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    PWR->CR |= PWR_CR_VOS;

    /* 2. Enable HSE and wait for it to stabilise */
    RCC->CR |= RCC_CR_HSEON;
    while (!(RCC->CR & RCC_CR_HSERDY));

    /* 3. Flash wait states — MUST be set before increasing clock */
    // At 168 MHz, VDD 2.7–3.6 V → 5 wait states required (see RM Table 10)
    FLASH->ACR = FLASH_ACR_LATENCY_5WS
               | FLASH_ACR_ICEN      // Instruction cache on
               | FLASH_ACR_DCEN      // Data cache on
               | FLASH_ACR_PRFTEN;   // Prefetch on

    /* 4. Configure PLL: HSE=25MHz, M=25, N=336, P=2 → 168 MHz
          SYSCLK = (25/25) × 336 / 2 = 168 MHz
          USB FS: (25/25) × 336 / 7 = 48 MHz  (Q=7)          */
    RCC->PLLCFGR = (25  << RCC_PLLCFGR_PLLM_Pos)  // M=25
                 | (336 << RCC_PLLCFGR_PLLN_Pos)  // N=336
                 | (0   << RCC_PLLCFGR_PLLP_Pos)  // P=2 (00=÷2)
                 | (7   << RCC_PLLCFGR_PLLQ_Pos)  // Q=7 for USB
                 | RCC_PLLCFGR_PLLSRC_HSE;          // Source = HSE

    /* 5. Enable PLL and wait */
    RCC->CR |= RCC_CR_PLLON;
    while (!(RCC->CR & RCC_CR_PLLRDY));

    /* 6. Set bus prescalers before switching source
          AHB=÷1 (168 MHz), APB1=÷4 (42 MHz), APB2=÷2 (84 MHz) */
    RCC->CFGR = RCC_CFGR_HPRE_DIV1
              | RCC_CFGR_PPRE1_DIV4
              | RCC_CFGR_PPRE2_DIV2;

    /* 7. Switch SYSCLK source to PLL */
    RCC->CFGR |= RCC_CFGR_SW_PLL;
    while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);

    /* 8. Update SystemCoreClock so HAL/SysTick functions work */
    SystemCoreClock = 168000000UL;
}
⚠️
Set flash wait states BEFORE increasing clock speed
If you switch to a fast clock before increasing flash latency, the CPU will outrun the flash and execute corrupt instructions — producing hard faults with no obvious cause. Always set FLASH->ACR first.

Continue the Series

Work through all 7 parts of Bare Metal STM32 to master low-level embedded development from the ground up.