当前位置:网站首页>STM32的时钟系统

STM32的时钟系统

2022-07-07 06:26:00 一只大喵咪1201

描述

时钟系统是为单片机工作提供能量的系统,时钟系统通过晶振振荡或者是RC振荡电路会提供一定频率的节拍,在这个节拍下,单片机的各个功能单位进行工作。这个节拍就像是我们生活中按一定频率拍手一样,时钟系统的作用就类似于每拍一次手,单片机内部就执行一条指令,这样它们的工作就有了一定的章程,不会混乱,当然,时钟系统的振荡次数(节拍数)和指令或者外设的工作进行有严格的数量关系。

时钟系统框图及说明

图

时钟源

图中有5个红色的矩形框,这就是STM32的5个时钟源,也就是给STM32提供时钟的能量来源。

  1. HSI是高速内部时钟信号。是由RC 振荡器产生的,频率约为 8MHz(因为RC振荡器产生的信号不稳定)。
  2. PLL为锁相环倍频输出,其时钟输入源可选择为 HSI/2、HSE 或者 HSE/2。倍频可选择为2~16 倍,但是其输出频率最大不得超过 72MHz
  3. HSE是高速外部时钟,可接石英/陶瓷谐振器等外部晶振,或者接外部时钟源,频率范围为4MHz~16MHz。本喵使用的STM32F10ZET6开发板接的是 8M 的晶振。
  4. LSE是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC (系统实时时钟)的时钟源。
  5. LSI 是低速内部时钟,RC 振荡器,频率约为 40kHz(因为RC振荡器产生的信号不稳定)。独立看门狗的时钟源只能是 LSI,同时LSI还可以作为 RTC 的时钟源。

对着5个时钟源进行分类:

  1. 按照高速和低速来分:可以分为高速时钟源(包括HSI,HSE,PLL三个时钟源),和低俗时钟源(包括LSI,LSE两个时钟源)。
  2. 按照内部和外部来分:可以分为外部时源(包括HSE,LSE两个时钟源),和内部时钟源(包括HSI,LSI,PLL三个时钟源)。

时钟输出

图中蓝色的框框是5个选择器,它可以选择时钟源。绿色的框框是时钟源提供的时钟的去向。本喵从A到E的顺序来介绍:
A:MCO 是 STM32 的一个时钟输出 IO(PA8),它可以选择一个时钟信号输出,可以选择为 PLL 输出的 2 分频、HSI、HSE、或者系统时钟。这个时钟可以用来给外部其他系统提供时钟源。

B:这里是 RTC(系统实时时钟) 时钟源,从图上可以看出,RTC 的时钟源可以选择 LSI,LSE,以及HSE 的 128 分频。

C:从图中可以看出 C 处 USB 的时钟是来自 PLL 时钟源。STM32 中有一个全速功能的 USB 模块,其串行接口需要一个频率为 48MHz 的时钟源。该时钟源只能从 PLL 输出端获取,可以选择为 1.5 分频或者 1 分频,也就是,当需要使用 USB模块时,PLL 必须使能,并且时钟频率配置为 48MHz 或 72MHz。

D:D 处就是 STM32 的系统时钟 SYSCLK,它是供 STM32 中绝大部分部件工作的时钟源。系统时钟可选择为 PLL 输出、HSI 或者 HSE。系统时钟最大频率为 72MHz,当然你也可以超频,不过一般情况为了系统稳定性是没有必要冒风险去超频的。

E:这里的 E 处是指其他所有外设了。从时钟图上可以看出,其他所有外设的时钟最终来源都是 SYSCLK。SYSCLK 通过 AHB 分频器分频后送给各模块使用。
这些模块包括:

  1. AHB 总线、内核、内存和 DMA 使用的 HCLK 时钟。
  2. 通过 8 分频后送给 Cortex 的系统定时器时钟,也就是 systick 了。
  3. 直接送给 Cortex 的空闲运行时钟 FCLK。
  4. 送给 APB1 分频器。APB1 分频器输出一路供 APB1 外设使用(PCLK1,最大频率 36MHz),另一路送给定时器(Timer)2、3、4 倍频器使用。
  5. 送给 APB2 分频器。APB2 分频器分频输出一路供 APB2 外设使(PCLK2,最大频率 72MHz),另一路送给定时器(Timer)1 倍频器使用,还有一路送给ADC分频使用(ADCCLK,最大频率是14MHZ)
  6. 经2分频后送给SDIO的AHB接口使用。

APB1和APB2的区别:APB1上挂载的都是低速外设,包括电源接口、备份接口、CAN、USB、I2C1、I2C2、UART2、UART3 等等,APB2上挂载的都是高速外设,包括 UART1、SPI1、Timer1、ADC1、ADC2、所有普通 IO 口(PA~PE)、第二功能 IO 口等。
**注意:**所有的时钟输出时都需要使能,也就是上面的6个模块,当使用到某个模块的时候,要对相应的时钟输出进行使能。通俗理解就是使用到哪个模块就需要将哪个时钟打开。具体的使用后面会说到。

RCC寄存器

RCC寄存器就是用来配置时钟系统的,它还包括很多寄存器。

时钟控制寄存器(RCC_CR)

图
这是它32个位的分布以情况。
图
这是它的具体控制情况。
使用较多的就是就是时钟源的使能,以及读取时钟源就绪标志位。
时钟源开启以后需要一定的时间来稳定,当稳定以后,就序标志位就会由硬件置1,当读取到该为是1以后才能进行后续的操作。

时钟配置寄存器(RCC_CFGR)

图
这是它32个位的部分情况。
图
这是它的具体控制情况。
使用较多的就是系统时钟来源的选择,可供选择的有HSI,HSEMPLL三个时钟源。
还有时钟输出时分频或者倍频的倍数选择,调整SYSCLK的频率。

AHB外设时钟使能寄存器 (RCC_AHBENR)

图
它每一位的分布情况。
图
这是它的具体控制情况。
它主要是对AHB总线上挂靠的外设进行使能。

APB2 外设时钟使能寄存器(RCC_APB2ENR)

这是在前面实验中使用的寄存器。详细请参考前面的跑马灯实验。

这些寄存器在使用的时候需要对照着手册一位一位的设置。
RCC存器中还有很多的寄存器,本喵只介绍了一些重要的,和目前使用到的,其他寄存器在后面使用到的时候会详细介绍。

APB1 外设时钟使能寄存器(RCC_APB1ENR)

图
这是它的分布情况。
图
这是它的具体控制情况。
RCC中的寄存器经常使用到的就是这5个,至于其他的,等使用到的时候本喵会进行详细的介绍。

时钟系统的配置

库函数简介

STM官方提供的标准库中对时钟系统进行了配置,实质就是对上面提到的寄存器的每一位进行了配置。我们每次在使用的时候直接调用库函数就可以,不需要一步一步进行配置。
图
这些函数就是对RCC进行配置的,在官方提供的库函数stm32f10x_rcc.h中可以看到。
主要分为7类库函数:

  1. 时钟使能配置:
RCC_LSEConfig()RCC_HSEConfig()RCC_HSICmd()RCC_LSICmd()RCC_PLLCmd() ……

这些函数是用来使能时钟源的,在使用哪个时钟源之前,需要对其使能,相当于打开该时钟源。

  1. 时钟源相关配置:
RCC_PLLConfig ()RCC_SYSCLKConfig()RCC_RTCCLKConfig()

这些函数是用来配置时钟源的一些参数的。

  1. 分频系数选择配置:
RCC_HCLKConfig()RCC_PCLK1Config()RCC_PCLK2Config()

这些函数是用来配置分频系数的。

  1. 外设时钟使能:
RCC_APB1PeriphClockCmd():  //APB1线上外设时钟使能
RCC_APB2PeriphClockCmd();  //APB2线上外设时钟使能
RCC_AHBPeriphClockCmd();   //AHB线上外设时钟使能

这些函数是用来对外设进行使能的,注意对外设的使能和对时钟源的使能是有区别的。

  1. 其他外设时钟配置:
RCC_ADCCLKConfig ()RCC_RTCCLKConfig()...

这些函数是用来配置其他外设的,比如ADC,RTC等。

  1. 状态参数获取参数:
RCC_GetClocksFreq()RCC_GetSYSCLKSource()RCC_GetFlagStatus()...

这些函数是用来获于与时钟相关的数据的。
7. RCC中断相关函数 :

RCC_ITConfig()RCC_GetITStatus()RCC_ClearITPendingBit()

这些函数是用来实现与RCC有关的中断的。
以上仅列举了一部分库函数,大致分类介绍了一下它们的作用。

时钟初始化函数分析

在stm32f10x.h中创建了一个结构体:

typedef struct
{
  __IO uint32_t CR;
  __IO uint32_t CFGR;
  __IO uint32_t CIR;
  __IO uint32_t APB2RSTR;
  __IO uint32_t APB1RSTR;
  __IO uint32_t AHBENR;
  __IO uint32_t APB2ENR;
  __IO uint32_t APB1ENR;
  __IO uint32_t BDCR;
  __IO uint32_t CSR;
} RCC_TypeDef;

将RCC中的各个寄存器放在这个结构体用来进行寄存器地址名的映射,这几个寄存器中就有上面提到的常用的五个寄存器(包括与使能相关的三个寄存器,以及与控制和状态有关的两个寄存器)。关于寄存器地址名的映射,本喵在前面专门有篇文章讲解。
开始讲解函数:

extern void SystemInit(void);

这就是时钟初始化函数,在system_stm32f10x.h中声明,在system_stm32f10x.c中进行了定义。

void SystemInit (void)
{
    
  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001;

  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
  RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
  RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */ 
  
  /* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

  /* Reset HSEBYP bit */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

  /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
  RCC->CFGR &= (uint32_t)0xFF80FFFF;

#ifdef STM32F10X_CL
  /* Reset PLL2ON and PLL3ON bits */
  RCC->CR &= (uint32_t)0xEBFFFFFF;

  /* Disable all interrupts and clear pending bits */
  RCC->CIR = 0x00FF0000;

  /* Reset CFGR2 register */
  RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
  /* Disable all interrupts and clear pending bits */
  RCC->CIR = 0x009F0000;

  /* Reset CFGR2 register */
  RCC->CFGR2 = 0x00000000;      
#else
  /* Disable all interrupts and clear pending bits */
  RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
    
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
  #ifdef DATA_IN_ExtSRAM
    SystemInit_ExtMemCtl(); 
  #endif /* DATA_IN_ExtSRAM */
#endif 

  /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
  /* 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 
}

这是它的全部代码,接下来本喵拆开来分析一下。
1.

  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001;

该语句将时钟控制寄存器(RCC_CR)中的第0位置1,将HSI时钟源打开。
2.

#ifndef STM32F10X_CL
  RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
  RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */ 

本喵的开发板是高容量的,所以不执行第一条语句,直接执行第二条语句,该语句的作用是将时钟配置寄存器(RCC_CFGR)中的 SW, HPRE, PPRE1, PPRE2, ADCPRE 和 MCO 相关的位全部清0。
3.

  /* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

该语句的作用是将钟控制寄存器(RCC_CR)中的第16位,19位,24位清0,
将HSE关闭,将安全时钟关闭,将PLL关闭。
4.

  /* Reset HSEBYP bit */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

该语句的作用是将钟控制寄存器(RCC_CR)中的第18位清0,选择没有外部高速时钟旁路。
5.

/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
  RCC->CFGR &= (uint32_t)0xFF80FFFF;

该语句是将时钟配置寄存器(RCC_CFGR)中的PLLSRC, PLLXTPRE, PLLMUL 和 USBPRE/OTGFSPRE清0。
6.

/* Disable all interrupts and clear pending bits */
 RCC->CIR = 0x009F0000;

这是将与时钟相关的中断全部关闭。
以上是RCC寄存器开始时的通用配置,不是重点关注的地方。
接下来调用了一个一个函数:

SetSysClock();

该还是才是时钟配置中的重中之重。

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

这里有一个判断,是根据系统的频率进入到不同的函数。

#define SYSCLK_FREQ_72MHz 72000000

这里是系统的频率我用户自己定义的,本喵使用的是72MHZ的频率,如果不用这个频率,只需要修改define定义的值即可。
根据72MHZ进入到SetSysClockTo72()函数:

static void SetSysClockTo72(void)
{
    
 __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
 
 /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/    
 /* Enable HSE */    
 RCC->CR |= ((uint32_t)RCC_CR_HSEON);

 /* Wait till HSE is ready and if Time out is reached exit */
 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;
 }  

 if (HSEStatus == (uint32_t)0x01)
 {
    
   /* Enable Prefetch Buffer */
   FLASH->ACR |= FLASH_ACR_PRFTBE;

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


   /* HCLK = SYSCLK */
   RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
     
   /* PCLK2 = HCLK */
   RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
   
   /* PCLK1 = HCLK */
   RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

#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 configuration: 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 */

   /* Enable PLL */
   RCC->CR |= RCC_CR_PLLON;

   /* Wait till PLL is ready */
   while((RCC->CR & RCC_CR_PLLRDY) == 0)
   {
    
   }
   
   /* Select PLL as system clock source */
   RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
   RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

   /* Wait till PLL is used as system clock source */
   while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
   {
    
   }
 }
 else
 {
     /* If HSE fails to start-up, the application will have wrong clock configuration. User can add here some code to deal with this error */
 }
}

这是它整个函数的代码。
1.

#define RCC_CR_HSEON ((uint32_t)0x00010000) 
RCC->CR |= ((uint32_t)RCC_CR_HSEON);

该语句的作用是将时钟控制寄存器(RCC_CR) 的第16位置1
图
将HSE时钟源使能。
2.

#define RCC_CR_HSERDY ((uint32_t)0x00020000)
#define HSE_STARTUP_TIMEOUT ((uint16_t)0x0500)
  do
  {
    
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

这里是在读取时钟控制寄存器(RCC_CR) 的第17位。

图
当这一位是0的时候便会一直循环,等待时钟源稳定,当时钟源稳定后,这一位便会被置1,然后开始继续往下执行程序。
3.

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)
  {
    
    HSEStatus = (uint32_t)0x01;
  }
  else
  {
    
    HSEStatus = (uint32_t)0x00;
  }  

这里是判断时钟源是否稳定,当稳定后就给HSEStatus变量赋值1
4.

   FLASH->ACR |= FLASH_ACR_PRFTBE;

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

这里是在闪存FLASH的相关操作。因为CPU执行的速度比较快,所以在闪存中需要设置使CPU进行一定的等待时间。
与闪存相关的内容在后面涉及到的时候再进行详细介绍,这里仅需要知道这部分代码是什么作用即可。
5.

	#define RCC_CFGR_HPRE_DIV1 ((uint32_t)0x00000000)
	#define RCC_CFGR_PPRE2_DIV1 ((uint32_t)0x00000000)
	#define RCC_CFGR_PPRE1_DIV2 ((uint32_t)0x00000400)
	
	/* 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;

这里是将时钟配置寄存器(RCC_CFGR) 中的AHB和APB2设置为不分频,也就是72MHZ,将APB1设置为2分频,也就是36MHZ。
图
图
图
6.

	#define RCC_CFGR_PLLSRC ((uint32_t)0x00010000)
	#define RCC_CFGR_PLLXTPRE ((uint32_t)0x00020000)
	#define RCC_CFGR_PLLMULL ((uint32_t)0x003C0000)
	
	/* PLL configuration: 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);

这里的代码是将PLL设置为9倍频输出,将HSE作为PLL的输入时钟,将HSE输入PLL时设置为2分频
图
图
图
此时PLL输出的频率就是8*9=72MHZ。
7.

/* Enable PLL */
RCC->CR |= RCC_CR_PLLON;

该语句是将时钟控制寄存器(RCC_CR)的第24为置1
图
将PLL时钟源使能,也就是打开该时钟。
8.

    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    
    }

这里是等待PLL时钟源稳定,待稳定后继续执行下面的程序。
9.

    #define RCC_CFGR_SW ((uint32_t)0x00000003)
    #define RCC_CFGR_SW_PLL ((uint32_t)0x00000002)
    
    /* Select PLL as system clock source */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;   

这里是将时钟配置寄存器(RCC_CFGR)中的位0和1先清零,再置为10
图
将HSE作为系统时钟。
10.

    #define RCC_CFGR_SWS ((uint32_t)0x0000000C)
    
    /* Wait till PLL is used as system clock source */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    
    }

该语句是在等待硬件做出指示,当检测到的结果是0x08的时候,说明此时PLL作为系统时钟。
图

初始化过程(图示)

图
整个时钟初始化的过程是按照上图中红色框框标号的顺序进行的。

总结

时钟的初始化是一个很复杂的过程,如果每次使用STM32的时候都挨个设置寄存器会很耗时间,而这个过程STM官方将其封装成了库函数,且不需要我们自己去调用,它在启动文件中自动的进行调用。如果在使用的时候,系统的频率或者设置有自己特殊的需求,可以对时钟初始化函数进行修改。

原网站

版权声明
本文为[一只大喵咪1201]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_63726869/article/details/124968207