一、认识RCC
设置系统时钟 SYSCLK、设置 AHB 分频因子(决定 HCLK 等于多少)、设置 APB2 分频因子(决定 PCLK2 等于多少)、设置 APB1 分频因子(决定 PCLK1 等于多少)、设置各个外设的分频因子; 控制 AHB、APB2 和 APB1 这三条总线时钟的开启、控制每个外设的时钟的开启。对于 SYSCLK、 HCLK、PCLK2、PCLK1 这四个时钟的配置一般是:PCLK2 = HCLK = SYSCLK=PLLCLK = 72M, PCLK1=HCLK/2 = 36M。这个时钟配置也是库函数的标准配置,我们用的最多的就是这个。
1.RCC 框图剖析—时钟部分
时钟树单纯讲理论的话会比较枯燥,如果选取一条主线,并辅以代码,先主后次讲解的话会很容 易,而且记忆还更深刻。我们这里选取库函数时钟系统时钟函数:**SetSysClockTo72();** 以这个函数的编写流程来讲解时钟树。
2.系统时钟
(1)HSE 高速外部时钟信号
HSE 是高速的外部时钟信号,可以由有源晶振或者无源晶振提供,频率从 4-16MHZ 不等。当使用有源晶振时,时钟从 OSC_IN 引脚进入,OSC_OUT 引脚悬空,当选用无源晶振时,时钟从 OSC_IN 和 OSC_OUT 进入,并且要配谐振电容。
(2)HSI高速内部时钟信号
当 HSE 故障的时候,如果 PLL 的时钟来源是 HSE,那么当 HSE 故障的时候,不仅 HSE 不能使用,连 PLL 也会被关闭,这个时候系统会自动切换 HSI 作为系统时钟,此时 SYSCLK=HSI=8M, 如果没有开启 CSS 和 CSS 中断的话,那么整个系统就只能在低速率运行,这是系统跟瘫痪没什么两样。如果开启了 CSS 功能的话,那么可以当 HSE 故障时,在 CSS 中断里面采取补救措施, 使用 HSI,并把系统时钟设置为更高的频率,最高是 64M,64M 的频率足够一般的外设使用,如: ADC、SPI、I2C 等。
(3)锁相环时钟 PLLCLK
通过设置 PLL 的倍频因子,可以对 PLL 的时钟来源进行倍频,倍频因子可以是:[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],具体设置成多少,由时钟配置寄存器 CFGR 的位 21-18:PLLMUL[3:0] 设置。我们这里设置为 9 倍频,因为上一步我们设置 PLL 的时钟来源为 HSE=8M,所以经过 PLL 倍频之后的 PLL 时钟:PLLCLK = 8M *9 = 72M。72M 是 ST 官方推荐的稳定运行时钟,如果你想超频的话,增大倍频因子即可, 最高为 128M。我们这里设置 PLL 时钟:PLLCLK = 8M *9 = 72M。
(4)系统时钟 SYSCLK
系统时钟来源可以是:HSI、PLLCLK、HSE,具体的时钟配置寄存器 CFGR 的位 1-0: SW[1:0] 设置。我们这里设置系统时钟:SYSCLK = PLLCLK = 72M。
(5)AHB 总线时钟 HCLK
系统时钟 SYSCLK 经过 AHB 预分频器分频之后得到时钟叫 APB 总线时钟,即 HCLK, 分频因子可以是:[1,2,4,8,16,64,128,256,512],具体的由时钟配置寄存器 CFGR 的位 7-4 :HPRE[3:0] 设置。片上大部分外设的时钟都是经过 HCLK 分频得到,至于 AHB 总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需 粗线条的设置好 APB 的时钟即可。我们这里设置为 1 分频,即 HCLK=SYSCLK=72M。
(6)APB2 总线时钟 PCLK2
APB2 总线时钟 PCLK2 由 HCLK 经过高速 APB2 预分频器得到,分频因子可以 是:[1,2,4,8,16],具体由时钟配置寄存器 CFGR 的位 13-11:PPRE2[2:0] 决定。PCLK2 属于高速的总线时钟,片上高速的外设就挂载到这条总线上,比如全部的 GPIO、US-ART1、SPI1 等。至于 APB2 总线上的外设的时钟设置为多少,得等到我们使用该外 设的时候才设置,我们这里只需粗线条的设置好 APB2 的时钟即可。我们这里设置为 1 分频,即 PCLK2 = HCLK = 72M。
(7)APB1 总线时钟 PCLK1
APB1总线时钟 PCLK1由HCLK 经过低速APB预分频器得到,分频因子可以是:[1,2,4,8,16],具体的由时钟配置寄存器 CFGR 的位 10-8:PRRE1[2:0] 决定。PCLK1 属于低速 的总线时钟,最高为 36M,片上低速的外设就挂载到这条总线上,比如 USART2/3/4/5、 SPI2/3,I2C1/2 等。至于 APB1 总线上的外设的时钟设置为多少,得等到我们使用该 外设的时候才设置,我们这里只需粗线条的设置好 APB1 的时钟即可。我们这里设置为2分频,即 PCLK1=HCLK/2 = 36M。
(8)其他时钟
ADC 时钟
ADC 时钟由 PCLK2 经过 ADC 预分频器得到,分频因子可以是 [2,4,6,8],具体的由 时钟配置寄存器 CFGR 的位 15-14:ADCPRE[1:0] 决定。很奇怪的是怎么没有 1 分频。 ADC 时钟最高只能是 14M,如果采样周期设置成最短的 1.5 个周期的话,ADC 的转 换时间可以达到最短的 1us。如果真要达到最短的转换时间 1us 的话,那 ADC 的时钟 就得是 14M,反推 PCLK2 的时钟只能是:28M、56M、84M、112M,鉴于 PCLK2 最 高是 72M,所以只能取 28M 和 56M。
RTC 时钟、独立看门狗时钟
RTC时钟可由HSE/128 分频得到,也可由低速外部时钟信号LSE提供,频率为 32.768KHZ,也可由低速内部时钟信号LSI提供,具体选用哪个时钟由备份域控制寄存器BDCR 的位 9-8:RTCSEL[1:0] 配置。独立看门狗的时钟由 LSI 提供,且只能是由 LSI 提供,LSI 是低速的内部时钟信号,频率为 30~60KHZ 直接不等,一般取 40KHZ。
MCO 时钟输出
MCO 是 microcontroller clock output 的缩写,是微控制器时钟输出引脚,在 STM32 F1 系列中由 PA8 复用所得,主要作用是可以对外提供时钟,相当于一个有源晶振。MCO 的时钟来源可以是:PLLCLK/2、HSI、HSE、SYSCLK,具体选哪个由时钟配置寄存器 CFGR 的位 26-24:MCO[2:0] 决定。除了对外提供时钟这个作用之外,我们还可以通过示波器监控 MCO 引脚的时钟输出来验证我们的系统时钟配置是否正确。
二、详细分析
1.硬件设计
RCC LED 一个
RCC 是单片机内部资源,不需要外部电路。通过 LED 闪烁的频率来直观的判断不同系统时钟频率对软件延时的效果。
2.软件设计
我们编写两个 RCC 驱动文件,bsp_clkconfig.h 和 bsp_clkconfig.c,用来存放 RCC 系统时钟配置函数。
3.编程要点
编程要点对应着时钟树图中的序号。
1、开启 HSE/HSI ,并等待 HSE/HSI 稳定
2、设置 AHB、APB2、APB1 的预分频因子
3、设置 PLL 的时钟来源,和 PLL 的倍频因子,设置各种频率主要就是在这里设置
4、开启 PLL,并等待 PLL 稳定
5、把 PLLCK 切换为系统时钟 SYSCLK
6、读取时钟切换状态位,确保 PLLCLK 被选为系统时钟
4.代码分析
(1)使用 HSE 配置系统时钟
/* * 使用HSE时,设置系统时钟的步骤 * 1、开启HSE ,并等待 HSE 稳定 * 2、设置 AHB、APB2、APB1的预分频因子 * 3、设置PLL的时钟来源,和PLL的倍频因子,设置各种频率主要就是在这里设置 * 4、开启PLL,并等待PLL稳定 * 5、把PLLCK切换为系统时钟SYSCLK * 6、读取时钟切换状态位,确保PLLCLK被选为系统时钟 */ /* 设置 系统时钟:SYSCLK, AHB总线时钟:HCLK, APB2总线时钟:PCLK2, APB1总线时钟:PCLK1 * PCLK2 = HCLK = SYSCLK * PCLK1 = HCLK/2,最高只能是36M * 参数说明:pllmul是PLL的倍频因子,在调用的时候可以是:RCC_PLLMul_x , x:[2,3,...16] * 举例:User_SetSysClock(RCC_PLLMul_9); 则设置系统时钟为:8MHZ * 9 = 72MHZ * User_SetSysClock(RCC_PLLMul_16); 则设置系统时钟为:8MHZ * 16 = 128MHZ,超频慎用 * * HSE作为时钟来源,经过PLL倍频作为系统时钟,这是通常的做法 */ void HSE_SetSysClock(uint32_t pllmul) { __IO uint32_t StartUpCounter = 0, HSEStartUpStatus = 0; // 把RCC外设初始化成复位状态 RCC_DeInit(); //使能HSE,开启外部晶振,野火开发板用的是8M RCC_HSEConfig(RCC_HSE_ON); // 等待 HSE 启动稳定 HSEStartUpStatus = RCC_WaitForHSEStartUp(); // 只有 HSE 稳定之后则继续往下执行 if (HSEStartUpStatus == SUCCESS) { //----------------------------------------------------------------------// // 这两句是操作FLASH闪存用到的,如果不操作FLASH的话,这两个注释掉也没影响 // 使能FLASH 预存取缓冲区 FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); // SYSCLK周期与闪存访问时间的比例设置,这里统一设置成2 // 设置成2的时候,SYSCLK低于48M也可以工作,如果设置成0或者1的时候, // 如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了 // 0:0 < SYSCLK <= 24M // 1:24< SYSCLK <= 48M // 2:48< SYSCLK <= 72M FLASH_SetLatency(FLASH_Latency_2); //----------------------------------------------------------------------// // AHB预分频因子设置为1分频,HCLK = SYSCLK RCC_HCLKConfig(RCC_SYSCLK_Div1); // APB2预分频因子设置为1分频,PCLK2 = HCLK RCC_PCLK2Config(RCC_HCLK_Div1); // APB1预分频因子设置为1分频,PCLK1 = HCLK/2 RCC_PCLK1Config(RCC_HCLK_Div2); //-----------------设置各种频率主要就是在这里设置-------------------// // 设置PLL时钟来源为HSE,设置PLL倍频因子 // PLLCLK = 8MHz * pllmul RCC_PLLConfig(RCC_PLLSource_HSE_Div1, pllmul); //------------------------------------------------------------------// // 开启PLL RCC_PLLCmd(ENABLE); // 等待 PLL稳定 while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) { } // 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // 读取时钟切换状态位,确保PLLCLK被选为系统时钟 while (RCC_GetSYSCLKSource() != 0x08) { } } else { // 如果HSE开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理 // 当HSE开启失败或者故障的时候,单片机会自动把HSI设置为系统时钟,HSI是内部的高速时钟,8MHZ while (1) { } } }
这个函数采用库函数编写,函数有个形参 pllmul,pllmul 用来设置 PLL 的倍频因子,在调用的时 候形参可以是:RCC_PLLMul_x , x:[2,3,…16],这些宏来源于库函数的定义,宏展开是一些 32 位的十六进制数,具体功能是配置了时钟配置寄存器 CFGR 的位 21-18PLLMUL[3:0],预先定义好倍频因子,方便调用。
(2)使用 HSI 配置系统时钟
/* * 使用HSI时,设置系统时钟的步骤 * 1、开启HSI ,并等待 HSI 稳定 * 2、设置 AHB、APB2、APB1的预分频因子 * 3、设置PLL的时钟来源,和PLL的倍频因子,设置各种频率主要就是在这里设置 * 4、开启PLL,并等待PLL稳定 * 5、把PLLCK切换为系统时钟SYSCLK * 6、读取时钟切换状态位,确保PLLCLK被选为系统时钟 */ /* 设置 系统时钟:SYSCLK, AHB总线时钟:HCLK, APB2总线时钟:PCLK2, APB1总线时钟:PCLK1 * PCLK2 = HCLK = SYSCLK * PCLK1 = HCLK/2,最高只能是36M * 参数说明:pllmul是PLL的倍频因子,在调用的时候可以是:RCC_PLLMul_x , x:[2,3,...16] * 举例:User_SetSysClock(RCC_PLLMul_9); 则设置系统时钟为:4MHZ * 9 = 36MHZ * User_SetSysClock(RCC_PLLMul_16); 则设置系统时钟为:4MHZ * 16 = 64MHZ * * HSI作为时钟来源,经过PLL倍频作为系统时钟,这是在HSE故障的时候才使用的方法 * HSI会因为温度等原因会有漂移,不稳定,一般不会用HSI作为时钟来源,除非是迫不得已的情况 * 如果HSI要作为PLL时钟的来源的话,必须二分频之后才可以,即HSI/2,而PLL倍频因子最大只能是16 * 所以当使用HSI的时候,SYSCLK最大只能是4M*16=64M */ void HSI_SetSysClock(uint32_t pllmul) { __IO uint32_t HSIStartUpStatus = 0; // 把RCC外设初始化成复位状态 RCC_DeInit(); //使能HSI RCC_HSICmd(ENABLE); // 等待 HSI 就绪 HSIStartUpStatus = RCC->CR & RCC_CR_HSIRDY; // 只有 HSI就绪之后则继续往下执行 if (HSIStartUpStatus == RCC_CR_HSIRDY) { //----------------------------------------------------------------------// // 这两句是操作FLASH闪存用到的,如果不操作FLASH的话,这两个注释掉也没影响 // 使能FLASH 预存取缓冲区 FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); // SYSCLK周期与闪存访问时间的比例设置,这里统一设置成2 // 设置成2的时候,SYSCLK低于48M也可以工作,如果设置成0或者1的时候, // 如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了 // 0:0 < SYSCLK <= 24M // 1:24< SYSCLK <= 48M // 2:48< SYSCLK <= 72M FLASH_SetLatency(FLASH_Latency_2); //----------------------------------------------------------------------// // AHB预分频因子设置为1分频,HCLK = SYSCLK RCC_HCLKConfig(RCC_SYSCLK_Div1); // APB2预分频因子设置为1分频,PCLK2 = HCLK RCC_PCLK2Config(RCC_HCLK_Div1); // APB1预分频因子设置为1分频,PCLK1 = HCLK/2 RCC_PCLK1Config(RCC_HCLK_Div2); //-----------------设置各种频率主要就是在这里设置-------------------// // 设置PLL时钟来源为HSE,设置PLL倍频因子 // PLLCLK = 4MHz * pllmul RCC_PLLConfig(RCC_PLLSource_HSI_Div2, pllmul); //------------------------------------------------------------------// // 开启PLL RCC_PLLCmd(ENABLE); // 等待 PLL稳定 while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) { } // 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // 读取时钟切换状态位,确保PLLCLK被选为系统时钟 while (RCC_GetSYSCLKSource() != 0x08) { } } else { // 如果HSI开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理 // 当HSE开启失败或者故障的时候,单片机会自动把HSI设置为系统时钟,HSI是内部的高速时钟,8MHZ while (1) { } } }
HSI 设置系统时钟函数跟 HSE 设置系统时钟函数在原理上是一样的,有一个区别的地方就是, HSI 必须 2 分频之后才能作为 PLL 的时钟来源,所以使用 HSI 时,最大的系统时钟 SYSCLK 只 能是 HSI/2*16=4*16=64MHZ。
// 软件延时函数,使用不同的系统时钟,延时不一样 void Delay(__IO uint32_t nCount) { for(; nCount != 0; nCount--); }
软件延时函数,使用不同的系统时钟,延时时间不一样,可以通过 LED 闪烁的频率来判断。
(3)初始化MCO引脚
在 STM32F103 系列中,PA8 可以复用为 MCO 引脚,对外提供时钟输出,我们也可以用示波器监控该引脚的输出来判断我们的系统时钟是否设置正确。
* * 初始化MCO引脚PA8 * 在F1系列中MCO引脚只有一个,即PA8,在F4系列中,MCO引脚会有两个 */ void MCO_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; // 开启GPIOA的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 选择GPIO8引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //设置为复用功能推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置IO的翻转速率为50M GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 初始化GPIOA8 GPIO_Init(GPIOA, &GPIO_InitStructure); }
(4)主函数
// 设置MCO引脚输出时钟,用示波器即可在PA8测量到输出的时钟信号 // MCO引脚输出可以是HSE,HSI,PLLCLK/2,SYSCLK //RCC_MCOConfig(RCC_MCO_HSE); // 8M //RCC_MCOConfig(RCC_MCO_HSI); // 8M //RCC_MCOConfig(RCC_MCO_PLLCLK_Div2); // 36M RCC_MCOConfig(RCC_MCO_SYSCLK); // 72M
我们初始化 MCO 引脚之后,可以直接调用库函数 RCC_MCOConfig() 来选择 MCO 时钟来源。
int main(void) { // 程序来到main函数之前,启动文件:statup_stm32f10x_hd.s已经调用SystemInit()函数把系统时钟初始化成72MHZ // SystemInit()在system_stm32f10x.c中定义 // 如果用户想修改系统时钟,可自行编写程序修改 // 重新设置系统时钟,根据需要修改,一般设置最高为128MHz // SYSCLK = 8M * RCC_PLLMul_x, x:[2,3,...16] //HSE_SetSysClock(RCC_PLLMul_9); HSI_SetSysClock(RCC_PLLMul_16); // MCO 引脚初始化 MCO_GPIO_Config(); // 设置MCO引脚输出时钟,用示波器即可在PA8测量到输出的时钟信号 // MCO引脚输出可以是HSE,HSI,PLLCLK/2,SYSCLK //RCC_MCOConfig(RCC_MCO_HSE); // 8M //RCC_MCOConfig(RCC_MCO_HSI); // 8M //RCC_MCOConfig(RCC_MCO_PLLCLK_Div2); // 36M RCC_MCOConfig(RCC_MCO_SYSCLK); // 72M // LED 端口初始化 LED_GPIO_Config(); while (1) { LED1( ON ); // 亮 Delay(0x0FFFFF); LED1( OFF ); // 灭 Delay(0x0FFFFF); } }
在主函数中,可以调用 HSE_SetSysClock() 或者 HSI_SetSysClock() 这两个函数把系统时钟设置成各种常用的时钟,然后通过 MCO 引脚监控,或者通过 LED 闪烁的快慢体验不同的系统时钟对同一个软件延时函数的影响。
MCO=SYSCLK=72M
MCO=HSI=8M