STM32F103学习笔记四 时钟系统

STM32F103学习笔记四 时钟系统

本文简述了自己学习时钟系统的一些框架,参照风水月

1. 单片机中时钟系统的理解

1.1 概述

  • 时钟是单片机的脉搏,是单片机的驱动源
  • 用任何一个外设都必须打开相应的时钟
  • 不使用一个外设的时候,把它的时钟关掉,从而可以降低系统的功耗,达到低功耗的效果
  • 每个时钟打开,系统都会处理一步数据,这样才能让工作不出现紊乱

1.2 分类

时钟发生器用于产生时钟,并提供给CPU和外部硬件设备。
有如下三种系统时钟:

(1)主系统时钟
①通过连接一个振荡器到X1和X2,该振荡电路产生fx=1到20MHZ的时钟;
②使用内部高速振荡器产生fRH=8MHZ的时钟。
(2)副系统时钟
①通过在XT1和XT2之间连接一个fXT=32.768KHZ的振荡器;
②通过XT2引脚提供一个外部副系统时钟fexclks=32.768KHZ。
(3)内部低速振荡时钟(看门狗定时器时钟)
①内部低速振荡器,以fRL=240KHZ的时钟振荡。该时钟不能作为CPU时钟。

2. STM32时钟介绍

2.1、STM32里的时钟分类

  1. HSI是高速内部时钟,RC振荡器,频率为8MHz
  2. HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz
  3. LSI是低速内部时钟,RC振荡器,频率为40kHz。(WDG看门狗 使用该时钟源)
  4. LSE是低速外部时钟,接频率为32.768kHz的石英晶体。(RTC实时时钟 使用该时钟源)
  5. PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz
    其中HSE和LSE是通过单片机外部的晶振输入的,一共四个管脚,HSE的输入管脚是OSC_IN和OSC_OUT(通常为8M),
    LSE的输入管脚对应的引脚为OSC32_IN和OSC32_OUT(32.768kHz)

2.2、STM32的时钟系统框图

*- SYSCLK(系统时钟)

  • AHB总线时钟
  • APB1总线时钟(低速),速度最高到36MHz
  • APB2总线时钟(高速),速度最高到72MHz
  • PLL时钟*
    STM32F103学习笔记四 时钟系统

(1)梯形表示SYSCLK系统时钟的来源可以使HSI RC、PLLCLK、HSE Osc(即HSI振荡器时钟、HSE振荡器时钟、PLL时钟)
STM32F103学习笔记四 时钟系统
(2)绿色方框 表示 预分频器(prescaler)

按照上图深红色 的路线来解释:PLL的时钟源 经过前面的选择器 假设为8MHz,经过PLL 9倍频后 PLLCLK的频率为72MHz,则经过选择器 SYSCLK(系统时钟)频率为72MHz,经过AHB分频器 1分频后 HCLK输出频率72MHz,经过APB1分频器 2分频后 PCLK1频率为36MHz; 经过APB2分频器 1分频后 PCLK2频率为72MHz。

STM32F103学习笔记四 时钟系统(3)时钟输出MCO脚(PA8),可以选择PLL输出的2分频、HSI、HSE或者系统时钟
STM32F103学习笔记四 时钟系统

2.3、 用HSE时钟,程序设置时钟参数流程:

01、将RCC寄存器重新设置为默认值 RCC_DeInit;
02、打开外部高速时钟晶振HSE RCC_HSEConfig(RCC_HSE_ON);
03、等待外部高速时钟晶振工作 HSEStartUpStatus = RCC_WaitForHSEStartUp();
04、设置AHB时钟 RCC_HCLKConfig;
05、设置高速AHB时钟 RCC_PCLK2Config;
06、设置低速速AHB时钟 RCC_PCLK1Config;
07、设置PLL RCC_PLLConfig;
08、打开PLL RCC_PLLCmd(ENABLE);
09、等待PLL工作 while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
10、设置系统时钟 RCC_SYSCLKConfig;
11、判断是否PLL是系统时钟 while(RCC_GetSYSCLKSource() != 0x08)
12、打开要使用的外设时钟 RCC_APB2PeriphClockCmd()/RCC_APB1PeriphClockCmd()

《STM32中文手册》中的时钟图

STM32F103学习笔记四 时钟系统

3. 时钟函数的设置

3.1、 RCC寄存器的结构体定义

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; //控制状态寄存器 
} RCC_TypeDef;

STM32F103学习笔记四 时钟系统

3.2、 RCC的配置函数(使用外部8MHz晶振)

/*******************************************************************************
* Function Name  : RCC_Configuration 
* Description    :  RCC配置(使用外部8MHz晶振)
* Input            : 无
* Output         : 无
* Return         : 无
*******************************************************************************/
void RCC_Configuration(void)
{
  /*将外设RCC寄存器重设为缺省值*/
  RCC_DeInit();
  /*设置外部高速晶振(HSE)*/
  RCC_HSEConfig(RCC_HSE_ON);   //RCC_HSE_ON——HSE晶振打开(ON)
  /*等待HSE起振*/
  HSEStartUpStatus = RCC_WaitForHSEStartUp();
  if(HSEStartUpStatus == SUCCESS)        //SUCCESS:HSE晶振稳定且就绪
  {
    /*设置AHB时钟(HCLK)*/ 
    RCC_HCLKConfig(RCC_SYSCLK_Div1);  //RCC_SYSCLK_Div1——AHB时钟= 系统时钟
    /* 设置高速AHB时钟(PCLK2)*/ 
    RCC_PCLK2Config(RCC_HCLK_Div1);   //RCC_HCLK_Div1——APB2时钟= HCLK
    /*设置低速AHB时钟(PCLK1)*/    
RCC_PCLK1Config(RCC_HCLK_Div2);   //RCC_HCLK_Div2——APB1时钟= HCLK / 2
    /*设置FLASH存储器延时时钟周期数*/
    FLASH_SetLatency(FLASH_Latency_2);    //FLASH_Latency_2  2延时周期
 /*选择FLASH预取指缓存的模式*/  
    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);       // 预取指缓存使能
    /*设置PLL时钟源及倍频系数*/ 
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);     
// PLL的输入时钟= HSE时钟频率;RCC_PLLMul_9——PLL输入时钟x 9
  /*使能PLL */
    RCC_PLLCmd(ENABLE); 
    /*检查指定的RCC标志位(PLL准备好标志)设置与否*/   
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)      
       {
       }
 
    /*设置系统时钟(SYSCLK)*/ 
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); 
//RCC_SYSCLKSource_PLLCLK——选择PLL作为系统时钟
 
    /* PLL返回用作系统时钟的时钟源*/
    while(RCC_GetSYSCLKSource() != 0x08)        //0x08:PLL作为系统时钟
       { 
       }
}
 /*使能或者失能APB2外设时钟*/    
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | 
RCC_APB2Periph_GPIOC , ENABLE); 
//RCC_APB2Periph_GPIOA    GPIOA时钟
//RCC_APB2Periph_GPIOB    GPIOB时钟
//RCC_APB2Periph_GPIOC    GPIOC时钟
//RCC_APB2Periph_GPIOD    GPIOD时钟
}


上面了解了 stm32默认给我们配置的是 什么样的时钟源,若我们想自定义,则需要借助系统库函数来进行修改,则我需要用到 stm32f10x_rcc.h里定义的库函数

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

3.3、 系统时钟初始化

上面仅仅是对 时钟控制器 做了一个概念性的介绍,在编写程序时我们一般不直接操作 RCC_TypeDef结构体,而是调用相应的库函数来编程。
在此我们先看一下 库函数中 系统默认如何初始化这些 寄存器的,我们先看一段汇编程序,我们知道c中默认的程序入口是main函数,那main函数的如何调用的呢,我们先来看如下程序(该程序在startup_stm32f10x_hd.s中可以找到):

#ifdef  STM32F10X_CL
  uint32_t prediv1source = 0, prediv1factor = 0, prediv2factor = 0, pll2mull = 0;
#endif /* STM32F10X_CL */

#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
  uint32_t prediv1factor = 0;
#endif /* STM32F10X_LD_VL or STM32F10X_MD_VL or STM32F10X_HD_VL */
    
  /* Get SYSCLK source -------------------------------------------------------*/
  tmp = RCC->CFGR & RCC_CFGR_SWS;
  
  switch (tmp)
  {
    case 0x00:  /* HSI used as system clock */
      SystemCoreClock = HSI_VALUE;
      break;
    case 0x04:  /* HSE used as system clock */
      SystemCoreClock = HSE_VALUE;
      break;
    case 0x08:  /* PLL used as system clock */

      /* Get PLL clock source and multiplication factor ----------------------*/
      pllmull = RCC->CFGR & RCC_CFGR_PLLMULL;
      pllsource = RCC->CFGR & RCC_CFGR_PLLSRC;
      
#ifndef STM32F10X_CL      
      pllmull = ( pllmull >> 18) + 2;
      
      if (pllsource == 0x00)
      {
        /* HSI oscillator clock divided by 2 selected as PLL clock entry */
        SystemCoreClock = (HSI_VALUE >> 1) * pllmull;
      }
      else
      {
 #if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
       prediv1factor = (RCC->CFGR2 & RCC_CFGR2_PREDIV1) + 1;
       /* HSE oscillator clock selected as PREDIV1 clock entry */
       SystemCoreClock = (HSE_VALUE / prediv1factor) * pllmull; 
 #else
        /* HSE selected as PLL clock entry */
        if ((RCC->CFGR & RCC_CFGR_PLLXTPRE) != (uint32_t)RESET)
        {/* HSE oscillator clock divided by 2 */
          SystemCoreClock = (HSE_VALUE >> 1) * pllmull;
        }
        else
        {
          SystemCoreClock = HSE_VALUE * pllmull;
        }
 #endif
      }
#else
      pllmull = pllmull >> 18;
      
      if (pllmull != 0x0D)
      {
         pllmull += 2;
      }
      else
      { /* PLL multiplication factor = PLL input clock * 6.5 */
        pllmull = 13 / 2; 
      }
            
      if (pllsource == 0x00)
      {
        /* HSI oscillator clock divided by 2 selected as PLL clock entry */
        SystemCoreClock = (HSI_VALUE >> 1) * pllmull;
      }
      else
      {/* PREDIV1 selected as PLL clock entry */
        
        /* Get PREDIV1 clock source and division factor */
        prediv1source = RCC->CFGR2 & RCC_CFGR2_PREDIV1SRC;
        prediv1factor = (RCC->CFGR2 & RCC_CFGR2_PREDIV1) + 1;
        
        if (prediv1source == 0)
        { 
          /* HSE oscillator clock selected as PREDIV1 clock entry */
          SystemCoreClock = (HSE_VALUE / prediv1factor) * pllmull;          
        }
        else
        {/* PLL2 clock selected as PREDIV1 clock entry */
          
          /* Get PREDIV2 division factor and PLL2 multiplication factor */
          prediv2factor = ((RCC->CFGR2 & RCC_CFGR2_PREDIV2) >> 4) + 1;
          pll2mull = ((RCC->CFGR2 & RCC_CFGR2_PLL2MUL) >> 8 ) + 2; 
          SystemCoreClock = (((HSE_VALUE / prediv2factor) * pll2mull) / prediv1factor) * pllmull;                         
        }
      }
#endif /* STM32F10X_CL */ 
      break;

    default:
      SystemCoreClock = HSI_VALUE;
      break;
  }
  
  /* Compute HCLK clock frequency ----------------*/
  /* Get HCLK prescaler */
  tmp = AHBPrescTable[((RCC->CFGR & RCC_CFGR_HPRE) >> 4)];
  /* HCLK clock frequency */
  SystemCoreClock >>= tmp;  
}

/**
  * @brief  Configures the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers.
  * @param  None
  * @retval None
  */
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) */ 
}

(1)从上面代码逐行往下看,我们知道我们前面的程序 定义的宏定义是 STM32F10X_HD,如下图
STM32F103学习笔记四 时钟系统(2)从而在初始化器确定了系统时钟频率为(72MHz):‘#define SYSCLK_FREQ_72MHz 72000000’
初始化智慧的状态为:

SYSCLK 72MHz
AHB 72MHz
PCLK1 36MHz
PCLK2 72MHz
PLL 72MHz

(3)SystemInit()函数的第一行代码RCC->CR |= (uint32_t)0x00000001;我们查手册 CR寄存器第一位置1,表示开启8MHz内部振荡器,后续的代码阅读如该句,都需要参照手册 来理解,在此不再说明。
(4)初始化之后可以通过变量SystemCoreClock获取系统变量。如果SYSCLK=72MHz,那么变量SystemCoreClock=72000000。

3.3、时钟频率

STM32F103内部8M的内部震荡,经过倍频后最高可以达到72M。目前TI的M3系列芯片最高频率可以达到80M。
在stm32固件库3.0中对时钟频率的选择进行了大大的简化,原先的一大堆操作都在后台进行。系统给出的函数为SystemInit()。但在调用前还需要进行一些宏定义的设置,具体的设置在system_stm32f10x.c文件中。
文件开头就有一个这样的定义:

//#define SYSCLK_FREQ_HSE HSE_Value
//#define SYSCLK_FREQ_20MHz 20000000
//#define SYSCLK_FREQ_36MHz 36000000
//#define SYSCLK_FREQ_48MHz 48000000
//#define SYSCLK_FREQ_56MHz 56000000
#define SYSCLK_FREQ_72MHz 72000000

ST 官方推荐的外接晶振是 8M,所以库函数的设置都是假定你的硬件已经接了 8M 晶振来运算的.以上东西就是默认晶振 8M 的时候,推荐的 CPU 频率选择.在这里选择了:
#define SYSCLK_FREQ_72MHz 72000000
也就是103系列能跑到的最大值72M
然后这个 C文件继续往下看

#elif defined SYSCLK_FREQ_72MHz
const uint32_t SystemFrequency = SYSCLK_FREQ_72MHz;
const uint32_t SystemFrequency_SysClk = SYSCLK_FREQ_72MHz;
const uint32_t SystemFrequency_AHBClk = SYSCLK_FREQ_72MHz;
const uint32_t SystemFrequency_APB1Clk = (SYSCLK_FREQ_72MHz/2);
const uint32_t SystemFrequency_APB2Clk = SYSCLK_FREQ_72MHz;

这就是在定义了CPU跑72M的时候,各个系统的速度了.他们分别是:硬件频率,系统时钟,AHB总线频率,APB1总线频率,APB2总线频率.再往下看,看到这个了:

#elif defined SYSCLK_FREQ_72MHz
static void SetSysClockTo72(void);

这就是定义 72M 的时候,设置时钟的函数.这个函数被 SetSysClock ()函数调用,而
SetSysClock ()函数则是被 SystemInit()函数调用.最后 SystemInit()函数,就是被你调用的了
所以设置系统时钟的流程就是:

首先用户程序调用 SystemInit()函数,这是一个库函数,然后 SystemInit()函数里面,进行了一些寄存器必要的初始化后,就调用 SetSysClock()函数. SetSysClock()函数根据那个#define SYSCLK_FREQ_72MHz 72000000 的宏定义,知道了要调用SetSysClockTo72()这个函数,于是,就一堆麻烦而复杂的设置[email protected]#$%^然后,CPU跑起来了,而且速度是 72M. 虽然说的有点累赘,但大家只需要知道,用户要设置频率,程序中就做的就两个事情:
第一个: system_stm32f10x.c 中 #define SYSCLK_FREQ_72MHz 72000000
第二个:调用SystemInit()