STM32-时钟系统

1  时钟的定义

        时钟是单片机运行的基础,时钟信号推动单片机内各个部分执行相应的指令。时钟系统就是CPU的脉搏,决定cpu速率,像人的心跳一样 只有有了心跳,人才能做其他的事情,而单片机有了时钟,才能够运行执行指令,才能够做其他的处理 (点灯,串口,ADC),时钟的重要性不言而喻。      

        为什么 STM32 要有多个时钟源呢?  

        STM32本身十分复杂,外设非常多  但我们实际使用的时候只会用到有限的几个外设,使用任何外设都需要时钟才能启动,但并不是所有的外设都需要系统时钟那么高的频率,为了兼容不同速度的设备,有些高速,有些低速,如果都用高速时钟,势必造成浪费   并且,同一个电路,时钟越快功耗越快,同时抗电磁干扰能力也就越弱,所以较为复杂的MCU都是采用多时钟源的方法来解决这些问题。所以便有了STM32的时钟系统和时钟树。

        总结:
        1)STM32时钟系统主要的目的就是给相对独立的外设模块提供时钟,也是为了降低整个芯片的耗能
        2)系统时钟,是处理器运行时间基准(每一条机器指令一个时钟周期)
        3)时钟是单片机运行的基础,时钟信号推动单片机内各个部分执行相应的指令。
        4)一个单片机内提供多个不同的系统时钟,可以适应更多的应用场合。
        5)不同的功能模块会有不同的时钟上限,因此提供不同的时钟,也能在一个单片机内放置更多的功能模块。对不同模块的时钟增加开启和关闭功能,可以降低单片机的功耗。
        6)STM32为了低功耗,他将所有的外设时钟都设置为disable(不使能),用到什么外设,只要打开对应外设的时钟就可以, 其他的没用到的可以还是disable(不使能),这样耗能就会减少。  这就是为什么不管你配置什么功能都需要先打开对应的时钟的原因

2  STM32F1时钟系统框图

         乍一看很吓人,但其实很好理解,我们看系统时钟SYSCLK 的左边,系统时钟有很多种选择,而左边的部分就是设置系统时钟使用那个时钟源,系统时钟SYSCLK 的右边,则是系统时钟通过AHB预分频器,给相对应的外设设置相对应的时钟频率。
        从左到右可以简单理解为:各个时钟源--->系统时钟来源的设置--->各个外设时钟的设置。

2.1  时钟系统各个时钟源(左边的部分)

        STM32 有4个独立时钟源:HSI、HSE、LSI、LSE。
                ①  HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。
                ②  HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz
                ③  LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。  
                ④  LSE是低速外部时钟,接频率为32.768kHz的石英晶体。
        其中LSI是作为IWDGCLK(独立看门狗)时钟源和RTC时钟源而独立使用 
        而HSI高速内部时钟、HSE高速外部时钟、PLL锁相环时钟这三个时钟经过分频或者倍频可作为系统时钟来使用
        PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。  通过倍频之后作为系统时钟的时钟源

        举个例子:Keil编写程序是默认的时钟为72Mhz,其实是这么来的:外部晶振(HSE)提供的8MHz(与电路板上的晶振的相关)通过PLLXTPRE分频器后,进入PLLSRC选择开关,进而通过PLLMUL锁相环进行倍频(x9)后,为系统提供72MHz的系统时钟(SYSCLK)。之后是AHB预分频器对时钟信号进行分频,然后为低速外设提供时钟。或者内部RC振荡器(HSI) 为8MHz/2为4MHz 进入PLLSRC选择开关,通过PLLMUL锁相环进行倍频(x18)后为72MHz。

2.2  系统时钟SYSCLK

        系统时钟SYSCLK可来源于三个时钟源:
                ①HSI振荡器时钟
                ②HSE振荡器时钟
                ③PLL时钟
        系统时钟SYSCLK最大为72Mhz

2.3  USB时钟

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

2.4  把时钟信号输出到外部

        STM32可以选择一个时钟信号输出到MCO脚(PA8)上,可以选择为PLL输出的2分频、HSI、HSE、或者系统时钟。可以把时钟信号输出供外部使用

2.5  系统时钟通过AHB分频器给外设提供时钟(右边的部分)  重点

        从左到右可以简单理解为:系统时钟--->   AHB分频器--->   各个外设分频倍频器--->  外设时钟的设置
        右边部分为:系统时钟SYSCLK通过AHB分频器分频后送给各模块使用,AHB分频器可选择1、2、4、8、16、64、128、256、512分频。其中AHB分频器输出的时钟送给5大模块使用: 
         ①内核总线:送给AHB总线、内核、内存和DMA使用的HCLK时钟。 
         ②Tick定时器:通过8分频后送给Cortex的系统定时器时钟。 
         ③I2S总线:直接送给Cortex的空闲运行时钟FCLK。 
         ④APB1外设:送给APB1分频器。APB1分频器可选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给通用定时器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2-7使用。 
         ⑤APB2外设:送给APB2分频器。APB2分频器可选择1、2、4、8、16分频,其输出一路供APB2外设使用(PCLK2,最大频率72MHz),另一路送给高级定时器。该倍频器可选择1或者2倍频,时钟输出供定时器1和定时器8使用。 
        另外,APB2分频器还有一路输出供ADC分频器使用,分频后送给ADC模块使用。ADC分频器可选择为2、4、6、8分频。 
        需要注意的是,如果 APB 预分频器分频系数是 1,则定时器时钟频率 (TIMxCLK) 为 PCLKx。否则,定      时器时钟频率将为 APB 域的频率的两倍:TIMxCLK = 2xPCLKx。 

2.6  APB1和APB2的对应外设

        F1系列

        APB1上面连接的是低速外设,包括电源接口、备份接口、CAN、USB、I2C1、I2C2、USART2、USART3、UART4、UART5、SPI2、SP3等;
        APB2上面连接的是高速外设,包括UART1、SPI1、Timer1、ADC1、ADC2、ADC3、所有的普通I/O口(PA-PE)、第二功能I/O(AFIO)口等。

        F4系列

        这个和F1系列类似,我们就举几个特殊的
        APB2总线:高级定时器timer1, timer8以及通用定时器timer9, timer10, timer11   UTART1,USART
        APB1总线:通用定时器timer2~timer5,通用定时器timer12~timer14以及基本定时器timer6,timer7  UTART2~UTART5
        F4系列的系统时钟频率最高能到168M
        具体可以在stm32f10x_rcc.h和stm32f40x_rcc.h中查看,或者通过 STM32参考手册搜索“系统架构”或者“系统结构”  查看外设挂在哪个时钟下

2.7  时钟监视系统(CSS)

        STM32还提供了一个时钟监视系统(CSS),用于监视高速外部时钟(HSE)的工作状态。倘若HSE失效,会自动切换(高速内部时钟)HSI作为系统时钟的输入,保证系统的正常运行。

3  STM32F1RCC相关寄存器:

        RCC 寄存器结构,RCC_TypeDef,在文件“stm32f10x.h”中定义如下:

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;

        可以对上上面的时钟框图和RCC寄存器来学习,对STM32的时钟系统有个大概的了解   其实也就是我们上面介绍的流程,理解了自然也就能写出来

3.1  RCC初始化

        我们使用HSE时钟,正常使用的时候也都是使用外部时钟,程序设置时钟参数流程:

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

3.2  代码实现

        对RCC的配置函数(使用外部8MHz晶振) :系统时钟72MHz,APH 72MHz,APB2 72MHz,APB1 32MHz,USB 48MHz TIMCLK=72M
        下面是STM32软件固件库的程序中对RCC的配置函数(使用外部8MHz晶振)

void RCC_Configuration(void)
{
	//----------使用外部RC晶振-----------
	RCC_DeInit();			//初始化为缺省值
	RCC_HSEConfig(RCC_HSE_ON);	//使能外部的高速时钟 
	while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);	//等待外部高速时钟使能就绪
	
	FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);	//Enable Prefetch Buffer
	FLASH_SetLatency(FLASH_Latency_2);		//Flash 2 wait state
	
	RCC_HCLKConfig(RCC_SYSCLK_Div1);		//HCLK = SYSCLK
	RCC_PCLK2Config(RCC_HCLK_Div1);			//PCLK2 =  HCLK
	RCC_PCLK1Config(RCC_HCLK_Div2);			//PCLK1 = HCLK/2
	RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);	//PLLCLK = 8MHZ * 9 =72MHZ
	RCC_PLLCmd(ENABLE);			//Enable PLLCLK
 
	while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);	//Wait till PLLCLK is ready
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);	//Select PLL as system clock
	while(RCC_GetSYSCLKSource()!=0x08);		//Wait till PLL is used as system clock source
	
	//---------打开相应外设时钟--------------------
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);	//使能APB2外设的GPIOA的时钟		 
}

        也就是我们时钟树框图从左到右的配置

4  STM32F1系统时钟详解&&移植

4.1  对系统时钟的设置的相关函数

        高人指点,我看了一下启动文件startup_stm32f10x_hd.s,其中有一段汇编:

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

        其中,IMPORT  __mainIMPORT  SystemInit语句:IMPORT 声明了需要引用C语言中的main函数、systeminit函数;
                LDR R0, = systeminint语句:把systeminit 函数地址放到r0 寄存器;
                BLX   R0语句:跳到到R0 寄存器中的地址执行;
        所以,使用库函数的时候,在系统启动之后会自动调用SystemInit()。在跳到main函数执行前,已经在SystemInit 函数中把系统时钟给设置好了;

4.2  启动外部晶振的操作

        STM32时钟系统的配置除了初始化的时候在system_stm32f10x.c中的SystemInit()函数中外,其他的配置主要在stm32f10x_rcc.c文件中,需要对这个文件好好研究一下。本文主要看一下初始化时的SystemInit函数,先到system_stm32f10x.h文件下的SystemInit()函数里面做更深入的了解:

/**
  * @摘要   设置微控制器系统,初始化嵌入式Flash接口,PLL和更新SystemCoreClock变量。
  * @注释   此功能只能在重置后使用。
  * @参数   无
  * @返回值 无
  */
void SystemInit (void)
{
  /* 将RCC时钟配置重置为默认的重置状态(用于调试) */
  /* 设置HSION位 */
  RCC->CR |= (uint32_t)0x00000001;

  /* 复位SW、HPRE、PPRE1、PPRE2、ADCPRE、MCO位 */
#ifndef STM32F10X_CL
  RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
  RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */   
  
  /* 重置HSEON, CSSON和PLLON位 */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

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

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

#ifdef STM32F10X_CL
  /* 复位PLL2ON和PLL3ON位 */
  RCC->CR &= (uint32_t)0xEBFFFFFF;

  /* 禁用所有中断并清除挂起位  */
  RCC->CIR = 0x00FF0000;

  /* 复位CFGR2寄存器 */
  RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
  /* 禁用所有中断并清除挂起位  */
  RCC->CIR = 0x009F0000;

  /* 复位CFGR2寄存器 */
  RCC->CFGR2 = 0x00000000;      
#else
  /* 禁用所有中断并清除挂起位  */
  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 

  /* 配置the System clock frequency, HCLK, PCLK2和PCLK1预分频器 */
  /* 配置Flash Latency周期并开启预取缓冲区*/
  SetSysClock();

#ifdef VECT_TAB_SRAM
  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* 在内部SRAM中的向量表重定位。 */
#else
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* 在内部FLASH中的矢量表重定位。 */
#endif 
}

        SystemInit()函数主要就是完成系统初始化时的默认状态的设置,同时系统时钟默认是在SetSysClock()函数中来进行判断的,而判断的依据则是通过宏定义设置的。先看看SetSysClocks()函数。我们进入system_stm32f10x.h文件下的SetSysClock()函数,看其中作了哪些操作:

/**
  * @摘要   配置“System clock frequency”、“HCLK”、“PCLK2”和“PCLK1”预分频器。
  * @参数   无
  * @返回值 无
  */
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
 
 /* 如果上面的定义都没有启用,则使用HSI作为系统时钟源(重置后默认) */ 
}

        这段代码很简单,就是判断系统宏定义的时钟是多少,然后设置相应值。系统默认的宏定义是72HMz。我项目中用的是此款芯片STM32f103ZET6,内部晶振是72MHz, 所以应进入执行system_stm32f10x.h文件SetSysClockTo72() 函数:

#elif defined SYSCLK_FREQ_72MHz
/**
  * @摘要   设置“系统时钟频率”为72MHz,并配置“HCLK”、“PCLK2” 和PCLK1预分频器。
  * @注释   此功能只能在重置后使用。
  * @参数   无
  * @返回值 无
  */
static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* SYSCLK, HCLK, PCLK2和PCLK1配置---------------------------*/    
  /* 使能HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* 等待HSE准备就绪,如果达到“Time out”,则退出 */
  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)
  {
    /* 使预取缓冲器 */
    FLASH->ACR |= FLASH_ACR_PRFTBE;

    /* Flash 2等待状态 */
    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
    /* 配置锁相环 ------------------------------------------------------*/
    /* PLL2配置: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
    /* PREDIV1配置: 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);
  
    /* 使能PLL2 */
    RCC->CR |= RCC_CR_PLL2ON;
    /* 等待PLL2准备好 */
    while((RCC->CR & RCC_CR_PLL2RDY) == 0)
    {
    }
    
   
    /* 锁相环配置: 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    
    /*  锁相环配置: 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;    

    /* 等待,直到使用锁相环作为系统时钟源 */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
  }
  else
  { /* 如果HSE启动失败,应用程序时钟错误配置。用户可以在这里添加一些代码来处理这个错误 */
  }
}
#endif

        解析:void SetSysClockTo72()函数中主要做了下面几件事:(不同颜色功能分别对应上面对应颜色代码部分)
                1.启动外部晶振作为系统时钟源;
                2.等待外部晶振起振;
                3.(起振后)对系统时钟源进行分频:(默认外部是HSE--8MHZ晶振)  PLL configuration: PLLCLK = HSE * 9 = 72 MHz
                4. 配置HCLK(72MHz)、PCLK1(36MHz)、PCLK2(72MHz) 与系统时钟源的关系;

        同时,在设置好系统时钟之后,可以通过变量SystemCoreClock来获取系统时钟值。这也是在stm32f10x.c文件中设置的:

#ifdef SYSCLK_FREQ_HSE
  uint32_t SystemCoreClock         = SYSCLK_FREQ_HSE;          /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_24MHz
  uint32_t SystemCoreClock         = SYSCLK_FREQ_24MHz;        /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_36MHz
  uint32_t SystemCoreClock         = SYSCLK_FREQ_36MHz;        /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_48MHz
  uint32_t SystemCoreClock         = SYSCLK_FREQ_48MHz;        /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_56MHz
  uint32_t SystemCoreClock         = SYSCLK_FREQ_56MHz;        /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_72MHz
  uint32_t SystemCoreClock         = SYSCLK_FREQ_72MHz;        /*!< System Clock Frequency (Core Clock) */
#else /*!< HSI Selected as System Clock source */
  uint32_t SystemCoreClock         = HSI_VALUE;                /*!< System Clock Frequency (Core Clock) */
#endif

4.3  串口时钟源跟系统时钟的关系

        回答这个问题前,我们先来看STM32f103ZET6芯片手册数据图:

         从图里面我们可看出串口1的时钟源是PCLK1;上面4.2小结的代码中我们看到PCLK1是时钟源72MHz的1分频,即还是72MHz。

4.4    STM32宏定义和更换外部晶振以及代码的修改

        STM32宏定义可以在KEIL下可以在项目的选项C/C++/PREPROMCESSOR symbols的Define栏里定义,比如STM32F10X_CL
        也可以在STM32F10X.H里用宏定义:

#if !defined (STM32F10X_LD) && !defined (STM32F10X_LD_VL) && !defined (STM32F10X_MD) && !defined (STM32F10X_MD_VL) && !defined (STM32F10X_HD) && !defined (STM32F10X_HD_VL) && !defined (STM32F10X_XL) && !defined (STM32F10X_CL) 
  /* #define STM32F10X_LD    */     /*!< STM32F10X_LD: STM32 Low density devices */
  /* #define STM32F10X_LD_VL */     /*!< STM32F10X_LD_VL: STM32 Low density Value Line devices */  
  /* #define STM32F10X_MD    */     /*!< STM32F10X_MD: STM32 Medium density devices */
  /* #define STM32F10X_MD_VL */     /*!< STM32F10X_MD_VL: STM32 Medium density Value Line devices */  
  /* #define STM32F10X_HD    */     /*!< STM32F10X_HD: STM32 High density devices */
  /* #define STM32F10X_HD_VL */     /*!< STM32F10X_HD_VL: STM32 High density value line devices */  
  /* #define STM32F10X_XL    */     /*!< STM32F10X_XL: STM32 XL-density devices */
  /* #define STM32F10X_CL    */     /*!< STM32F10X_CL: STM32 Connectivity line devices */
#endif

        如果外部晶振更换,例如STM32用的是8M晶振。比如你想更换到为外部晶振为12M,但是主频仍想用72M的。该如何设置?或者想倍频到更高的主频该怎么修改?

属性原来现在
外部晶振8M12M
倍频96
主频72M72M

        想从原来的8M修改到现在的12M,但是主频仍为72M,该如何修改:

        (1)【设置外部晶振】打开stm32f10x.h,找到下图部分:

         修改之前如上图一样,HSE_VALUE为外部晶振,如果你用的是f103的芯片,则修改下边这个为12M;

         (2)【设置主频】打开system_stm32f10x.c,找到下图部分:

         也可以根据自己的需要修改为56M、48M等;

         (3)【设置倍频】打开system_stm32f10x.c,找到自己的想要的主频函数:

123

         再找到这里(默认的情况下):

         ①修改F103(外部晶振12M倍频到72M):

         ②修改F105、F107(外部晶振12M倍频到72M):

         还有一个是F103倍频到128M的教程。单片机超频太多不稳定,建议先更换更快的晶振,在进行倍频到128M。

5  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

        配置过程如下图所示: 

6  STM32F429系统时钟的初始化寄存器源码分析

         在系统进入主函数之前,首先会执行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

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

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

        14)//配置主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外部震荡时钟

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

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

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

        17)设置FLASH寄存器参数

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

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

        19)等待主PLL设置为系统主时钟成功:while((RCC->CFGR&(3<<2))!=(2<<2));
                语句含义:当CFGR的3:2位不等于01时就说明主PLL还未就绪,就绪等待。

        20)最后一步,配置向量表。

                通过这样的设置系统时钟的整体配置就OK了。
                如果调用函数:u8 Sys_Clock_Set(u32 plln,u32 pllm,u32 pllp,u32 pllq),产生180Mhz的主频
                设置参数:外部晶振为25M的时候:plln=360,pllm=25,pllp=2,pllq=8.

  • 24
    点赞
  • 96
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
STM32系统时钟是指控制STM32芯片内部各个模块和外设运行的时钟信号。STM32时钟系统相对复杂,对于初学者来说可能会感到困惑。时钟系统的重要性不言而喻,它类似于人体的心脏脉搏,是整个系统的基础。\[1\] 为了更好地理解STM32时钟系统,初学者可以参考开发板的源码进行设置,但这种方法可能会导致在遇到问题或需要修改代码时感到困惑。要深入理解STM32时钟系统,以便进行底层驱动设计,需要对其有更深入的了解。\[2\] 在配置STM32时钟系统时,可以根据实际情况进行自定义设置。一般情况下,可以以大容量芯片72MHz的系统时钟为例进行讲解,原理都是一样的。\[3\] 总之,STM32系统时钟是控制芯片内部各个模块和外设运行的关键信号,对于深入理解和设计底层驱动非常重要。 #### 引用[.reference_title] - *1* *3* [STM32-时钟系统详解](https://blog.csdn.net/qq_44016222/article/details/123223733)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [【STM32】初学者必读STM32时钟系统详解](https://blog.csdn.net/m0_54916619/article/details/130182697)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值