Clock configuration
SystemClock_Config
/**
* @brief System Clock Configuration
* The system Clock is configured as follow :
* System Clock source = PLL (HSE)
* SYSCLK(Hz) = 400000000 (Cortex-M7 CPU Clock)
* HCLK(Hz) = 200000000 (Cortex-M4 CPU, Bus matrix Clocks)
* AHB Prescaler = 2
* D1 APB3 Prescaler = 2 (APB3 Clock 100MHz)
* D2 APB1 Prescaler = 2 (APB1 Clock 100MHz)
* D2 APB2 Prescaler = 2 (APB2 Clock 100MHz)
* D3 APB4 Prescaler = 2 (APB4 Clock 100MHz)
* HSE Frequency(Hz) = 25000000
* PLL_M = 5
* PLL_N = 160
* PLL_P = 2
* PLL_Q = 4
* PLL_R = 2
* VDD(V) = 3.3
* Flash Latency(WS) = 4
* @param None
* @retval None
*/
static void SystemClock_Config(void) {
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
HAL_StatusTypeDef ret = HAL_OK;
/*!< Supply configuration update enable */
HAL_PWREx_ConfigSupply(PWR_DIRECT_SMPS_SUPPLY);
/* The voltage scaling allows optimizing the power consumption when the device is
clocked below the maximum system frequency, to update the voltage scaling value
regarding system frequency refer to product datasheet. */
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {
}
/* Enable HSE Oscillator and activate PLL with HSE as source */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSIState = RCC_HSI_OFF;
RCC_OscInitStruct.CSIState = RCC_CSI_OFF;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 5;
RCC_OscInitStruct.PLL.PLLN = 160;
RCC_OscInitStruct.PLL.PLLFRACN = 0;
RCC_OscInitStruct.PLL.PLLP = 2;
RCC_OscInitStruct.PLL.PLLR = 2;
RCC_OscInitStruct.PLL.PLLQ = 4;
RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2;
ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);
if (ret != HAL_OK) {
Error_Handler();
}
/* Select PLL as system clock source and configure bus clocks dividers */
RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK
| RCC_CLOCKTYPE_D1PCLK1 | RCC_CLOCKTYPE_PCLK1 |
RCC_CLOCKTYPE_PCLK2 | RCC_CLOCKTYPE_D3PCLK1);
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;
ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
if (ret != HAL_OK) {
Error_Handler();
}
/*
Note : The activation of the I/O Compensation Cell is recommended with communication interfaces
(GPIO, SPI, FMC, QSPI ...) when operating at high frequencies(please refer to product datasheet)
The I/O Compensation Cell activation procedure requires :
- The activation of the CSI clock
- The activation of the SYSCFG clock
- Enabling the I/O Compensation Cell : setting bit[0] of register SYSCFG_CCCSR
*/
/*activate CSI clock mondatory for I/O Compensation Cell*/
__HAL_RCC_CSI_ENABLE();
/* Enable SYSCFG clock mondatory for I/O Compensation Cell */
__HAL_RCC_SYSCFG_CLK_ENABLE();
/* Enables the I/O Compensation Cell */
HAL_EnableCompensationCell();
}
Both init structs need the = {0} initialiser. RCC_OscInitTypeDef in
particular has members this function never assigns — LSEState, HSI48State,
PLL.PLLFRACN on some HAL versions — and HAL_RCC_OscConfig acts on all of
them. Without the initialiser they hold whatever was on the stack.
Why it is needed after HAL_Init
HAL_Init() establishes a working time base but leaves
the clock tree at its reset defaults, which are deliberately conservative:
- After reset the part runs from the internal HSI, 16 MHz effective on the H7. Enough to execute start-up code, far short of the 400 MHz the core supports.
- Performance-sensitive work — signal processing, graphics, high-speed interfaces — needs the full rate, and peripheral throughput scales with the bus clocks feeding it.
- Peripherals have their own requirements. USB needs exactly 48 MHz; Ethernet, SDMMC and the LTDC each have constraints only a deliberate configuration satisfies.
The trade-off runs the other way too: power scales with frequency, so battery-powered designs reconfigure the tree at run time rather than sitting at maximum.
Walkthrough
Supply configuration
HAL_PWREx_ConfigSupply(PWR_DIRECT_SMPS_SUPPLY) selects the internal switching
regulator as the core supply. This must match how the board is actually wired —
the wrong setting either browns the core out immediately or wastes power in the
LDO. Check the schematic, not another project's code.
Voltage scaling
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1) raises the core
voltage to the level the highest frequencies require. The loop on
PWR_FLAG_VOSRDY waits for the regulator to settle before anything depends on
it.
Voltage scale and maximum frequency are paired in the datasheet. Scale 1 permits the top frequency at the highest current draw; lower scales cap the frequency but cut power. Requesting a frequency the current scale does not permit gives an unstable part rather than a clean error.
Oscillator and PLL
HSE is enabled as the PLL source; HSI and CSI are turned off. The arithmetic:
HSE = 25 MHz
/ PLLM (5) = 5 MHz reference into the PLL
× PLLN (160) = 800 MHz VCO output
/ PLLP (2) = 400 MHz SYSCLK
/ PLLQ (4) = 200 MHz peripheral clock branch
/ PLLR (2) = 400 MHz further branch
The VCO runs at 800 MHz, not 400 — the P divider produces the 400 MHz system
clock. PLLRGE declares which band the 5 MHz reference falls into and
PLLVCOSEL selects the wide VCO range; both must agree with the numbers above
or the PLL will not lock. See Phase-locked loop for what
the dividers are doing.
HSEState has two forms that are easy to confuse:
RCC_HSE_ON— a crystal or resonator is fitted across OSC_IN and OSC_OUT, and the internal oscillator drives it.RCC_HSE_BYPASS— an external oscillator module feeds a clock signal into OSC_IN, and OSC_OUT is unused.
Selecting ON on a board wired for BYPASS means the PLL never locks and
HAL_RCC_OscConfig times out, which surfaces as a call to Error_Handler()
with no further explanation.
System clock and bus dividers
SYSCLK takes the PLL output undivided at 400 MHz. AHBCLKDivider = RCC_HCLK_DIV2 gives HCLK 200 MHz for the bus matrix and the Cortex-M4 core.
APB1 through APB4 each divide by two again, so every peripheral bus runs at
100 MHz.
Timers on an APB bus whose prescaler is not 1 are clocked at twice the bus
frequency — 200 MHz here, not 100 MHz. Getting this wrong is the usual reason a
timer period comes out a factor of two off. HAL_RCC_GetPCLK1Freq() returns the
bus clock, not the timer clock.
Flash latency
FLASH_LATENCY_4 is passed to HAL_RCC_ClockConfig, which programs the wait
states before raising the clock. Flash cannot keep up with a 400 MHz core, so it
needs four wait states at this frequency and supply voltage. Too few and the
part fetches corrupted instructions; too many only costs performance. The
correct value is a table in the reference manual indexed by frequency and
voltage scale.
I/O compensation cell
Enabling the CSI and SYSCFG clocks, then HAL_EnableCompensationCell(),
activates the cell that trims I/O slew rate against process and voltage
variation. ST recommends it whenever fast interfaces such as FMC, QSPI or SPI
run at high frequency. Skipping it shows up as signal-integrity problems —
marginal timing, occasional corruption — not as an obvious failure.
Note the CSI is required here even though it was disabled above as a PLL source. The compensation cell uses it independently.
Order matters
Supply, voltage scaling, wait for ready, PLL, then the clock switch with the new flash latency. Raising the frequency before the voltage has settled, or before flash wait states are increased, produces intermittent faults that are extremely hard to diagnose.
HAL_RCC_ClockConfig handles part of this internally: it increases latency
before switching up and decreases it after switching down. That is why the
latency is an argument to that call rather than programmed separately.
Peripheral kernel clocks
SystemClock_Config sets the bus clocks. Many peripherals additionally take a
kernel clock selected independently, through HAL_RCCEx_PeriphCLKConfig:
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1;
PeriphClkInit.Usart16ClockSelection = RCC_USART16CLKSOURCE_D2PCLK2;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) {
Error_Handler();
}
The peripheral computes its own dividers from the kernel clock, not from the bus clock. A UART configured for 115200 baud against the wrong kernel clock source produces a working port at the wrong bit rate — garbage at every terminal setting. The same applies to SDMMC, SPI, I2C, FDCAN and the USB 48 MHz input.