Bare Metal STM32 · Part 2 of 7
Clock Configuration with RCC: From HSI to PLL
22:10
Part 2 / 7
⚙️ Bare Metal STM32 Series · 7 Parts
Part 2 of 7
Part 01
HAL vs Bare Metal: Which Should You Use?
38:22
Part 02
Clock Configuration with RCC: From HSI to PLL
22:10
Part 03
Interrupts & NVIC: Complete Configuration Guide
47:22
Part 04
SPI DMA Transfers: No CPU Overhead
33:05
Part 05
ADC DMA Circular Mode: Continuous Sampling
29:45
Part 06
Debugging with SWO Trace & ITM Printf
18:30
Part 07
Low-Power Stop Mode with Wake-Up on EXTI
34:12
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 metalclock.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.