cancel
Showing results for 
Search instead for 
Did you mean: 

Mastering the STM32 Clock Tree & Flash Latency with CubeMX

ST1.png

 

You flipped the power on and… nothing. USB won’t enumerate. The device resets intermittently. Or ADC samples at the wrong rate. Before blaming the hardware, check the clock tree and flash latency. CubeMX gives a helpful UI, but you must interpret the numbers — PLL inputs/outputs, AHB/APB prescalers, timer clock doubling, and flash wait states or subtle configuration mismatches will bite you.

The big picture

  • SYSCLK (system clock) drives the CPU and the AHB bus.

  • AHB/APB prescalers split SYSCLK into peripheral bus clocks (APB1, APB2). Peripherals (UART, SPI, I2C, ADC, timers) use these derived clocks.

  • PLLs are used to multiply/divide crystal or internal oscillator frequencies to reach the target SYSCLK and special 48 MHz domain for USB, RNG, etc.

  • Flash latency (wait states) must be configured so the CPU can read Flash at the chosen SYSCLK. If flash latency is too low the CPU will fetch wrong instructions → crashes or strange behavior.

  • Voltage scaling / power range often limits maximum allowed SYSCLK; pick correct PWR/VOS for your target frequency.

  • CubeMX gives a visual clock tree and warnings, but it can hide why something fails.

 

Key concepts you must understand

PLL basics (common on STM32 families)

  • PLL has input divider M, multiplier N, and output divider P (and Q for USB/48MHz domain).

  • VCO frequency = (InputClock / M) × N.

  • PLL output for SYSCLK = VCO / P.

  • USB/48MHz domain = VCO / Q (must be exactly 48 MHz for USB/SDIO/RNG depending on family).

AHB / APB prescalers and timers

  • AHB clock (HCLK) = SYSCLK / AHB_prescaler (often left as SYSCLK).

  • APB1 clock = HCLK / APB1_prescaler. APB2 clock = HCLK / APB2_prescaler.

  • Timer peripheral clocks: if APB prescaler > 1, timer clock = APB clock × 2 (most STM32 families). So timers often run at double the APB clock when a prescaler is active — good for timer resolution but must be considered when setting PWM / prescalers.

Flash latency & supply voltage

  • Flash memory cannot be read at unlimited speed. The CPU core must wait a number of clock cycles (wait states) for Flash read. The required number depends on:

    • SYSCLK frequency

    • VDD voltage (lower VDD usually requires more wait states)

    • device family (F0/F1/F4/F7/H7/L4 all differ)

  • Always verify the flash latency chart in the device datasheet — CubeMX suggests values, but check the family datasheet if you push speeds or voltages.

Peripheral special clocks

  • USB/SDIO/RNG usually need exact 48 MHz. That means you must set PLLQ (or PLL48CLK) so VCO/Q = 48 MHz exactly.

  • Some peripherals (I2S, SAI, RNG) may use separate PLLs (PLLI2S/PLLSAI). Don’t forget them.

 

CubeMX workflow (practical step-by-step)

 

  1. Open your .ioc and go to Clock Configuration tab.

  2. Set the source: HSE (external crystal), HSI (internal), or PLL (source HSE/HSI). For precise USB, prefer HSE.

  3. Enter the crystal/frequency value (e.g., 8 MHz or 25 MHz) in the top-left (if using HSE).

  4. Edit PLL M, N, P, Q (and other PLLs if needed) until the Clock Configuration tool shows the desired SYSCLK and a 48MHz PLL output (if needed). CubeMX will show green if valid, red if out-of-spec.

  5. Set AHB / APB prescalers to respect peripheral max frequencies (datasheet).

  6. Check the Flash latency field — CubeMX usually sets it automatically, but confirm with the datasheet if uncertain.

  7. Look at peripheral clocks (below the tree) to ensure peripherals get required clock rates.

  8. Generate code and inspect SystemClock_Config() in system_stm32… / main.c. CubeMX will generate the HAL RCC init code.

 

Concrete worked example — STM32F4 (HSE = 8 MHz → SYSCLK 168 MHz)

This is the canonical example many projects use. I’ll do the arithmetic step-by-step.

Goal: 168 MHz SYSCLK using 8 MHz HSE; USB needs 48 MHz.

CubeMX recommended PLL values: M = 8, N = 336, P = 2, Q = 7.

Compute everything digit-by-digit:

  1. PLL input frequency = HSE / M.

    • HSE = 8,000,000 Hz.

    • M = 8.

    • 8,000,000 ÷ 8 = 1,000,000 Hz.

  2. VCO frequency = PLL input × N.

    • PLL input = 1,000,000 Hz.

    • N = 336.

    • 1,000,000 × 336 = 336,000,000 Hz (VCO).

  3. SYSCLK = VCO ÷ P.

    • VCO = 336,000,000 Hz.

    • P = 2.

    • 336,000,000 ÷ 2 = 168,000,000 Hz (SYSCLK or CPU clock).

  4. USB / 48 MHz domain = VCO ÷ Q.

    • VCO = 336,000,000 Hz.

    • Q = 7.

    • 336,000,000 ÷ 7 = 48,000,000 Hz → perfect for USB.

  5. AHB & APB derived clocks (choose safe prescalers): APB1 max in many F4 parts = 42 MHz; APB2 max = 84 MHz. So pick:

    • AHB prescaler = 1 → HCLK = 168,000,000 Hz.

    • APB1 prescaler = 4 → APB1 = 168,000,000 ÷ 4 = 42,000,000 Hz.

    • APB2 prescaler = 2 → APB2 = 168,000,000 ÷ 2 = 84,000,000 Hz.

  6. Timer clocks:

    • APB1 prescaler > 1 → TIMx on APB1 runs at APB1 × 2 = 42,000,000 × 2 = 84,000,000 Hz.

    • APB2 prescaler > 1 → TIMx on APB2 runs at APB2 × 2 = 84,000,000 × 2 = 168,000,000 Hz.

  7. Flash latency: for STM32F4 at 168 MHz (VDD ≈ 3.0-3.6V) set 5 wait states. (CubeMX populates FLASH_LATENCY_5 and HAL uses __HAL_FLASH_SET_LATENCY(FLASH_LATENCY_5).)

CubeMX tip: If CubeMX shows red for any node, fix the PLL/prescalers or voltage scale. It also warns when APB1/APB2 exceed their maximums.

SharedScreenshot.jpg

 

The code CubeMX generates

Below is a representative snippet CubeMX will generate. It’s copy-paste ready into main.c if you selected HAL and the F4 family.

/* Example SystemClock_Config for STM32F4 (HSE=8MHz => SYSCLK=168MHz) */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /* Enable Power Control clock and configure voltage scaling (if needed) */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /* Configure the main PLL */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource  = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM       = 8;    /* divide HSE 8MHz -> 1MHz */
  RCC_OscInitStruct.PLL.PLLN       = 336;  /* VCO = 1MHz * 336 = 336MHz */
  RCC_OscInitStruct.PLL.PLLP       = RCC_PLLP_DIV2; /* SYSCLK = 336/2 = 168MHz */
  RCC_OscInitStruct.PLL.PLLQ       = 7;    /* 336/7 = 48MHz for USB/OTG */
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); }

  /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 clocks dividers */
  RCC_ClkInitStruct.ClockType      = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |
                                      RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
  RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;   /* HCLK = 168 MHz */
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;     /* PCLK1 = 42 MHz */
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;     /* PCLK2 = 84 MHz */

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) { Error_Handler(); }
}

 

Common gotchas and how to avoid them

1) USB doesn’t enumerate

  • Cause: PLLQ doesn’t produce 48.000 MHz, or HSE not present, or USB clock not selected.

  • Fix: Recompute VCO/Q. Example arithmetic above shows 336,000,000 ÷ 7 = 48,000,000. If you change M/N, update Q.

2) CPU crashes / random behavior after increasing SYSCLK

  • Cause: Flash wait states too low or voltage scaling wrong.

  • Fix: Increase FLASH_LATENCY to datasheet-required value, check power scale (VOS). If using CubeMX, confirm the generated HAL_RCC_ClockConfig(..., FLASH_LATENCY_x) value matches datasheet requirements.

3) Peripherals run at unexpected speed

  • Cause: APB prescalers or timer doubling misunderstood.

  • Fix: Recompute APB clocks and remember timers double when APB prescaler > 1.

4) ADC sample rate wrong

  • Cause: ADC clock derived from APB or dedicated prescalers; sampling time selection + ADC clock produce wrong throughput.

  • Fix: Calculate ADC clock from APB prescalers and ADC prescaler. Use longer sample time if ADC clock high.

5) Cache / D-cache issues on M7 families (F7/H7)

  • Cause: DMA and cache coherency: CPU cache contains stale data.

  • Fix: Clean/invalidate D-cache around DMA buffers or use MPU regions non-cacheable. CubeMX does not fix this for you: you must handle it in code.

6) Re-running CubeMX overwrote hand edits

  • Cause: manually edited generated code outside designated USER CODE sections.

  • Fix: Put hand edits in /* USER CODE BEGIN */ regions or move reusable code into separate files.

Debugging checklist

  1. In CubeMX Clock Configuration: confirm Clock Status shows green for SYSCLK, AHB, APB and 48MHz domain (if needed).

  2. Confirm HSE/HSI/LSE values are correct (enter correct crystal frequency).

  3. Calculate PLL math manually (Input/M/N/P/Q) — verify VCO and outputs.

  4. Confirm APB1/APB2 are within device limits.

  5. Confirm flash latency in HAL_RCC_ClockConfig matches datasheet for chosen VDD and SYSCLK.

  6. If using timers, compute timer clock = APB × (2 if prescaler >1 else 1).

  7. If USB fails, check that PLLQ (or dedicated PLL48) equals 48,000,000 Hz.

  8. If DMA used on cache-enabled cores, ensure cache clean/invalidate or use non-cacheable memory.

  9. After generation, inspect SystemClock_Config() and SystemCoreClock via debugger to verify running clocks.

  10. If changing clock at runtime, call SystemCoreClockUpdate() and reconfigure SysTick if needed.

Example “troubleshooting scenario”

Case: USB works on dev board with crystal = 8 MHz, but on a custom board with 25 MHz crystal USB does not enumerate.
Diagnosis steps:

  1. Check CubeMX: HSE set to 25 MHz? (common error: developer left 8 MHz in .ioc)

  2. Recompute PLL parameters — M, N, Q usually must change so VCO/Q=48 MHz. Example: with 25 MHz HSE you might pick M=25 to make PLL input 1 MHz, then N and Q adjusted accordingly.

  3. Confirm PLLQ produces 48 MHz.

  4. Check HSE oscillator on hardware (scope the pin or check RCC flags) — maybe crystal not populated or wrong footprint.

  5. After fix, verify in debugger that RCC->CFGR and RCC->PLLCFGR are as expected.

2 REPLIES 2
Issamos
Lead II

Ahlin @__Mohamed__Ayman__ST 

Thank you so much for this article. It should be helpful for STM32 users. I don’t know if you are an ST employee or trainee (according to your picture) but I think it will be better to be published on the knowledge base side of the forum (after review maybe) for more visibility . @Laurids_PETERSEN 

Best Regards.

II

Hi @Issamos 

Thank you so much for your positive feedback! I'm really glad you found the article helpful.

That's an excellent suggestion to move it to the knowledge base for more visibility. I appreciate you recommending it and tagging @Laurids_PETERSEN .

@Laurids_PETERSEN , I would be honored if you find the article suitable. Please let me know if there's anything you need from my side.

And you have a very good eye! I'm not a current employee, but I was fortunate to be a long-term intern at ST last year. It was a great experience, and I'm happy to be able to share what I learned with the community.

Best Regards.