G
N
I
D
A
O
L

STM32学习笔记(3)——时钟系统

一、STM32时钟系统

1. STM32时钟系统框图

STM32的时钟系统非常强大,但也非常复杂。下面为时钟树:

下面分别介绍图中的各个元素 (STM32中文参考手册6.2节时钟)

(1)最左边

最左边的OSC_INOSC_OUT是两个引脚,默认是外部晶振引脚。(我们的板子接了8MHz的晶振,数据手册表示可以接4——16MHz的晶振)

MCO(microcontroller clock output)是输出内部时钟,该功能能将STM32内部的时钟通过引脚PA8输出,要进行引脚复用与功能设置。我们可以通过示波器(可惜我们没有,要到实验室去接)监控 MCO 引脚的时钟输出来验证系统时钟配置是否正确。STM32可以选择一个时钟信号输出到MCO上,可以选择为PLL输出的2分频、HSI、HSE、或者系统时钟(SYSCLK)。

(2) STM32的5个时钟源(蓝色框图)

这些时钟源由晶振电路或RC振荡器组成,为系统提供一定的工作频率。

  • HSI(high speed internal)是高速内部时钟,RC振荡器,频率为8MHz,精度不高。 

  • HSE(high speed external)是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。 

  • LSI(low speed internal)是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。 

  • LSE(low speed external)是低速外部时钟,接频率为32.768kHz的石英晶体。

  • PLL(phase locked loop)为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。

系统时钟SYSCLK可来源于三个时钟源:HSI振荡器时钟、HSE振荡器时钟、PLL时钟。

(3)几个重要的时钟(黑色字体)

这些时钟都是通过时钟源、倍频器和预分频器的各种配置而得到的:

  • SYSCLK(系统时钟)
  • AHB(advanced high performance bus,高级高性能总线)总线时钟
  • APB1(advanced peripheral bus,高级外围设备总线)总线时钟(低速):速度最高为36MHz
  • APB2总线时钟(高速):速度最高为72MHz
  • PLL时钟

以及还有一些未提及的外设时钟(最右侧),这些外设的时钟要根据外设的需求来进行配置。

(4)预分频器(绿色框图)

分频器的作用是:系统时钟先经过固定的分频系数后产生相应频率的时钟,提供给单片机定时器的计时输入。

图中绿色的框图为预分频器,图中已写出每个分频器的可分频因子。分频器的相关知识数电讲过。

(5)选择器(灰色框图)

图中灰色的梯形框图为选择器,数电也曾经讲过。这个形状应该很熟悉吧!

(6)时钟安全系统CSS(橙色框图)

如果外部晶振出了问题(例如被短路),系统时钟(SYSCLK)就会崩溃。因此,STM32时钟系统设置了CSS时钟安全系统,一旦HSE失效,则自动切换至SYSCLK = HSI,意味着系统用内部RC振荡器作为时钟源(对照时钟树的路线看看是怎么进行切换的!)。当然RC振荡器并不是十分精确的,当外部晶振恢复正常后,我们还是希望系统切换回外部晶振。

2.其中一条线路举例(可对照时钟树进行查看)

OSC_IN (8MHz) --> HSE_OSC --> 选择器 --> 选择器 --> PLL (8MHz X 9 = 72MHz) --> 作为PLLCLK --> 选择器 --> 作为SYSCLK --> AHB预分频器 --> 作为HCLK(advanced high performance bus clock,高级高性能总线时钟)。

3. 与RCC相关的寄存器定义以及函数

本节可参考STM32中文参考手册6.3节RCC寄存器描述

与RCC寄存器相关的定义如下(位于stm32f10x.h头文件):

/** 
  * @brief Reset and Clock Control
  */

typedef struct
{
  __IO uint32_t CR; //HSI,HSE,CSS,PLL等的使能和就绪标志位
  __IO uint32_t CFGR; //PLL等的时钟源选择,分频系数设定
  __IO uint32_t CIR; // 清除/使能 时钟就绪中断
  __IO uint32_t APB2RSTR; //APB2线上外设复位寄存器
  __IO uint32_t APB1RSTR; //APB1线上外设复位寄存器
  __IO uint32_t AHBENR; //DMA,SDIO等时钟使能
  __IO uint32_t APB2ENR; //APB2线上外设时钟使能
  __IO uint32_t APB1ENR; //APB1线上外设时钟使能
  __IO uint32_t BDCR; //备份域控制寄存器
  __IO uint32_t CSR; //控制状态寄存器

#ifdef STM32F10X_CL  
  __IO uint32_t AHBRSTR;
  __IO uint32_t CFGR2;
#endif /* STM32F10X_CL */ 

#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)   
  uint32_t RESERVED0;
  __IO uint32_t CFGR2;
#endif /* STM32F10X_LD_VL || STM32F10X_MD_VL || STM32F10X_HD_VL */ 
} RCC_TypeDef;

需要注意,STM32F10X_CL这些都属于互联型,编写者为了兼容更多机型而采用了条件编译的办法。我们使用的是大容量(STM32F10X_HD),因此有条件编译的语句只看STM32F10X_HD的即可。在库函数中经常涉及这种情况。

与RCC寄存器相关的函数如下(位于stm32f10x.h头文件):

(1)时钟使能配置:
      RCC_LSEConfig() 、RCC_HSEConfig()、
      RCC_HSICmd() 、 RCC_LSICmd() 、 RCC_PLLCmd() ……
      
(2)时钟源相关配置:
      RCC_PLLConfig ()、 RCC_SYSCLKConfig() 、
     RCC_RTCCLKConfig() …
     
(3)分频系数选择配置:
      RCC_HCLKConfig() 、 RCC_PCLK1Config() 、 RCC_PCLK2Config()…
      
(4)外设时钟使能:
      RCC_APB1PeriphClockCmd():  //APB1线上外设时钟使能
     RCC_APB2PeriphClockCmd();  //APB2线上外设时钟使能
     RCC_AHBPeriphClockCmd();   //AHB线上外设时钟使能
     
(5)其他外设时钟配置:
     RCC_ADCCLKConfig ();  RCC_RTCCLKConfig();
     
(6)状态参数获取参数:
     RCC_GetClocksFreq();
     RCC_GetSYSCLKSource();
     RCC_GetFlagStatus()
     
(7)RCC中断相关函数:
     RCC_ITConfig() 、 RCC_GetITStatus() 、 RCC_ClearITPendingBit()…

二、系统初始化时的时钟配置

本节将详细介绍时钟初始化的流程。

上一次学习笔记我们可以看到,即使我们没有一步一步对时钟进行配置,只是打开引脚相应的时钟,LED和蜂鸣器依然可以正常工作,这是为什么呢?原来,在系统刚启动时,会运行启动程序(该程序为汇编程序startup_stm32f10x_hd.s),调用SystemInit函数,从而配置好默认的系统时钟:

Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit
                BLX     R0               
                LDR     R0, =__main
                BX      R0
                ENDP

这就意味着,当程序来到main函数里的时候,系统的时钟已经被配置成72MHz。

下面我们进入SystemInit()函数,去一探时钟初始化的究竟。

1. 时钟系统初始化函数SystemInit()

这个函数位于源文件system_stm32f10x.c,它是与时钟配置相关的文件,在它的开头,定义了宏,可以看到默认情况下使用的是72MHz:

#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE    HSE_VALUE */
 #define SYSCLK_FREQ_24MHz  24000000
#else
/* #define SYSCLK_FREQ_HSE    HSE_VALUE */
/* #define SYSCLK_FREQ_24MHz  24000000 */ 
/* #define SYSCLK_FREQ_36MHz  36000000 */
/* #define SYSCLK_FREQ_48MHz  48000000 */
/* #define SYSCLK_FREQ_56MHz  56000000 */
#define SYSCLK_FREQ_72MHz  72000000
#endif

system_stm32f10x.c的第200多行,为SystemInit()函数,可对照STM32中文参考手册6.3节RCC寄存器描述 (为简短起见,我们将无关代码删去了)(星号注释为本人翻译):

void SystemInit (void)
{
  /* 重置RCC时钟配置至默认状态(为了调试功能) */
  /* 置 HSION 位为1 */
  RCC->CR |= (uint32_t)0x00000001;

  /* 置 SW, HPRE, PPRE1, PPRE2, ADCPRE 和 MCO 位为0 */
  RCC->CFGR &= (uint32_t)0xF0FF0000; 
  
  /* 置 HSEON, CSSON 和 PLLON 位为0 */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

  /* 置 HSEBYP 位为1 */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

  /* 置 PLLSRC, PLLXTPRE, PLLMUL 和 USBPRE/OTGFSPRE 位为1 */
  RCC->CFGR &= (uint32_t)0xFF80FFFF;

  /* 关掉中断相应的位  */
  RCC->CIR = 0x009F0000; 

  /* 配置系统时钟 */
  /* Configure the Flash Latency cycles and enable prefetch buffer */
  SetSysClock(); // 下一节会涉及该函数的详细介绍

#ifdef VECT_TAB_SRAM
  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif 
}

这里调用了一个函数SetSysClock(),下面跳到该函数去看看!

2. 系统时钟配置函数SetSysClockTo72()

system_stm32f10x.c的第400多行,为SetSysClock()函数,可对照STM32中文参考手册6.3节RCC寄存器描述

static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
  SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
  SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
  SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
  SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
  SetSysClockTo56();  
#elif defined SYSCLK_FREQ_72MHz
  SetSysClockTo72();
#endif
 
 /* If none of the define above is enabled, the HSI is used as System clock
    source (default after reset) */ 
}

根据该文件开头的宏定义,程序将执行函数SetSysClockTo72()。在文件差不多1000多行,为该函数的完整定义(本程序已删去无关代码):

static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  // 1.本人注:在头文件core_cm3.h中将__IO宏定义为volatile
  
  /* 配置 SYSCLK, HCLK, PCLK2 和 PCLK1  ---------------------------*/    
  /* 2.使能 HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* 3.等待 HSE 就绪,如果超时,则退出循环不再等待 */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
  
  // 4.本人注:标志HSE此时的状态是否启动成功
  if ((RCC->CR & RCC_CR_HSERDY) != RESET)
  {
    HSEStatus = (uint32_t)0x01;
  }
  else
  {
    HSEStatus = (uint32_t)0x00;
  }  

  // 本人注:如果HSE启动成功,则程序继续执行
  if (HSEStatus == (uint32_t)0x01)
  {
    // 5.本人注:此处参考STM32F10xxx闪存编程手册3寄存器说明
    /* Enable Prefetch Buffer */
    FLASH->ACR |= FLASH_ACR_PRFTBE;

    /* 6.Flash 2 wait state */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    

    // 本人注:以下参考STM32中文参考手册6.3.2节时钟配置寄存器
    /* 7.HCLK = SYSCLK = 72MHz*/
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /* 8.PCLK2 = HCLK = 72MHz*/
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /* 9.PCLK1 = HCLK / 2 = 36MHz (原文漏了/2,疑似笔误) */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

    /* 10.锁相环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);

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

    /* 12.等待 PLL 就绪 */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }
    
    /* 13.选择 PLL 作为系统时钟 */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

    /* 14.等待 PLL 切换为系统时钟 */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
  }
  else
  { /* 15.如果HSE启动失败,说明时钟拥有错误的配置。
         编写者可在这里添加处理错误的代码 */
  }
}
#endif

对照时钟树发现程序的配置顺序和时钟树的顺序不一致,其实无所谓,打个比方,只有打开所有开关系统才能正常运行,打开部分开关还是运行不了。

初始化之后的状态:

  • SYSCLK = 72MHz
  • AHB = 72MHz
  • PCLK1(peripheral bus clock,外围设备总线时钟) = 36MHz
  • PCLK2 = 72MHz
  • PLL = 72MHz

3.小结

系统初始化时钟的流程:startup(汇编程序) -> SystemInit() -> SetSysClock() -> SetSysClockTo72() -> main()。

三、使用库函数配置系统时钟HSE

在第一部分我们已经列举出了一些RCC常用的函数,接下来我们自己写一个函数来配置我们想要的时钟频率。代码如下(这里的步骤与上面代码的步骤一一对应):

#include "rcc_config.h"
#include "stm32f10x.h"

void HSE_SetSysClk( uint32_t RCC_PLLMul_x )
{
    // 1.定义HSE标志位
	ErrorStatus HSEStatus;
	
	// 初始化时钟,这里的作用是清除复位
	RCC_DeInit();
	
	// 2.使能HSE
	RCC_HSEConfig(RCC_HSE_ON);
	// 3.等待 HSE 就绪,如果超时,则退出循环不再等待
	HSEStatus = RCC_WaitForHSEStartUp();
	
	 // 4.如果HSE启动成功,则程序继续执行
	if( HSEStatus == SUCCESS )
	{
	    // 5&6.设置Flash预缓冲
		FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
		FLASH_SetLatency(FLASH_Latency_2);
		
		// 7.设置AHB时钟HCLK
		RCC_HCLKConfig(RCC_SYSCLK_Div1);
		// 8.设置低速APB时钟
		RCC_PCLK1Config(RCC_HCLK_Div2);
		// 9.设置高速APB时钟
		RCC_PCLK2Config(RCC_HCLK_Div1);
		
		// 10.设置PLL
		RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_x);
		// 11.使能PLL
		RCC_PLLCmd(ENABLE);
		// 12.等待PLL就绪
		while( RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
		
		// 13.选择PLL作为系统时钟
		RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
		// 14.等待 PLL 切换为系统时钟
		while( RCC_GetSYSCLKSource() != 0x08 );
	}
	else
	{
		// 15.用户在这里添加处理错误的代码
	}
}

现在我们在主函数里调用自己写的时钟配置函数。这里我写了个流水灯程序,在main函数一开始调用了自己写的HSE_SetSysClk()函数,参数为PLL锁相环的倍频因子,可以看到我这里用的是12倍频因子,8MHz*12=96MHz,超频(因为最高为72MHz),delay函数的延时效果会更加短。

#include "stm32f10x.h"
#include  "led.h"
#include "rcc_config.h"

void delay( uint32_t count )
{
	for(; count != 0; count--);
}

int main(void)
{
 
HSE_SetSysClk(RCC_PLLMul_12); //这里可以选择自己想要的倍数
//倍数越高,流水灯越快,但同时也有超频的可能
LED_Init();

while(1){

	GPIO_SetBits(GPIOB,GPIO_Pin_5);
	GPIO_ResetBits(GPIOE,GPIO_Pin_5);
	delay(0xFFFFF);

	GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	GPIO_SetBits(GPIOE,GPIO_Pin_5);
	delay(0xFFFFF);

}
}
posted @ 2021-04-29 11:05  漫舞八月(Mount256)  阅读(794)  评论(0编辑  收藏  举报