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.
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.
Click New Project. Using the MCU Selector filters, find our device (STM32F072RBTx):
Then click OK. The project window opens.
Open the Clock Configuration tab:
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:
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:
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:
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):
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):
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
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;
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):
USART1 clock source → PCLK1 (8MHz)
USART2 clock source → PCLK1 (8MHz)
I2C1 clock source → HSI (8MHz)
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:
Looking at the board schematics, we see that the 2 options have been anticipated on the PCB. We can either use:
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…
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.
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:
Obviously, getting 48MHz from 8MHz clock source involves the PLL with a multiplication factor of x6. Otherwise, it is only signal routing.
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.
Edit the top of the main.c file in order to:
/*
* 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:
Before starting code execution, open the Expressions view and add ‘SystemCoreClock’:
You must have a value of 8000000 (8Mhz), corresponding to nominal clock settings at startup.
Now, step-over 2 lines and watch the Expressions view:
The clock should be now 48MHz.
Run the program and probe PA8 with an oscilloscope. You should see a 3MHz clock:
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.
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.