Page 1 of 1

STM32 Programming. Part 4: Setting the RCC

Posted: 15 Oct 2023, 23:51
by Oleg
In the previous part we have studied the clocking system of STM32 microcontrollers. In this part we will study the RCC registers and customize it.
image.png
image.png (59.63 KiB)
Viewed 3555 times
Content:

CR and CFGR registers
Coefficients
Registers in CMSIS
Customization

CR and CFGR registers

Open the Reference manual for the STM32F103x8 microcontroller and go to section 7: Low-, medium-, high- and XL-density reset and clock control (RCC). The RCC has quite a few registers, as many as 10 of them. However, we need only 2 of them to configure the clock source and bus dividers.
Figure 1. Bits of the CR register
image.png (17.47 KiB)
Figure 1. Bits of the CR register Viewed 3555 times
Description of the main bits of the register:

PLLRDY - PLL Ready Flag. It is set by hardware and signals that the PLL is locked.

PLLON - Enable PLL. Set and reset by software. Reset by hardware when going to Stop or Standby mode. This bit cannot be reset if the PLL is used as a system clock source.

CSSON - enable CSS system

HSEBYP - If we want to use an external rectangular clock signal instead of the HSE crystal resonator, this bit must be set to 1.

HSERDY - HSE generator ready flag. It is set to 1 after successful start-up and stabilization of the HSE generator frequency.

HSEON - Start HSE generator. It is set and reset programmatically. When going to Stop or Standby mode, it is reset by hardware and the HSE generator is stopped. This bit cannot be reset if the HSE is used as a system clock source.

HSIRDY - Same as HSERDY, only for the on-board RC oscillator of the HSI

HSION - same as HSEON, only for the built-in HSI RC oscillator.
Figure 2. Bits of the CFGR register
image.png (17.97 KiB)
Figure 2. Bits of the CFGR register Viewed 3555 times
Description of the main bits of the register:

MCO - supplying a clock signal to the MCO pin of the microcontroller.
  • 0xx: The function is disabled
  • 100: System clock (SYSCLK) selected
  • 101: HSI signal selected
  • 110: HSE signal selected
  • 111: Selected signal from PLL, which is divided by 2.
PLLMUL is the multiplication factor of the PLL. These bits can only be written programmatically when the PLL is disabled
  • 0000: Input frequency PLL multiplied by 2
  • 0001: -//- by 3
  • 0010: -//- by 4
  • 0011: -//- by 5
  • 0100: -//- by 6
  • 0101: -//- by 7
  • 0110: -//- by 8
  • 0111: -//- on 9
  • 1000: -//- on 10
  • 1001: -//- on 11
  • 1010: -//- on 12
  • 1011: -//- on 13
  • 1100: -//- on 14
  • 1101: -//- on 15
  • 1110: -//- on 16
  • 1111: -//- on 16
The last two values correspond to the same multiplication factor.

PLLXTPRE - Frequency divider from the HSE of the oscillator before feeding the PLL. This bit cannot be changed if the PLL is running. If set to 1, the HSE frequency will be divided by 2. If 0, the divider is disabled.

PLLSRC - Source of the PLL input frequency. Cannot be changed if the PLL is running.
  • 0: HSI oscillator frequency divided by 2
  • 1: HSE oscillator frequency. The divider can be selected by the PLLXTPRE bit.
PPRE2 - APB2 prescaler bus divider

0xx: HCLK without division
100: HCLK / 2
101: HCLK / 4
110: HCLK / 8
111: HCLK / 16

PPRE1 - APB1 prescaler bus divider. The APB1 bus frequency must not exceed 36 MHz.

0xx: HCLK without division

100: HCLK / 2
101: HCLK / 4
110: HCLK / 8
111: HCLK / 16

HPRE - AHB prescaler
  • 0xxx: SYSCLK without division
  • 1000: SYSCLK / 2
  • 1001: SYSCLK / 4
  • 1010: SYSCLK / 8
  • 1011: SYSCLK / 16
  • 1100: SYSCLK / 64
  • 1101: SYSCLK / 128
  • 1110: SYSCLK / 256
  • 1111: SYSCLK / 512
SWS - State of the system clocking switch. Set by hardware and indicates the current clocking source.
  • 00: HSI oscillator is used as system clocking source
  • 01: HSE oscillator is used as system clock source
  • 10: PLL is used as the system clocking source.
SW - System clocking source switch. Changeable by software to select the SYSCLK source. It is set by hardware to force switching to HSI oscillator in Stop or Standby mode or in case of failure of HSE generation, which is used as SYSCLK source (only if CSS system is active).
  • 00: HSI selected as system clock source
  • 01: HSE selected as system clock source
  • 10: PLL selected as system clock source
Coefficients

In the previous article we looked at clocking the clocking system from the HSE oscillator via a PLL, for convenience I will copy that here:
  • 8 MHz HSE quartz
  • PLLXTPRE: without division
  • PLLSRC: HSE oscillator
  • PLLMUL = 9
  • SW = PLLCLK
  • AHB Prescaler = 1
  • APB1 Prescaler = 2
  • APB2 Prescaler = 1
And a picture, too:
Figure 3. Clock signal flow diagram when using PLL together with HSE
image.png (87.54 KiB)
Figure 3. Clock signal flow diagram when using PLL together with HSE Viewed 3555 times
Registers in CMSIS

In the second part we learned how to connect CMSIS library to IAR, now we will need this project, as we are moving on to practice. But before that, let's talk a bit about how the access to peripheral registers in CMSIS is organized.

Each instance of a peripheral is a structure that holds all the registers pertaining to that device. In almost all cases, the name of the structure is the same as the name of the peripheral module. For the STM32F103C8 microcontroller, all peripheral module structures are declared in the stm32f103xb.h file:

Code: Select all

#define TIM2                ((TIM_TypeDef *)TIM2_BASE)
#define TIM3                ((TIM_TypeDef *)TIM3_BASE)
#define TIM4                ((TIM_TypeDef *)TIM4_BASE)
#define RTC                 ((RTC_TypeDef *)RTC_BASE)
#define WWDG                ((WWDG_TypeDef *)WWDG_BASE)
#define IWDG                ((IWDG_TypeDef *)IWDG_BASE)
#define SPI2                ((SPI_TypeDef *)SPI2_BASE)
#define USART2              ((USART_TypeDef *)USART2_BASE)
#define USART3              ((USART_TypeDef *)USART3_BASE)
#define I2C1                ((I2C_TypeDef *)I2C1_BASE)
#define I2C2                ((I2C_TypeDef *)I2C2_BASE)
#define USB                 ((USB_TypeDef *)USB_BASE)
#define CAN1                ((CAN_TypeDef *)CAN1_BASE)
#define BKP                 ((BKP_TypeDef *)BKP_BASE)
#define PWR                 ((PWR_TypeDef *)PWR_BASE)
#define AFIO                ((AFIO_TypeDef *)AFIO_BASE)
#define EXTI                ((EXTI_TypeDef *)EXTI_BASE)
#define GPIOA               ((GPIO_TypeDef *)GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *)GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *)GPIOC_BASE)
#define GPIOD               ((GPIO_TypeDef *)GPIOD_BASE)
#define GPIOE               ((GPIO_TypeDef *)GPIOE_BASE)
#define ADC1                ((ADC_TypeDef *)ADC1_BASE)
#define ADC2                ((ADC_TypeDef *)ADC2_BASE)
#define ADC12_COMMON        ((ADC_Common_TypeDef *)ADC1_BASE)
#define TIM1                ((TIM_TypeDef *)TIM1_BASE)
#define SPI1                ((SPI_TypeDef *)SPI1_BASE)
#define USART1              ((USART_TypeDef *)USART1_BASE)
#define SDIO                ((SDIO_TypeDef *)SDIO_BASE)
#define DMA1                ((DMA_TypeDef *)DMA1_BASE)
#define DMA1_Channel1       ((DMA_Channel_TypeDef *)DMA1_Channel1_BASE)
#define DMA1_Channel2       ((DMA_Channel_TypeDef *)DMA1_Channel2_BASE)
#define DMA1_Channel3       ((DMA_Channel_TypeDef *)DMA1_Channel3_BASE)
#define DMA1_Channel4       ((DMA_Channel_TypeDef *)DMA1_Channel4_BASE)
#define DMA1_Channel5       ((DMA_Channel_TypeDef *)DMA1_Channel5_BASE)
#define DMA1_Channel6       ((DMA_Channel_TypeDef *)DMA1_Channel6_BASE)
#define DMA1_Channel7       ((DMA_Channel_TypeDef *)DMA1_Channel7_BASE)
#define RCC                 ((RCC_TypeDef *)RCC_BASE)
#define CRC                 ((CRC_TypeDef *)CRC_BASE)
#define FLASH               ((FLASH_TypeDef *)FLASH_R_BASE)
#define OB                  ((OB_TypeDef *)OB_BASE)
#define DBGMCU              ((DBGMCU_TypeDef *)DBGMCU_BASE)
Let's see how registers are accessed from a C program. For example, we need to set the HSEON bit in the RCC_CR register. This can be done in one of the following ways:

Code: Select all

RCC->CR |= RCC_CR_HSEON_Msk;
или так:

RCC->CR |= (1 << RCC_CR_HSEON_Pos);

I think those who used to program for AVR microcontrollers will see something familiar in these records. Let's consider the first case:
image.png
image.png (3.76 KiB)
Viewed 3555 times
First comes the name of the peripheral module, in our case "RCC". Then the "->" character followed by the register name "CR". RCC_CR_HSEON_Msk is a #define like this:

Code: Select all

#define RCC_CR_HSEON_Msk    (1<<16)
where 16 is the number of the HSEON bit in the CR register (see Fig. 1). RCC_CR_HSEON_Msk is nothing but a bit mask whose name consists of the peripheral module name, register and bit name, and the postfix _Msk. In CMSIS there is another #define which is synonymous with RCC_CR_HSEON_Msk:

Code: Select all

#define RCC_CR_HSEON    RCC_CR_HSEON_Msk
In fact, it's the same, only without _Msk.

The second case looks similar:
image.png
image.png (4.74 KiB)
Viewed 3555 times
where

Code: Select all

#define RCC_CR_HSEON_Pos    16
That is, RCC_CR_HSEON_Pos is the bit position in the register, as indicated by the _Pos postfix.

What about parameters that have several bits? For example, in the CFGR register we want to set the PLL multiplier value equal to nine, which has the code 0111 (see Fig. 2 PLLMUL bits). Here is the solution:

Code: Select all

RCC->CFGR |= RCC_CFGR_PLLMULL_0 | RCC_CFGR_PLLMULL_1 | RCC_CFGR_PLLMULL_2;
RCC->CFGR &= ~(RCC_CFGR_PLLMULL_3);
With the first line we set bits 0, 1 and 2 of PLLMUL to ones, with the second line we reset bit 3 to zero, resulting in 0111. However, if we are sure that all PLLMUL bits are initially set to zeros, we can skip the second line:

Code: Select all

RCC->CFGR |= RCC_CFGR_PLLMULL_0 | RCC_CFGR_PLLMULL_1 | RCC_CFGR_PLLMULL_2;
And there is another option: the value 0111 in decimal form is the number 7. Then you can do it like this:

Code: Select all

RCC->CFGR |= (7 << RCC_CFGR_PLLMULL_Pos);
But even here we should remember that such an entry is valid only if the initial values of all PLLMUL bits are equal to zeros.

So, here we go! Let's take as a basis the project we created in Part 2, when we connected CMSIS:
Downloaded 452 times
Let's create a function to which we will add the initialization code:

int ClockInit(void)
{
}
The first thing to do is to start the HSE generator:

Code: Select all

RCC->CR |= (1<<RCC_CR_HSEON_Pos); //Start the HSE generator
After that we need to wait for the HSERDY flag to be set, which indicates that the generator has been successfully started. We can do it in a simple while() {} loop, but then we risk hanging in it forever if something happens to the quartz resonator. I'd like to be able to somehow signal the failure to start. Here is my implementation:

Code: Select all

  __IO int StartUpCounter;
  //Wait for a successful start or timeout expiration
  for(StartUpCounter=0; ; StartUpCounter++)
  {
    //If it was successfully started, then
    //exit the loop
    if(RCC->CR & (1<<RCC_CR_HSERDY_Pos))
      break;
    
    //If it did not start, then
    //disable everything that was enabled
    //and return an error
    if(StartUpCounter > 0x1000)
    {
      RCC->CR &= ~(1<<RCC_CR_HSEON_Pos); //Stop HSE
      return 1;
    }
  }
The timeout for HSE start is implemented here. If the generator has managed to start before the condition if(StartUpCounter > 0x1000) occurs, we exit the for() loop using the break instruction.

So, the HSE has been started. Let's move on to PLL setting:

Code: Select all

//Customize PLL
  RCC->CFGR |= (0x07<<RCC_CFGR_PLLMULL_Pos) //PLL multiplier is equal to 9
            | (0x01<<RCC_CFGR_PLLSRC_Pos); //Tactivate PLL from HSE.
Here we simply adjust the multiplication factor and select the PLL clocking source. Next, start the PLL:

RCC->CR |= (1<<RCC_CR_PLLON_Pos); //Start the PLL
After that we wait for a successful start. Here the waiting is implemented in the same way as for HSE:

Code: Select all

//Wait for a successful start or timeout expiration
  for(StartUpCounter=0; ; StartUpCounter++)
  {
    //If it was successfully started, then
    //exit the loop
    if(RCC->CR & (1<<RCC_CR_PLLRDY_Pos))
      break;
    
    //If for some reason PLL did not start, then
    //disable everything that was enabled
    //and return an error
    if(StartUpCounter > 0x1000)
    {
      RCC->CR &= ~(1<<RCC_CR_HSEON_Pos); //Stop HSE
      RCC->CR &= ~(1<<RCC_CR_CR_PLLON_Pos); //Stop PLL
      return 2;
    }
  }
After that, we configure the FLASH and dividers:

Code: Select all

  //Set 2 wait cycles for Flash
  //since the kernel frequency will be 48 MHz < SYSCLK <= 72 MHz
  FLASH->ACR |= (0x02<<FLASH_ACR_LATENCY_Pos);
  
  //Delivers
  RCC->CFGR |= (0x00<<RCC_CFGR_PPRE2_Pos) //The APB2 bus divider is disabled (leave 0 by default).
            | (0x04<<RCC_CFGR_PPRE1_Pos) //The APB1 niche divider is 2
            | (0x00<<RCC_CFGR_HPRE_Pos); //AHB tire splitter disabled (leave 0 by default)
And the solemn moment of switching to PLL operation:

RCC->CFGR |= (0x02<<RCC_CFGR_SW_Pos); //Switch to PLL operation.
Wait for switching to be completed:

Code: Select all

  //Waiting for switching
  while((RCC->CFGR & RCC_CFGR_SWS_Msk) != (0x02<<RCC_CFGR_SWS_Pos))
  {
  }
Congratulations! We have started from the PLL! After that we can disable the HSI RC oscillator, since we don't need it anymore:

Code: Select all

 //After switching to an
  //external tac source
  //disable the internal RC generator
  //to save power
  RCC->CR &= ~(1<<RCC_CR_HSION_Pos);
Here is the whole code of the function of switching to PLL operation:

Code: Select all

//Customize the system clocking from the external quartz
//through PLL at the highest possible frequencies.
//The external quartz should be at 8MHz
//Returns:
// 0 - completed successfully
// 1 - quartz oscillator failed to start
// 2 - PLL did not start
int ClockInit(void)
{
  __IO int StartUpCounter;
  
  ////////////////////////////////////////////////////////////
  //Start the crystal oscillator
  ////////////////////////////////////////////////////////////
  
  RCC->CR |= (1<<RCC_CR_HSEON_Pos); //Start HSE generator
  
  //Wait for successful start or end of timeout
  for(StartUpCounter=0; ; StartUpCounter++)
  {
    //If it was successfully started, then
    //exit the loop
    if(RCC->CR & (1<<RCC_CR_HSERDY_Pos))
      break;
    
    //If it did not start, then
    //disable everything that was enabled
    //and return an error
    if(StartUpCounter > 0x1000)
    {
      RCC->CR &= ~(1<<RCC_CR_HSEON_Pos); //Stop HSE
      return 1;
    }
  }
  
  ////////////////////////////////////////////////////////////
  //Customize and run PLL
  ////////////////////////////////////////////////////////////
  
  //Customize PLL
  RCC->CFGR |= (0x07<<RCC_CFGR_PLLMULL_Pos) //PLL multiplier is equal to 9
            | (0x01<<RCC_CFGR_PLLSRC_Pos); //Tactivate PLL from HSE
  
  
  RCC->CR |= (1<<RCC_CR_PLLON_Pos); //Start PLL
  
  //Wait for a successful start or timeout expiration
  for(StartUpCounter=0; ; StartUpCounter++)
  {
    //If it has successfully started, then
    //exit the loop
    if(RCC->CR & (1<<RCC_CR_PLLRDY_Pos))
      break;
    
    //If for some reason PLL did not start, then
    //disable everything that was enabled
    //and return an error
    if(StartUpCounter > 0x1000)
    {
      RCC->CR &= ~(1<<RCC_CR_HSEON_Pos); //Stop HSE
      RCC->CR &= ~(1<<RCC_CR_CR_PLLON_Pos); //Stop PLL
      return 2;
    }
  }
  
  ////////////////////////////////////////////////////////////
  //Customize FLASH and dividers
  ////////////////////////////////////////////////////////////
  
  //Set 2 wait cycles for Flash
  //since the core frequency will be 48 MHz < SYSCLK <= 72 MHz
  FLASH->ACR |= (0x02<<FLASH_ACR_LATENCY_Pos);
  
  //Delivers
  RCC->CFGR |= (0x00<<RCC_CFGR_PPRE2_Pos) // APB2 bus divider disabled
            | (0x04<<RCC_CFGR_PPRE1_Pos) //The APB1 niche divider is 2
            | (0x00<<RCC_CFGR_HPRE_Pos); //AHB bus divider is disabled
  
  
  RCC->CFGR |= (0x02<<RCC_CFGR_SW_Pos); //Switch to PLL operation
  
  //Wait until we switch
  while((RCC->CFGR & RCC_CFGR_SWS_Msk) != (0x02<<RCC_CFGR_SWS_Pos))
  {
  }
  
  //After switching to the
  //external tucking source
  //disable the internal RC generator
  //to save power
  RCC->CR &= ~(1<<RCC_CR_HSION_Pos);
  
  //System setup and reclocking
  //to an external crystal oscillator
  //and the PLL has succeeded.
  //Exit
  return 0;
}
That's it! In the next part we will finally start writing "Hello, World" for the STM32F103C8 microcontroller.