STM32 Programming. Part 4: Setting the RCC
Posted: 15 Oct 2023, 23:51
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.
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.
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.
Description of the main bits of the register:
MCO - supplying a clock signal to the MCO pin of the microcontroller.
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.
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
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:
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:
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:
или так:
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:
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:
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:
In fact, it's the same, only without _Msk.
The second case looks similar:
where
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:
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:
And there is another option: the value 0111 in decimal form is the number 7. Then you can do it like this:
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: 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:
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:
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:
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:
After that, we configure the FLASH and dividers:
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:
Congratulations! We have started from the PLL! After that we can disable the HSI RC oscillator, since we don't need it anymore:
Here is the whole code of the function of switching to PLL operation:
That's it! In the next part we will finally start writing "Hello, World" for the STM32F103C8 microcontroller.
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.
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.
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.
- 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
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.
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
- 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.
- 00: HSI selected as system clock source
- 01: HSE selected as system clock source
- 10: PLL selected as system clock source
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
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)
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:
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)
Code: Select all
#define RCC_CR_HSEON RCC_CR_HSEON_Msk
The second case looks similar:
where
Code: Select all
#define RCC_CR_HSEON_Pos 16
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);
Code: Select all
RCC->CFGR |= RCC_CFGR_PLLMULL_0 | RCC_CFGR_PLLMULL_1 | RCC_CFGR_PLLMULL_2;
Code: Select all
RCC->CFGR |= (7 << RCC_CFGR_PLLMULL_Pos);
So, here we go! Let's take as a basis the project we created in Part 2, when we connected CMSIS: 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
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;
}
}
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.
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;
}
}
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)
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))
{
}
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);
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;
}