STM32时钟系统以及配置及源码分析

 

目录

1.STM32F429时钟概述

2.系统时钟的初始化寄存器源码分析

3.系统时钟的初始化HAL库函数源码分析


 


1.STM32F429时钟概述

时钟系统是 CPU 的脉搏,就像人的心跳一样。所以时钟系统的重要性就不言而喻了。 STM32有多个时钟来源的选择,采用一个系统时钟不是很简单吗?为什么 STM32 要有多个时钟源呢? 因为首先 STM32 本身非常复杂,外设非常的多,但是并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及 RTC 只需要几十 k 的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的 MCU 一般都是采取多时钟源的方法来解决这些问题。

下面是stm32f4的时钟系统图:


STM32 有5个时钟源:HSI、HSE、LSI、LSE、PLL。

 ①、HSI是高速内部时钟,RC振荡器,频率为16MHz,精度不高。可以直接作为系统时钟或者用作PLL时钟输入。  

 ②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~26MHz。(此处以25M为例)

 ③、LSI是低速内部时钟,RC振荡器,频率为32kHz,提供低功耗时钟。主要供独立看门狗和自动唤醒单元使用。  

 ④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。RTC  

 ⑤、PLL为锁相环倍频输出。

附录:

 PLL 为锁相环倍频输出。 STM32F4 有三个 PLL:
1) 主 PLL(PLL)由 HSE 或者 HSI 提供时钟信号,并具有两个不同的输出时钟。第一个输出 PLLP 用于生成高速的系统时钟(最高 180MHz)第二个输出 PLLQ 为 48M 时钟, 用于 USB OTG FS 时钟,随机数发生器的时钟和 SDIO时钟。


2) 第一个专用 PLL(PLLI2S)用于生成精确时钟, 在 I2S 和 SAI1 上实现高品质音频性能。 其中, N 是用于 PLLI2S vco 的倍频系数,其取值范围是: 192~432; R 是 I2S 时钟的分频系数,其取值范围是: 2~7; Q 是 SAI 时钟分频系数,其取值范围是: 2~15; P 没用到


3) 第二个专用 PLL(PLLSAI)同样用于生成精确时钟,用于 SAI1 输入时钟,同时还为 LCD_TFT接口提供精确时钟。 其中, N 是用于 PLLSAI vco 的倍频系数,其取值范围是: 192~432;Q 是 SAI 时钟分频系数,其取值范围是: 2~15; R 是 LTDC 时钟的分频系数,其取值范围是: 2~7; P 没用到

例子:主 PLL 时钟第一个高速时钟输出 PLLP 的计算方法:配置180MHz为例:

分析:主 PLL 时钟的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器出来之后还需要经过一个分频系数为 P(第一个输出 PLLP)或者 Q(第二个输出 PLLQ)的分频器分频之后,最后才生成最终的主 PLL 时钟。例如我们的外部晶振选择 25MHz。同时我们设置相应的分频器 M=25,倍频器倍频系数 N=360,分频器分频系数 P=2,那么主 PLL 生成的第一个输出高速时钟 PLLP 为:

PLL=25MHz * N/ (M*P)=25MHz* 360 /(25*2) = 180MHz

配置过程如下图所示: 


2.系统时钟的初始化寄存器源码分析

在系统进入主函数之前,首先会执行SystemInit这个函数对系统进行初始化

看一看这个程序的内容:

源代码如下:

void SystemInit(void)
{
/* FPU 设置------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
/* 复位 RCC 时钟配置为默认配置-----------*/
RCC->CR |= (uint32_t)0x00000001;//打开 HSION 位
RCC->CFGR = 0x00000000;//复位 CFGR 寄存器
RCC->CR &= (uint32_t)0xFEF6FFFF;//复位 HSEON, CSSON and PLLON 位
RCC->PLLCFGR = 0x24003010; //复位寄存器 PLLCFGR
RCC->CR &= (uint32_t)0xFFFBFFFF;//复位 HSEBYP 位
RCC->CIR = 0x00000000;//关闭所有中断
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* 配置中断向量表地址=基地址+偏移地址 ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;
#endif
}

可以看出这段代码的作用是:
1) FPU 设置
2) 复位 RCC 时钟配置为默认复位值(默认开始了 HIS)
3) 外部存储器配置
4) 中断向量表地址配置

做了这些工作,但是在F4的HAL库汇总SystemInit函数,并没有设置系统的主频和外设时钟的频率,所以所以需要自己去写这个函数。

首先先分析一下使用寄存器版本来写的SystemInit函数吧:

//系统时钟初始化函数
//plln:主PLL倍频系数(PLL倍频),取值范围:64~432.
//pllm:主PLL和音频PLL分频系数(PLL之前的分频),取值范围:2~63.
//pllp:系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)
//pllq:USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.
void Stm32_Clock_Init(u32 plln,u32 pllm,u32 pllp,u32 pllq)
{  
	RCC->CR|=0x00000001;		//设置HISON,开启内部高速RC振荡
	RCC->CFGR=0x00000000;		//CFGR清零 
	RCC->CR&=0xFEF6FFFF;		//HSEON,CSSON,PLLON清零 
	RCC->PLLCFGR=0x24003010;	//PLLCFGR恢复复位值 
	RCC->CR&=~(1<<18);			//HSEBYP清零,外部晶振不旁路
	RCC->CIR=0x00000000;		//禁止RCC时钟中断 
	Sys_Clock_Set(plln,pllm,pllp,pllq);//设置时钟 
	//配置向量表				  
#ifdef  VECT_TAB_RAM
	MY_NVIC_SetVectorTable(1<<29,0x0);
#else   
	MY_NVIC_SetVectorTable(0,0x0);
#endif 
}		  

这个函数需要的参数是4个,分别与系统原理图对应的是,如下图所示:

接下来,查看时钟控制CR寄存器的描述:

1.开启HISON,开启内部高速RC震荡

2.时钟配置寄存器CFRG清零

3.配置时钟控制CR寄存器, 使位HSEON,CSSON,PLLON清零(第16、19、24位) 

4.RCC PLL 配置寄存器 (RCC_PLLCFGR),恢复默认值:RCC->PLLCFGR=0x24003010;    //PLLCFGR恢复复位值

5.设置CR寄存器,外部晶振不旁路

6.RCC 时钟中断寄存器 (RCC_CIR),RCC->CR=0X00000000;  //禁止RCC时钟中断

7.设置时钟,使用函数Sys_Clock_Set(plln,pllm,pllp,pllq);//设置时钟,函数原型如下:

u8 Sys_Clock_Set(u32 plln,u32 pllm,u32 pllp,u32 pllq)
{ 
	u16 retry=0;
	u8 status=0;
	RCC->CR|=1<<16;				//HSE 开启 
	while(((RCC->CR&(1<<17))==0)&&(retry<0X1FFF))retry++;//等待HSE RDY
	if(retry==0X1FFF)status=1;	//HSE无法就绪
	else   
	{
		RCC->APB1ENR|=1<<28;	//电源接口时钟使能
		PWR->CR|=3<<14; 		//高性能模式,时钟可到180Mhz
		RCC->CFGR|=(0<<4)|(5<<10)|(4<<13);//HCLK 不分频;APB1 4分频;APB2 2分频. 
		RCC->CR&=~(1<<24);	//关闭主PLL
		RCC->PLLCFGR=pllm|(plln<<6)|(((pllp>>1)-1)<<16)|(pllq<<24)|(1<<22);//配置主PLL,PLL时钟源来自HSE
		RCC->CR|=1<<24;			//打开主PLL
		while((RCC->CR&(1<<25))==0);//等待PLL准备好 
		FLASH->ACR|=1<<8;		//指令预取使能.
		FLASH->ACR|=1<<9;		//指令cache使能.
		FLASH->ACR|=1<<10;		//数据cache使能.
		FLASH->ACR|=5<<0;		//5个CPU等待周期. 
		RCC->CFGR&=~(3<<0);		//清零
		RCC->CFGR|=2<<0;		//选择主PLL作为系统时钟	 
		while((RCC->CFGR&(3<<2))!=(2<<2));//等待主PLL作为系统时钟成功. 
	} 
	return status;
}  

8.RCC->CR|=1<<16;                //设置CR寄存器的第16位为1,HSE 开启

9.等待HSE时钟就绪,判断CR寄存器的第17位是否为1,返回1准备就绪,如果超时,返回标志位status=1.

10.如果准备就绪,使能电源接口时钟,RCC->APB1ENR|=1<<28;    //设置APB1ENR寄存器28位为1电源接口时钟使能

11.开始电源的高性能模式,PWR->CR|=3<<14;         //高性能模式,时钟可到180Mhz

11.配置RCC的CFGR寄存器,//HCLK 不分频;APB1 4分频;APB2 2分频

12.RCC->CR&=~(1<<24);    //关闭主PLL,配置主PLL的一些参数,配置完再重新打开

13.//配置主PLL,PLL时钟源来自HSE,RCC->PLLCFGR=pllm|(plln<<6)|(((pllp>>1)-1)<<16)|(pllq<<24)|(1<<22);

  • pllm,设置PLLM

  • (plln<<6),设置PLLN

  • (((pllp>>1)-1)<<16),设置系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)

为何是:(((pllp>>1)-1)<<16)呢?如下解释,当我们取值pllp为2时,可以得到(((pllp>>1)-1)<<16)=0

当pllp取值为4时,(((pllp>>1)-1)<<16)=1,同理,pllp取值为6时,(((pllp>>1)-1)<<16)=3.其实就是在调用函数的时候方便设置。可以看都这个寄存器的位,当第17:16为0时,就是这只pllp为2分频。

  • (pllq<<24),pllq:USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.

  • (1<<22),选择主PLL的时钟源是HSE外部震荡时钟

所以第13步骤,完成的工作就是如下如所示的设置:

14.设置完主PLL之后,重新打开主PLL:  RCC->CR|=1<<24;            //打开主PLL

15.每次打开PLL之前,都要等待就绪:while((RCC->CR&(1<<25))==0);//等待PLL准备好 

16.设置FLASH寄存器参数:

FLASH->ACR|=1<<8;        //指令预取使能.
FLASH->ACR|=1<<9;        //指令cache使能.
FLASH->ACR|=1<<10;        //数据cache使能.
FLASH->ACR|=5<<0;        //5个CPU等待周期.

17.设置CFRG(RCC时钟配置寄存器)选择PLL作为系统时钟,RCC->CFGR&=~(3<<0);      //清零 RCC->CFGR|=2<<0;     

18.等待主PLL设置为系统主时钟成功:while((RCC->CFGR&(3<<2))!=(2<<2));

语句含义:当CFGR的3:2位不等于01时就说明主PLL还未就绪,就绪等待。

19.最后一步,配置向量表。

通过这样的设置系统时钟的整体配置就OK了。

如果调用函数:u8 Sys_Clock_Set(u32 plln,u32 pllm,u32 pllp,u32 pllq),产生180Mhz的主频

设置参数:外部晶振为25M的时候:plln=360,pllm=25,pllp=2,pllq=8.


3.系统时钟的初始化HAL库函数源码分析

3.1.HAL库主要是把寄存器进行封装,然后把一些参数合并到一个结构体,通过调用结构体的方式对寄存器进行赋值,间接的完成对STM32寄存器的配置。

主要是下面两个结构体:

1.RCC_OscInitTypeDef结构体:

解释:从结构体的名称可以看的出来,这个结构体主要是选择时钟源,然后是时钟的状态(开启还是关闭)

可以看到RCC_OscInitTypeDef 里面还嵌套了一个RCC_PLLInitTypeDef结构体,主要是用于主PLL配置用的。比如设置M分频,vco倍频等。

  • RCC_PLLInitTypeDef 结构体如下:

1.所以,第一步是定义一个RCC_OscInitTypeDef 结构体变量,并给这个结构体变量赋值,然后调用,HAL_RCC_OscConfig函数,把这个结构体变量传进去,使设置生效。

代码如下:

RCC_OscInitTypeDef RCC_OscInitStructure;  //定义一个结构体变量


RCC_OscInitStructure.OscillatorType=RCC_OSCILLATORTYPE_HSE;    //时钟源为HSE
RCC_OscInitStructure.HSEState=RCC_HSE_ON;                      //打开HSE
RCC_OscInitStructure.PLL.PLLState=RCC_PLL_ON;//打开PLL
RCC_OscInitStructure.PLL.PLLSource=RCC_PLLSOURCE_HSE;//PLL时钟源选择HSE
RCC_OscInitStructure.PLL.PLLM=pllm; //主PLL和音频PLL分频系数(PLL之前的分频),取值范围:2~63.
RCC_OscInitStructure.PLL.PLLN=plln; //主PLL倍频系数(PLL倍频),取值范围:64~432.  
RCC_OscInitStructure.PLL.PLLP=pllp; //系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)
RCC_OscInitStructure.PLL.PLLQ=pllq; //USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.
ret=HAL_RCC_OscConfig(&RCC_OscInitStructure);//初始化

最重要的还是看一下HAL库里面是如何对我们赋予这个结构体变量的值进行操作的:也就是HAL_RCC_OscConfig函数:

  • OscillatorType 这个成员变量是选择时钟时钟源的,可以看一下他的取值范围:取值范围小于15

其实在HAL库有对每一个时钟源进行宏定义的,如下:

在程序中,我们设置这个成员变量的值是:RCC_OSCILLATORTYPE_HSE ,就是值为:0x00000001

  • HSEState 是选择时钟的工作方式,此处选择:RCC_HSE_ON,值为:0x01

HAL_RCC_OscConfig函数中对HSE的配置如下,贴出一部分代码:

/* Check the parameters */
  assert_param(IS_RCC_OSCILLATORTYPE(RCC_OscInitStruct->OscillatorType));
  /*------------------------------- HSE Configuration ------------------------*/ 
  if(((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSE) == RCC_OSCILLATORTYPE_HSE)
  {
    /* Check the parameters */
    assert_param(IS_RCC_HSE(RCC_OscInitStruct->HSEState));
    /* When the HSE is used as system clock or clock source for PLL in these cases HSE will not disabled */
	  //判断HSE是否作为系统时钟,或者作为PLL时钟的来源
	  
	  
	 // RCC_CFGR_SWS_HSE=0x00000004
	 //__HAL_RCC_GET_SYSCLK_SOURCE函数,获取CFGR位 3:2 SWS: 系统时钟切换状态 (System clock switch status)
	  //判断3:2位是否为01,含义:01: HSE 振荡器用作系统时钟,这两个位为只读
	  //也就是判断此时系统时钟或者主PLL时钟是否已经设置为HSE
	  
	  //RCC_CFGR_SWS_PLL=0x00000008 ,判断CFGR的第3为是否为1
    if((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_HSE)                                                                     ||\
      ((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_PLL) && ((RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) == RCC_PLLCFGR_PLLSRC_HSE)))
    {   //PLL作为系统时钟,  RCC_PLLCFGR_PLLSRC=0x00400000,第22位,是否选择:1:选择 HSE 振荡器时钟作为 PLL 和 PLLI2S 时钟输入
		
		
		//如果HSE作为系统时钟来源,或者作为PLL时钟来源的话
      if((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) && (RCC_OscInitStruct->HSEState == RCC_HSE_OFF))
      {
		  //此时HSE已经打开了,或HSE没有使能,这里的任何一种情况都会导致失败
        return HAL_ERROR;
      }
    }
    else  //否则的话,系统的时钟还没有进行初始化
    {
      /* Reset HSEON and HSEBYP bits before configuring the HSE --------------*/
		
		//对RCC->CFGR 寄存器的23:16 清零,也就是复位HSEON(关闭振荡器) 和重置就绪位
      __HAL_RCC_HSE_CONFIG(RCC_HSE_OFF);
      
		//获取当前系统时间戳,用于判断关闭HSE是否超时
      /* Get Start Tick*/
      tickstart = HAL_GetTick();
      
      /* Wait till HSE is disabled */  
      while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)  //等待HSE关闭
      {
        if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)
        {
          return HAL_TIMEOUT;  //超过最大时间为关闭HSE则,出错
        }       
      }
      
      /* Set the new HSE configuration ---------------------------------------*/
	  //重新设置HSE,这个值来自于结构体,我们使用就需要使能,通过设置就可以对RCC->CFGR 寄存器的23:16,写此值
      __HAL_RCC_HSE_CONFIG(RCC_OscInitStruct->HSEState);
      
      /* Check the HSE State */
	  //再一次进行确认,我们是否设置了打开HSE
      if((RCC_OscInitStruct->HSEState) != RCC_HSE_OFF)
      {
		  //进入说明,我们的确是打开了
        /* Get Start Tick*/
		  //获取当前系统时间戳
        tickstart = HAL_GetTick();
      
        /* Wait till HSE is ready */ 
			//获取HSE就绪标志位,未就绪就等待
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)
        {
          if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)
          {
            return HAL_TIMEOUT;
          } 
        }
      }
      else //否则没有打开HSE的开关咯
      {
        /* Get Start Tick*/
        tickstart = HAL_GetTick();
         
        /* Wait till HSE is bypassed or disabled */
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)
        {
          if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE) //HSE_TIMEOUT_VALUE=5000
          {
            return HAL_TIMEOUT;
          } 
        }
      }
    }
  }

分析:

1.第一句是判断我们选择的时钟来源,此处选择的是RCC_OSCILLATORTYPE_HSE ,所以这个条件成立

if(((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSE) == RCC_OSCILLATORTYPE_HSE)

2.接着再进行判断

if((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_HSE)                                                                     ||\
      ((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_PLL) && ((RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) == RCC_PLLCFGR_PLLSRC_HSE)))
    {   //PLL作为系统时钟,  RCC_PLLCFGR_PLLSRC=0x00400000,第22位,是否选择:1:选择 HSE 振荡器时钟作为 PLL 和 PLLI2S 时钟输入
		
		
		//如果HSE作为系统时钟来源,或者作为PLL时钟来源的话
      if((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) && (RCC_OscInitStruct->HSEState == RCC_HSE_OFF))
      {
		  //此时HSE已经打开了,或HSE没有使能,这里的任何一种情况都会导致失败
        return HAL_ERROR;
      }
    }

此处有三个条件的判断,成立才会执行if里面的语句,否则执行后面else的语句咯。

重点看一下函数:__HAL_RCC_GET_SYSCLK_SOURCE(),是什么意思。

再看看,RCC_CFGR_SWS_HSE 是什么意思,就是一个数组

那if语句的第一个判断条件的意思就是,判断当前的系统时钟来源是否是HSE(外部高速时钟),如果是的话,因为后面接的是一个或的符号,那么这个条件就算成立了。

再者后面还有一条语句如下,看看什么意思:

((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_PLL) && ((RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) == RCC_PLLCFGR_PLLSRC_HSE))

&&前面的一个语句,就是判断当前系统时钟来源是否为PLL,后面语句是获取RCC的PLL配置寄存器的内容

从下面的宏定义可以看的出来,其实就是获取获取RCC的PLL配置寄存器的22位进行判断此位是0还是1

那第22位的含义是什么?还是得查数据手册,查手册得知,此位就是用于判断PLL的时钟来源,是HSE还是HSI

那么总结上面的整条语句的意思:

就是说,如果系统检测到你当前的系统时钟来源是HSE,或者说检测到系统失踪来源是PLL,而且PLL的时钟来源是HSE的话,那我就执行if里面的语句,其实里面就是返回一个错误提示,很好理解,因为上面的条件成立,说明你已经配置过时钟了,就不需要重新配置了。

其实在时钟系统图上也可以这样表示:

已上任何一种情况都代表系统时钟已经初始化了。所以就没必要再执行下面的语句了。

3.如果上面的条件不成立,那么就说明系统时钟还没有进行配置,接下来就是进行系统时钟的配置了

//对RCC->CFGR 寄存器的23:16 清零,也就是复位HSEON(关闭振荡器) 和重置就绪位
      __HAL_RCC_HSE_CONFIG(RCC_HSE_OFF);

通过查看__HAL_RCC_HSE_CONFIG(),即可知,这个函数的含义就是网一个寄存器里面写一个值

那么这个地址是什么意思呢?其实上面也是有英文备注的,备注是CR寄存器的第三个字节的基地址

那我们具体到数据手册看看吧,找到寄存器地址映射

返回的其实是四字节的一个地址,现在强制把它转换为一个8位的地址,那其实就是RCC的CR寄存器的第三个字节咯,直接往这个地址写入我们想要的值。

可以看看CR寄存器的第三个字节表达的什么意思?

再次回到源程序中,在此函数中写入的是:RCC_HSE_OFF,字面意思就是关闭HSE呗,这些的含义其实是一个8为的二进制数值

此处就是把CR寄存器的第三个字节的数据全部清零,目的就是为了关闭HSE。


4.获取当前系统的戳,判断关闭HSE是否超时

//获取当前系统时间戳,用于判断关闭HSE是否超时
      /* Get Start Tick*/
      tickstart = HAL_GetTick();

5.检测HSE就绪状态标志位

/* Wait till HSE is disabled */  
      while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)  //等待HSE关闭
      {
        if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)
        {
          return HAL_TIMEOUT;  //超过最大时间为关闭HSE则,出错
        }       
      }

其中最重要的是这个函数:__HAL_RCC_GET_FLAG(__FLAG__)

看看函数的定义如下,这个函数看起来有点长,对于初次接触这种函数的人来说,其实研究透了真的会觉得很奇妙,原来函数还可以这样写,接下来就分析一下吧,顺便学习一下他们的编程方式:

#define __HAL_RCC_GET_FLAG(__FLAG__) 
(((((((__FLAG__) >> 5) == 1)? RCC->CR :((((__FLAG__) >> 5) == 2) ? RCC->BDCR :((((__FLAG__) >> 5) == 3)? RCC->CSR :RCC->CIR))) & ((uint32_t)1 << ((__FLAG__) & RCC_FLAG_MASK)))!= 0)? 1 : 0)

首先先解释一下这个函数的功能,可以从这个函数中看到有

1.RCC->CR   2.RCC->BDCR  3.RCC->BDCR  4.RCC->CSR

还有__HAL_RCC_GET_FLAG(__FLAG__)函数需要一个参数,根据参数的值不同,通过计算就可以得到上面这四个寄存器中我们想要位的值而在stm32f4xx_hal_rcc.h 这个文件中有定义,如下:

比如这个程序中获取的是 RCC_FLAG_HSERDY 的值,从字面意思就是获取CR寄存器中 HSERDY位的值(RCC中CR寄存器的第17位)这个数值是:0x31,转换成二进制为:00110001。

此处有点不好解释,因为这句好长好长,勉强解释一下:

#define __HAL_RCC_GET_FLAG(__FLAG__) 
(((((((__FLAG__) >> 5) == 1)? RCC->CR :((((__FLAG__) >> 5) == 2) ? RCC->BDCR :((((__FLAG__) >> 5) == 3)? RCC->CSR :RCC->CIR))) & ((uint32_t)1 << ((__FLAG__) & RCC_FLAG_MASK)))!= 0)? 1 : 0)

上面这个是源语句,提取处倒数第一个问号之前的语句,当这个语句的判定结果为真时,返回的结果是1,否则返回0,如下:

((((((__FLAG__) >> 5) == 1)? RCC->CR :((((__FLAG__) >> 5) == 2) ? RCC->BDCR :((((__FLAG__) >> 5) == 3)? RCC->CSR :RCC->CIR))) & ((uint32_t)1 << ((__FLAG__) & RCC_FLAG_MASK)))!= 0)

再从上面中提取处  !=  符号之前的语句,当此处的返回结果为不等于0(等于1),返回真,否则返回假,那么完整的语句就可以简化为下面的语句,如果下面的语句的结果大于0,最终结果返回1,否则返回0。

(((((__FLAG__) >> 5) == 1)? RCC->CR :((((__FLAG__) >> 5) == 2) ? RCC->BDCR :((((__FLAG__) >> 5) == 3)? RCC->CSR :RCC->CIR))) & ((uint32_t)1 << ((__FLAG__) & RCC_FLAG_MASK)))

上面这条语句还可以分成两个部分,使用颜色区分一下

((((__FLAG__) >> 5) == 1)? RCC->CR :((((__FLAG__) >> 5) == 2) ? RCC->BDCR :((((__FLAG__) >> 5) == 3)? RCC->CSR :RCC->CIR)))

其中粉红色的部分是根据__FLAG__的值返回相应的寄存器的(如,CR、BDCR、CSR、CIR)

上面假设获取获取CR寄存器中 HSERDY位的值,那么__FLAG__ 的值为:0x31


①.如果(__FLAG__) >> 5 ==1 的话那就是返回   RCC->CR 寄存器的值

②.否则返回  ((((__FLAG__) >> 5) == 2) ? RCC->BDCR :((((__FLAG__) >> 5) == 3)? RCC->CSR :RCC->CIR))

含义是什么?

③.如果 (__FLAG__) >> 5 不为1,则执行上面这句,执行(__FLAG__) >> 5 判断是否为2,如果为2
返回RCC->BDCR 寄存器。

④.如果 (__FLAG__) >> 5 不等于2 ,就执行(((__FLAG__) >> 5) == 3)是否成立,如果成立返回
RCC->CSR 否则返回RCC->CIR
结论:其实就是判断我们输入的 (__FLAG__) 数值右移5位的结果,如果右移动5为1,返回RCC->CR寄存器值右移5位为2,返回RCC->BDCR寄存器值,右移5位为3,返回RCC->CSR寄存器值,右移5位不等于前面情况的数值,则返回RCC->CIR 寄存器的值。

上面返回的结果还要 与(&)上  ((uint32_t)1 << ((__FLAG__) & RCC_FLAG_MASK))

注意:

此处:#define RCC_FLAG_MASK  ((uint8_t)0x1F)

则:((uint32_t)1 << ((__FLAG__) & 0x1F))

__FLAG__  的值使用0x31代替,则有:((uint32_t)1 << ((0x31) & 0x1F))==>((uint32_t)1 << 0x11)==>

((uint32_t)1 << 17)==> 0x0200

即如果__FLAG__  的值使用0x31,最终得到的结果就是 :CR & 0x0200  获取CR寄存器第17位的数值是否为1,如果为1,最终结果返回1,否则返回0.

可以看数据手册,CR寄存器的第十七位,正是HSERDY(HSE振荡器准备就绪位)

所以当我调用了语句:__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) ,返回1代表CR寄存器HSERDY位为1,否则为0.

这个函数的其他参数也是一样的效果,只是获取的是不同的位而已。

接着上面的分析吧、

 /* Wait till HSE is disabled */  
      while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)  //等待HSE关闭
      {
        if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)
        {
          return HAL_TIMEOUT;  //超过最大时间为关闭HSE则,出错
        }       
      }

所以上面的整体语句就是,HSERDY 不等于0,则等待,如果超时则退出。

6.选择 HSE 振荡器作为系统时钟

/* Set the new HSE configuration ---------------------------------------*/
	  //重新设置HSE,这个值来自于结构体,我们使用就需要使能,通过设置就可以对RCC->CFGR 寄存器的23:16,写此值
      __HAL_RCC_HSE_CONFIG(RCC_OscInitStruct->HSEState);   

7.打开HSE振荡器,然后等待就绪

/* Check the HSE State */
	  //再一次进行确认,我们是否设置了打开HSE
      if((RCC_OscInitStruct->HSEState) != RCC_HSE_OFF)
      {
		  //进入说明,我们的确是打开了
        /* Get Start Tick*/
		  //获取当前系统时间戳
        tickstart = HAL_GetTick();
      
        /* Wait till HSE is ready */ 
			//获取HSE就绪标志位,未就绪就等待
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)
        {
          if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)
          {
            return HAL_TIMEOUT;
          } 
        }
      }

8.未打开HSE,等待打开

 else //否则没有打开HSE的开关咯
      {
        /* Get Start Tick*/
        tickstart = HAL_GetTick();
         
        /* Wait till HSE is bypassed or disabled */
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)
        {
          if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE) //HSE_TIMEOUT_VALUE=5000
          {
            return HAL_TIMEOUT;
          } 
        }
      }

经过上面的设置我们就打开了一个HSE振荡器,还有就是等待HSE就绪,还挺麻烦,但是相对来说严谨一点,毕竟人家是官方的嘛。 


那么接下来的步骤也就是配置主 PLL 咯。配置的源码又是下面的一串代码,这得很有耐心啊。

/*-------------------------------- PLL Configuration -----------------------*/
  /* Check the parameters */
  assert_param(IS_RCC_PLL(RCC_OscInitStruct->PLL.PLLState));
  if ((RCC_OscInitStruct->PLL.PLLState) != RCC_PLL_NONE)
  {
    /* Check if the PLL is used as system clock or not */
    if(__HAL_RCC_GET_SYSCLK_SOURCE() != RCC_CFGR_SWS_PLL)
    { 
      if((RCC_OscInitStruct->PLL.PLLState) == RCC_PLL_ON)
      {
        /* Check the parameters */
        assert_param(IS_RCC_PLLSOURCE(RCC_OscInitStruct->PLL.PLLSource));
        assert_param(IS_RCC_PLLM_VALUE(RCC_OscInitStruct->PLL.PLLM));
        assert_param(IS_RCC_PLLN_VALUE(RCC_OscInitStruct->PLL.PLLN));
        assert_param(IS_RCC_PLLP_VALUE(RCC_OscInitStruct->PLL.PLLP));
        assert_param(IS_RCC_PLLQ_VALUE(RCC_OscInitStruct->PLL.PLLQ));
      
        /* Disable the main PLL. */
        __HAL_RCC_PLL_DISABLE();
        
        /* Get Start Tick*/
        tickstart = HAL_GetTick();
        
        /* Wait till PLL is ready */  
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) != RESET)
        {
          if((HAL_GetTick() - tickstart ) > PLL_TIMEOUT_VALUE)
          {
            return HAL_TIMEOUT;
          }
        }        

        /* Configure the main PLL clock source, multiplication and division factors. */
        WRITE_REG(RCC->PLLCFGR, (RCC_OscInitStruct->PLL.PLLSource                                            | \
                                 RCC_OscInitStruct->PLL.PLLM                                                 | \
                                 (RCC_OscInitStruct->PLL.PLLN << POSITION_VAL(RCC_PLLCFGR_PLLN))             | \
                                 (((RCC_OscInitStruct->PLL.PLLP >> 1) -1) << POSITION_VAL(RCC_PLLCFGR_PLLP)) | \
                                 (RCC_OscInitStruct->PLL.PLLQ << POSITION_VAL(RCC_PLLCFGR_PLLQ))));
        /* Enable the main PLL. */
        __HAL_RCC_PLL_ENABLE();

        /* Get Start Tick*/
        tickstart = HAL_GetTick();
        
        /* Wait till PLL is ready */  
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET)
        {
          if((HAL_GetTick() - tickstart ) > PLL_TIMEOUT_VALUE)
          {
            return HAL_TIMEOUT;
          } 
        }
      }
      else
      {
        /* Disable the main PLL. */
        __HAL_RCC_PLL_DISABLE();
 
        /* Get Start Tick*/
        tickstart = HAL_GetTick();
        
        /* Wait till PLL is ready */  
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) != RESET)
        {
          if((HAL_GetTick() - tickstart ) > PLL_TIMEOUT_VALUE)
          {
            return HAL_TIMEOUT;
          }
        }
      }
    }
    else
    {
      return HAL_ERROR;
    }
  }

再看看调用这个函数设置的数值吧。

    RCC_OscInitStructure.PLL.PLLState=RCC_PLL_ON;//打开PLL

#define RCC_PLL_NONE                   ((uint8_t)0x00)
#define RCC_PLL_OFF                      ((uint8_t)0x01)
#define RCC_PLL_ON                        ((uint8_t)0x02)

    RCC_OscInitStructure.PLL.PLLSource=RCC_PLLSOURCE_HSE;//PLL时钟源选择HSE

#define RCC_PLLSOURCE_HSI                 RCC_PLLCFGR_PLLSRC_HSI
#define RCC_PLLSOURCE_HSE                RCC_PLLCFGR_PLLSRC_HSE

#define  RCC_PLLCFGR_PLLSRC                     ((uint32_t)0x00400000)
#define  
RCC_PLLCFGR_PLLSRC_HSE            ((uint32_t)0x00400000)
#define  
RCC_PLLCFGR_PLLSRC_HSI              ((uint32_t)0x00000000)
    RCC_OscInitStructure.PLL.PLLM=pllm; //主PLL和音频PLL分频系数(PLL之前的分频),取值范围:2~63.
    RCC_OscInitStructure.PLL.PLLN=plln; //主PLL倍频系数(PLL倍频),取值范围:64~432.  
    RCC_OscInitStructure.PLL.PLLP=pllp; //系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)
    RCC_OscInitStructure.PLL.PLLQ=pllq; //USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.

1.首先判断我们是否设置PLL的开关是否打开

2.判断PLL是否设置为系统时钟

if(__HAL_RCC_GET_SYSCLK_SOURCE() != RCC_CFGR_SWS_PLL)

__HAL_RCC_GET_SYSCLK_SOURCE(),这条语句的作用?

原句如下:

#define __HAL_RCC_GET_SYSCLK_SOURCE() ((uint32_t)(RCC->CFGR & RCC_CFGR_SWS))

/*!< SWS configuration */
#define  RCC_CFGR_SWS                        ((uint32_t)0x0000000C)        /*!< SWS[1:0] bits (System Clock Switch Status) */
#define  RCC_CFGR_SWS_0                      ((uint32_t)0x00000004)        /*!< Bit 0 */
#define  RCC_CFGR_SWS_1                      ((uint32_t)0x00000008)        /*!< Bit 1 */

所以__HAL_RCC_GET_SYSCLK_SOURCE()的作用就是获取CFGR寄存器的SWS位的数值:

以正点原子系统初始化函数为例子进行分析:

//时钟设置函数
// VCO 频率 Fvco=Fs*(plln/pllm);
//系统时钟频率 Fsys=Fvco/pllp=Fs*(plln/(pllm*pllp));
// USB,SDIO,RNG 等的时钟频率 Fusb=Fvco/pllq=Fs*(plln/(pllm*pllq));
//Fs:PLL 输入时钟频率,可以是 HSI,HSE 等.
//plln:主 PLL 倍频系数(PLL 倍频),取值范围:64~432.
//pllm:主 PLL 和音频 PLL 分频系数(PLL 之前的分频),取值范围:2~63.
//pllp:系统时钟的主 PLL 分频系数(PLL 之后的分频),取值范围:2,4,6,8.(仅限这 4 个值!)
//pllq:USB/SDIO/随机数产生器等的主 PLL 分频系数(PLL 之后的分频),取值范围:2~15.
//外部晶振为 25M 的时候,推荐值:plln=360,pllm=25,pllp=2,pllq=8.
//得到:Fvco=25*(360/25)=360Mhz
// Fsys=360/2=180Mhz
// Fusb=360/8=45Mhz
//返回值:0,成功;1,失败
void Stm32_Clock_Init(u32 plln,u32 pllm,u32 pllp,u32 pllq)
{
HAL_StatusTypeDef ret = HAL_OK;
RCC_OscInitTypeDef RCC_OscInitStructure;
RCC_ClkInitTypeDef RCC_ClkInitStructure;
__HAL_RCC_PWR_CLK_ENABLE(); //使能 PWR 时钟
//下面这个设置用来设置调压器输出电压级别,以便在器件未以最大频率工作
//时使性能与功耗实现平衡,此功能只有 STM32F42xx 和 STM32F43xx 器件有,
__HAL_PWR_VOLTAGESCALING_CONFIG(
PWR_REGULATOR_VOLTAGE_SCALE1);
RCC_OscInitStructure.OscillatorType=RCC_OSCILLATORTYPE_HSE; //时钟源为 HSE
RCC_OscInitStructure.HSEState=RCC_HSE_ON; //打开 HSE
RCC_OscInitStructure.PLL.PLLState=RCC_PLL_ON; //打开 PLL
RCC_OscInitStructure.PLL.PLLSource=RCC_PLLSOURCE_HSE;//PLL 时钟源为 HSE
RCC_OscInitStructure.PLL.PLLM=pllm;
RCC_OscInitStructure.PLL.PLLN=plln;
RCC_OscInitStructure.PLL.PLLP=pllp;
RCC_OscInitStructure.PLL.PLLQ=pllq;
ret=HAL_RCC_OscConfig(&RCC_OscInitStructure);
if(ret!=HAL_OK) while(1);
ret=HAL_PWREx_EnableOverDrive(); //开启 Over-Driver 功能

if(ret!=HAL_OK) while(1);
//选中 PLL 作为系统时钟源并且配置 HCLK,PCLK1 和 PCLK2
RCC_ClkInitStructure.ClockType=(RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1
|RCC_CLOCKTYPE_PCLK2);
RCC_ClkInitStructure.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStructure.AHBCLKDivider=RCC_SYSCLK_DIV1;
RCC_ClkInitStructure.APB1CLKDivider=RCC_HCLK_DIV4;
RCC_ClkInitStructure.APB2CLKDivider=RCC_HCLK_DIV2;
ret=HAL_RCC_ClockConfig(&RCC_ClkInitStructure,FLASH_LATENCY_5);
if(ret!=HAL_OK) while(1);
}


 


 

发布了91 篇原创文章 · 获赞 247 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_36243942/article/details/83655339