STM32基础--RCC—使用 HSE/HSI 配置时钟

这玩意很重要,就这么给你说吧,这玩意就像是北方吃席的肘子。
RCC :reset clock control 复位和时钟控制器。本章我们主要讲解时钟部分,特别是要着重理解时钟树,理解了时钟树,STM32 的一切时钟的来龙去脉都会了如指掌。

RCC 主要作用—时钟部分

设置系统时钟 SYSCLK、设置 AHB 分频因子(决定 HCLK 等于多少)、设置 APB2 分频因子(决定 PCLK2 等于多少)、设置 APB1 分频因子(决定 PCLK1 等于多少)、设置各个外设的分频因子;控制 AHB、APB2 和 APB1 这三条总线时钟的开启、控制每个外设的时钟的开启。对于 SYSCLK、HCLK、PCLK2、PCLK1 这四个时钟的配置一般是:PCLK2 = HCLK = SYSCLK=PLLCLK = 72M,PCLK1=HCLK/2 = 36M。这个时钟配置也是库函数的标准配置,我们用的最多的就是这个。

RCC 框图剖析—时钟部分(我的建议是代码配合这个图一起看)

时钟树单纯讲理论的话会比较枯燥,如果选取一条主线,并辅以代码,先主后次讲解的话会很容易,而且记忆还更深刻。我们这里选取库函数时钟系统时钟函数:SetSysClockTo72(); 以这个函数的编写流程来讲解时钟树,这个函数也是我们用库的时候默认的系统时钟设置函数。该函数的功能是利用 HSE 把时钟设置为:PCLK2 = HCLK = SYSCLK = 72M,PCLK1=HCLK/2 = 36M。下面我们就以这个代码的流程为主线,来分析时钟树,对应的是图中的黄色部分,代码流程在时钟树中以数字的大小顺序标识。
STM32基础--RCC—使用 HSE/HSI 配置时钟_第1张图片

系统时钟

HSE 高速外部时钟信号(我的建议是对比图多看几遍,写的顺序就是按照1到7来的)

HSE 是高速的外部时钟信号,可以由有源晶振或者无源晶振提供,频率从 4-16MHZ 不等。当使用有源晶振时,时钟从 OSC_IN 引脚进入,OSC_OUT 引脚悬空,当选用无源晶振时,时钟从OSC_IN 和 OSC_OUT 进入,并且要配谐振电容。

HSE 最常使用的就是 8M 的无源晶振。当确定 PLL 时钟来源的时候,HSE 可以不分频或者 2 分频,这个由时钟配置寄存器 CFGR 的位 17:PLLXTPRE 设置,我们设置为 HSE 不分频。

  • PLL 时钟源
    PLL 时钟来源可以有两个,一个来自 HSE,另外一个是 HSI/2,具体用哪个由时钟配置寄存器 CFGR 的位 16:PLLSRC 设置。HSI 是内部高速的时钟信号,频率为 8M,根据温度和环境的情况频率会有漂移,一般不作为 PLL 的时钟来源。这里我们选 HSE作为 PLL 的时钟来源。

  • PLL 时钟 PLLCLK
    通过设置 PLL 的倍频因子,可以对 PLL 的时钟来源进行倍频,倍频因子可以是:[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],具体设置成多少,由时钟配置寄存器 CFGR的位 21-18:PLLMUL[3:0] 设置。我们这里设置为 9 倍频,因为上一步我们设置 PLL的时钟来源为 HSE=8M,所以经过 PLL 倍频之后的 PLL 时钟:PLLCLK = 8M *9 =72M。72M 是 ST 官方推荐的稳定运行时钟,如果你想超频的话,增大倍频因子即可,最高为 128M。我们这里设置 PLL 时钟:PLLCLK = 8M *9 = 72M。

  • 系统时钟 SYSCLK
    系统时钟来源可以是:HSI、PLLCLK、HSE,具体的时钟配置寄存器 CFGR 的位 1-0:SW[1:0] 设置。我们这里设置系统时钟:SYSCLK = PLLCLK = 72M。

  • AHB 总线时钟 HCLK
    系统时钟 SYSCLK 经过 AHB 预分频器分频之后得到时钟叫 APB 总线时钟,即 HCLK,分频因子可以是:[1,2,4,8,16,64,128,256,512],具体的由时钟配置寄存器 CFGR 的位7-4 :HPRE[3:0] 设置。片上大部分外设的时钟都是经过 HCLK 分频得到,至于 AHB 总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好 APB 的时钟即可。我们这里设置为 1 分频,即 HCLK=SYSCLK=72M。

  • APB2 总线时钟 PLCK2
    APB2 总线时钟 PCLK2 由 HCLK 经过高速 APB2 预分频器得到,分频因子可以是:[1,2,4,8,16],具体由时钟配置寄存器 CFGR 的位 13-11:PPRE2[2:0] 决定。PLCK2属于高速的总线时钟,片上高速的外设就挂载到这条总线上,比如全部的 GPIO、US-ART1、SPI1 等。至于 APB2 总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好 APB2 的时钟即可。我们这里设置为1 分频,即 PCLK2 = HCLK = 72M。

  • APB1 总线时钟 PLCK1
    APB1 总线时钟 PCLK1 由 HCLK 经过低速 APB 预分频器得到,分频因子可以是:[1,2,4,8,16],具体的由时钟配置寄存器 CFGR 的位 10-8:PRRE1[2:0] 决定。PLCK1 属于低速的总线时钟,最高为 36M,片上低速的外设就挂载到这条总线上,比如 USART2/3/4/5、SPI2/3,I2C1/2 等。至于 APB1 总线上的外设的时钟设置为多少,得等到我们使用该
    外设的时候才设置,我们这里只需粗线条的设置好 APB1 的时钟即可。我们这里设置为 2 分频,即 PCLK1 = HCLK/2 = 36M。

设置系统时钟库函数

直接从固件库文件 system_stm32f10x.c扒的,就是注释翻译了一遍,配置步骤和上面描述的一样。

/**
  * @brief  Sets System clock frequency to 72MHz and configure HCLK, PCLK2 
  *         and PCLK1 prescalers. 
  * @note   This function should be used only after reset.
  * @param  None
  * @retval None
  */
static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* 系统时钟SYSCLK, AHB总线时钟HCLK, APB2总线时钟PCLK2 和 APB1总线时钟PCLK1 配置 ---------------------------*/    
  /* 使能 HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* 等待HSE稳定,如果超时则超时处理 */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)
  {
    HSEStatus = (uint32_t)0x01;
  }
  else
  {
    HSEStatus = (uint32_t)0x00;
  }  
	//HSE启动成功,则继续往下处理
  if (HSEStatus == (uint32_t)0x01)
  {
    /* 是能 FLASH预存取缓冲区,32为了速度更快采用预取指,参考指令流水线 */
    FLASH->ACR |= FLASH_ACR_PRFTBE;
	
// SYSCLK 周期与闪存访问时间的比例设置,这里统一设置成 2
// 设置成 2 的时候,SYSCLK 低于 48M 也可以工作,如果设置成 0 或者 1 的时候,
// 如果配置的 SYSCLK 超出了范围的话,则会进入硬件错误,程序就死了
// 0:0 < SYSCLK <= 24M
// 1:24< SYSCLK <= 48M
// 2:48< SYSCLK <= 72M 
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    

 //   设置 AHB、APB2、APB1 预分频因子
    /* HCLK = SYSCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /* PCLK2 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /* PCLK1 = HCLK/2 */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
//以下是互联性,咱用的F103为基础型不管了
#ifdef STM32F10X_CL
    /* Configure PLLs ------------------------------------------------------*/
    /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
    /* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */
        
    RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
                              RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
    RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
                             RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);
  
    /* Enable PLL2 */
    RCC->CR |= RCC_CR_PLL2ON;
    /* Wait till PLL2 is ready */
    while((RCC->CR & RCC_CR_PLL2RDY) == 0)
    {
    }
    

    /* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */ 
    RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | 
                            RCC_CFGR_PLLMULL9); 
#else  

 //从这里开始看  
    /* 设置 PLL 时钟来源,设置 PLL 倍频因子,PLLCLK = HSE * 9 = 72 MHz */ 
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
                                        RCC_CFGR_PLLMULL));
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */

    /* 使能 PLL */
    RCC->CR |= RCC_CR_PLLON;

    /* 等待 PLL 稳定 */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }
    
    /*  选择 PLL 作为系统时钟来源 */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

    /* 读取时钟切换状态位,确保 PLLCLK 被选为系统时钟 */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
  }
  else
  { /* 
		 如果HESE启动失败,这里将开启错误时钟的处理,用户可以在这里添加处理错误的代码
		 */
  }
}

其他时钟

通过对系统时钟设置的讲解,整个时钟树我们已经把握的有六七成,剩下的时钟部分我们讲解几
个重要的。

  • A、USB 时钟
    USB 时钟是由 PLLCLK 经过 USB 预分频器得到,分频因子可以是:[1,1.5],具体的由时钟配置寄存器 CFGR 的位 22:USBPRE 配置。USB 的时钟最高是 48M,根据分频因子反推过来算,PLLCLK 只能是 48M 或者是 72M。一般我们设置 PLLCLK=72M,USBCLK=48M。USB 对时钟要求比较高,所以 PLLCLK 只能是由 HSE 倍频得到,不能使用 HSI 倍频。

  • B、Cortex 系统时钟
    Cortex 系统时钟由 HCLK 8 分频得到,等于 9M,Cortex 系统时钟用来驱动内核的系统定时器 SysTick,SysTick 一般用于操作系统的时钟节拍,也可以用做普通的定时。

  • C、ADC 时钟
    ADC 时钟由 PCLK2 经过 ADC 预分频器得到,分频因子可以是 [2,4,6,8],具体的由时钟配置寄存器 CFGR 的位 15-14:ADCPRE[1:0] 决定。很奇怪的是怎么没有 1 分频。ADC 时钟最高只能是 14M,如果采样周期设置成最短的 1.5 个周期的话,ADC 的转换时间可以达到最短的 1us。如果真要达到最短的转换时间 1us 的话,那 ADC 的时钟就得是 14M,反推 PCLK2 的时钟只能是:28M、56M、84M、112M,鉴于 PCLK2 最高是 72M,所以只能取 28M 和 56M。

  • D、RTC 时钟、独立看门狗时钟
    RTC 时钟可由 HSE/128 分频得到,也可由低速外部时钟信号 LSE 提供,频率为32.768KHZ,也可由低速内部时钟信号 LSI 提供,具体选用哪个时钟由备份域控制寄存器 BDCR 的位 9-8:RTCSEL[1:0] 配置。独立看门狗的时钟由 LSI 提供,且只能是由 LSI 提供,LSI 是低速的内部时钟信号,频率为 30~60KHZ 直接不等,一般取40KHZ。

  • E、MCO 时钟输出
    MCO 是 microcontroller clock output 的缩写,是微控制器时钟输出引脚,在 STM32 F1系列中由 PA8 复用所得,主要作用是可以对外提供时钟,相当于一个有源晶振。MCO的时钟来源可以是:PLLCLK/2、HSI、HSE、SYSCLK,具体选哪个由时钟配置寄存器CFGR 的位 26-24:MCO[2:0] 决定。除了对外提供时钟这个作用之外,我们还可以通过示波器监控 MCO 引脚的时钟输出来验证我们的系统时钟配置是否正确。

配置系统时钟实验

使用 HSE

一般情况下,我们都是使用 HSE,然后 HSE 经过 PLL 倍频之后作为系统时钟。通常的配置是:HSE=8M,PLL 的倍频因子为:9,系统时钟就设置成:SYSCLK = 8M * 9 = 72M。使用 HSE,系统时钟 SYSCLK 最高是 128M。我们使用的库函数就是这么干的,当程序来到 main 函数之前,启动文件:statup_stm32f10x_hd.s 已经调用 SystemInit() 函数把系统时钟初始化成 72MHZ,SystemInit()在库文件:system_stm32f10x.c 中定义。如果我们想把系统时钟设置低一点或者超频的话,可以修改底层的库文件,但是为了维持库的完整性,我们可以根据时钟树的流程自行写一个。

使用 HSI

当 HSE 故障的时候,如果 PLL 的时钟来源是 HSE,那么当 HSE 故障的时候,不仅 HSE 不能使用,连 PLL 也会被关闭,这个时候系统会自动切换 HSI 作为系统时钟,此时 SYSCLK=HSI=8M,如果没有开启 CSS 和 CSS 中断的话,那么整个系统就只能在低速率运行,这是系统跟瘫痪没什么两样。如果开启了 CSS 功能的话,那么可以当 HSE 故障时,在 CSS 中断里面采取补救措施,使用 HSI,并把系统时钟设置为更高的频率,最高是 64M,64M 的频率足够一般的外设使用,如:ADC、SPI、I2C 等。但是这里就又有一个问题了,原来 SYSCLK=72M,现在因为故障改成 64M,那么那些外设的时钟肯定被改变了,那么外设工作就会被打乱,那我们是不是在设置 HSI 时钟的时候,也重新调整外设总线的分频因子,即 AHB,APB2 和 APB1 的分频因子,使外设的时钟达到跟 HSE 没有故障之前一样。但是这个也不是最保障的办法,毕竟不能一直使用 HSI,所以当HSE 故障时还是要采取报警措施。(HSI还不准,容易受影响,当时我们遇到个I2C通信的就是内部时钟不准,时序都要起飞了,最后给那个玩意加了壳子才正常)

还有一种情况是,有些用户不想用 HSE,想用 HSI,但是又不知道怎么用 HSI 来设置系统时钟,因为调用库函数都是使用 HSE,下面我们给出个使用 HSI 配置系统时钟例子,起个抛砖引玉的作用。

硬件设计

  1. RCC
  2. LED 一个
    RCC 是单片机内部资源,不需要外部电路。通过 LED 闪烁的频率来直观的判断不同系统时钟频率对软件延时的效果。

软件设计

我们编写四个 RCC 驱动文件,bsp_mcooutput.c,bsp_mcooutput.h,bsp_clkconfig.h 和 bsp_clkconfig.c,用来存放 RCC 系统时钟配置函数。(别忘了添加.h文件的路径)

编程要点

编程要点对应着时钟树图中的序号。
1、开启 HSE/HSI ,并等待 HSE/HSI 稳定
2、设置 AHB、APB2、APB1 的预分频因子
3、设置 PLL 的时钟来源,和 PLL 的倍频因子,设置各种频率主要就是在这里设置
4、开启 PLL,并等待 PLL 稳定
5、把 PLLCK 切换为系统时钟 SYSCLK

HSE 作为系统时钟来源(在bsp_clkconfig.c里面)

/**
 * @brief  HSE设置为系统时钟SYSCLK, AHB总线时钟HCLK, APB2总线时钟PCLK2 和 APB1总线时钟PCLK1 配置 
 * @note  None
 * @param  uint32_t pllmul:可以选择几倍频,SYSCLK = 8M * pllmul,pllmul=RCC_PLLMul_x , x:[2,3,...16],最高是128M
 * @retval None
 */

void HSE_SetSysClock(uint32_t pllmul)
{
   
   __IO uint32_t HSEStartUpStatus = 0;

   // 把RCC外设初始化成复位状态,这句是必须的
   		RCC_DeInit();

 //使能HSE,开启外部晶振
   		RCC_HSEConfig(RCC_HSE_ON);

 // 等待 HSE 启动稳定
   		HSEStartUpStatus = RCC_WaitForHSEStartUp();
   
   // 只有 HSE 稳定之后则继续往下执行
   	if(HSEStartUpStatus == SUCCESS){
//----------------------------------------------------------------------//
   // 使能FLASH 预存取缓冲区
   	FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);

   // SYSCLK周期与闪存访问时间的比例设置,这里统一设置成2
   	// 设置成2的时候,SYSCLK低于48M也可以工作,如果设置成0或者1的时候,
   	// 如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了
   	// 0:0 < SYSCLK <= 24M
   	// 1:24< SYSCLK <= 48M
   	// 2:48< SYSCLK <= 72M
   	FLASH_SetLatency(FLASH_Latency_2);
//----------------------------------------------------------------------//		
   	
   // AHB预分频因子设置为1分频,HCLK = SYSCLK 
   			RCC_HCLKConfig(RCC_HCLK_Div1);
 
   // APB2预分频因子设置为1分频,PCLK2 = HCLK
   			RCC_PCLK2Config(RCC_HCLK_Div1);

   // APB1预分频因子设置为1分频,PCLK1 = HCLK/2 
   			RCC_PCLK1Config(RCC_HCLK_Div2);

//-----------------设置各种频率主要就是在这里设置-------------------//
   // 设置PLL时钟来源为HSE,设置PLL倍频因子
   	// PLLCLK = 8MHz * pllmul
   		  RCC_PLLConfig(RCC_PLLSource_HSE_Div1,pllmul);
//------------------------------------------------------------------//

   // 开启PLL 
   			RCC_PLLCmd(ENABLE);

   // 等待 PLL稳定
   			while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);

   // 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK
   			RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

   // 读取时钟切换状态位,确保PLLCLK被选为系统时钟		
   	    while (RCC_GetSYSCLKSource() != 0x08);
   	
   	
   	
   	}else{
   	// 如果HSE开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理
   	// 当HSE开启失败或者故障的时候,单片机会自动把HSI设置为系统时钟,
   	// HSI是内部的高速时钟,8MHZ

   	    while (1);
   	} 
}

这个函数采用库函数编写,函数有个形参 pllmul,pllmul 用来设置 PLL 的倍频因子,在调用的时候形参可以是:RCC_PLLMul_x , x:[2,3,⋯16],这些宏来源于库函数的定义,宏展开是一些 32 位的十六进制数,具体功能是配置了时钟配置寄存器 CFGR 的位 21-18PLLMUL[3:0],预先定义好倍频因子,方便调用。

函数调用举例:HSE_SetSysClock(RCC_PLLMul_9); 则设置系统时钟为:8MHZ * 9 = 72MHZ。HSE_SetSysClock(RCC_PLLMul_16); 则设置系统时钟为:8MHZ * 16 = 128MHZ 超频慎用。

/**
 * @brief  等待 HSI 启动
 * @param  None
 * @retval An ErrorStatus enumuration value:
 * - SUCCESS: HSI oscillator is stable and ready to use
 * - ERROR: HSI oscillator not yet ready
 */
static ErrorStatus RCC_WaitForHSIStartUp(void)
{
 __IO uint32_t StartUpCounter = 0;
 ErrorStatus status = ERROR;
 FlagStatus HSIStatus = RESET;
 
 /* Wait till HSI is ready and if Time out is reached exit */
 do
 {
   HSIStatus = RCC_GetFlagStatus(RCC_FLAG_HSIRDY);
   StartUpCounter++;  
 } while((StartUpCounter != HSI_STARTUP_TIMEOUT) && (HSIStatus == RESET));
 
 if (RCC_GetFlagStatus(RCC_FLAG_HSIRDY) != RESET)
 {
   status = SUCCESS;
 }
 else
 {
   status = ERROR;
 }  
 return (status);
}

 /**
 * @brief  HSI设置为系统时钟SYSCLK, AHB总线时钟HCLK, APB2总线时钟PCLK2 和 APB1总线时钟PCLK1 配置 
 * @note  None
 * @param  uint32_t pllmul:可以选择几倍频,SYSCLK = 8M/2 * pllmul,pllmul=RCC_PLLMul_x , x:[2,3,...16],最高是64M
 * @retval None
 */
void HSI_SetSysClock(uint32_t pllmul)
{
   __IO uint32_t HSIStartUpStatus = 0;
   
   	// 把RCC外设初始化成复位状态,这句是必须的
 RCC_DeInit();

 //使能HSI
   RCC_HSICmd(ENABLE);
   
   // 等待 HSI稳定
   while (RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET);
   // 读取 HSI 就绪状态
   HSIStartUpStatus = RCC_WaitForHSIStartUp();
   
   if(HSIStartUpStatus == SUCCESS){
   //----------------------------------------------------------------------//
   // 使能FLASH 预存取缓冲区
   FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);

   // SYSCLK周期与闪存访问时间的比例设置,这里统一设置成2
   	// 设置成2的时候,SYSCLK低于48M也可以工作,如果设置成0或者1的时候,
   	// 如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了
   	// 0:0 < SYSCLK <= 24M
   	// 1:24< SYSCLK <= 48M
   	// 2:48< SYSCLK <= 72M
   FLASH_SetLatency(FLASH_Latency_2);
//----------------------------------------------------------------------//

   // AHB预分频因子设置为1分频,HCLK = SYSCLK 
   RCC_HCLKConfig(RCC_SYSCLK_Div1); 
 
   // APB2预分频因子设置为1分频,PCLK2 = HCLK
   RCC_PCLK2Config(RCC_HCLK_Div1); 

   // APB1预分频因子设置为1分频,PCLK1 = HCLK/2 
   RCC_PCLK1Config(RCC_HCLK_Div2);
   	
//-----------------设置各种频率主要就是在这里设置-------------------//
   // 设置PLL时钟来源为HSI,设置PLL倍频因子
   	// PLLCLK = 4MHz * pllmul
   	RCC_PLLConfig(RCC_PLLSource_HSI_Div2, pllmul);
//------------------------------------------------------------------//

   // 开启PLL 
   RCC_PLLCmd(ENABLE);

   // 等待 PLL稳定
   while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
   	
   	 // 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK
   RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

   // 读取时钟切换状态位,确保PLLCLK被选为系统时钟
   while (RCC_GetSYSCLKSource() != 0x08);

   }else{ 
   	// 如果HSI开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理
   	// 当HSE开启失败或者故障的时候,单片机会自动把HSI设置为系统时钟,
   	// HSI是内部的高速时钟,8MHZ
   while (1);
 }
   
}

HSI 设置系统时钟函数跟 HSE 设置系统时钟函数在原理上是一样的,有一个区别的地方就是,HSI 必须 2 分频之后才能作为 PLL 的时钟来源,所以使用 HSI 时,最大的系统时钟 SYSCLK 只能是 HSI/216=416=64MHZ。
函数调用举例:HSI_SetSysClock(RCC_PLLMul_9); 则设置系统时钟为:4MHZ * 9 = 36MHZ。

MCO 输出( bsp_mcooutput.c里面)

在 STM32F103 系列中,PA8 可以复用为 MCO 引脚,对外提供时钟输出,我们也可以用示波器监控该引脚的输出来判断我们的系统时钟是否设置正确。

/**
  * @brief  初始化MCO引脚PA8, 在F1系列中MCO引脚只有一个,即PA8,在F4系列中,MCO引脚会有两个
  * @note  None
  * @param  None
  * @retval None
  */
void MCO_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	// 开启GPIOA的时钟
	RCC_APB2PeriphClockCmd(MCO_GPIO_CLK, ENABLE);
	
	// 选择GPIO8引脚
  GPIO_InitStructure.GPIO_Pin = MCO_GPIO_PIN;
	
	//设置为复用功能推挽输出
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	
	//设置IO的翻转速率为50M
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	// 初始化GPIOA8
  GPIO_Init(MCO_GPIO_PORT, &GPIO_InitStructure);
}

测试函数

bsp_mcooutput.c里面

/**
	* @brief MCO测试,直接示波器接PA8
	* @param 无
	* @retval	无
**/

void MCO_GPIO_Test(void)
{
		// MCO 引脚初始化
		MCO_GPIO_Config();
		// 设置MCO引脚输出时钟,用示波器即可在PA8测量到输出的时钟信号,
	// 我们可以把PLLCLK/2作为MCO引脚的时钟来检测系统时钟是否配置准确
	// MCO引脚输出可以是HSE,HSI,PLLCLK/2,SYSCLK	
	//RCC_MCOConfig(RCC_MCO_HSE);	             	        
	//RCC_MCOConfig(RCC_MCO_HSI);	                   
	//RCC_MCOConfig(RCC_MCO_PLLCLK_Div2);    	
		RCC_MCOConfig(RCC_MCO_SYSCLK);	
		while(1);
}

bsp_clkconfig.c里面

/**
	* @brief HSI内部时钟测试,小灯闪烁时间
	* @param 无
	* @retval	无
**/
void HSI_SetSysClock_Test(void)
{
			//8M的速度
			HSI_SetSysClock(RCC_PLLMul_16);
			LED_GPIO_Test();
}
 
 /**
	* @brief HSE外部时钟测试,小灯闪烁时间
	* @param 无
	* @retval	无
**/
void HSE_SetSysClock_Test(void)
{
			//128M的速度
			HSE_SetSysClock(RCC_PLLMul_16);
			LED_GPIO_Test();
}

main.c里面

int main(void)	
{
	// 来到这里的时候,系统的时钟已经被配置成72M。
	
	//MCO口输出时钟信号
//	MCO_GPIO_Test();

	#if 0 //这里设置写0则是测试HSE,写1则是测试HSI
			HSI_SetSysClock_Test();
	#else 
			HSE_SetSysClock_Test();
	#endif
}

代码

main.c

/**
 * *****************************************************************************
 * @file        main.c
 * @brief       主函数
 * @author       (六千里)
 * @date        2024-03-10
 * @copyright   无
 * *****************************************************************************
 */
#include "stm32f10x.h"  

int main(void)	
{
	// 来到这里的时候,系统的时钟已经被配置成72M。
	
	//MCO口输出时钟信号
//	MCO_GPIO_Test();

	#if 0 //这里设置写0则是测试HSE,写1则是测试HSI
			HSI_SetSysClock_Test();
	#else 
			HSE_SetSysClock_Test();
	#endif
}

bsp_mcooutput.c

/**
 * *****************************************************************************
 * @file        bsp_mcooutput.c
 * @brief     	引脚输出时钟
 * @author       (六千里)
 * @date        2024-03-10
 * @copyright   无
 * *****************************************************************************
 */
#include "bsp_mcooutput.h"

 /**
  * @brief  初始化MCO引脚PA8, 在F1系列中MCO引脚只有一个,即PA8,在F4系列中,MCO引脚会有两个
  * @note  None
  * @param  None
  * @retval None
  */
void MCO_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	// 开启GPIOA的时钟
	RCC_APB2PeriphClockCmd(MCO_GPIO_CLK, ENABLE);
	
	// 选择GPIO8引脚
  GPIO_InitStructure.GPIO_Pin = MCO_GPIO_PIN;
	
	//设置为复用功能推挽输出
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	
	//设置IO的翻转速率为50M
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	// 初始化GPIOA8
  GPIO_Init(MCO_GPIO_PORT, &GPIO_InitStructure);
}

/**
	* @brief MCO测试,直接示波器接PA8
	* @param 无
	* @retval	无
**/

void MCO_GPIO_Test(void)
{
		// MCO 引脚初始化
		MCO_GPIO_Config();
		// 设置MCO引脚输出时钟,用示波器即可在PA8测量到输出的时钟信号,
	// 我们可以把PLLCLK/2作为MCO引脚的时钟来检测系统时钟是否配置准确
	// MCO引脚输出可以是HSE,HSI,PLLCLK/2,SYSCLK	
	//RCC_MCOConfig(RCC_MCO_HSE);	             	        
	//RCC_MCOConfig(RCC_MCO_HSI);	                   
	//RCC_MCOConfig(RCC_MCO_PLLCLK_Div2);    	
		RCC_MCOConfig(RCC_MCO_SYSCLK);	
		while(1);
}

bsp_mcooutput.h

#ifndef __MCOOUTPUT_H
#define	__MCOOUTPUT_H

/**
 * *****************************************************************************
 * 包含的头文件
 * *****************************************************************************
 */
#include "stm32f10x.h"


 /**
 * *****************************************************************************
 * 宏定义
 * *****************************************************************************
 */
 #define 		MCO_GPIO_CLK											RCC_APB2Periph_GPIOA
 #define	  MCO_GPIO_PORT											GPIOA
 #define 		MCO_GPIO_PIN											GPIO_Pin_8
  /**
 * *****************************************************************************
 * .c文件中包含的函数
 * *****************************************************************************
 */
void MCO_GPIO_Config(void);
void MCO_GPIO_Test(void);

#endif /* __MCOOUTPUT */

bsp_clkconfig.c

/**
 * *****************************************************************************
 * @file        bsp_clkconfig.c
 * @brief      	重新配置单片机的时钟
 * @author       (六千里)
 * @date        2024-03-09
 * @copyright   无
 * *****************************************************************************
 */
#include "bsp_clkconfig.h"

/*
 * 使用HSE时,设置系统时钟的步骤
 * 1、开启HSE ,并等待 HSE 稳定
 * 2、设置 AHB、APB2、APB1的预分频因子
 * 3、设置PLL的时钟来源,和PLL的倍频因子,设置各种频率主要就是在这里设置
 * 4、开启PLL,并等待PLL稳定
 * 5、把PLLCK切换为系统时钟SYSCLK
 * 6、读取时钟切换状态位,确保PLLCLK被选为系统时钟
 */

/* 设置系统时钟:SYSCLK, AHB总线时钟:HCLK, APB2总线时钟:PCLK2, APB1总线时钟:PCLK1
 * PCLK2 = HCLK = SYSCLK
 * PCLK1 = HCLK/2,最高只能是36M
 * 参数说明:pllmul是PLL的倍频因子,在调用的时候可以是:RCC_PLLMul_x , x:[2,3,...16]
 * 举例:User_SetSysClock(RCC_PLLMul_9);  则设置系统时钟为:8MHZ * 9 = 72MHZ
 *       User_SetSysClock(RCC_PLLMul_16); 则设置系统时钟为:8MHZ * 16 = 128MHZ,超频慎用,外设不兼容,而且超多了容易给PLL弄坏
 *
 * HSE作为时钟来源,经过PLL倍频作为系统时钟,这是通常的做法
 */
 
 /**
  * @brief  HSE设置为系统时钟SYSCLK, AHB总线时钟HCLK, APB2总线时钟PCLK2 和 APB1总线时钟PCLK1 配置 
  * @note  None
  * @param  uint32_t pllmul:可以选择几倍频,SYSCLK = 8M * pllmul,pllmul=RCC_PLLMul_x , x:[2,3,...16],最高是128M
  * @retval None
  */
 
void HSE_SetSysClock(uint32_t pllmul)
{
	
	__IO uint32_t HSEStartUpStatus = 0;

	// 把RCC外设初始化成复位状态,这句是必须的
			RCC_DeInit();

  //使能HSE,开启外部晶振
			RCC_HSEConfig(RCC_HSE_ON);

  // 等待 HSE 启动稳定
			HSEStartUpStatus = RCC_WaitForHSEStartUp();
	
	// 只有 HSE 稳定之后则继续往下执行
		if(HSEStartUpStatus == SUCCESS){
//----------------------------------------------------------------------//
    // 使能FLASH 预存取缓冲区
		FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);

    // SYSCLK周期与闪存访问时间的比例设置,这里统一设置成2
		// 设置成2的时候,SYSCLK低于48M也可以工作,如果设置成0或者1的时候,
		// 如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了
		// 0:0 < SYSCLK <= 24M
		// 1:24< SYSCLK <= 48M
		// 2:48< SYSCLK <= 72M
		FLASH_SetLatency(FLASH_Latency_2);
//----------------------------------------------------------------------//		
		
    // AHB预分频因子设置为1分频,HCLK = SYSCLK 
				RCC_HCLKConfig(RCC_HCLK_Div1);
  
    // APB2预分频因子设置为1分频,PCLK2 = HCLK
				RCC_PCLK2Config(RCC_HCLK_Div1);

    // APB1预分频因子设置为1分频,PCLK1 = HCLK/2 
				RCC_PCLK1Config(RCC_HCLK_Div2);

//-----------------设置各种频率主要就是在这里设置-------------------//
    // 设置PLL时钟来源为HSE,设置PLL倍频因子
		// PLLCLK = 8MHz * pllmul
			  RCC_PLLConfig(RCC_PLLSource_HSE_Div1,pllmul);
//------------------------------------------------------------------//

    // 开启PLL 
				RCC_PLLCmd(ENABLE);

    // 等待 PLL稳定
				while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);

    // 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK
				RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

    // 读取时钟切换状态位,确保PLLCLK被选为系统时钟		
		    while (RCC_GetSYSCLKSource() != 0x08);
		
		
		
		}else{
		// 如果HSE开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理
		// 当HSE开启失败或者故障的时候,单片机会自动把HSI设置为系统时钟,
		// HSI是内部的高速时钟,8MHZ

		    while (1);
		} 
}

/*
 * 使用HSI时,设置系统时钟的步骤
 * 1、开启HSI ,并等待 HSI 稳定
 * 2、设置 AHB、APB2、APB1的预分频因子
 * 3、设置PLL的时钟来源,和PLL的倍频因子,设置各种频率主要就是在这里设置
 * 4、开启PLL,并等待PLL稳定
 * 5、把PLLCK切换为系统时钟SYSCLK
 * 6、读取时钟切换状态位,确保PLLCLK被选为系统时钟
 */

/* 设置 系统时钟:SYSCLK, AHB总线时钟:HCLK, APB2总线时钟:PCLK2, APB1总线时钟:PCLK1
 * PCLK2 = HCLK = SYSCLK
 * PCLK1 = HCLK/2,最高只能是36M
 * 参数说明:pllmul是PLL的倍频因子,在调用的时候可以是:RCC_PLLMul_x , x:[2,3,...16]
 * 举例:HSI_SetSysClock(RCC_PLLMul_9);  则设置系统时钟为:4MHZ * 9 = 72MHZ
 *       HSI_SetSysClock(RCC_PLLMul_16); 则设置系统时钟为:4MHZ * 16 = 64MHZ
 *
 * HSI作为时钟来源,经过PLL倍频作为系统时钟,这是在HSE故障的时候才使用的方法
 * HSI会因为温度等原因会有漂移,不稳定,一般不会用HSI作为时钟来源,除非是迫不得已的情况
 * 如果HSI要作为PLL时钟的来源的话,必须二分频之后才可以,即HSI/2,而PLL倍频因子最大只能是16
 * 所以当使用HSI的时候,SYSCLK最大只能是4M*16=64M
 */
 
 /**
  * @brief  等待 HSI 启动
  * @param  None
  * @retval An ErrorStatus enumuration value:
  * - SUCCESS: HSI oscillator is stable and ready to use
  * - ERROR: HSI oscillator not yet ready
  */
static ErrorStatus RCC_WaitForHSIStartUp(void)
{
  __IO uint32_t StartUpCounter = 0;
  ErrorStatus status = ERROR;
  FlagStatus HSIStatus = RESET;
  
  /* Wait till HSI is ready and if Time out is reached exit */
  do
  {
    HSIStatus = RCC_GetFlagStatus(RCC_FLAG_HSIRDY);
    StartUpCounter++;  
  } while((StartUpCounter != HSI_STARTUP_TIMEOUT) && (HSIStatus == RESET));
  
  if (RCC_GetFlagStatus(RCC_FLAG_HSIRDY) != RESET)
  {
    status = SUCCESS;
  }
  else
  {
    status = ERROR;
  }  
  return (status);
}
 
  /**
  * @brief  HSI设置为系统时钟SYSCLK, AHB总线时钟HCLK, APB2总线时钟PCLK2 和 APB1总线时钟PCLK1 配置 
  * @note  None
  * @param  uint32_t pllmul:可以选择几倍频,SYSCLK = 8M/2 * pllmul,pllmul=RCC_PLLMul_x , x:[2,3,...16],最高是64M
  * @retval None
  */
void HSI_SetSysClock(uint32_t pllmul)
{
	__IO uint32_t HSIStartUpStatus = 0;
	
		// 把RCC外设初始化成复位状态,这句是必须的
  RCC_DeInit();

  //使能HSI
	RCC_HSICmd(ENABLE);
	
	// 等待 HSI稳定
	while (RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET);
	// 读取 HSI 就绪状态
	HSIStartUpStatus = RCC_WaitForHSIStartUp();
	
	if(HSIStartUpStatus == SUCCESS){
	//----------------------------------------------------------------------//
    // 使能FLASH 预存取缓冲区
    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);

    // SYSCLK周期与闪存访问时间的比例设置,这里统一设置成2
		// 设置成2的时候,SYSCLK低于48M也可以工作,如果设置成0或者1的时候,
		// 如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了
		// 0:0 < SYSCLK <= 24M
		// 1:24< SYSCLK <= 48M
		// 2:48< SYSCLK <= 72M
    FLASH_SetLatency(FLASH_Latency_2);
//----------------------------------------------------------------------//
 
    // AHB预分频因子设置为1分频,HCLK = SYSCLK 
    RCC_HCLKConfig(RCC_SYSCLK_Div1); 
  
    // APB2预分频因子设置为1分频,PCLK2 = HCLK
    RCC_PCLK2Config(RCC_HCLK_Div1); 

    // APB1预分频因子设置为1分频,PCLK1 = HCLK/2 
    RCC_PCLK1Config(RCC_HCLK_Div2);
		
//-----------------设置各种频率主要就是在这里设置-------------------//
    // 设置PLL时钟来源为HSI,设置PLL倍频因子
		// PLLCLK = 4MHz * pllmul
		RCC_PLLConfig(RCC_PLLSource_HSI_Div2, pllmul);
//------------------------------------------------------------------//

    // 开启PLL 
    RCC_PLLCmd(ENABLE);

    // 等待 PLL稳定
    while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
		
		 // 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

    // 读取时钟切换状态位,确保PLLCLK被选为系统时钟
    while (RCC_GetSYSCLKSource() != 0x08);

	}else{ 
		// 如果HSI开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理
		// 当HSE开启失败或者故障的时候,单片机会自动把HSI设置为系统时钟,
		// HSI是内部的高速时钟,8MHZ
    while (1);
  }
	
}	

/**
	* @brief HSI内部时钟测试,小灯闪烁时间
	* @param 无
	* @retval	无
**/
void HSI_SetSysClock_Test(void)
{
			//8M的速度
			HSI_SetSysClock(RCC_PLLMul_16);
			LED_GPIO_Test();
}
 
 /**
	* @brief HSE外部时钟测试,小灯闪烁时间
	* @param 无
	* @retval	无
**/
void HSE_SetSysClock_Test(void)
{
			//128M的速度
			HSE_SetSysClock(RCC_PLLMul_16);
			LED_GPIO_Test();
}

bsp_clkconfig.h

#ifndef __BSP_CLKCONFIG_H
#define __BSP_CLKCONFIG_H

/**
 * *****************************************************************************
 * 包含的头文件
 * *****************************************************************************
 */
 #include "stm32f10x_rcc.h"
 #include "stm32f10x_flash.h"
 #include "bsp_led.h"
 
 /**
 * *****************************************************************************
 * 宏定义
 * *****************************************************************************
 */
 
 #define HSI_STARTUP_TIMEOUT   ((uint16_t)0x0500) /*!< Time out for HSI start up */
 
 
 /**
 * *****************************************************************************
 * .c文件中包含的函数
 * *****************************************************************************
 */
 static ErrorStatus RCC_WaitForHSIStartUp(void);
 void HSE_SetSysClock(uint32_t pllmul);
 void HSI_SetSysClock(uint32_t pllmul);
 void HSI_SetSysClock_Test(void);
 void HSE_SetSysClock_Test(void);
 #endif /*__BSP_CLKCONFIG_H*/

stm32f10x_conf.h

/**
  ******************************************************************************
  * @file    Project/STM32F10x_StdPeriph_Template/stm32f10x_conf.h 
  * @author  MCD Application Team
  * @version V3.5.0
  * @date    08-April-2011
  * @brief   Library configuration file.
  ******************************************************************************
  * @attention
  *
  * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
  * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
  * TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY
  * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
  * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
  * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
  *
  * 

© COPYRIGHT 2011 STMicroelectronics

****************************************************************************** */ /* Define to prevent recursive inclusion -------------------------------------*/ #ifndef __STM32F10x_CONF_H #define __STM32F10x_CONF_H /* Includes ------------------------------------------------------------------*/ /* Uncomment/Comment the line below to enable/disable peripheral header file inclusion */ #include "stm32f10x_adc.h" #include "stm32f10x_bkp.h" #include "stm32f10x_can.h" #include "stm32f10x_cec.h" #include "stm32f10x_crc.h" #include "stm32f10x_dac.h" #include "stm32f10x_dbgmcu.h" #include "stm32f10x_dma.h" #include "stm32f10x_exti.h" #include "stm32f10x_flash.h" #include "stm32f10x_fsmc.h" #include "stm32f10x_gpio.h" #include "stm32f10x_i2c.h" #include "stm32f10x_iwdg.h" #include "stm32f10x_pwr.h" #include "stm32f10x_rcc.h" #include "stm32f10x_rtc.h" #include "stm32f10x_sdio.h" #include "stm32f10x_spi.h" #include "stm32f10x_tim.h" #include "stm32f10x_usart.h" #include "stm32f10x_wwdg.h" #include "misc.h" /* High level functions for NVIC and SysTick (add-on to CMSIS functions) */ /** *自己书写文件的头文件 **/ #include "bsp_led.h" #include "bsp_key.h" #include "bsp_led_bitbang.h" #include "bsp_clkconfig.h" #include "bsp_mcooutput.h" /* Exported types ------------------------------------------------------------*/ /* Exported constants --------------------------------------------------------*/ /* Uncomment the line below to expanse the "assert_param" macro in the Standard Peripheral Library drivers code */ /* #define USE_FULL_ASSERT 1 */ /* Exported macro ------------------------------------------------------------*/ #ifdef USE_FULL_ASSERT /** * @brief The assert_param macro is used for function's parameters check. * @param expr: If expr is false, it calls assert_failed function which reports * the name of the source file and the source line number of the call * that failed. If expr is true, it returns no value. * @retval None */ #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__)) /* Exported functions ------------------------------------------------------- */ void assert_failed(uint8_t* file, uint32_t line); #else #define assert_param(expr) ((void)0) #endif /* USE_FULL_ASSERT */ #endif /* __STM32F10x_CONF_H */ /******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

注意:测试里面LED的那个test不是固件库的,是我自己写的,需要的看我往期。

参考:https://doc.embedfire.com/products/link/zh/latest/index.html

你可能感兴趣的:(STM32,stm32,嵌入式硬件,单片机)