STM32的HSE_VALUE为什么需要修改成对应晶振频率?

问题起因

最近给队员讲修改外部晶振频率时特别强调了要改好PLL_M以及HSE_VALUE,但是还是有队员没有将频率改过来,为了要队员更加清楚的明白修改晶振频率的意义,所以在此特别说明一下STM32程序中的HSE_VALUE的意义。(下面以STM32标准库为例进行说明)

外部晶振更换后该如何改动程序

system_stm32f4xx.c

1.将system_stm32f4xx.c文件中PLL Parameters里对应芯片下的PLL_M的宏定义的值改成外部晶振频率,如外部晶振是12MHz,则将PLL_M的值改为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.将stm32f4xx.h文件中HSE_VALUE的值改成自己芯片对应的频率,如12MHz则改为12000000

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

为什么需要修改对应的参数

1.PLL_M

根据时钟树我们知道系统时钟SYSCLK是由PLL输入过来的,而要确定SYSCLK的值我们通常只需要确定好PLL_N、PLL_P、PLL_M的值就能算出对应的值。
SYSCLK = (HSE_VALUE / PLL_M)xPLL_N/PLL_P
因此通常我们将PLL_M的值设为HSE_VALUE(外部晶振频率)的系数,
如HSE_VALUE为12MHz,则设PLL_M为12,这样SYSCLK的值就只取决于PLL_N和PLL_P了,通常我们也将PLL_P设为2,这样SYSCLK的值就为PLL_N的一半了。
如PLL_M = 12, PLL_N = 336, PLL_P = 2(HSE_VALUE = 12MHz)
这样输出的SYSCLK的值即为168MHz。
在这里插入图片描述
而我们在上面提到的PLL_M的宏定义即为STM32程序运行前内部进行的系统时钟设置时需要用到的初始参数。因此我们需要将其改为对应的值,同时其它两个参数PLL_N和PLL_P也可以根据需要修改为对应的值。

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

我们能找到在SetSysClock函数中,程序根据PLL_M,PLL_N,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

这个宏定义用来干什么呢?
我们直接用对这个宏定义用 Ctrl + F
在这里插入图片描述
搜到了很多引用的地方,有许多地方是注释。总结起来有两个函数中使用到了HSE_VALUE。
第一个是SystemCoreClockUpdate函数,这个函数只在程序启动后需要更改系统时钟频率时才会用到,一般情况下这个函数不会被调用。

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

第二个是RCC_GetClocksFreq函数,这个函数我们看名字就知道是干什么的,用于获取时钟频率。

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;
}

因为我们使用的是PLL时钟,所以HSE_VALUE被下面这两行代码所使用

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

可以清楚的看到,HSE_VALUE被用来确定SYSCLK_Frequency的值,也就是后面所有用到SYSCLK_Frequency的地方,都由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;

可以看到RCC_Clocks中的HCLK、PCLK1、PCLK2都由HCLK_Frequency决定,即由HSE_VALUE所决定。

这时候我们对RCC_GetClocksFreq使用 Ctrl + F
在这里插入图片描述
可以看到,只有串口usart.c文件中调用了它。
我们点进去看看
好家伙,竟然是USART_Init函数调用了RCC_GetClocksFreq函数。
仔细看看用它干了什么。

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

到现在为止我们很清楚它被用来干啥了,被用来配置串口波特率了!
/* Configure the USART Baud Rate */
这么大的一行英文看到了吗!

具体使用到的是

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

也就是PCLK1、PCLK2被用来确定apbclock。而apbclock与后面的波特率确定有着直接关系。

到这里也就很清楚的知道了HSE_VALUE用来干了啥了。
配置串口波特率!

这也就是为什么有些人没有将HSE_VALUE改成对应晶振频率后会出现串口乱码了。因为HSE_VALUE直接影响着串口波特率的配置,一旦HSE_VALUE错误,串口波特率也就和自己设定的不一致,表现出来的就很明显了——乱码。

版权归作者(Alliance战队水木皆Ming)所有,需要转载或引用请注明来历和作者,若为其他用途或者有其他疑问则联系本文作者

猜你喜欢

转载自blog.csdn.net/zhang1079528541/article/details/120963177
今日推荐