We will use STM32F103 series for these examples. First, before we do anything, we must set up the chip clock. That means to determine frequency at which the chip will work, select clock source, enable/disable real time clock if we need it etc... First we will download the reference manual for STM32F103C8T6 from ST page and open it on chapter 7, Low, medium, high and XL density reset and clock control (RCC). RCC is short name for that register group.
From this schematics we can see exactly what we need to do. If we look at the symbol named SW, we can choose between 3 clock sources:
External oscillator, PLL and internal 8MHz oscillator.
We will use PLL with external oscillator so we need to set SW at PLLCLK and choose PLLSRC as HSE OSC. PLLMUL is PLL multiplier, we will set it to 9.
According to schematics, maximum clock speed for APB1 is 36MHz, so we will need to use APB1 prescaler of 2 to lower the clock speed from 72MHz down to 36MHz. AHB prescaler can stay 1.
Starting point of our coding is manufacturer's website. https://www.st.com/en/microcontrollers-microprocessors/stm32f103c8.html
Download datasheet and reference manual. We start on page 90 of reference manual: Low-, medium-, high- and XL-density reset and clock control (RCC)
RCC->CR|=RCC_CR_HSEON_Msk;
while(!(RCC->CR & RCC_CR_HSERDY))
RCC->CFGR&=~0xFFFF;
RCC->CFGR|=0b0111<< RCC_CFGR_PLLMULL_Pos;
CFGR|=RCC_CFGR_PLLSRC_Msk;
RCC->CR|=RCC_CR_PLLON_Msk;
Enable HSE - High speed external oscillator by writing 1 into Clock control register (RCC_CR) bit HSE ON
RCC->CR |=RCC_CR_HSEON;
And now we wait until HSE Ready flag has been set
while(!(RCC->CR & RCC_CR_HSERDY))
Although CFGR register should be 0 at default value, it doesn't hurt to actually make it 0.
RCC->CFGR&=~0xFFFF;
We want to disable external oscillator clock division by 2.
That function is controlled by bit PLLXTPRE in RCC->CFGR register. We want to set it to 0.
Bit 17 : PLLXTPRE: HSE divider for PLL entry
Set and cleared by software to divide HSE before PLL entry. This bit can be written only when PLL is disabled.
0: HSE clock not divided
1: HSE clock divided by 2
we will choose not to divide HSE clock, doesn't matter really since we can adjust the PLL multiplier. So we leave this one alone or set it to 0 to be sure it's zero.
RCC->CFGR &=~RCC_CFGR_PLLXTPRE;
Bit that controls this is located in CFGR register.
Bit 16
PLLSRC: PLL entry clock source
Set and cleared by software to select PLL clock source. This bit can be written only when PLL is disabled.
0: HSI oscillator clock / 2 selected as PLL input clock
1: HSE oscillator clock selected as PLL input clock
We want to use HSE oscillator as PLL input clock so we set it to 1:
RCC->CFGR |=RCC_CFGR_PLLSRC;
RCC_CFGR register, bits 18-21
To get main clock of 72MHz from external oscillator of frequency 8MHz we will choose multiplier 9 and from datasheet we find out what value we must write into a register 0111: PLL input clock x 9
We need to write 0b0111 shifted by 18 bits
RCC->CFGR |=0b0111<< RCC_CFGR_PLLMUL_Pos;
RCC_CFGR_PLLMUL_Pos is just a name (define) for number 18 - position of PLLMUL bits
Last step is to actually turn the PLL on. We do that by setting a bit PLLON in CR register to 1.
RCC->CR|=RCC_CR_PLLON_Msk;
Now we can set the dividers for APB1, APB2 and AHB buses.
We do that by settings prescalers for each bus while taking care that we don't exceed 36MHz on APB1 bus. We can do that in multiple ways, depending on exactly what you need.
But if you are not concerned about power consumption or getting maximum performance out of this chip you can, for now set the AHB prescaler to 1 (no clock division), APB1 prescaler to 2 and APB2 prescaler to 2.
Prescaler control bits are also placed in RCC->CFGR register. Their names are PPRE2, PPRE1 and HPRE for APB2, APB1, AHB respectively.
There is an explanation of what we need to write to control bits to set up the prescalers.
0xx: HCLK not divided
100: HCLK divided by 2
101: HCLK divided by 4
110: HCLK divided by 8
111: HCLK divided by 16
Where HCLK represents the clock coming from after AHB prescalers.
To get the prescaler of 2, we must write binary 0b100 to register RCC->CFGR to bit group PPRE2. Before that we must clear any bits that may be set in that bit group.
RCC->CFGR&=~(0b111<<RCC_CFGR_PPRE1_Pos);
RCC->CFGR|=0b100<<RCC_CFGR_PPRE1_Pos;
RCC->CFGR&=~(0b111<<RCC_CFGR_PPRE2_Pos);
RCC->CFGR|=0b100<<RCC_CFGR_PPRE2_Pos;
Copyright 2024-2025, Mario Matovina
Send me an e-mail