STM32单片机学习笔记(七)-RCC时钟控制:简单了解时钟树(一)

写在前面:本系列内容均为自学笔记,参考资料为野火指南者开发板资料及芯片参考手册等,使用野火指南者开发板进行学习,该系列内容仅用于记录笔记,不做其他用途,笔记的内容可能会存在不准确或者错误等,如有大佬看到错误内容还望能够评论指正,感谢各位。
本节包括前几节的程序,请参考野火开发板资料,里面由更加清晰的教学,野火B站账号:野火官方B站账号链接

学习目标

1、简单了解RCC时钟树;
2、为了更好的理解时钟树,尝试写一个通过HSE、HSI设置系统时钟的函数,并尝试超频(固件库版本);

电路图

本次实验基于STM32F103核心板或开发板,其晶振使用8M无源晶振,特此说明
本次使用以下电路图,主要是为了在后续根据时钟树写一个72M的系统时钟函数,然后用LED灯进行验证:
图7-1:
在这里插入图片描述
除了上述电路图以外,本节内容可能还会涉及到GPIOA_Pin_8,这个主要是为了使用示波器看波形,这个后面会提到;

一、简单了解时钟树(以STM32F103为例)

注:根据官方库文件中的SetSysClockTo72()函数进行

图7-2是芯片官方参考手册(中文版)中截图的,本节将围绕图片内容简单了解时钟树,根据官方库文件中的SetSysClockTo72()函数进行,主要了解设计HSE和HSI的部分,顺带了解PLL时钟、系统时钟、AHB时钟、ABP1时钟、APB2时钟,然后根据时钟树,结合库函数写程序控制系统时钟,这里提一下:官方建议使用72MHz,并且当我们写程序进入主程序后,如果没有主动设置过,系统就会自动设置成72MHz,这个在之前几节的程序中有标注过。
图7-2:时钟树(图片来自芯片官方参考手册-RCC章节,自己加了选框)
在这里插入图片描述

1、STM32时钟树简略介绍:先介绍本节内容用到的

下方图片是关于时钟树的一个简略图,主要包含了本节所需要用到的几个时钟及其关系,这个图是自己画的,可能会有问题,请以图7-2为准,下图仅供参考:
图7-3:时钟树部分时钟关系简略图(自己画的,可能不准,请以图7-2为准)
在这里插入图片描述
后面关于时钟树所涉及的内容只做简单介绍,旨在更简单的理解时钟树,所以会有部分内容不写进来,详情请看芯片的参考手册中RCC的章节。

①、HSE高速外部时钟信号

HSE时钟来源有外部时钟源(有源晶振)和外部晶体/陶瓷谐振器(无源晶振),按照参考手册所说,有源晶振最高频率为25MHz,无源晶振使用4~16MHz可更精确;不过通常情况下HSE使用的是8MHz的无源晶振,使用时需要用电容起振,类似下图的方式:
图7-4:晶振电路实例
在这里插入图片描述
图7-2中标号①中当芯片接入HSE晶振后可以有两块条路走,一条不分频(或者叫1分频),另一条2分频,不分频时输入到系统的频率跟HSE晶振的频率相同,相应的2分频时输入的频率要除2,在本节中使用不分频的路线,此时顺着HSE时钟路线往前走就到达PLL时钟,不过在进入PLL时钟前,要先进入标号③,作为PLL时钟的来源之一,PLL选择时钟时除HSE时钟外,另一个就是HSI时钟;
HSE无源晶振在使用时可以通过RCC_CR寄存器的HSEON位(位16)控制开启或关闭,如图7-5:
图7-5:HSE时钟控制位
在这里插入图片描述

②、HSI高速内部时钟信号

HSI时钟信号是由芯片内部一个8MHz的RC振荡器产生的,它不需要任何外部器件即可为系统提供时钟信号,在图7-2中时标号②,从HSI时钟信号往前走可以发现有两条路选,一条是不分频的,这条路直接走到标号⑤,作为系统时钟来源的一个选项,另一条路是2分频,这个同HSE一样,信号在分频后进入PLL时钟,作为PLL时钟来源之一;
HSI时钟在使用时起振时间是要比HSE时钟起振快的,但是在精确度上HSI时钟比HSE时钟差,它会受到不同温度或电压的影响而有所偏差,虽然可以校准,但精确度仍会收到影响;不过值得注意的是,在使用HSE作为信号来源时,一旦HSE振荡器失效,HSI时钟将作为备用时钟源继续为系统提供时钟信号,直到HSE时钟可以正常使用;
HSI晶振在使用时可以通过RCC_CR寄存器的HSION位(位0)控制开启或关闭,如图7-6:
图7-6:HSI时钟控制位
在这里插入图片描述

③、PLL时钟 PLLCLK

PLL时钟分为两部分,第一部分是图7-2中的标号③,这部分主要是用于选择PLL时钟的信号来源,上文提到信号来源有两个,其中HSI时钟源,由于其会收到温度、电压等的影响导致频率不稳定,所以一般不作为时钟信号的主要来源,因此这里选用HSE作为PLL时钟的信号来源;第二部分是倍频因子即图中标号④,这部分主要是用来将由HSE输入来的时钟信号放大用的,倍数在2~16之间选择,即HSE时钟选择8MHz时,经过PLL倍频后,频率最低16M,最高128M,具体设置成多少由RCC_CFGR寄存器的PLLMUL[3:0]位控制(如图7-7),另外如果选择HSI作为信号来源则最大频率为64M,原因是HSI信号要2分频;
需要注意的是,上述两个部分的配置必须在PLL时钟被激活前完成,一旦PLL时钟被激活,各项参数将不能更改;
当然一般情况下经过倍频后的PLL时钟选用72MHz,这个频率也是官方推荐的可以稳定运行的频率,所以倍频因子选择9倍,那么可以得到PLLCLK=8M*9=72M;
另外,如果需要在应用中使用USB接口,PLL必须被设置为输出48或72MHZ时钟,用于提供48MHz的USBCLK时钟;
图7-7:PLL时钟配置位
在这里插入图片描述

④、系统时钟 SYSCLK

系统时钟SYSCLK在图7-2中是标号⑤,这部分主要是选择系统时钟的来源,这个来源有3个:HSE、HSI、PLLCLK,这个选择由RCC_CFGR寄存器的SW[1:0]位控制(如图7-8),通常系统时钟选择72M,所以相应的时钟来源一般将会选用PLLCLK,所以SYSCLK=PLLCLK=72M;
图7-8:系统时钟配置位
在这里插入图片描述

⑤、AHB总线时钟 HCLK

AHB时钟在图7-2中是标号⑥,这部分是一个预分频器,其信号来源是系统时钟,这个时钟可以通过RCC_CFGR寄存器的HPRE[3:0]位来设置以下分频:[1、2、4、8、16、64、128、256、512](如图7-9),HCLK的输出信号将除以相应的分频数,它的信号将会在分频后传递给与之相关的各个片上外设,至于分频设置成多少要等使用相关外设是才设置,我们只需要先大致设置以下即可,这里我们设置成1分频,即HCLK=SYSCLK=72M;
图7-9:AHB总线时钟配置位
在这里插入图片描述

⑥、APB1总线时钟 PCLK1

ABP1总线时钟由AHB时钟信号经过APB预分频后得到,它可以通过RCC_CFGR寄存器的PPRE1[2:0位]设置分频[1、2、4、8、16](如图7-10),片上低速外设就挂载在这条总线上,如:USART2/3/4/5、IIC1/2、SPI2/3等,还是一样,这条总线上的外设需要多大的分频等使用时在进行配置,我们只进行大致配置;
这个总线时钟最大只能设置36M,所以这里将其设置成最大频率,即选择2分频,所以得到PCLK1=HCLK/2=36M;
图7-10:APB1总线时钟配置位
在这里插入图片描述

⑦、APB2总线时钟 PCLK2

APB2总线时钟由AHB时钟信号经过高速APB预分频得到,它可以通过RCC_CFGR寄存器的PPRE2[2:0位]设置分频[1、2、4、8、16](如图7-11),片上高速外设就挂载在这条总线上,如GPIO等,这条总线上的外设需要多大的分频等使用时在进行配置,我们只进行大致配置;
这个总线时钟的最大频率为72M,所以这里选择1分频将其设置成最大频率即PCLK2=HCLK=72M;
图7-11:APB2总线时钟配置位
在这里插入图片描述

2、第1点中未提到但仍会用到的内容

①、MCO时钟

这部分在时钟树上的位置可以看图7-2的左下角,该时钟是由芯片内部向外部输出的,它的主要来源由HSE时钟、HSI时钟、PLL时钟的2分频以及系统时钟,时钟的选择由时钟配置寄存器(RCC_CFGR)中的MCO[2:0]位控制。
这个时钟的引脚是GPIOA_Pin_8复用得到的,因此我们可以通过GPIOA_Pin_8来读取从芯片内部输出出来的时钟信号,这个在后文中会有程序展示,我们会用这个引脚来读取时钟信号,并将其输出到示波器,观察波形并读取频率,将读取到的频率与我们预设的频率做对比,检查程序的正确性;
图7-12:MCO时钟配置位
在这里插入图片描述

②、FLASH

这部分在时钟树上并未提到,但是在实际写程序时会用到FLASH的部分功能,这里只简单说明程序中用到的部分,详情请观看《STM32F10xx闪存编程手册》:
我们写好的程序默认存放与flash中,当系统从flash中读取一条指令并执行的同时,会预先再次从flash中读取一条指令,而这时就需要用到预取缓存区,所以在实际写程序时需要把预取缓存区打开,并且系统读取指令时需要有间隔的,所以打开预取缓存区后需要设置间隔(时延),在本文中设置为2(理论上讲,设置为2后低频率应该也可以用),这部分的内容可以看下图寄存器:
图7-13:闪存访问控制寄存器
在这里插入图片描述

3、前两点未提到的内容(简单说明)

这部分内容并未通过程序去实现相关功能,所以笔者理解有限,这里只将笔者从参考手册中看到的部分内容展示出来,详情请看芯片参考手册;

①、LSE低速外部时钟信号

LSE时钟是由一个32.768KHz的低速外部晶体/陶瓷谐振器提供的,它为实时时钟或其他定时器提供一个低功耗且精确的时钟源;当然它如同HSE一样也可以设置外部时钟源(有源晶振),此时需要外部提供一个32.768KHz的外部时钟源;
LSE无源晶振通过在备份域控制寄存器(RCC_BDCR)里的LSEON位启动和关闭,而外部时钟源通过设置在备份域控制寄存器(RCC_BDCR)里的LSEBYP和LSEON位来选择这个模式;
图7-14:
在这里插入图片描述

②、LSI低速内部时钟源

LSI RC担当一个低功耗时钟源的角色,它可以在停机和待机模式下保持运行,为独立看门狗和自动唤醒单元提供时钟。LSI时钟频率大约40kHz(在30kHz和60kHz之间),LSI RC可以通过控制/状态寄存器(RCC_CSR)里的LSION位来启动或关闭;
图7-15:
在这里插入图片描述

③、CSS时钟安全系统

这部分内容参考手册中写的还是挺多的,笔者个人理解是这样的:这个功能可以通过软件激活,一旦激活,就可以用它来检测HSE时钟,当HSE时钟发生故障时将会产生中断,此时可以通过软件进行补救,同时如果HSE时钟正在被使用,此时如果HSE发生故障,那么HSI时钟将自动替代HSE继续为系统提供时钟,不过此时的系统时钟频率比之前低,下面是参考手册中的解释:
图7-16:
在这里插入图片描述

④、RTC时钟和看门狗时钟

RTC时钟的来源有:LSE时钟、LSI时钟、HSE时钟128分频后的时钟信号;
看门狗时钟的来源:LSI;
这两部分在参考手册中的解释如下图:
图7-17:
在这里插入图片描述

二、程序(以STM32F103为例)

在本文最后配有与本文相配套的点灯程序,可以使用闪灯程序进行验证,在不改变闪灯程序的基础上,不同频率下灯的闪烁频率也不一样;

1、系统库文件中的SetSysClockTo72()函数

这个函数官方时使用寄存器写的,它可以为系统提供一个72MHz的时钟频率,程序如下:
程序7-1:官方72M程序(寄存器版)

static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* SYSCLK, HCLK, PCLK2 and PCLK1 配置 ---------------------------*/    
  /* 使能HSE时钟,外部输入8M */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* 等待HSE使能成功,如果不成功则做超时处理*/
  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语句,否则进入最后的else语句*/
  if (HSEStatus == (uint32_t)0x01)
  {
    /* 使能预取指 */
    FLASH->ACR |= FLASH_ACR_PRFTBE;

    /* 设置等待时延为2,这里是设置访问时间,统一设置成2个,以防系统工作时出问题 */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    

    /* 设置AHB时钟=SYSCLK=72M */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /* 设置APB1时钟=HCLK=72M */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /* 设置APB2时钟=HCLK/2=36M */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

#ifdef STM32F10X_CL	//我们使用的是STM32F103,所以这部分没用,为方便大家在源文件中找这个函数这部分就不删除了,我们使用下面#else部分的程序
    /* Configure PLLs ------------------------------------------------------*/
    /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
    /* PREDIV1 configuration: 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);
  
    /* Enable PLL2 */
    RCC->CR |= RCC_CR_PLL2ON;
    /* Wait till PLL2 is ready */
    while((RCC->CR & RCC_CR_PLL2RDY) == 0)
    {
    }
    
   
    /* PLL configuration: 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中的几句话
#else    
    /*  设置PLLCLK: 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启动失败,用户可以在这里设置处理错误的程序 */
  }
}

通过上述程序可以发现,在官方程序中,先对HSE时钟和AHB时钟、APB1时钟、APB2时钟进行配置,当这几个配置好后在配置PLL时钟和系统时钟,而这些配置全都是通过直接操作寄存器来完成的,并且官方的配置顺序跟前面时钟树的顺序也是有差别的,在这个程序中是先配置好AHB、APB1、APB2后才配置的PLL时钟和系统时钟的,不过既然官方这样配置了,那就说明这个顺序是没问题的,当然按照时钟树的顺序写也是可以的,不过后面我们自己写的时候还是按照官方的顺序来写;

2、通过库函数写一个72M的系统时钟函数

①、用HSE作为时钟的最初来源

先说一下下面的程序中需要注意的一个地方,下面的函数使用了一个形参uint32_t RCC_PLLMul_x,在调用这个函数时需要同时设置形参,设置这个形参主要是为了便于改变PLL时钟的倍频因子,正常情况下x的数字选择9,即形参的位置用参数RCC_PLLMul_9替代,这个参数在系统固件库中是有定义的,另外想要实现学习目标中的超频只需要把形参的数字设置的比9大即可,数字最大设置16,这一点在后面解释函数时会提到,下面看程序:
程序7-2:bsp_rccclkconfig.c

#include "bsp_rccclkconfig.h"
//HSE高速外部时钟配置
void HSE_SetSysClk(uint32_t RCC_PLLMul_x)
{//x=2、3、4 ... 16,设置成9后系统时钟就被设置成72MHz(推荐),数字比9大时即可实现超频
	ErrorStatus HSEStatus;
	//使RCC各寄存器复位,以便能成功配置
	RCC_DeInit();
	//使能HSE
	RCC_HSEConfig(RCC_HSE_ON);
	//等待使能,将函数的返回值赋值给HSEStatus,以便后面判断
	HSEStatus = RCC_WaitForHSEStartUp();
	//判断是否使能,成功则继续
	if(HSEStatus == SUCCESS)
	{
		//使能预取指
		FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
		//72M,等待两个周期
		FLASH_SetLatency(FLASH_Latency_2);
		//各时钟分频处理,AHB:1分频,APB1:2分频,APB2:1分频
		RCC_HCLKConfig(RCC_SYSCLK_Div1);
		RCC_PCLK1Config(RCC_HCLK_Div2);
		RCC_PCLK2Config(RCC_HCLK_Div1);
		//锁相环配置(PLLLCK),PLLLCK = HSE * RCC_PLLMul_x,HSE=8M,1分频
		RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_x);
		//使能PLL
		RCC_PLLCmd(ENABLE);
		//判断是否使能成功
		while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
		//选择PLLLCK作为系统时钟
		RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
		//判断是否选择成功
		while(RCC_GetSYSCLKSource() != 0x08);
	}
	else
	{
		/* 如果HSE 启动失败,用户可以在这里添加处理错误的代码 */
	}
}
//MCO可用于验证时钟是否配置成功,该时钟由PA8复用所得,主要是对外输出时钟,相当于有源晶振
//MCO的函数在正常使用时可以不写,这里只是为了检验上面的时钟函数最终输出的时钟频率是否正确
void MCO_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;		//定义变量,方便赋值
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

	GPIO_InitStruct.GPIO_Pin = (GPIO_Pin_8);//
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOA, &GPIO_InitStruct);	
}

程序7-3:bsp_rccclkconfig.h

#ifndef __RCCCLKCONFIG_H
#define __RCCCLKCONFIG_H

#include "stm32f10x.h"
//函数声明
void HSE_SetSysClk(uint32_t RCC_PLLMul_x);
void MCO_GPIO_Config(void);

#endif /*__RCCCLKCONFIG_H*/


具体的步骤已经标注在程序中了,我们只需要按照步骤来,然后找到相应的程序即可,在这里大致介绍以下上面用到的部分函数的功能:

(1)固件库版本中使用到的部分函数释义

注:下列各函数的释义以STM32F103为例,上面程序中MCO的函数就不作解释了,这个函数主要还是对GPIO的配置,并未涉及到固件库中的其他函数,下方函数的释义请以官方固件库中的英文释义为准,建议去固件库程序中观看英文解释。
void RCC_DeInit(void);
用于将RCC中的寄存器复位,防止配置失败;

void RCC_HSEConfig(uint32_t RCC_HSE);
用于配置HSE时钟,系统已经配置好,使用时只需要改变形参RCC_HSE即可,形参的状态有下面三种:
  打开:RCC_HSE_ON(本文章程序选用这个,直接打开)
  关闭:RCC_HSE_OFF
  通过外部时钟绕过:RCC_HSE_Bypass
<注意>:如果直接使用HSE或通过PLL作为系统时钟,则HSE不能停止。

ErrorStatus RCC_WaitForHSEStartUp(void);
用于等待HSE时钟配置完成,这个函数中可以返回两个参数:SUCCESSERROR,其中前者表示HSE时钟配置成功,后者表示配置失败;
在使用过程中可以定义一个ErrorStatus类型的参数,在程序7-2中的定义是ErrorStatus HSEStatus;,定义好后只需要判断返回的参数是否正确即可,具体请参考上述程序;

void FLASH_PrefetchBufferCmd(uint32_t FLASH_PrefetchBuffer);
用于启动预取指(打开或禁用预取缓存区),形参的两个选项是
  FLASH_PrefetchBuffer_Enable(本文章程序选用这个,直接打开)
  FLASH_PrefetchBuffer_Disable
这个函数可以用于所有的STM32F10x设备;

void FLASH_SetLatency(uint32_t FLASH_Latency);
用于设置程序的延迟值(SYSCLK周期与闪存访问时间的比例),形参可以设置为:
  FLASH_Latency_0
  FLASH_Latency_1
  FLASH_Latency_2(本文章程序选用这个,让时间长一点,防止出意外)
这个函数可以用于所有的STM32F10x设备;

void RCC_HCLKConfig(uint32_t RCC_SYSCLK);
用于配置AHB时钟的分频,它的形参可以设置为下面的参数,后面的数字表示分频数:
  RCC_SYSCLK_Div1(本文章程序选用这个,让它的时钟频率达到最大)
  RCC_SYSCLK_Div2
  RCC_SYSCLK_Div4
  RCC_SYSCLK_Div8
  RCC_SYSCLK_Div16
  RCC_SYSCLK_Div64
  RCC_SYSCLK_Div128
  RCC_SYSCLK_Div256
  RCC_SYSCLK_Div512
这个时钟来自系统时钟SYSCLK;

void RCC_PCLK1Config(uint32_t RCC_HCLK);
用于设置APB1时钟的分频数,其形参有以下几种设置:
  RCC_HCLK_Div1
  RCC_HCLK_Div2(本文章程序选用这个,APB1最大频率为36M)
  RCC_HCLK_Div4
  RCC_HCLK_Div8
  RCC_HCLK_Div16
这个时钟来自AHB时钟HCLK;

void RCC_PCLK2Config(uint32_t RCC_HCLK);
用于设置APB1时钟的分频数,其形参有以下几种设置:
  RCC_HCLK_Div1(本文章程序选用这个,APB2最大频率为72M)
  RCC_HCLK_Div2
  RCC_HCLK_Div4
  RCC_HCLK_Div8
  RCC_HCLK_Div16
这个时钟来自AHB时钟HCLK;

void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t RCC_PLLMul);
用于配置PLL时钟的时钟来源、时钟来源的分频因子以及倍频因子
可设置的时钟源及其分频因子:
  RCC_PLLSource_HSI_Div2,(来源是HSI时设置这个)
  RCC_PLLSource_HSE_Div1(来源是HSE时设置这个)
  RCC_PLLSource_HSE_Div2
可设置的倍频因子是RCC_PLLMul_x,其中x取值范围为[2~16]
<注意>:该功能只能在关闭PLL时钟的情况下使用,一旦启用PLL时钟,其相关参数将不能更改;

void RCC_PLLCmd(FunctionalState NewState);
用于启动PLL时钟,它只有两种状态
  ENABLE启动
  DISABLE关闭
这个函数设置为启动状态时要放在PLL配置函数的后面,等PLL时钟配置完成后再启动;

FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);
用于检验是否设置了RCC标志,在这里用于判断PLL时钟是否使能成功,其形参可以设置为下列参数(部分参数未翻译):
  RCC_FLAG_HSIRDYHSI时钟
  RCC_FLAG_HSERDYHSE时钟
  RCC_FLAG_PLLRDYPLL时钟
  RCC_FLAG_LSERDYLSE时钟
  RCC_FLAG_LSIRDYLSI时钟
  RCC_FLAG_PINRST引脚重置
  RCC_FLAG_PORRSTPOR/PDR复位
  RCC_FLAG_SFTRST软件复位
  RCC_FLAG_IWDGRST独立看门狗复位
  RCC_FLAG_WWDGRST窗口看门狗故为
  RCC_FLAG_LPWRRST低功耗重置
这个函数有两个返回值SET(启动成功)和RESET(启动失败)
使用时可以通过判断返回值来判断是否成功启动;

void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource);
用于选择系统时钟的来源,其形参可设置成下列参数,本文章中选用PLLCLK:
  RCC_SYSCLKSource_HSI
  RCC_SYSCLKSource_HSE
  RCC_SYSCLKSource_PLLCLK(本文章中选用这个)

uint8_t RCC_GetSYSCLKSource(void);
用于判断系统的时钟来源是否选择正确,这个函数有三个返回值:
  0x00选用HSI作为系统时钟来源
  0x04选用HSE作为系统时钟来源
  0x08选用PLLC作为系统时钟来源

void RCC_MCOConfig(uint8_t RCC_MCO);
用于选择输出在MCO引脚上的时钟源,这个形参有下面几个参数可选:
  RCC_MCO_NoClock不选择时钟
  RCC_MCO_SYSCLK系统时钟
  RCC_MCO_HSIHSI时钟
  RCC_MCO_HSEHSE时钟
  RCC_MCO_PLLCLK_Div2PLL时钟2分频后的时钟信号

②、用HSI作为时钟的最初来源

直接看程序,关于程序的解释在程序下面:
程序7-4:bsp_rccclkconfig.c

#include "bsp_rccclkconfig.h"
//HSI低速外部时钟配置
void HSI_SetSysClk(uint32_t RCC_PLLMul_x)
{//x=2、3、4 ... 16,设置成16后系统时钟就被设置成64MHz(最大)
	__IO uint32_t	HSIStatus = 0;
	//使RCC各寄存器复位,以便能成功配置
	RCC_DeInit();
	//使能HSI
	RCC_HSICmd(ENABLE);
	//等待使能
	HSIStatus = RCC->CR & RCC_CR_HSERDY;
	//判断是否使能,成功则继续
	if(HSIStatus == RCC_CR_HSERDY)
	{
		//使能预取指
		FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
		//72M,等待两个周期
		FLASH_SetLatency(FLASH_Latency_2);
		//各时钟分频处理,AHB:1分频,APB1:2分频,APB2:1分频
		RCC_HCLKConfig(RCC_SYSCLK_Div1);
		RCC_PCLK1Config(RCC_HCLK_Div2);
		RCC_PCLK2Config(RCC_HCLK_Div1);
		//锁相环配置(PLLLCK),PLLLCK = HSI / 2 * RCC_PLLMul_x,HSI=8M,2分频
		RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_x);
		//使能PLL
		RCC_PLLCmd(ENABLE);
		//判断是否使能成功
		while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
		//选择PLLLCK作为系统时钟
		RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
		//判断是否选择成功
		while(RCC_GetSYSCLKSource() != 0x08);
	}
	else
	{
		/* 如果HSI 启动失败,用户可以在这里添加处理错误的代码 */
	}
}
//MCO可用于验证时钟是否配置成功,该时钟由PA8复用所得,主要是对外输出时钟,相当于有源晶振
void MCO_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;		//定义变量,方便赋值
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

	GPIO_InitStruct.GPIO_Pin = (GPIO_Pin_8);//
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOA, &GPIO_InitStruct);	
}

程序7-4:bsp_rccclkconfig.h

#ifndef __RCCCLKCONFIG_H
#define __RCCCLKCONFIG_H

#include "stm32f10x.h"
//函数声明
void MCO_GPIO_Config(void);
void HSI_SetSysClk(uint32_t RCC_PLLMul_x);

#endif /*__RCCCLKCONFIG_H*/


上述程序大致与HSE的程序相差不大,需要注意的是HSI的分频是2分频,另外就是各参数设置时要设置成与HSI相关的参数;
另外就是程序中有这样几段程序:

__IO uint32_t	HSIStatus = 0;
HSIStatus = RCC->CR & RCC_CR_HSERDY;
if(HSIStatus == RCC_CR_HSERDY)
{
}

这两段程序中,第一段是一个定义,第二段程序是寄存器操作,这两几段程序的功能是等待HSI启动成功,如果启动成功则执行if语句中的内容,这个和程序7-2中HSE的等待函数HSEStatus = RCC_WaitForHSEStartUp();功能相同,不过HSI没有自己的等待函数,所以这里按照系统配置72M时钟的方式直接操作寄存器,这几段话就是从系统寄存器版本那里直接复制过来的,不过这里的i语句的f判断进行了简化;

③、与上述两点中程序相配套的闪灯程序

上述关于时钟配置的程序可以使用下面的LED闪灯程序进行验证:
程序7-5:main.c

#include "stm32f10x.h"   // 相当于51单片机中的  #include <reg51.h>
#include "bsp_rccclkconfig.h"
#include "bsp_led.h"

void Delay(uint32_t count)
{
	for(;count != 0;count--);
}

int main(void)
{
	// 来到这里的时候,系统的时钟已经被配置成72M。
	HSE_SetSysClk(RCC_PLLMul_9);//HSE作为时钟来源,设置成9则与系统默认设置的频率相同
	//HSI_SetSysClk(RCC_PLLMul_9);//HSI作为时钟来源
	LED_GPIO_Config();	//GPIO初始化
	/*可以用下面两句话来对外输出系统时钟,然后用示波器采集波形,进而查验系统时钟是否配置正确
	MCO_GPIO_Config();//初始化MCO
	RCC_MCOConfig(RCC_MCO_SYSCLK);//使能
	*/
	while(1)
	{
		LED(OFF);		//关灯
		Delay(0xFFFFF);
		LED(ON);		//开灯
		Delay(0xFFFFF);

	}
}


程序7-6:led.c

#include "bsp_led.h"

void LED_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;		//定义变量,方便赋值
	
	RCC_APB2PeriphClockCmd(LED_G_GPIO_CLK,ENABLE);	//打开APB2时钟,GPIO挂载在APB2

	GPIO_InitStruct.GPIO_Pin = (LED_G_GPIO_PIN);	//设置需要用到的管脚,LED_G_GPIO_PIN看.h文件
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;	//设置输出模式为推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;	//设置输出速率为50MHz,LED_G_GPIO_CLK看.h文件
	
	GPIO_Init(LED_G_GPIO_PORT, &GPIO_InitStruct);	//加上&,方便取值	//初始化GPIO
}


程序7-7:led.h

#ifndef _BSP_LED_H
#define _BSP_LED_H

#include "stm32f10x.h"		//要包含固件库的.h文件

#define LED_G_GPIO_PIN						GPIO_Pin_0		//定义绿灯管脚号
#define LED_G_GPIO_PORT						GPIOB			//定义用到的GPIO
#define LED_G_GPIO_CLK						RCC_APB2Periph_GPIOB	//定义RCC时钟寄存器
//下面用到的“\”符号为续行符,其后面不能由任何东西,意为这行下面的一行跟这行是一起的,分行写看起来比较清晰
#define ON						1
#define OFF						0		
#define LED(a)					if(a) \
									GPIO_ResetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN); \
								else \
									GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);

void LED_GPIO_Config(void);		//.c文件中的函数声明

#endif	/*_BSP_LED_H*/


以上内容仅为个人理解,可能会存在理解偏差,发现错误后会在本文进行更正,各位如果有看到错误也烦请评论或私信告诉笔者,并且上述内容中有一些时钟如LSE、LSI等并未理解,只是单纯放了各介绍,这些就放到《RCC时钟控制:简单了解时钟树(二)》中了,当然,这个什么时候会更出来就不确定了,有想了解的,如果在笔者主页没看到这个标题的文章,那就是没更新,可以在本网站搜索其他大佬的文章,感谢观看。

以上仅为笔记记录,不作教学等用途,感谢观看。

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值