Why does STM32's HSE_VALUE need to be modified to correspond to the crystal oscillator frequency?

Cause of problem

When I recently taught team members about modifying the frequency of the external crystal oscillator, I particularly emphasized the need to change PLL_M and HSE_VALUE. However, some team members still did not change the frequency. In order to make the team members more clearly understand the meaning of modifying the crystal oscillator frequency, here is a special explanation of the STM32 program. The meaning of HSE_VALUE in . (The following uses the STM32 standard library as an example for explanation)

How to modify the program after replacing the external crystal oscillator

system_stm32f4xx.c

1. Change the value of the macro definition of PLL_M under the corresponding chip in the PLL Parameters in the system_stm32f4xx.c file to the external crystal oscillator frequency. If the external crystal oscillator is 12MHz, change the value of PLL_M to 12

/************************* PLL Parameters *************************************/
#if defined (STM32F40_41xxx) || defined (STM32F427_437xx) || defined (STM32F429_439xx) || defined (STM32F401xx)
/* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N */
#define PLL_M      12  //如果是STM32F407IG系列的,在此处将12改成对应的晶振频率
#else /* STM32F411xE */
#if defined (USE_HSE_BYPASS)
#define PLL_M      8    
#else /* STM32F411xE */   
#define PLL_M      16
#endif /* USE_HSE_BYPASS */
#endif /* STM32F40_41xxx || STM32F427_437xx || STM32F429_439xx || STM32F401xx */ 

stm32f4xx.h

2. Change the value of HSE_VALUE in the stm32f4xx.h file to the frequency corresponding to your own chip, such as 12MHz, change it to 12000000

//将12000000改成对应的晶振频率,如果是8MHz,则改为8000000
  #define HSE_VALUE    ((uint32_t)12000000) /*!< Value of the External oscillator in Hz */

Why do you need to modify the corresponding parameters?

1.PLL_M

According to the clock tree, we know that the system clock SYSCLK is input from the PLL. To determine the value of SYSCLK, we usually only need to determine the values ​​of PLL_N, PLL_P, and PLL_M to calculate the corresponding value.
SYSCLK = (HSE_VALUE / PLL_M)xPLL_N/PLL_P
Therefore, we usually set the value of PLL_M to the coefficient of HSE_VALUE (external crystal oscillator frequency).
If HSE_VALUE is 12MHz, set PLL_M to 12, so that the value of SYSCLK only depends on PLL_N and PLL_P , usually we also set PLL_P to 2, so that the value of SYSCLK is half of PLL_N.
For example, PLL_M = 12, PLL_N = 336, PLL_P = 2 (HSE_VALUE = 12MHz)
, the output SYSCLK value is 168MHz.
Insert image description here
The macro definition of PLL_M we mentioned above is the initial parameter needed to set the system clock internally before running the STM32 program. Therefore, we need to change it to the corresponding value. At the same time, the other two parameters PLL_N and PLL_P can also be modified to the corresponding value as needed.

#if defined (STM32F40_41xxx)
#define PLL_N      336
/* SYSCLK = PLL_VCO / PLL_P */
#define PLL_P      2
#endif /* STM32F40_41xxx */

We can find that in the SetSysClock function, the program sets the input value of the system clock according to PLL_M, PLL_N, and PLL_P.

    /* Configure the main PLL */
    RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
                   (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);

2.HSE_VALUE

What is this macro definition used for?
We directly used Ctrl + F
Insert image description here
to search for this macro definition and found many reference places, many of which were comments. In summary, HSE_VALUE is used in two functions.
The first is the SystemCoreClockUpdate function. This function is only used when the system clock frequency needs to be changed after the program is started. Under normal circumstances, this function will not be called.

  *      - SystemCoreClockUpdate(): Updates the variable SystemCoreClock and must
  *                                 be called whenever the core clock is changed
  *                                 during program execution.

The second is the RCC_GetClocksFreq function. We can tell what this function does by looking at its name. It is used to obtain the clock frequency.

void RCC_GetClocksFreq(RCC_ClocksTypeDef* RCC_Clocks)
{
    
    
  uint32_t tmp = 0, presc = 0, pllvco = 0, pllp = 2, pllsource = 0, pllm = 2;

  /* Get SYSCLK source -------------------------------------------------------*/
  tmp = RCC->CFGR & RCC_CFGR_SWS;

  switch (tmp)
  {
    
    
    case 0x00:  /* HSI used as system clock source */
      RCC_Clocks->SYSCLK_Frequency = HSI_VALUE;
      break;
    case 0x04:  /* HSE used as system clock  source */
      RCC_Clocks->SYSCLK_Frequency = HSE_VALUE;
      break;
    case 0x08:  /* PLL used as system clock  source */

      /* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLLM) * PLLN
         SYSCLK = PLL_VCO / PLLP
         */    
      pllsource = (RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) >> 22;
      pllm = RCC->PLLCFGR & RCC_PLLCFGR_PLLM;
      
      if (pllsource != 0)
      {
    
    
        /* HSE used as PLL clock source */
        pllvco = (HSE_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6);
      }
      else
      {
    
    
        /* HSI used as PLL clock source */
        pllvco = (HSI_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6);      
      }

      pllp = (((RCC->PLLCFGR & RCC_PLLCFGR_PLLP) >>16) + 1 ) *2;
      RCC_Clocks->SYSCLK_Frequency = pllvco/pllp;
      break;
    default:
      RCC_Clocks->SYSCLK_Frequency = HSI_VALUE;
      break;
  }
  /* Compute HCLK, PCLK1 and PCLK2 clocks frequencies ------------------------*/

  /* Get HCLK prescaler */
  tmp = RCC->CFGR & RCC_CFGR_HPRE;
  tmp = tmp >> 4;
  presc = APBAHBPrescTable[tmp];
  /* HCLK clock frequency */
  RCC_Clocks->HCLK_Frequency = RCC_Clocks->SYSCLK_Frequency >> presc;

  /* Get PCLK1 prescaler */
  tmp = RCC->CFGR & RCC_CFGR_PPRE1;
  tmp = tmp >> 10;
  presc = APBAHBPrescTable[tmp];
  /* PCLK1 clock frequency */
  RCC_Clocks->PCLK1_Frequency = RCC_Clocks->HCLK_Frequency >> presc;

  /* Get PCLK2 prescaler */
  tmp = RCC->CFGR & RCC_CFGR_PPRE2;
  tmp = tmp >> 13;
  presc = APBAHBPrescTable[tmp];
  /* PCLK2 clock frequency */
  RCC_Clocks->PCLK2_Frequency = RCC_Clocks->HCLK_Frequency >> presc;
}

Because we are using the PLL clock, HSE_VALUE is used by the following two lines of code

 pllvco = (HSE_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6);
RCC_Clocks->SYSCLK_Frequency = pllvco/pllp;

It can be clearly seen that HSE_VALUE is used to determine the value of SYSCLK_Frequency, that is, all subsequent uses of SYSCLK_Frequency are determined by HSE_VALUE.

RCC_Clocks->HCLK_Frequency = RCC_Clocks->SYSCLK_Frequency >> presc;
RCC_Clocks->PCLK1_Frequency = RCC_Clocks->HCLK_Frequency >> presc;
RCC_Clocks->PCLK2_Frequency = RCC_Clocks->HCLK_Frequency >> presc;

You can see that HCLK, PCLK1, and PCLK2 in RCC_Clocks are all determined by HCLK_Frequency, that is, determined by HSE_VALUE.

At this time, when we use Ctrl + F on RCC_GetClocksFreq
Insert image description here
, we can see that it is only called in the serial port usart.c file.
Let's click in and take a look.
Good guy, it turns out that the USART_Init function calls the RCC_GetClocksFreq function.
Take a closer look at what you're doing with it.

/*---------------------------- USART BRR Configuration -----------------------*/
  /* Configure the USART Baud Rate */
  RCC_GetClocksFreq(&RCC_ClocksStatus);

  if ((USARTx == USART1) || (USARTx == USART6))
  {
    
    
    apbclock = RCC_ClocksStatus.PCLK2_Frequency;
  }
  else
  {
    
    
    apbclock = RCC_ClocksStatus.PCLK1_Frequency;
  }
  
  /* Determine the integer part */
  if ((USARTx->CR1 & USART_CR1_OVER8) != 0)
  {
    
    
    /* Integer part computing in case Oversampling mode is 8 Samples */
    integerdivider = ((25 * apbclock) / (2 * (USART_InitStruct->USART_BaudRate)));    
  }
  else /* if ((USARTx->CR1 & USART_CR1_OVER8) == 0) */
  {
    
    
    /* Integer part computing in case Oversampling mode is 16 Samples */
    integerdivider = ((25 * apbclock) / (4 * (USART_InitStruct->USART_BaudRate)));    
  }
  tmpreg = (integerdivider / 100) << 4;

  /* Determine the fractional part */
  fractionaldivider = integerdivider - (100 * (tmpreg >> 4));

  /* Implement the fractional part in the register */
  if ((USARTx->CR1 & USART_CR1_OVER8) != 0)
  {
    
    
    tmpreg |= ((((fractionaldivider * 8) + 50) / 100)) & ((uint8_t)0x07);
  }
  else /* if ((USARTx->CR1 & USART_CR1_OVER8) == 0) */
  {
    
    
    tmpreg |= ((((fractionaldivider * 16) + 50) / 100)) & ((uint8_t)0x0F);
  }

By now we know exactly what it is used for, it is used to configure the serial port baud rate!
/* Configure the USART Baud Rate */
Have you seen such a big line in English!

Specifically used are

  if ((USARTx == USART1) || (USARTx == USART6))
  {
    
    
    apbclock = RCC_ClocksStatus.PCLK2_Frequency;
  }
  else
  {
    
    
    apbclock = RCC_ClocksStatus.PCLK1_Frequency;
  }

That is, PCLK1 and PCLK2 are used to determine apbclock. The apbclock is directly related to the subsequent baud rate determination.

At this point it is clear what HSE_VALUE is used for.
Configure the serial port baud rate!

This is why some people have serial port garbled characters without changing HSE_VALUE to the corresponding crystal oscillator frequency. Because HSE_VALUE directly affects the configuration of the serial port baud rate, once the HSE_VALUE is wrong, the serial port baud rate will be inconsistent with the one you set, and the performance will be obvious - garbled characters.

The copyright belongs to the author (Alliance Team Mizuki Mining). If you need to reprint or quote, please indicate the source and author. If you want to use it for other purposes or have any other questions, please contact the author of this article.

Guess you like

Origin blog.csdn.net/zhang1079528541/article/details/120963177