前言
这节课我们开始学习使用HSE配置系统时钟并使用MCO输出监控系统时钟,上节课我们讲了固件库里的系统时钟配置函数,是机器写的,我们现在自己来写一个。
STM32第九节(中级篇):RCC(第三节)—— 使用HSE配置系统时钟并使用MCO输出监控系统时钟
创建固件库文件
我们先新建两个驱动文件,命名为bsp_rccclkconfig.c文件和bsp_rccclkconfig.h文件。我们这节课使用固件库编程的方式来实现我们的库函数编写。然后将我们所写的文件导入到USER中并初始化文件使之一一对应
编写HSE_SetSysClk函数
我们想要自己编写一个时钟函数,并以超频的方式输出,那么我们就需要配置HSE时钟(在正常情况下)。我们可以看到,在主函数的模板中,系统时钟已经配置为72MHz,而我们要重新配置我们自己的时钟,就需要先把寄存器复位,然后再编写我们的程序。这个函数编写需要一步一步来,接下来我们就逐步分析如何使用固件库的方式编写代码。
int main(void)
{
// 来到这里的时候,系统的时钟已经被配置成72M。
复位RCC寄存器
我们可以在stm32f10x_rcc.h库中找到RCC_DeInit(void)函数,它的作用是初始化RCC寄存器的值(Resets the RCC clock configuration to the default reset state.)。在初始化RCC后我们就可以随心所欲地编写代码了。
//把RCC寄存器复位到复位值
RCC_DeInit();
HSE时钟的配置
我们观察手册可知,我们的时钟树有一个基本的流程,我们先进行HSE时钟的配置。
在这里,我们需要先对HSE时钟使能,在我们的库函数中找到void RCC_HSEConfig(uint32_t RCC_HSE)函数。我们观察发现,该函数引入了一个参量,是RCC_HSE的一个状态,分为了RCC_HSE_OFF,RCC_HSE_ON,RCC_HSE_Bypass,我们要打开HSE时钟,这里我们选择使用RCC_HSE_ON。
/**
* @brief Configures the External High Speed oscillator (HSE).
* @note HSE can not be stopped if it is used directly or through the PLL as system clock.
* @param RCC_HSE: specifies the new state of the HSE.
* This parameter can be one of the following values:
* @arg RCC_HSE_OFF: HSE oscillator OFF
* @arg RCC_HSE_ON: HSE oscillator ON
* @arg RCC_HSE_Bypass: HSE oscillator bypassed with external clock
* @retval None
*/
既然我们使能了HSE时钟,那么我们还应该等待HSE使能成功。它这个使能过程有一定的延迟。我们在库函数中找到ErrorStatus RCC_WaitForHSEStartUp(void)函数,并查询其定义类型:
typedef enum {ERROR = 0, SUCCESS = !ERROR} ErrorStatus;
ErrorStatus RCC_WaitForHSEStartUp(void);
我们可以看到,枚举这个结构体为ErrorStatus,然后定义其值。我们在这里定义为ErrorStatus HSEStatus;然后等待使能成功。
//把RCC寄存器复位到复位值
RCC_DeInit();
ErrorStatus HSEStatus;
//使能HSE
RCC_HSEConfig(RCC_HSE_ON);
//等待HSE使能成功
HSEStatus = RCC_WaitForHSEStartUp();
紧接着我们打开库函数具体函数代码,我们发现该函数是具有返回值的函数(结构体变量类型),然后我们观察,发现如果(RCC_GetFlagStatus(RCC_FLAG_HSERDY) != RESET),则status = SUCCESS,即返回使能结果:
/**
* @brief Waits for HSE start-up.
* @param None
* @retval An ErrorStatus enumuration value:
* - SUCCESS: HSE oscillator is stable and ready to use
* - ERROR: HSE oscillator not yet ready
*/
ErrorStatus RCC_WaitForHSEStartUp(void)
{
__IO uint32_t StartUpCounter = 0;
ErrorStatus status = ERROR;
FlagStatus HSEStatus = RESET;
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC_GetFlagStatus(RCC_FLAG_HSERDY);
StartUpCounter++;
} while((StartUpCounter != HSE_STARTUP_TIMEOUT) && (HSEStatus == RESET));
if (RCC_GetFlagStatus(RCC_FLAG_HSERDY) != RESET)
{
status = SUCCESS;
}
else
{
status = ERROR;
}
return (status);
}
然后我们判断该返回值是否为SUCCESS,若是,则运行接下来的代码;若否,则使用if——else语句实现编写运行错误的代码。当我们运行成功之后,就要进行下一步:使能预取指及配置总线分频因子。
使能预取指及配置总线分频因子
在确认为真后,我们在这里就要用到关于FLASH的相关知识来配置使能预取指。我们观察如下代码,我们首先要打开FLASH文件中的FLASH_PrefetchBufferCmd:
/**
* @brief Enables or disables the Prefetch Buffer.
* @note This function can be used for all STM32F10x devices.
* @param FLASH_PrefetchBuffer: specifies the Prefetch buffer status.
* This parameter can be one of the following values:
* @arg FLASH_PrefetchBuffer_Enable: FLASH Prefetch Buffer Enable
* @arg FLASH_PrefetchBuffer_Disable: FLASH Prefetch Buffer Disable
* @retval None
*/
void FLASH_PrefetchBufferCmd(uint32_t FLASH_PrefetchBuffer)
{
/* Check the parameters */
assert_param(IS_FLASH_PREFETCHBUFFER_STATE(FLASH_PrefetchBuffer));
/* Enable or disable the Prefetch Buffer */
FLASH->ACR &= ACR_PRFTBE_Mask;
FLASH->ACR |= FLASH_PrefetchBuffer;
}
在打开之后,我们还需要配置FLASH_SetLatency(uint32_t FLASH_Latency)函数,我们选择FLASH_Latency_2: FLASH Two Latency cycles模式。
//使能预取指
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_2);
然后我们配置总线分频因子,这也是我们老生常谈的一个话题,我们这里就不过多赘述:
void RCC_HCLKConfig(uint32_t RCC_SYSCLK);
void RCC_PCLK1Config(uint32_t RCC_HCLK);
void RCC_PCLK2Config(uint32_t RCC_HCLK);
//配置总线分频因子
RCC_HCLKConfig(RCC_SYSCLK_Div1); //72MHz
RCC_PCLK1Config(RCC_HCLK_Div2); //36MHz
RCC_PCLK2Config(RCC_HCLK_Div1); //72MHz
配置锁相环时钟
到了配置锁相环这一步,我们需要选择系统时钟,先配置好锁相环时钟的来源以及配频因子。我们找到RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t RCC_PLLMul)函数,我们发现,该函数我们选择 RCC_PLLSource_HSE_Div1 HSE一分频,然后因为我们要超频工作,所以我们在原有函数参数部分就不再是 void 空类型,而是一个int 类型的参量。
然后我们就要对PLL时钟进行使能操作,找到RCC_PLLCmd 函数,调整参数为 ENABLE。然后就是等待稳定,和上面的步骤相同。然后就是选择我们的PLLCLK作为系统时钟,使用 RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); 函数,然后识别是否真正地使用了PLLCLK。
/**
* @brief Returns the clock source used as system clock.
* @param None
* @retval The clock source used as system clock. The returned value can
* be one of the following:
* - 0x00: HSI used as system clock
* - 0x04: HSE used as system clock
* - 0x08: PLL used as system clock
*/
uint8_t RCC_GetSYSCLKSource(void)
{
return ((uint8_t)(RCC->CFGR & CFGR_SWS_Mask));
}
代码如下:
if(HSEStatus == SUCCESS)
{
//使能预取指
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_2);
//配置总线分频因子
RCC_HCLKConfig(RCC_SYSCLK_Div1); //72MHz
RCC_PCLK1Config(RCC_HCLK_Div2); //36MHz
RCC_PCLK2Config(RCC_HCLK_Div1); //72MHz
//配置锁相环时钟
//配置锁相环时钟来源,配频因子
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_x);
//使能PLL
RCC_PLLCmd(ENABLE);
//等待PLL稳定
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
//选择系统时钟
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(RCC_GetSYSCLKSource() != 0x08);
代码展示(CV)
#include "bsp_rccclkconfig.h"
void HSE_SetSysClk(uint32_t RCC_PLLMul_x)
{
//把RCC寄存器复位到复位值
RCC_DeInit();
ErrorStatus HSEStatus;
//使能HSE
RCC_HSEConfig(RCC_HSE_ON);
//等待HSE使能成功
HSEStatus = RCC_WaitForHSEStartUp();
if(HSEStatus == SUCCESS)
{
//使能预取指
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_2);
//配置总线分频因子
RCC_HCLKConfig(RCC_SYSCLK_Div1); //72MHz
RCC_PCLK1Config(RCC_HCLK_Div2); //36MHz
RCC_PCLK2Config(RCC_HCLK_Div1); //72MHz
//配置锁相环时钟
//配置锁相环时钟来源,配频因子
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_x);
//使能PLL
RCC_PLLCmd(ENABLE);
//等待PLL稳定
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
//选择系统时钟
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(RCC_GetSYSCLKSource() != 0x08);
}else{
//编写运行错误的代码
}
}
使用MCO输出监控系统时钟
我们观察原理图可知,MCO可以控制io口输出,那么我们可以使用MCO输出监控系统时钟。我们找到LED的代码,稍作修改,将接口改为GPIOA,改为GPIO_Pin_8,输出模式改为GPIO_Mode_AF_PP。
void MCO_GPIO_Config()
{
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);
}
main函数修改
我们先初始化MCO以及LED,然后使用16倍超频工作模式,找到RCC_MCOConfig(RCC_MCO_SYSCLK);函数,调整参数为RCC_MCO_SYSCLK,实现选择系统时钟(已经配置好)。到这里我们就完成了全部的代码:
#include "stm32f10x.h" // 相当于51单片机中的 #include <reg51.h>
#include "bsp_led.h"
#include "bsp_rccclkconfig.h"
void Delay(uint32_t count)
{
for(;count!=0;count--);
}
int main(void)
{
// 来到这里的时候,系统的时钟已经被配置成72M。
HSE_SetSysClk(RCC_PLLMul_16);
MCO_GPIO_Config();
RCC_MCOConfig(RCC_MCO_SYSCLK);
LED_GPIO_Config();
while(1)
{
LED_G(OFF);
Delay(0xFFFFF);
LED_G(ON);
Delay(0xFFFFF);
}
}
小结
本节课到这里就结束啦,我们学习了 使用HSE配置系统时钟并使用MCO输出监控系统时钟,编写了库函数文件,使用固件库的方式编写程序。希望同学们每天都有所进步!