STM32中级篇:使用HSE配置系统时钟并输出MCO监控系统时钟

前言

        这节课我们开始学习使用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输出监控系统时钟,编写了库函数文件,使用固件库的方式编写程序。希望同学们每天都有所进步!

作者:Zhang-jk

物联沃分享整理
物联沃-IOTWORD物联网 » STM32中级篇:使用HSE配置系统时钟并输出MCO监控系统时钟

发表评论