一、概述
时钟系统是CPU的脉搏,所以说要想深入理解STM32的外设,学习时钟系统是非常有必要的。首先,让我们看看网上找到的STM32F1时钟框图:
![STM32F1时钟系统框图](https://i-blog.csdnimg.cn/blog_migrate/7786bf58e741b9390baaf57f53499a99.jpeg)
在STM32中,有5个时钟源,为HSI、HSE、LSI、LSE和PLL。从时钟频率来分可以分为高速时钟源和低速时钟源,在这5个中HIS、HSE以及PLL是高速时钟,LSI和LSE是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中HSE和LSE是外部时钟源,其他的是内部时钟源。
下面我们具体分析这5个时钟源:
- HSI是高速内部时钟,RC振荡器,频率为8MHz。
- HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
- LSI是低速内部时钟,RC振荡器,频率为40KHz。独立看门狗的时钟源只能是LSI,同时LSI还可以作为RTC的时钟源。
- LSE是低速外部时钟,接频率为32.768KHz的石英晶体。这个主要用于作为内部RTC的时钟源。
- PLL为锁相环倍频输出,其时钟输入源可以选择HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。
从上面我们已经基本理解STM32的5个时钟源的特性,那么这个5个时钟源主要是给什么外设和系统提供时钟的呢?我们继续从《STM32中文参考手册_V10》提供的时钟框图继续分析。
![](https://i-blog.csdnimg.cn/blog_migrate/822dfc2150376075ed2af7846f24b4fe.png)
- A:MCO是STM32的一个时钟输出IO,它可以选择一个时钟信号输出,可以选择为PLL输出的2分频、HSI、HSE 或者系统时钟。这个时钟可以用来给外部其他系统提供时钟源。
- B:这里是RTC时钟源,从图上可以看出,RTC的时钟源可以选择LSI,LSE以及HSE的128分频。
- C:这里是USB的时钟源,STM32可以作为一个全速的USB设备,其串行接口引擎需要一个频率为48MHz的时钟源。该时钟源只能从PLL输出端获取,可以选择1.5分频或者1分频,也就是说使用USB模块时,需要开启PLL时钟,并将时钟频率配置成48MHz或者72MHz。
- D:这里是STM32的系统时钟SYSCLK,它是供STM32中绝大部分部件工作的时钟源。系统时钟可以选择为PLL输出、HSI或者HSE,系统时钟最大频率为72MHz。
- E:这里就是其他所有外设的时钟源了。从时钟图上可以看出,其他所有外设的时钟最终来源都是SYSCLK。SYSCLK通过 AHB分频器分频后送给各模块使用。
二、代码分析
void RCC_Configuration(void)
{
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON); //RCC_HSE_ON——HSE晶振打开(ON)
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if(HSEStartUpStatus == SUCCESS) //SUCCESS:HSE晶振稳定且就绪
{
RCC_HCLKConfig(RCC_SYSCLK_Div1); //RCC_SYSCLK_Div1——AHB时钟 = 系统时钟
RCC_PCLK2Config(RCC_HCLK_Div1); //RCC_HCLK_Div1——APB2时钟 = HCLK
RCC_PCLK1Config(RCC_HCLK_Div2); //RCC_HCLK_Div2——APB1时钟 = HCLK / 2
FLASH_SetLatency(FLASH_Latency_2); //FLASH_Latency_2 2延时周期
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); // 预取指缓存使能
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
// PLL的输入时钟 = HSE时钟频率;RCC_PLLMul_9——PLL输入时钟x 9
RCC_PLLCmd(ENABLE);
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) ;
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
//RCC_SYSCLKSource_PLLCLK——选择PLL作为系统时钟
while(RCC_GetSYSCLKSource() != 0x08); //0x08:PLL作为系统时钟
}
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
RCC_APB2Periph_GPIOC , ENABLE);
}
使用HSE时钟,程序设置时钟参数流程:
- 将RCC寄存器重新设置为默认值(RCC_DeInit)
- 打开外部高速时钟晶振HSE(RCC_HSEConfig(RCC_HSE_ON))
- 等待外部高速时钟晶振工作(HSEStartUpStatus = RCC_WaitForHSEStartUp())
- 设置AHB时钟(RCC_HCLKConfig)
- 设置高速AHB时钟(RCC_PCLK2Config)
- 设置低速速AHB时钟(RCC_PCLK1Config)
- 设置PLL(RCC_PLLConfig)
- 打开PLL(RCC_PLLCmd(ENABLE))
- 等待PLL工作(while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET))
- 设置系统时钟(RCC_SYSCLKConfig)
- 判断是否PLL是系统时钟(while(RCC_GetSYSCLKSource() != 0x08))
- 打开要使用的外设时钟(RCC_APB2PeriphClockCmd()/RCC_APB1PeriphClockCmd())
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
...
...
...
通过分析STM32的启动过程,我们可以知道单片机复位后会调用外部文件的SystemInit函数。
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
}
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) */
}
通过分析这两个库函数,我们可以知道要设置系统时钟无非就是对一些宏定义的设置,其余的工作库函数已经帮我们做了。所以我们只需对应打开所需的频率设置宏定义即可,比如我们需要配置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
这里总结一下SystemInit()函数中设置的系统时钟大小:
SYSCLK(系统时钟) = 72MHz
AHB总线时钟(使用SYSCLK) = 72MHz
APB1总线时钟(PCLK1) = 36MHz
APB2总线时钟(PCLK2) = 72MHz
PLL时钟 = 72MHz
三、总结
通过简单分析STM32的时钟系统,我们可以初步了解STM32的系统架构,对我们深度学习STM32有很大的助力。由于库函数提供的时钟频率并没有64MHz,如果有项目需要使用内部HSI倍频至64MHz的,请参考:STM32F1一种相对简单的使用HSI配置系统时钟为64MHz的方法
参考文献:
STM32中文参考手册_V10.pdf