Try the new version: www.pomad.fr/POMAD_2024/welcome

1.8. Clock settings

Submitted by admin on Sat, 05/15/2021 - 18:38

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

Open your blink3 project and edit the main.c file. Add a call to the SystemCoreClockUpdate() at the beginning of the main() function. The SystemCoreClockUpdate() is implemented in system_stm32f0xx.c and is part of the CMSIS package. What it does is an update of the global variable SystemCoreClock using current settings to calculate actual core clock frequency.

/*
 * main.c
 *
 *  Created on: 15 mai 2021
 *      Author: Laurent
 */

#include "stm32f0xx.h"

int main()
{
	uint32_t i;

	SystemCoreClockUpdate();	// <-- Add this line

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

	// Configure PA5 as output
	GPIOA->MODER &= ~GPIO_MODER_MODER5_Msk;
	GPIOA->MODER |= (0x01 <<GPIO_MODER_MODER5_Pos);

	while(1)
	{
		// LED toggle
		GPIOA->ODR ^= GPIO_ODR_5;

		// Wait
		for(i=0; i<100000; i++);
	}
}

 

Save , build , and fire a debug session .

In the Expressions view, add the SystemCoreClock global variable, then step over the SystemCoreClock() function and watch the value of SystemCoreClock:

SystemCoreClock = 8000000, so actual CPU frequency is 8MHz. The device datasheet reveals that SMT32F072 runs fine up to 48MHz, so we have room for a 6× faster CPU. How can we do that?

  1. By understanding the clock scheme of the device.
  2. By writing a function to configure the clock scheme for 48MHz operation.

 

3. Understanding the Clock scheme

CubeMX provides a handy graphical view of the clock scheme. You can open the first blink project, then open blink.ioc file into the graphical editor and select the Clock Configuration tab.

The above picture represents the full hardware architecture of the MCU internal clock system. We find:

  • Internal clock sources (RC oscillators) → Available at no additional costs, but neither very precise, nor very stable across the temperature range
    • High Speed : HSI, HSI48, HSI14 → default RC-based oscillator for CPU and peripherals.
    • Low Speed : LSI → always used with the watchdog timer.
  • Inputs for external clock sources (Crystal oscillators) → Required if clock precision and stability are required
    • High Speed : HSE → the de facto input for high-speed crystal-based oscillator for CPU and peripherals fast and stable clocking.
    • Low Speed : LSE → mostly used with RTC.
  • Multiplexers for clock routing (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

 

4. Understanding the default configuration

In our blink3 project, so far, the clock configuration is done at startup via a call to the SystemInit() function. Note that SystemInit() is called just before main().

Yet, as you can see, the SystemInit() function (in system_stm32f0xx.c) has been left empty, and that's up to you to customize it.

/**
  * @brief  Setup the microcontroller system
  * @param  None
  * @retval None
  */
void SystemInit(void)
{
  /* NOTE :SystemInit(): This function is called at startup just after reset and 
                         before branch to main program. This call is made inside
                         the "startup_stm32f0xx.s" file.
                         User can setups the default system clock (System clock source, PLL Multiplier
                         and Divider factors, AHB/APBx prescalers and Flash settings).
   */
}

 

As a result, the 8MHz we observe for the system core clock comes from the default clock configuration at startup (reset state), which in turn is a consequence of the registers that configure the above clock scheme reset states . These registers are part of the RCC (Reset & Clock Control) peripheral. Refer to the the Reference Manual to fully understand the table below.

  RESET STATE BITS EFFECT
RCC_CR 0x0000 XX83
  • HSI ON = 1
  • HSI RDY = 1
  • HSI TRIM = 16
  • The HSI oscillator is ON and ready, with the trimmer set to 8MHz.
  • HSE and PLL are OFF
RCC_CFGR 0x0000 0000
  • SW = 0
  • HPRE = 0
  • PPRE = 0
  • The HSI input is selected at the System Clock Mux input. SYSCLK = 8MHz.
  • AHB Prescaler and APB Prescaler are set to /1, therefore :
    • HCLK = 8MHz
    • PCLK = 8MHz
    • → all peripherals receive a 8MHz clock.

The corresponding path is highlighted below. This is the configuration you have if you do nothing.

 

5. Writing a new clock configuration function

Assume we want to push the processor to its 48MHz limit, and then use HSE as the clock source for more stability.

The HSE hardware supports two different configurations (modes):

  • The Oscillator Mode: In this mode, HSE is connected to a crystal/capacitor network. HSE drives this network (unstable closed-loop amplifier) in order to produce oscillations
  • The Bypass Mode: In this mode, HSE receives a clock from an external source on the board. HSE does nothing but letting that clock passing through it.

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

  1. the X3/C33/C34 network with HSE in Oscillator Mode
  2. or the ST-Link MCO clock source with HSE in Bypass Mode

But looking closer on the board itself, you will see that X3/C33/C34 and R35, R37 are not actually fitted. Footprints are there, but if you want it, you have to buy the parts and then find a soldering iron...

So we're left with the second option, i.e. using the ST-Link MCO (Master Clock Output) as our HSE source, with HSE in Bypass Mode. The User Manual also states that ST-Link MCO is a fixed 8MHz frequency clock.

As we want to achieve a 48MHz CPU clock frequency from the 8MHz external clock source, we need something that multiplies frequencies. This is the purpose of the PLL (Phase Locked Loop) circuit. Using a ×6 multiplication factor within the PLL, we can obtain the targeted 48MHz for the CPU and peripherals.

In summary, the configuration we need to implement is:

Practically, this will be achieved by writing a function that does (in this order):

  1. Enable HSE in Bypass Mode and make sure it is READY (should already be)
  2. Choose HSE as the selected input on the PLL Source Mux
  3. Set the PLL prescaler PREDiv to /1 and the multiplication factor PLLMul to ×6
  4. Start the PLL and make sure it is READY
  5. Make sure AHB Prescaler and APB Prescaler are set such that system (CPU & peripherals) will cope with the new frequency
  6. Switch the System Clock Mux intput, from HSI to PLLCLK → At this moment only will the system clock get boosted.

 

6. Let's boost the system

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

In addition, it sets PA8 pin as MCO (Master Clock Output). This is not mandatory. It only provides a convenient way to measure the internal clock frequency with an oscilloscope. Yet, in order to cope with oscilloscopes bandwidth, the 48MHz is divided by /16 so that MCO pin actually exhibits a 3MHz square signal.

/*
 * 	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();
}

 

Then declare the SystemClock_Config() function prototype at the beginning of main.c  and insert a call to this function at the beginning of main() :

static void SystemClock_Config (void);        // <-- Function prototype

int main()
{
	uint32_t i;

	SystemClock_Config();                // <-- Call to the clock configuration function

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

	// Configure PA5 as output
	GPIOA->MODER &= ~GPIO_MODER_MODER5_Msk;
	GPIOA->MODER |= (0x01 <<GPIO_MODER_MODER5_Pos);

	while(1)
	{
		// LED toggle
		GPIOA->ODR ^= GPIO_ODR_5;

		// Wait
		for(i=0; i<100000; i++);
	}
}

 

Save , build , and fire a debug session .

At the beginning of main(), the SystemCoreClock variable should still be 8000000 (8MHz). Then step over the SystemClock_Config() function and watch the Expression view.

The SystemCoreClock variable should be now 48000000 (48MHz). Well done!

Run  the program and watch the LED... blinking has become pretty nervous, hasn't it?

Probe PA8 pin with an oscilloscope. You should see a 3MHz clock (as a result of 48MHz /16):

Then suspend the execution  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.

7. Summary

In this tutorial, we introduced the clock setup in the STM32. User settings define clock frequencies for the CPU and for the various peripherals (buses). This is a matter of primary importance:

  • You can't configure peripherals, especially timers and communication peripherals if you don't know what is their clock frequency.
  • The clock frequency can be changed on-the-fly, depending on the application requirements or the available power at a given moment.
  • As you can see, there are several clock domains in the MCU. Fine tuning each domain to the minimum required frequency, at every moment of the application life, is a key for power savings.

At this moment, we tuned clock for maximum operating frequency. Therefore, we considered the performance before the power consumption. All along the subsequent tutorials, we will assume that performance is the priority and keep using that clock configuration, but keep in mind that it is not power friendly. A good approach would be to start a development at maximum speed, then measure what is really needed for the application to perform flawlessly, and finally to reduce the frequency to what is just needed.

Add new comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.