01 The L4R5 clock tree & why 120 MHz needs boost
Every clock on the STM32L4R5 is derived by the RCC (Reset and Clock Control) block from one of four sources. SYSCLK feeds the AHB prescaler to make HCLK (the CPU/AHB clock), and two APB prescalers derive PCLK1 and PCLK2 for the peripheral buses.
MSI / HSI16 / HSE ---> [PLL] ---> SYSCLK ---> /HPRE ---> HCLK (Cortex-M4F, AHB, SysTick)
---> /PPRE1 ---> PCLK1 (APB1: TIM2-7, USART2/3, I2C, SPI2/3...)
---> /PPRE2 ---> PCLK2 (APB2: USART1, SPI1, TIM1/8, ADC...)
Reaching the part's maximum 120 MHz is not just "spin up the PLL." Three things must be prepared in the right order before you switch SYSCLK onto the PLL:
After reset the STM32L4R5 runs on MSI at 4 MHz (MSIRANGE = 6, taken from RCC_CSR), SYSCLK = 4 MHz, Range 1 normal, 0 wait states. All the code below assumes this starting point.
| Source | Typ. freq | Kind | Notes |
|---|---|---|---|
| MSI | 100 kHz – 48 MHz | Internal RC | Default SYSCLK; 12 selectable ranges; can be PLL source |
| HSI16 | 16 MHz | Internal RC | Factory-trimmed ~1%; fast wake; good PLL source |
| HSE | 4 – 48 MHz | Crystal / ext | Crystal, or external clock via HSEBYP |
| PLL | up to 120 MHz | Multiplier | Fed by MSI/HSI16/HSE; only path to 120 MHz |
| LSE / LSI | 32.768 kHz / 32 kHz | Low speed | RTC / watchdog; not a SYSCLK path |
02 Oscillators: MSI, HSI16, HSE (RCC_CR)
The three high-speed sources are enabled and observed through RCC_CR (base RCC = 0x4002_1000, offset 0x00). Each has an enable bit and a read-only ready flag — always spin on the ready flag before using a source.
| Field | Bits | Meaning |
|---|---|---|
| MSION / MSIRDY | 0 / 1 | MSI enable / ready |
| MSIPLLEN | 2 | Hardware auto-trim MSI against LSE (PLL-mode MSI) |
| MSIRGSEL | 3 | 0 = range from RCC_CSR (reset); 1 = range from MSIRANGE below |
| MSIRANGE | 7:4 | MSI frequency range (see table); writable only when MSI off or ready |
| HSION / HSIRDY | 8 / 10 | HSI16 enable / ready |
| HSEON / HSERDY | 16 / 17 | HSE enable / ready |
| HSEBYP | 18 | 1 = external clock on OSC_IN (no crystal); set while HSEON = 0 |
| PLLON / PLLRDY | 24 / 25 | Main PLL enable / locked |
MSI ranges (RCC_CR.MSIRANGE)
To pick an MSI frequency at runtime you must set MSIRGSEL = 1 so the range is read from MSIRANGE rather than from RCC_CSR. The field encodes:
| MSIRANGE | Freq | MSIRANGE | Freq |
|---|---|---|---|
| 0000 | 100 kHz | 0110 | 4 MHz (reset) |
| 0001 | 200 kHz | 0111 | 8 MHz |
| 0010 | 400 kHz | 1000 | 16 MHz |
| 0011 | 800 kHz | 1001 | 24 MHz |
| 0100 | 1 MHz | 1010 | 32 MHz |
| 0101 | 2 MHz | 1011 | 48 MHz |
For 120 MHz any of the three works. MSI @ 4 MHz (used in the full example) needs no extra setup — it is already running at reset. HSI16 is the go-to when you want no external parts and better accuracy than MSI. HSE is required when you need tight ppm accuracy (USB, precise UART baud).
03 PWR voltage scaling: Range 1 boost
The core supply is set by the PWR block (base PWR = 0x4000_7000, on APB1). Higher SYSCLK requires higher core voltage. The L4+ adds a Range 1 boost tier on top of the classic L4 ranges — boost is the only way to exceed 80 MHz.
| Mode | VOS / R1MODE | Core V | Max HCLK |
|---|---|---|---|
| Range 1 boost | VOS=01, R1MODE=0 | 1.2 V | 120 MHz |
| Range 1 normal | VOS=01, R1MODE=1 (reset) | 1.1 V | 80 MHz |
| Range 2 | VOS=10 | 1.0 V | 26 MHz |
| Register | Field | Bits | Use |
|---|---|---|---|
| PWR_CR1 | VOS | 10:9 | 01 = Range 1, 10 = Range 2 |
| PWR_CR5 | R1MODE | 8 | 0 = Range 1 boost, 1 = Range 1 normal (reset) |
| PWR_SR2 | VOSF | 10 | 1 while the regulator is ramping; poll until 0 |
Note the inverted sense: R1MODE = 0 means boost, and its reset value is 1 (normal). So enabling boost is a bit clear, not a set:
/* PWR is on APB1: its bus clock MUST be enabled before any PWR access */
RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN;
(void)RCC->APB1ENR1; /* read-back: guarantee the write landed */
/* Select voltage scaling Range 1 (VOS = 0b01) */
MODIFY_REG(PWR->CR1, PWR_CR1_VOS, (0x1UL << PWR_CR1_VOS_Pos));
/* Enter Range 1 BOOST: clear R1MODE (reset value 1 = Range 1 normal, 80 MHz cap) */
PWR->CR5 &= ~PWR_CR5_R1MODE;
/* Wait until the internal regulator has settled at the new voltage */
while (PWR->SR2 & PWR_SR2_VOSF) { } /* VOSF = 1 while voltage is changing */
Enable the PWR bus clock (RCC_APB1ENR1.PWREN) first — PWR registers read as 0 otherwise. Then always wait on VOSF before raising the clock; switching SYSCLK to 120 MHz before the regulator has stabilised can hang or crash the core.
04 Flash latency & wait states (FLASH_ACR)
Embedded flash cannot be read in a single cycle at 120 MHz, so the flash interface inserts wait states. Set the latency in FLASH_ACR (base FLASH = 0x4002_2000, offset 0x00) and, per RM0432, read it back to confirm the value was accepted before you speed up.
| Field | Bits | Meaning |
|---|---|---|
| LATENCY | 3:0 | Number of wait states (0–5 valid on L4R5) |
| PRFTEN | 8 | Prefetch enable |
| ICEN | 9 | Instruction cache enable |
| DCEN | 10 | Data cache enable |
Wait states vs. HCLK
The step size depends on the voltage mode. In Range 1 boost each wait state buys another 20 MHz, so 120 MHz needs the maximum of 5:
| WS (LATENCY) | Range 1 boost | Range 1 normal | Range 2 |
|---|---|---|---|
| 0 | ≤ 20 MHz | ≤ 16 MHz | ≤ 8 MHz |
| 1 | ≤ 40 MHz | ≤ 32 MHz | ≤ 16 MHz |
| 2 | ≤ 60 MHz | ≤ 48 MHz | ≤ 26 MHz |
| 3 | ≤ 80 MHz | ≤ 64 MHz | — |
| 4 | ≤ 100 MHz | ≤ 80 MHz | — |
| 5 | ≤ 120 MHz | — | — |
/* Raise wait states BEFORE increasing the clock, then read back to confirm.
* 120 MHz in Range 1 boost needs 5 WS. Writing LATENCY is not enough - the
* value must be verified, because the flash accepts it only when stable. */
MODIFY_REG(FLASH->ACR, FLASH_ACR_LATENCY, FLASH_ACR_LATENCY_5WS);
while ((FLASH->ACR & FLASH_ACR_LATENCY) != FLASH_ACR_LATENCY_5WS) { }
/* Prefetch + instruction cache + data cache: free performance on the L4+ */
FLASH->ACR |= FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN;
When increasing the clock: raise wait states first, then increase SYSCLK. When decreasing: lower SYSCLK first, then reduce wait states. Getting the order wrong means the CPU fetches from flash faster than it can respond — a hard fault or lock-up.
05 PLL configuration & the 120 MHz math (RCC_PLLCFGR)
The main PLL is programmed through RCC_PLLCFGR (RCC offset 0x0C). The input is divided by M, multiplied by N into the VCO, then divided by R to make SYSCLK (P and Q feed peripherals like SAI/USB).
fVCO = (fPLLIN / PLLM) * PLLN constraints: 2.66..16 MHz in, 64..344 MHz VCO
SYSCLK = fVCO / PLLR PLLR in {2,4,6,8}, SYSCLK <= 120 MHz
| Field | Bits | Encoding |
|---|---|---|
| PLLSRC | 1:0 | 00 none, 01 MSI, 10 HSI16, 11 HSE |
| PLLM | 7:4 | Divider = field + 1 → /1../16 (L4+ is 4 bits wide) |
| PLLN | 14:8 | Multiplier, 8..127 |
| PLLPEN / PLLP | 16 / 17 | P output enable / legacy /7 or /17 (see PLLPDIV) |
| PLLQEN / PLLQ | 20 / 22:21 | Q output enable / 00=/2, 01=/4, 10=/6, 11=/8 |
| PLLREN / PLLR | 24 / 26:25 | R output enable / 00=/2, 01=/4, 10=/6, 11=/8 |
| PLLPDIV | 31:27 | Explicit P divider 2..31 (0 = use PLLP bit) |
PLLM holds M−1 (so M=1 → field 0), while PLLR/PLLQ hold a code (0→/2, 1→/4, 2→/6, 3→/8). Also: PLLCFGR is writable only while PLLON = 0.
Worked examples for SYSCLK = 120 MHz
| Source | fIN | M | N | VCO | R | SYSCLK |
|---|---|---|---|---|---|---|
| MSI | 4 MHz | 1 | 60 | 240 MHz | 2 | 120 MHz |
| HSI16 | 16 MHz | 2 | 30 | 240 MHz | 2 | 120 MHz |
| HSE | 8 MHz | 1 | 30 | 240 MHz | 2 | 120 MHz |
Register-level PLL setup for the MSI case (the full main.c is in section 07):
/* Ensure the PLL source (MSI, 4 MHz by reset) is running and ready */
RCC->CR |= RCC_CR_MSION;
while (!(RCC->CR & RCC_CR_MSIRDY)) { }
/* PLLCFGR is writable only while the PLL is OFF */
RCC->CR &= ~RCC_CR_PLLON;
while (RCC->CR & RCC_CR_PLLRDY) { }
/* VCO = (fIN / PLLM) * PLLN = (4 MHz / 1) * 60 = 240 MHz (64..344 MHz OK)
* SYSCLK= VCO / PLLR = 240 MHz / 2 = 120 MHz
* NOTE: the PLLM field holds (M - 1); the PLLR field encodes /2=0,/4=1,/6=2,/8=3 */
RCC->PLLCFGR =
(0x1UL << RCC_PLLCFGR_PLLSRC_Pos) /* PLLSRC = 01 -> MSI */
| (0UL << RCC_PLLCFGR_PLLM_Pos) /* PLLM = /1 (field = M-1 = 0) */
| (60UL << RCC_PLLCFGR_PLLN_Pos) /* PLLN = x60 */
| (0UL << RCC_PLLCFGR_PLLR_Pos) /* PLLR = /2 (field 0b00) */
| RCC_PLLCFGR_PLLREN; /* enable the PLLCLK (R) output */
/* Start the PLL and wait for lock */
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY)) { }
The HSE bypass variant (typical of a Nucleo-L4R5ZI fed by the ST-LINK 8 MHz MCO):
/* HSE variant (e.g. Nucleo-L4R5ZI: 8 MHz from ST-LINK MCO, HSE bypass).
* 8 MHz /1 x30 /2 = 120 MHz. For a real crystal, drop HSEBYP. */
RCC->CR |= RCC_CR_HSEON | RCC_CR_HSEBYP; /* bypass = external clock in */
while (!(RCC->CR & RCC_CR_HSERDY)) { }
RCC->PLLCFGR =
(0x3UL << RCC_PLLCFGR_PLLSRC_Pos) /* PLLSRC = 11 -> HSE */
| (0UL << RCC_PLLCFGR_PLLM_Pos) /* /1 (8 MHz PLL input) */
| (30UL << RCC_PLLCFGR_PLLN_Pos) /* x30 -> VCO 240 MHz */
| (0UL << RCC_PLLCFGR_PLLR_Pos) /* /2 -> 120 MHz */
| RCC_PLLCFGR_PLLREN;
06 AHB/APB prescalers & peripheral clock enable
SYSCLK is divided down for the CPU and the two peripheral buses in RCC_CFGR (offset 0x08). In Range 1 boost every bus is rated for the full 120 MHz, so all three prescalers stay at /1.
| Field | Bits | Drives | Max @ boost |
|---|---|---|---|
| SW / SWS | 1:0 / 3:2 | SYSCLK source select / status (11 = PLL) | — |
| HPRE | 7:4 | AHB prescaler → HCLK | 120 MHz |
| PPRE1 | 10:8 | APB1 prescaler → PCLK1 | 120 MHz |
| PPRE2 | 13:11 | APB2 prescaler → PCLK2 | 120 MHz |
| HPRE code | Divide | PPRE1/2 code | Divide |
|---|---|---|---|
| 0xxx | /1 | 0xx | /1 |
| 1000 | /2 | 100 | /2 |
| 1001 | /4 | 101 | /4 |
| 1010 | /8 | 110 | /8 |
| 1011..1111 | /16../512 | 111 | /16 |
Peripheral clock gating
Beyond the bus clocks, each peripheral has an individual gate in an RCC AHBxENR / APBxENR register. GPIO ports live on AHB2 (RCC_AHB2ENR), most timers/UARTs on APB1 (RCC_APB1ENR1/2), and USART1/SPI1/TIM1/ADC on APB2 (RCC_APB2ENR).
/* Peripheral clock gating lives in the RCC AHBxENR / APBxENR registers.
* A peripheral is DEAD (reads as 0, writes ignored) until its bit is set,
* and you must read the register back before touching the peripheral so the
* one- or two-cycle clock-enable latency has elapsed. */
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN /* GPIO ports A and B */
| RCC_AHB2ENR_GPIOBEN;
RCC->APB1ENR1 |= RCC_APB1ENR1_TIM2EN /* TIM2 (32-bit) on APB1 */
| RCC_APB1ENR1_USART2EN;
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN /* SYSCFG + USART1 on APB2 */
| RCC_APB2ENR_USART1EN;
(void)RCC->APB2ENR; /* mandatory dummy read */
When an APB prescaler is /1, the timers on that bus are clocked at PCLK. As soon as the prescaler is > 1, the RCC doubles the timer clock (2×PCLK). With all buses at /1 here, every timer sees a clean 120 MHz.
07 Complete register-level init (+ HAL variant)
The whole sequence in one file. It is self-contained C for the STM32L4R5 CMSIS headers — PWR boost, flash 5 WS, PLL from MSI, buses at /1, then the SYSCLK switch. Compile with -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -DSTM32L4R5xx.
#include "stm32l4xx.h" /* umbrella header: pulls in stm32l4r5xx.h + CMSIS */
/* ---------------------------------------------------------------------------
* Bring the STM32L4R5 from its 4 MHz MSI reset state up to 120 MHz.
* MSI 4 MHz --> PLL (M=1, N=60, R=2) --> SYSCLK = 120 MHz
* HCLK = PCLK1 = PCLK2 = 120 MHz
* Prerequisites handled in order: PWR boost, flash WS, PLL, bus, clock switch.
* ------------------------------------------------------------------------- */
void SystemClock_Config_120MHz(void)
{
/* 1. Power interface clock + Range 1 BOOST voltage scaling ------------- */
RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN;
(void)RCC->APB1ENR1;
MODIFY_REG(PWR->CR1, PWR_CR1_VOS, (0x1UL << PWR_CR1_VOS_Pos)); /* Range 1 */
PWR->CR5 &= ~PWR_CR5_R1MODE; /* boost */
while (PWR->SR2 & PWR_SR2_VOSF) { }
/* 2. Flash: 5 wait states (verified) + prefetch and caches ------------- */
MODIFY_REG(FLASH->ACR, FLASH_ACR_LATENCY, FLASH_ACR_LATENCY_5WS);
while ((FLASH->ACR & FLASH_ACR_LATENCY) != FLASH_ACR_LATENCY_5WS) { }
FLASH->ACR |= FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN;
/* 3. Main PLL: MSI(4) /1 x60 /2 = 120 MHz ----------------------------- */
RCC->CR |= RCC_CR_MSION;
while (!(RCC->CR & RCC_CR_MSIRDY)) { }
RCC->CR &= ~RCC_CR_PLLON;
while (RCC->CR & RCC_CR_PLLRDY) { }
RCC->PLLCFGR =
(0x1UL << RCC_PLLCFGR_PLLSRC_Pos) /* MSI */
| (0UL << RCC_PLLCFGR_PLLM_Pos) /* /1 (M-1 = 0) */
| (60UL << RCC_PLLCFGR_PLLN_Pos) /* x60 */
| (0UL << RCC_PLLCFGR_PLLR_Pos) /* /2 */
| RCC_PLLCFGR_PLLREN;
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY)) { }
/* 4. Bus prescalers (all /1) then switch SYSCLK to the PLL ------------- */
MODIFY_REG(RCC->CFGR,
RCC_CFGR_HPRE | RCC_CFGR_PPRE1 | RCC_CFGR_PPRE2,
RCC_CFGR_HPRE_DIV1 | RCC_CFGR_PPRE1_DIV1 | RCC_CFGR_PPRE2_DIV1);
MODIFY_REG(RCC->CFGR, RCC_CFGR_SW, RCC_CFGR_SW_PLL);
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL) { }
SystemCoreClock = 120000000UL;
}
int main(void)
{
SystemClock_Config_120MHz();
/* SYSCLK is now 120 MHz. Enable a couple of peripheral clocks as proof. */
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN; /* GPIOA on AHB2 */
RCC->APB1ENR1 |= RCC_APB1ENR1_USART2EN; /* USART2 on APB1 (120 MHz) */
RCC->APB2ENR |= RCC_APB2ENR_USART1EN; /* USART1 on APB2 (120 MHz) */
(void)RCC->APB2ENR; /* barrier before first access */
for (;;) { }
}
HAL equivalent
The same hardware steps expressed with STM32Cube HAL. PWR_REGULATOR_VOLTAGE_SCALE1_BOOST performs the R1MODE clear + VOSF wait, and passing FLASH_LATENCY_5 to HAL_RCC_ClockConfig programs the wait states in the correct order:
/* Same 120 MHz target using the STM32Cube HAL. Identical hardware sequence,
* just wrapped: voltage boost -> flash latency (auto) -> PLL -> bus switch. */
void SystemClock_Config(void)
{
RCC_OscInitTypeDef osc = {0};
RCC_ClkInitTypeDef clk = {0};
/* 1. Range 1 BOOST. HAL clears PWR_CR5.R1MODE and waits for VOSF. */
__HAL_RCC_PWR_CLK_ENABLE();
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST) != HAL_OK)
Error_Handler();
/* 2. MSI 4 MHz -> PLL (M=1, N=60, R=2) = 120 MHz */
osc.OscillatorType = RCC_OSCILLATORTYPE_MSI;
osc.MSIState = RCC_MSI_ON;
osc.MSIClockRange = RCC_MSIRANGE_6; /* 4 MHz */
osc.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
osc.PLL.PLLState = RCC_PLL_ON;
osc.PLL.PLLSource = RCC_PLLSOURCE_MSI;
osc.PLL.PLLM = 1;
osc.PLL.PLLN = 60;
osc.PLL.PLLR = 2; /* -> SYSCLK 120 MHz */
osc.PLL.PLLP = 2;
osc.PLL.PLLQ = 2;
if (HAL_RCC_OscConfig(&osc) != HAL_OK)
Error_Handler();
/* 3. Buses all /1; HAL derives FLASH_LATENCY_5 from the 120 MHz target. */
clk.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
clk.AHBCLKDivider = RCC_SYSCLK_DIV1;
clk.APB1CLKDivider = RCC_HCLK_DIV1;
clk.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_5) != HAL_OK)
Error_Handler();
}
08 Gotchas & common mistakes
The failures below account for the vast majority of "my board won't run at 120 MHz" reports.
PWR_SR2.VOSF clears runs the core faster than the regulator can supply — brown-out and lock-up.RCC_APB1ENR1.PWREN set, PWR_CR1/CR5 reads return 0 and your voltage writes silently do nothing.SystemCoreClock global (or call SystemCoreClockUpdate()). SysTick, HAL timeouts and UART baud are all computed from it — leave it at 4 MHz and every delay is 30× too short.120 MHz checklist
- Enable PWR clock → VOS = Range 1 → clear R1MODE (boost) → wait VOSF = 0
- FLASH_ACR: LATENCY = 5 (verify read-back) + prefetch/ICEN/DCEN
- PLL off → PLLCFGR (src, M−1, N, R code, PLLREN) → PLLON → wait PLLRDY
- HPRE/PPRE1/PPRE2 = /1 → SW = PLL → wait SWS = PLL → set SystemCoreClock