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 BufferFLASH_SetLatency(FLASH_Latency_2);      //Flash 2 wait stateRCC_HCLKConfig(RCC_SYSCLK_Div1);        //HCLK = SYSCLKRCC_PCLK2Config(RCC_HCLK_Div1);         //PCLK2 =  HCLKRCC_PCLK1Config(RCC_HCLK_Div2);         //PCLK1 = HCLK/2RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);    //PLLCLK = 8MHZ * 9 =72MHZRCC_PLLCmd(ENABLE);         //Enable PLLCLKwhile(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);    //Wait till PLLCLK is readyRCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);   //Select PLL as system clockwhile(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   PROCEXPORT  Reset_Handler             [WEAK]IMPORT  __mainIMPORT  SystemInitLDR     R0, =SystemInitBLX     R0               LDR     R0, =__mainBX      R0ENDP

其中,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_CLRCC->CFGR &= (uint32_t)0xF8FF0000;
#elseRCC->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_ExtSRAMSystemInit_ExtMemCtl(); #endif /* DATA_IN_ExtSRAM */
#endif /* 配置the System clock frequency, HCLK, PCLK2和PCLK1预分频器 *//* 配置Flash Latency周期并开启预取缓冲区*/SetSysClock();#ifdef VECT_TAB_SRAMSCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* 在内部SRAM中的向量表重定位。 */
#elseSCB->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_HSESetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHzSetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHzSetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHzSetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHzSetSysClockTo56();
#elif defined SYSCLK_FREQ_72MHzSetSysClockTo72();
#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_HSEuint32_t SystemCoreClock         = SYSCLK_FREQ_HSE;          /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_24MHzuint32_t SystemCoreClock         = SYSCLK_FREQ_24MHz;        /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_36MHzuint32_t SystemCoreClock         = SYSCLK_FREQ_36MHz;        /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_48MHzuint32_t SystemCoreClock         = SYSCLK_FREQ_48MHz;        /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_56MHzuint32_t SystemCoreClock         = SYSCLK_FREQ_56MHz;        /*!< System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_72MHzuint32_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的。该如何设置?或者想倍频到更高的主频该怎么修改?

属性 原来 现在
外部晶振 8M 12M
倍频 9 6
主频 72M 72M

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

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

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

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

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

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

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

①修改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_RAMMY_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 RDYif(retry==0X1FFF)status=1;  //HSE无法就绪else   {RCC->APB1ENR|=1<<28; //电源接口时钟使能PWR->CR|=3<<14;         //高性能模式,时钟可到180MhzRCC->CFGR|=(0<<4)|(5<<10)|(4<<13);//HCLK 不分频;APB1 4分频;APB2 2分频. RCC->CR&=~(1<<24);    //关闭主PLLRCC->PLLCFGR=pllm|(plln<<6)|(((pllp>>1)-1)<<16)|(pllq<<24)|(1<<22);//配置主PLL,PLL时钟源来自HSERCC->CR|=1<<24;          //打开主PLLwhile((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.

STM32-时钟系统相关推荐

  1. esp32 rtc 时钟设置不对_STM32入门系列-STM32时钟系统,STM32时钟树

    时钟对于单片机来说是非常重要的,它为单片机工作提供一个稳定的机器周期从而使系统能够正常运行.时钟系统犹如人的心脏,一旦有问题整个系统就崩溃.我们知道STM32属于高级单片机,其内部有很多的外设,但不是 ...

  2. STM32 时钟系统

    STM32时钟系统的基本概念 概念及意义 (1)概念:时钟系统是由振荡器(信号源).定时唤醒器.分频器等组成的电路.常用的信号源有晶体振荡器和RC振荡器. (2)意义:时钟对数字电路而言非常重要,没有 ...

  3. STM32——时钟系统

    STM32--时钟系统 宗旨:技术的学习是有限的,分享的精神是无限的. 一.时钟树 普通的MCU,一般只要配置好GPIO 的寄存器,就可以使用了.STM32为了实现低功耗,设计了非常复杂的时钟系统,必 ...

  4. STM32时钟系统的概念及意义

    STM32时钟系统的基本概念 概念及意义 概念 时钟系统是由振荡器(信号源).定时唤醒器.分频器等组成的电路.常用的信号源有晶体振荡器和RC振荡器 意义 时钟是嵌入式系统的脉搏,处理器内核在时钟驱动下 ...

  5. 2021.4.14 第四次 STM32时钟系统

    STM32时钟系统 一. STM32时钟系统介绍 二. 时钟系统框图 三. 时钟配置相关函数 1.1 时钟系统介绍: 时钟是单片机运行的基础,时钟信号推动单片机内各个部分执行相应的指令.STM32本身 ...

  6. STM32时钟系统(1)-时钟框图解释

    STM32时钟系统(2)-时钟系统常用寄存器和库函数 STM32时钟系统 官方文档说明: Three different clock sources can be used to drive the ...

  7. STM32时钟系统了解

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一.时钟系统是什么? 二.为什么STM32需要多个时钟源? 三.STM32LE/E4xx时钟树 四.时钟源分类 五.以下是对 ...

  8. STM32——时钟系统RCC详细介绍

    STM32的时钟系统学习,主要集中在时钟树的分析应用,时钟树里面有很多的时钟,它具体怎么配置,或者说我们的固件库函数是怎么配置的,这就是我们需要掌握的地方,最后我们达到的目的就是编写自己的库函数,实现 ...

  9. ARM开发初级-STM32时钟系统以及如何正确使用HAL_Delay-学习笔记08

    文章目录 1. STM32时钟系统概述 1.1 时钟系统的概念及意义 1.2 常见振荡器介绍 1.3 时钟树分析 2. STM32时钟配置实例 3. SysTick定时器讲解 3.1 SysTick ...

  10. 【STM32】初学者必读STM32时钟系统详解

    目录 1 前言 2 时钟系统介绍 3 时钟源 3.1 系统时钟源 3.2 次级时钟源 3.3 时钟源特点 4 时钟 4.1 AHB总线时钟 4.2 APB1总线时钟 4.3 APB2总线时钟 5 时钟 ...

最新文章

  1. java目录更改当前_Java-MVC:查看目录更改的最佳方法
  2. vue修改代码同步页面_vue修改数组中对象属性值页面不同步更新渲染问题处理
  3. 评阅百篇博士论文后我发现:博士生发SCI越多,通常科研能力越差!
  4. CAMB输出保留小数位数问题
  5. Python 知识点全解析系列之列表推导式(list comprehension)
  6. mysql配置多个域名访问吗,tomcat部署多个项目,通过域名解析访问,不同的网站...
  7. 6.2.2 二叉树的创建
  8. 如何使用VAIO Care恢复和还原VAIO
  9. echart的进阶使用(option)
  10. c语言函数的使用步骤,c语言打开文件函数使用方法
  11. std中稳定排序算法_源代码库已开放 | 哈工大硕士生用 Python 实现了 11 种经典数据降维算法...
  12. Linux内存管理之内存寻址:分段机制的实现方式
  13. 树莓派安装Jupyter Notebook实现网页编程
  14. 获取当前的格林尼治时间
  15. 《炬丰科技-半导体工艺》薄晶圆处理挑战和新兴解决方案
  16. 哈工大计算机网络考研题,哈工大计算机考研历年复试试题(完全版).PDF
  17. 呼叫中心中间件-网关配置
  18. 【DSA_Fall2020】2. Trees (Templates in C)
  19. axios 官方指南翻译
  20. 360搜索“触链”聚焦版权领域,“图刻”是噱头还是不忘安全初心?

热门文章

  1. python使用布隆过滤器筛选数据
  2. pandas dataframe根据筛选结果修改值
  3. Overleaf 显示中文
  4. 奥鹏计算机基础2021,18春北语《计算机基础》作业3(参考答案)
  5. html5 canvas修改颜色,html5 canvas 笔记二(添加样式和颜色)
  6. 力扣有没有java_【Java】一篇文章带你玩转用Java刷力扣
  7. ant编译无法依赖rt.jar
  8. 存储引擎:engine
  9. 【VMware】宿主机连接wifi,虚拟机中的Linux系统配置连接wifi
  10. jquery获取div/div之间的内容.text() 和 .html()区别