1.5. Clock settings


1. Introduction

Clock settings are of primary importance when you start a new microcontroller project. The way you configure the clocking scheme has direct impact on peripheral programing, application performance and power consumption. This tutorial details the default configuration and provides a function to setup clock for maximum performance and stability.
You absolutely need to understand and master the concepts introduced here.

2. STM32 Clock architecture

To get a better understanding of the complex STM32 clock architecture, you can install CubeMX software.

You can get CubeMX from ST website :

 
Follow STM32CubeMX installation procedure and launch it.

image013.png

 

Click New Project. Using the MCU Selector filters, find our device (STM32F072RBTx):

image015.png

 

Then click OK. The project window opens.

image016.png

Open the Clock Configuration tab:

image018.png

The above picture represents the full hardware clock tree inside of the MCU. Blue boxes are the existing internal clock sources (oscillators). Gray boxes represent external oscillators you may (or may not) wish to use instead.

Other boxes are:

  • Multiplexer (one input among n is connected to the output)
  • Frequency dividers (prescalers)
  • Frequency multiplier (PLL)

By fine tuning all these boxes, you can achieve a large amount of different clock configurations. Why is it that complex? Because clock and power consumption are tightly linked, and you want the most of the MCU, with as low current as possible.
Hence, in power-aware embedded systems, a general rule is:

  • Only clock the hardware your application needs
  • Lower the clock frequency as much as you can
  • Use Low-Power modes (sleep, stop, standby) whenever you can

Internal clock sources are based on RC oscillators, since up to now CMOS technology is not able to embed crystal oscillators onto the MCU die. RC oscillators are convenient, but neither precise in frequency, nor stable over temperature variations.

For these reasons:

  • If the application do not requires precise timing, and you want save cost: use internal oscillators.
  • If the application requires precise and stable clock frequency: use an external crystal clock source. As soon as your application involves asynchronous serial communication such as UART or USB, then you need a stable clock. You may also use RC oscillator together with regular calibration routine.

 

3. Default configuration

In our template project, so far, the clock settings are done during the startup procedure via a call to the SystemInit() function. Note that SystemInit() is called just before main().

startup_stm32fà72xb.S (lines 98-103)

/* Call the clock system intitialization function.*/
  bl  SystemInit
/* Call static constructors */
  bl __libc_init_array
/* Call the application's entry point.*/
  bl main

SystemInit() function is written in the system_stm32f0xx.c file.

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

#if defined (STM32F051x8) || defined (STM32F058x8)
  /* Reset SW[1:0], HPRE[3:0], PPRE[2:0], ADCPRE and MCOSEL[2:0] bits */
  RCC->CFGR &= (uint32_t)0xF8FFB80CU;
#else
  /* Reset SW[1:0], HPRE[3:0], PPRE[2:0], ADCPRE, MCOSEL[2:0], MCOPRE[2:0] and PLLNODIV bits */
  RCC->CFGR &= (uint32_t)0x08FFB80CU;
#endif /* STM32F051x8 or STM32F058x8 */
  
  /* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= (uint32_t)0xFEF6FFFFU;

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

  /* Reset PLLSRC, PLLXTPRE and PLLMUL[3:0] bits */
  RCC->CFGR &= (uint32_t)0xFFC0FFFFU;

  /* Reset PREDIV[3:0] bits */
  RCC->CFGR2 &= (uint32_t)0xFFFFFFF0U;

#if defined (STM32F072xB) || defined (STM32F078xx)
  /* Reset USART2SW[1:0], USART1SW[1:0], I2C1SW, CECSW, USBSW and ADCSW bits */
  RCC->CFGR3 &= (uint32_t)0xFFFCFE2CU;

  /* … */
  
#else
 #warning "No target selected"
#endif

  /* Reset HSI14 bit */
  RCC->CR2 &= (uint32_t)0xFFFFFFFEU;

  /* Disable all interrupts */
  RCC->CIR = 0x00000000U;
}

Let us analyze this line by line.

 

The first one turns on HSI (High Speed Internal) oscillator, providing an internal 8MHz clock source.

  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001U;

RCC_CR Register (reset value = 0x0000 XX83):

image024.png
image028.png

 

The next line select HSI as main clock source and tune the AHB and APB prescalers to /1 division:

  /* Reset SW[1:0], HPRE[3:0], PPRE[2:0], ADCPRE, MCOSEL[2:0], MCOPRE[2:0] and PLLNODIV bits */
  RCC->CFGR &= (uint32_t)0x08FFB80CU;

RCC_CFGR Register (reset value = 0x0000 0000):

image036.png

SW[1:0]    = 00      → HSI is selected as system clock
HPRE[3:0]  = 0000    → AHB prescaler is /1
PPRE[2:0]  = 000     → APB prescaler is /1

image038.png

 

Then HSE (High Speed External) is disabled (it is probably useless upon reset, but you might need to call this function later to return to initial clock settings after having set another configuration):

/* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= (uint32_t)0xFEF6FFFFU;

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

  /* Reset PLLSRC, PLLXTPRE and PLLMUL[3:0] bits */
  RCC->CFGR &= (uint32_t)0xFFC0FFFFU;

  /* Reset PREDIV[3:0] bits */
  RCC->CFGR2 &= (uint32_t)0xFFFFFFF0U;
image040.png

 

image045.png

 
Finally, clock source for USART1, USART2 and I2C1 are set:

  /* Reset USART2SW[1:0], USART1SW[1:0], I2C1SW, CECSW, USBSW and ADCSW bits */
  RCC->CFGR3 &= (uint32_t)0xFFFCFE2CU;

RCC_CFGR3 Register (reset value = 0x0000 0000):

image047.png

USART1 clock source  → PCLK1    (8MHz)
USART2 clock source  → PCLK1    (8MHz)
I2C1 clock source    → HSI      (8MHz)

image052.png
 

4. Full-Speed HSE configuration

Let us write another clock configuration function, in order to achieve the maximum MCU frequency (48MHz), and benefit from the available stable external reference clock.
HSE (High-Speed External) hardware supports two different configurations:

 

  • Oscillator Mode: In oscillator mode, HSE is connected to a crystal/capacitor network. HSE drives this network (unstable closed-loop amplifier) in order to produce oscillations
image055.png

 

  • Bypass Mode: In bypass mode, HSE receives a clock from an external source on the board. HSE does nothing but letting that clock passing through it.
image057.png

Looking at the board schematics, we see that the 2 options have been anticipated on the PCB. We can either use:

  • The ST-Link MCO clock source (HSE in bypass mode)
  • The X3/C33/C34 network (HSE in oscillator mode)

 

image059.png
image060.png

 

Now, looking closer on the board itself, you will see that X3/C33/C34 are not actually fitted. Footprints are there, but if you want it, you must solder it by yourself…

image061.png

Well, we will use the 8MHz MCO with HSE bypass mode. X3/C33/C34 should be fitted only if you want the cut the ST-Link part away. In this case HSE must be used in oscillator mode.

In CubeMX, open the Pinout tab, and set RCC HSE in BYPASS mode. Then open the Clock Configuration tab back.

image063.png

 

Let us try and get the maximum 48MHz speed for the MCU based on that 8MHz HSE, playing with the configurator. You may end up with something like this:

image064.png

 

Obviously, getting 48MHz from 8MHz clock source involves the PLL with a multiplication factor of x6. Otherwise, it is only signal routing.

image065.png

Well done! Let’s write the associated code now…

Copy/paste the following function below the main() function in the main.c file. Comments should help you understand what the code does but basically, it just sets the RCC peripheral as above discussed.

/*
 * 	Clock configuration for the Nucleo STM32F072RB board
 * 	HSE input Bypass Mode 			-> 8MHz
 * 	SYSCLK, AHB, APB1 				-> 48MHz
 *  	PA8 as MCO with /16 prescaler 		-> 3MHz
 *  
 *  Laurent Latorre - 05/08/2017  
 */

static void SystemClock_Config()
{
	uint32_t	HSE_Status;
	uint32_t	PLL_Status;
	uint32_t	SW_Status;
	uint32_t	timeout = 0;

	timeout = 1000000;

	// Start HSE in Bypass Mode
	RCC->CR |= RCC_CR_HSEBYP;
	RCC->CR |= RCC_CR_HSEON;

	// Wait until HSE is ready
	do
	{
		HSE_Status = RCC->CR & RCC_CR_HSERDY_Msk;
		timeout--;
	} while ((HSE_Status == 0) && (timeout > 0));

	// Select HSE as PLL input source
	RCC->CFGR &= ~RCC_CFGR_PLLSRC_Msk;
	RCC->CFGR |= (0x02 <<RCC_CFGR_PLLSRC_Pos);

	// Set PLL PREDIV to /1
	RCC->CFGR2 = 0x00000000;

	// Set PLL MUL to x6
	RCC->CFGR &= ~RCC_CFGR_PLLMUL_Msk;
	RCC->CFGR |= (0x04 <<RCC_CFGR_PLLMUL_Pos);

	// Enable the main PLL
	RCC-> CR |= RCC_CR_PLLON;

	// Wait until PLL is ready
	do
	{
		PLL_Status = RCC->CR & RCC_CR_PLLRDY_Msk;
		timeout--;
	} while ((PLL_Status == 0) && (timeout > 0));

        // Set AHB prescaler to /1
	RCC->CFGR &= ~RCC_CFGR_HPRE_Msk;
	RCC->CFGR |= RCC_CFGR_HPRE_DIV1;

	//Set APB1 prescaler to /1
	RCC->CFGR &= ~RCC_CFGR_PPRE_Msk;
	RCC->CFGR |= RCC_CFGR_PPRE_DIV1;

	// Enable FLASH Prefetch Buffer and set Flash Latency
	FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;

	/* --- Until this point, MCU was still clocked by HSI at 8MHz ---*/
	/* --- Switching to PLL at 48MHz Now!  Fasten your seat belt! ---*/

	// Select the main PLL as system clock source
	RCC->CFGR &= ~RCC_CFGR_SW;
	RCC->CFGR |= RCC_CFGR_SW_PLL;

	// Wait until PLL becomes main switch input
	do
	{
		SW_Status = (RCC->CFGR & RCC_CFGR_SWS_Msk);
		timeout--;
	} while ((SW_Status != RCC_CFGR_SWS_PLL) && (timeout > 0));

	/* --- Here we go! ---*/

	/*--- Use PA8 as MCO output at 48/16 = 3MHz ---*/

	// Set MCO source as SYSCLK (48MHz)
	RCC->CFGR &= ~RCC_CFGR_MCO_Msk;
	RCC->CFGR |=  RCC_CFGR_MCOSEL_SYSCLK;

	// Set MCO prescaler to /16 -> 3MHz
	RCC->CFGR &= ~RCC_CFGR_MCOPRE_Msk;
	RCC->CFGR |=  RCC_CFGR_MCOPRE_DIV16;

	// Enable GPIOA clock
	RCC->AHBENR |= RCC_AHBENR_GPIOAEN;

	// Configure PA8 as Alternate function
	GPIOA->MODER &= ~GPIO_MODER_MODER8_Msk;
	GPIOA->MODER |= (0x02 <<GPIO_MODER_MODER8_Pos);

	// Set to AF0 (MCO output)
	GPIOA->AFR[1] &= ~(0x0000000F);
	GPIOA->AFR[1] |=  (0x00000000);

	// Update SystemCoreClock global variable
	SystemCoreClockUpdate();
}

 
The SystemCoreClockUpdate() function is provided in system_stm32f0xx.c. It does the reverse job of computing actual CPU clock frequency based on your RCC settings and then updates the SystemCoreClock global variable. It is a convenient way to check if things have been set as expected.

Using PA8 as MCO (Master Clock Output) is not mandatory. It is just a convenient way to measure if the setup works as expected. In order to cope with oscilloscope bandwidth, the 48MHz is divided by /16 so that MCO pin provides a 3MHz clock.

 

5. Final test

Edit the top of the main.c file in order to:

  • Declare the SystemClock_Config() function prototype
  • Insert a call to this function at the beginning of main()
/*
 * main.c
 *
 *  Created on: 5 août 2017
 *      Author: Laurent
 */


#include "stm32f0xx.h"

static void SystemClock_Config(void);

void main()
{
	uint8_t i = 0;

	SystemClock_Config();

	while(1)
	{
		i++;
	}
}

/*
 * 	Clock configuration for the Nucleo STM32F072RB board
 * 	HSE input Bypass Mode 			-> 8MHz
 * 	SYSCLK, AHB, APB1 				-> 48MHz
 *  	PA8 as MCO with /16 prescaler 		-> 3MHz
 *  
 *  Laurent Latorre - 05/08/2017  
 */

static void SystemClock_Config()
{
  ...
}

 

Note that by declaring a function as static you can only call it from within the same file (main.c). Save all, and build the project. You should have no error or warning.

Launch a debug session:

image071.png
 

Before starting code execution, open the Expressions view and add ‘SystemCoreClock’:

image072.png

You must have a value of 8000000 (8Mhz), corresponding to nominal clock settings at startup.

 

Now, step-over image073.png 2 lines and watch the Expressions view:

image074.png

The clock should be now 48MHz.

image075.png

 

Run image076.png the program and probe PA8 with an oscilloscope. You should see a 3MHz clock:

image077.png

 

Then suspend the execution image079.png and keep probing PA8. What do you see? Can you explain?

You can terminate  the debug session and switch back to the C/C++ perspective.

 

6. Summary

In this (really important) tutorial, you’ve learned to setup clock in the STM32. These settings define clock frequencies for the CPU and for the various peripherals buses (AHB, APB1, APB2). You need to know at which frequency a given peripheral is clocked if you want to use it. You need to refer to the datasheet diagram next page to know which peripheral is attached to which bus.
CubeMX can be used to get a global view of clock settings and as a simple frequency calculator. Although CubeMX is able to automatically generate initialization code, we do not use this capability here.