写在前面:本系列内容均为自学笔记,参考资料为野火指南者开发板资料及芯片参考手册等,使用野火指南者开发板进行学习,该系列内容仅用于记录笔记,不做其他用途,笔记的内容可能会存在不准确或者错误等,如有大佬看到错误内容还望能够评论指正,感谢各位。
本节包括前几节的程序,请参考野火开发板资料,里面由更加清晰的教学,野火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时钟配置完成,这个函数中可以返回两个参数:SUCCESS
和ERROR
,其中前者表示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_HSIRDY
HSI时钟
RCC_FLAG_HSERDY
HSE时钟
RCC_FLAG_PLLRDY
PLL时钟
RCC_FLAG_LSERDY
LSE时钟
RCC_FLAG_LSIRDY
LSI时钟
RCC_FLAG_PINRST
引脚重置
RCC_FLAG_PORRST
POR/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_HSI
HSI时钟
RCC_MCO_HSE
HSE时钟
RCC_MCO_PLLCLK_Div2
PLL时钟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时钟控制:简单了解时钟树(二)》中了,当然,这个什么时候会更出来就不确定了,有想了解的,如果在笔者主页没看到这个标题的文章,那就是没更新,可以在本网站搜索其他大佬的文章,感谢观看。