STM32F103 Clock Setup using Registers

This tutorial will cover Clock setup, Timer Setup for Delay, and GPIO configuration for STM32F103C8 (BluePill) using the Register based programming. I will cover all the steps, and the link to download the code is at the end of this post.

Before starting the main setup, Let’s see the clock setup that we will be using in this tutorial.

As you can see above, the system will run at 72 MHz. Also take note of the APB1, and APB2 clocks, since they will be used in the timer setup


This part is very simple in BluePill compared to STM32F4 devices. The reason is that the function to set up the clock is predefined in the core files that we include in our project. Check the code below

void SystemInit (void)
  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001;

  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
  RCC->CFGR &= (uint32_t)0xF8FF0000;
  RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */   
  /* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

  /* Reset HSEBYP bit */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

  RCC->CFGR &= (uint32_t)0xFF80FFFF;

#ifdef STM32F10X_CL
  /* Reset PLL2ON and PLL3ON bits */
  RCC->CR &= (uint32_t)0xEBFFFFFF;

  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x00FF0000;

  /* Reset CFGR2 register */
  RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x009F0000;

  /* Reset CFGR2 register */
  RCC->CFGR2 = 0x00000000;      
  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
  #ifdef DATA_IN_ExtSRAM
  #endif /* DATA_IN_ExtSRAM */

  /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
  /* Configure the Flash Latency cycles and enable prefetch buffer */

  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */

This function SystemInit() is already present in system_stm32f10x.c file. All we need to do is call it in our main function and the clock setup will be complete

If you take a look at the top of the system_stm32f10x.c file, you can see that we have options to select different clocks

#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE    HSE_VALUE */
 #define SYSCLK_FREQ_24MHz  24000000
// #define SYSCLK_FREQ_24MHz  24000000 
// #define SYSCLK_FREQ_36MHz  36000000 
/* #define SYSCLK_FREQ_48MHz  48000000 */
/* #define SYSCLK_FREQ_56MHz  56000000 */
#define SYSCLK_FREQ_72MHz  72000000

As you can see in the above code, we can select any of the predefined clocks. All you need to do is uncomment that line.
Here 72 MHz in uncommented and that’s why the system will run at 72 MHz

This is it for the clock setup. Now let’s take a look at the timer setup

Timer Configuration

In order to configure the timer, we need to follow some set of steps, and they are given below

/************** STEPS TO FOLLOW *****************
1. Enable Timer clock
2. Set the prescalar and the ARR
3. Enable the Timer, and wait for the update Flag to set

Let’s see them one by one

1. Enable the Timer Clock

I am choosing timer 2 for this tutorial. And to enable the timer 2 clock, we need to see the RCC_APB1ENR Register

RCC->APB1ENR |= (1<<0); // enable clock for TIM2

So all we need to do is write a 1 in the 0th position of the RCC_APB1ENR Register

Now let’s see how to set the presclalar and Auto Reload Register

2. Set Prescalar and ARR

Timers are connected to the Peripheral Clocks. In F103C8, there are 2 peripheral clocks i.e APB1 and APB2. In this tutorial I have decided to use the Timer 2, which is basically connected to the APB2.

We know by now that our system clock is at 72 MHz, and the rest of the clock setup will be as shown below

As shown above, the APB1 Clock is at 72 MHz. The Prescalar is used to bring this clock down to our working range. Here in this tutorial we will be focusing on creating delays in micro and milliseconds, so our working range is around 1 MHz.

As according to the above formula, If we use the prescalar of 71, the timer clock will come down to [72MHz/(71+1)] = 1 MHz

TIM2->PSC = 72-1;      	// Prescalar value

After setting the timer for 1 MHz, if we start the timer, the counter starts counting from 0.
Each count by the counter takes 1 microsecond. The maximum count this counter can achieve is the value of the ARR.
After this the timer will overflow and our time delay will be messed up.

To avoid this we will keep the ARR value to the maximum possible. This is a 16 bit register, so the maximum value can be 0xffff.

TIM2->ARR = 0xffff-1;  	// ARR value


In order to enable the Timer, we will modify the Control Register 1 (TIM_CR1).

As shown above, the 0th bit of the TIMx_CR1 Register enable the timer, and to do so we need to write a 1 in the 0th position

TIM2->CR1 |= (1<<0);  	// enable timer

To confirm if the timer has started, we will check the status Register (TIMx_SR)

UIF Bit is set whenever there is update event. So we will wait until this bit is set

while (!(TIM2->SR & (1<<0)));  // UIF: Update interrupt flag..  This bit is set by hardware when the registers are updated

This is it for the Timer setup. Let’s configure the GPIO now


I am using timer 2 to blink a LED, which is connected to PC13. In order to set PC13 as output, we need to follow some set of steps and they are as follows

/************** STEPS TO FOLLOW *****************
	1. Enable the GPIOC clock
	2. Configure the Pin as output

1. Enable the GPIOC Clock

To enable the GPIOC clock, we need to modify the APB2 Enable Register (RCC_APB2ENR)

As you can see in the picture above, the 4th bit of RCC_APB2ENR controls the GPIOC Clock. SO we need to write a 1 to the 4th position in order to enable the PORT C clock

RCC->APB2ENR |= (1<<4);  // Enable GPIOC Clock


Now we will configure the pin as output pin. To do this, we need to modify the Port Configuration Register.

This Register is actually divided into 2 categories. Since we are using Pin PC13, we will be using Port configuration register high (GPIOx_CRH). For Pin 0 to 7, we will use GPIOx_CRL

Port Configuration Register controls both, mode and configuration for the Pin. 4 Bits are used to setup a single pin, for example, in order to set up PIN 10, we have to use bits 11:10:9:8

Since we are using the Pin PC13 for blinking the LED, we need to set it as the output mode. I am using the 10 MHz speed for the pin (there is no particular reason for it).

For the configuration part, i am choosing output Push Pull mode, as it’s best to use in simple GPIO operations.

This setup will lead us to configure the bits (23:22:21:20) as 0:0:0:1

GPIOC->CRH &= ~(0xF<<20)  // Clear Bits 23:22:21:20
GPIOC->CRH |= (1<<20);  // PC13 Output mode 10 MHz, push pull


int main ()
	SystemInit ();
	GPIO_Config ();
	TIM2_Config ();
	while (1)
		GPIOC->BSRR |= (1<<13);  // Set the pin PC13
		Delay_ms (1000);
		GPIOC->BSRR |= (1<<29);  // RESET the pin PC13
		Delay_ms (1000);
  • Here we will first call the SystemInit to configure the clocks
  • next GPIO_Config to configure the PC13 as output
  • Then TIM2_Config will configure the timer for the delay purpose

Now inside the while loop, we will turn the LED ON and OFF. To handle this, we need to look into the Port Bit Set Reset Register (GPIOx_BSRR)

GPIOx_BSRR is a 32 bit Register. The lower 16 Bits (0 to 15) are used to SET the Corresponding Bit. And the upper 16 bits (15 to 31) are used to RESET the corresponding bit

Since we are using PC13, in order to SET this pin, we will write a 1 to the 13th position, and in order to RESET it, we will write a 1 to the 29th position

There is s delay of 1 second between these two statements, and this will keep LED blinking every 1 second


Check out the Video Below


You can help with the development by DONATING
To download the code, click DOWNLOAD button and view the Ad. The project will download after the Ad is finished.

19 Comments. Leave new

  • Enable the GPIOC Clock. register RCC_APB2ENR not from stm32F103, correct

  • GPIOC->CRH &= ~(0xF<<20) // Clear Bits 23:22:21:20
    Sir ” ; ” is missing @ the end of a line on a web page.

  • jes_dev_stm32
    August 19, 2022 12:19 AM

    hi! i am using stm32f030k6t6 for clock setup register with rcc internal 48 mhz, what is the step? this is equal?

  • Hello, After connecting ST-Link to Blue-Pill I am getting an Error of “Not an geniune ST Device! Abort Connection.. Can Someone Please Help.. Thanks..

  • Sanjeet Malawade
    August 1, 2021 11:07 AM

    Hey, where can I find the steps that are needed to set up the clock? Is it available in the reference manual? Thanks.

  • HI ! Loved your tutorials.
    I want to ask one thing. Can we use this style of coding(through registers directly) in stm32cube ide also?
    I want to use this but I use linux and dont have the option to install keil mkd on linux system.

    • yes you can, but you have to generate the code through cubeMX (the integrated one). You can delete the content of the main file, and later write the registers only

      • Hi ! I installed keil 4.7mdk through wine on lubuntu. Tried this code and commented out the delay completely.
        Saw the output on oscilloscope, It says the frequency is 2.4Mhz. I expected it to be 36MHz because of the 72Mhz clock of the system. And also the waveform looks like a slowly rising saw tooth wave(this is what surprised me the most though). I wanted to attach waveforms with this comment but no option here.
        Then I installed Cube ide. Initialized using MX tool. Using HAL GPIO_Write to toggle pin, checked on scope, freq – 758KHz(square wave).
        Then replaced HAL GPIO Write with your code in while loop without delay. This time the freq came out to be 1.6Mhz. But its a nice square wave again.
        Can you please explain whats the matter here?
        Shouldn’t the output be 32MHz if we assume that controller takes one clock per instruction.
        Also is there something that needs to be changed in the initialization part of your code to make it a square wave, because not just at high freq, but also at lower frequencies with your code, the waveform rises slowly for some reason, the falling edge is pretty squared though.

        • i don’t know about the keil run through wine…

          Coming to cubeIDE, like you said you tried the HAL version first. I am sure that the clock setup is wrong in your case. I need more details to solve the issue… you can contact on discord or telegram

          • Sure. I’ll catch up on discord. Where can I get a link to your discord
            Edit: Just saw icon on top right of the page. Thanks

      • Also tested by replacing the main file in cube ide with your code, had to change the include stm32f1x.h to xx.h in both delay and main.
        the output wave is 307KHz now. And the problem of slowly rising edge persists even at this frequency.
        This low frequency makes me think that the clock might be set to 8MHz instead of 72MHz for some reason. This is my first day trying stm so don’t know how to do stuff here.
        Have you tried using plain register access in cube ide. What changes might be needed here

        • Lastly replaced the whole code in main file in cube ide with your code, removed all the hal related files, only CMSIS folder remains inside Drivers.Had to add stm32f10x.c and .h and a couple of files from the keil installation folder to stm32cube project to make it compile.
          The code compiled and the output is again a slowly rising(falling edge is square though) saw tooth wave. commented out the delay lines, the ffrequency came out to be 1.85MHz.
          So my last question would be, why are the results so different for the exact same code compiled from keil and cubeide.
          Keil- 2.4MhZ Saw Tooth Wave
          MX and HAL – 758KHz Square Wave
          HAL initialization but direct register access inside while- 1.6MHz Square Wave
          Removed HAL and replaced whole code with keil code -1.85MHz Saw Tooth

    • Utkarsh Sethi
      June 17, 2021 1:47 PM

      If you are planning to exclusively use CMSIS, try embedded studio. I find it stable on Ubuntu, a good alternative to Keil. Some things will be different.

      You will need a bit of a set-up to use ST-Link as a debugger, but you can flash J-Link on a disco board.

  • GPIOC->CRH |= (1<<20); // PC13 output open drain mode
    GPIOC->CRH = 0x44344444; // PC13 general purpose output push-pull


Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.


Adblocker detected! Please consider reading this notice.

We've detected that you are using AdBlock Plus or some other adblocking software which is preventing the page from fully loading.

We don't have any banner, Flash, animation, obnoxious sound, or popup ad. We do not implement these annoying types of ads!

We need money to operate the site, and almost all of it comes from our online advertising.

Please add to your ad blocking whitelist or disable your adblocking software.