一、STM32时钟系统
1. STM32时钟系统框图
STM32的时钟系统非常强大,但也非常复杂。下面为时钟树:
下面分别介绍图中的各个元素 (STM32中文参考手册6.2节时钟) :
(1)最左边
最左边的OSC_IN和OSC_OUT是两个引脚,默认是外部晶振引脚。(我们的板子接了8MHz的晶振,数据手册表示可以接4——16MHz的晶振)
MCO(microcontroller clock output)是输出内部时钟,该功能能将STM32内部的时钟通过引脚PA8输出,要进行引脚复用与功能设置。我们可以通过示波器(可惜我们没有,要到实验室去接)监控 MCO 引脚的时钟输出来验证系统时钟配置是否正确。STM32可以选择一个时钟信号输出到MCO上,可以选择为PLL输出的2分频、HSI、HSE、或者系统时钟(SYSCLK)。
(2) STM32的5个时钟源(蓝色框图)
这些时钟源由晶振电路或RC振荡器组成,为系统提供一定的工作频率。
HSI(high speed internal)是高速内部时钟,RC振荡器,频率为8MHz,精度不高。
HSE(high speed external)是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
LSI(low speed internal)是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。
LSE(low speed external)是低速外部时钟,接频率为32.768kHz的石英晶体。
PLL(phase locked loop)为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。
系统时钟SYSCLK 可来源于三个时钟源:HSI振荡器时钟、HSE振荡器时钟、PLL时钟。
(3)几个重要的时钟(黑色字体)
这些时钟都是通过时钟源、倍频器和预分频器的各种配置而得到的:
SYSCLK(系统时钟)
AHB(advanced high performance bus,高级高性能总线)总线时钟
APB1(advanced peripheral bus,高级外围设备总线)总线时钟(低速) :速度最高为36MHz
APB2总线时钟(高速) :速度最高为72MHz
PLL时钟
以及还有一些未提及的外设时钟(最右侧),这些外设的时钟要根据外设的需求来进行配置。
(4)预分频器(绿色框图)
分频器的作用是:系统时钟先经过固定的分频系数后产生相应频率的时钟,提供给单片机定时器的计时输入。
图中绿色 的框图为预分频器,图中已写出每个分频器的可分频因子。分频器的相关知识数电讲过。
(5)选择器(灰色框图)
图中灰色 的梯形框图为选择器,数电也曾经讲过。这个形状应该很熟悉吧!
(6)时钟安全系统CSS(橙色框图)
如果外部晶振出了问题(例如被短路),系统时钟(SYSCLK)就会崩溃。因此,STM32时钟系统设置了CSS时钟安全系统,一旦HSE失效,则自动切换至SYSCLK = HSI,意味着系统用内部RC振荡器作为时钟源(对照时钟树的路线看看是怎么进行切换的!)。当然RC振荡器并不是十分精确的,当外部晶振恢复正常后,我们还是希望系统切换回外部晶振。
2.其中一条线路举例(可对照时钟树进行查看)
OSC_IN (8MHz) –> HSE_OSC –> 选择器 –> 选择器 –> PLL (8MHz X 9 = 72MHz) –> 作为PLLCLK –> 选择器 –> 作为SYSCLK –> AHB预分频器 –> 作为HCLK(advanced high performance bus clock,高级高性能总线时钟)。
3. 与RCC相关的寄存器定义以及函数
本节可参考STM32中文参考手册6.3节RCC寄存器描述 。
与RCC寄存器相关的定义如下(位于stm32f10x.h头文件):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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; #ifdef STM32F10X_CL __IO uint32_t AHBRSTR; __IO uint32_t CFGR2; #endif #if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL) uint32_t RESERVED0; __IO uint32_t CFGR2; #endif } RCC_TypeDef;
需要注意,STM32F10X_CL这些都属于互联型,编写者为了兼容更多机型而采用了条件编译的办法。我们使用的是大容量(STM32F10X_HD),因此有条件编译的语句只看STM32F10X_HD的即可。在库函数中经常涉及这种情况。
与RCC寄存器相关的函数如下(位于stm32f10x.h头文件):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 (1 )时钟使能配置: RCC_LSEConfig() 、RCC_HSEConfig()、 RCC_HSICmd() 、 RCC_LSICmd() 、 RCC_PLLCmd() …… (2 )时钟源相关配置: RCC_PLLConfig ()、 RCC_SYSCLKConfig() 、 RCC_RTCCLKConfig() … (3 )分频系数选择配置: RCC_HCLKConfig() 、 RCC_PCLK1Config() 、 RCC_PCLK2Config()… (4 )外设时钟使能: RCC_APB1PeriphClockCmd(): RCC_APB2PeriphClockCmd(); RCC_AHBPeriphClockCmd(); (5 )其他外设时钟配置: RCC_ADCCLKConfig (); RCC_RTCCLKConfig(); (6 )状态参数获取参数: RCC_GetClocksFreq(); RCC_GetSYSCLKSource(); RCC_GetFlagStatus() (7 )RCC中断相关函数: RCC_ITConfig() 、 RCC_GetITStatus() 、 RCC_ClearITPendingBit()…
二、系统初始化时的时钟配置
本节将详细介绍时钟初始化的流程。
上一次学习笔记我们可以看到,即使我们没有一步一步对时钟进行配置,只是打开引脚相应的时钟,LED和蜂鸣器依然可以正常工作,这是为什么呢?原来,在系统刚启动时,会运行启动程序(该程序为汇编程序startup_stm32f10x_hd.s),调用SystemInit函数,从而配置好默认的系统时钟:
1 2 3 4 5 6 7 8 9 Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT __main IMPORT SystemInit LDR R0, =SystemInit BLX R0 LDR R0, =__main BX R0 ENDP
这就意味着,当程序来到main函数里的时候,系统的时钟已经被配置成72MHz。
下面我们进入SystemInit()函数,去一探时钟初始化的究竟。
1. 时钟系统初始化函数SystemInit()
这个函数位于源文件system_stm32f10x.c,它是与时钟配置相关的文件,在它的开头,定义了宏,可以看到默认情况下使用的是72MHz:
1 2 3 4 5 6 7 8 9 10 11 #if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL) #define SYSCLK_FREQ_24MHz 24000000 #else #define SYSCLK_FREQ_72MHz 72000000 #endif
在system_stm32f10x.c的第200多行,为SystemInit()函数,可对照STM32中文参考手册6.3节RCC寄存器描述 (为简短起见,我们将无关代码删去了)(星号注释为本人翻译):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void SystemInit (void ) { RCC->CR |= (uint32_t )0x00000001 ; RCC->CFGR &= (uint32_t )0xF0FF0000 ; RCC->CR &= (uint32_t )0xFEF6FFFF ; RCC->CR &= (uint32_t )0xFFFBFFFF ; RCC->CFGR &= (uint32_t )0xFF80FFFF ; RCC->CIR = 0x009F0000 ; SetSysClock(); #ifdef VECT_TAB_SRAM SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; #else SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; #endif }
这里调用了一个函数SetSysClock(),下面跳到该函数去看看!
2. 系统时钟配置函数SetSysClockTo72()
在system_stm32f10x.c的第400多行,为SetSysClock()函数,可对照STM32中文参考手册6.3节RCC寄存器描述 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 }
根据该文件开头的宏定义,程序将执行函数SetSysClockTo72()。在文件差不多1000多行,为该函数的完整定义(本程序已删去无关代码):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 static void SetSysClockTo72 (void ) { __IO uint32_t StartUpCounter = 0 , HSEStatus = 0 ; RCC->CR |= ((uint32_t )RCC_CR_HSEON); 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->ACR &= (uint32_t )((uint32_t )~FLASH_ACR_LATENCY); FLASH->ACR |= (uint32_t )FLASH_ACR_LATENCY_2; RCC->CFGR |= (uint32_t )RCC_CFGR_HPRE_DIV1; RCC->CFGR |= (uint32_t )RCC_CFGR_PPRE2_DIV1; RCC->CFGR |= (uint32_t )RCC_CFGR_PPRE1_DIV2; 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); RCC->CR |= RCC_CR_PLLON; while ((RCC->CR & RCC_CR_PLLRDY) == 0 ) { } 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 { } } #endif
对照时钟树发现程序的配置顺序和时钟树的顺序不一致,其实无所谓,打个比方,只有打开所有开关系统才能正常运行,打开部分开关还是运行不了。
初始化之后的状态:
SYSCLK = 72MHz
AHB = 72MHz
PCLK1(peripheral bus clock,外围设备总线时钟) = 36MHz
PCLK2 = 72MHz
PLL = 72MHz
3.小结
系统初始化时钟的流程:startup(汇编程序) -> SystemInit() -> SetSysClock() -> SetSysClockTo72() -> main()。
三、使用库函数配置系统时钟HSE
在第一部分我们已经列举出了一些RCC常用的函数,接下来我们自己写一个函数来配置我们想要的时钟频率。代码如下(这里的步骤与上面代码的步骤一一对应):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include "rcc_config.h" #include "stm32f10x.h" void HSE_SetSysClk ( uint32_t RCC_PLLMul_x ) { ErrorStatus HSEStatus; RCC_DeInit(); RCC_HSEConfig(RCC_HSE_ON); HSEStatus = RCC_WaitForHSEStartUp(); if ( HSEStatus == SUCCESS ) { FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); FLASH_SetLatency(FLASH_Latency_2); RCC_HCLKConfig(RCC_SYSCLK_Div1); RCC_PCLK1Config(RCC_HCLK_Div2); RCC_PCLK2Config(RCC_HCLK_Div1); RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_x); RCC_PLLCmd(ENABLE); while ( RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while ( RCC_GetSYSCLKSource() != 0x08 ); } else { } }
现在我们在主函数里调用自己写的时钟配置函数。这里我写了个流水灯程序,在main函数一开始调用了自己写的HSE_SetSysClk()函数,参数为PLL锁相环的倍频因子,可以看到我这里用的是12倍频因子,8MHz*12=96MHz,超频(因为最高为72MHz),delay函数的延时效果会更加短。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include "stm32f10x.h" #include "led.h" #include "rcc_config.h" void delay ( uint32_t count ) { for (; count != 0 ; count--); } int main (void ) { HSE_SetSysClk(RCC_PLLMul_12); LED_Init(); while (1 ){ GPIO_SetBits(GPIOB,GPIO_Pin_5); GPIO_ResetBits(GPIOE,GPIO_Pin_5); delay(0xFFFFF ); GPIO_ResetBits(GPIOB,GPIO_Pin_5); GPIO_SetBits(GPIOE,GPIO_Pin_5); delay(0xFFFFF ); } }