第9章 RCC时钟配置-MCO输出

第九章 RCC-MCO输出

1. 使用HSE

一般情况下,我们都是使用HSE,然后HSE经过PLL倍频之后作为系统时钟。通常的配置是:HSE=8M,PLL的倍频因子为:9, 系统时钟就设置成:SYSCLK = 8M * 9 = 72M。使用HSE,系统时钟SYSCLK最高是128M。我们使用的库函数就是这么干的, 当程序来到main函数之前,启动文件:statup_stm32f10x_hd.s已经调用SystemInit()函数把系统时钟初始化成72MHZ, SystemInit()在库文件:system_stm32f10x.c中定义。如果我们想把系统时钟设置低一点或者超频的话,可以修改底层的库文件, 但是为了维持库的完整性,我们可以根据时钟树的流程自行写一个。

更多内容参考:STM32入坑(六)使用HSE配置系统时钟_stm32使用hse时钟-CSDN博客

2. 使用HSI

当HSE故障的时候,如果PLL的时钟来源是HSE,那么当HSE故障的时候,不仅HSE不能使用,连PLL也会被关闭,这个时候系统会自动切换HSI作为系统时钟, 此时SYSCLK=HSI=8M,如果没有开启CSS和CSS中断的话,那么整个系统就只能在低速率运行,这是系统跟瘫痪没什么两样。如果开启了CSS功能的话, 那么可以当HSE故障时,在CSS中断里面采取补救措施,使用HSI,并把系统时钟设置为更高的频率,最高是64M,64M的频率足够一般的外设使用, 如:ADC、SPI、I2C等。但是这里就又有一个问题了,原来SYSCLK=72M,现在因为故障改成64M,那么那些外设的时钟肯定被改变了, 那么外设工作就会被打乱,那我们是不是在设置HSI时钟的时候,也重新调整外设总线的分频因子,即AHB,APB2和APB1的分频因子, 使外设的时钟达到跟HSE没有故障之前一样。但是这个也不是最保障的办法,毕竟不能一直使用HSI,所以当HSE故障时还是要采取报警措施。

还有一种情况是,有些用户不想用HSE,想用HSI,但是又不知道怎么用HSI来设置系统时钟,因为调用库函数都是使用HSE, 下面我们给出个使用HSI配置系统时钟例子,起个抛砖引玉的作用。

更多内容参考:STM32系统时钟的配置方法——内部高速时钟HSI作为系统时钟源_stm32 hsi-CSDN博客

3. 硬件设计

  • RCC

这个是内部资源,不用管

  • LED一个

    关于开发板上面的LED电路我们已经分析过了

4. 软件设计

我们编写两个RCC驱动文件,bsp_clkconfig.h和bsp_clkconfig.c,用来存放RCC系统时钟配置函数。

4.1 编程概要

编程要点对应着时钟树图中的序号。

1、开启HSE/HSI ,并等待 HSE/HSI 稳定

2、设置 AHB、APB2、APB1的预分频因子

3、设置PLL的时钟来源,和PLL的倍频因子,设置各种频率主要就是在这里设置

4、开启PLL,并等待PLL稳定

5、把PLLCK切换为系统时钟SYSCLK

6、读取时钟切换状态位,确保PLLCLK被选为系统时钟

4.2 代码分析

使用HSE/HSI配置系统时钟

#include "bsp_clkconfig.h"
#include "stm32f10x_rcc.h"

/*
使用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_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)
    {
    }
  }
}

/*
使用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]
举例:HSI_SetSysClock(RCC_PLLMul_9);  则设置系统时钟为:4MHZ * 9 = 36MHZ
     HSI_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稳定
  while (RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET)
  {
  }    
  // 读取 HSI 就绪状态
  HSIStartUpStatus = RCC->CR & RCC_CR_HSIRDY;

    // 只有 HSI就绪之后则继续往下执行
  if (HSIStartUpStatus == RCC_CR_HSIRDY)
  {

    // 使能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时钟来源为HSI,设置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)
    {
    }
  }
}

MCO输出

#include "bsp_mcooutput.h"
#include "stm32f10x_rcc.h"

/*
 * 初始化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);
}

主函数

#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_clkconfig.h"
#include "bsp_mcooutput.h"

// 软件延时函数,使用不同的系统时钟,延时不一样
void Delay(__IO u32 nCount); 

int main(void)
{    
    // 程序来到main函数之前,启动文件:statup_stm32f10x_hd.s已经调用
    // SystemInit()函数把系统时钟初始化成72MHZ
    // SystemInit()在system_stm32f10x.c中定义
    // 如果用户想修改系统时钟,可自行编写程序修改

    // 重新设置系统时钟,这时候可以选择使用HSE还是HSI

    // 使用HSE时,SYSCLK = 8M * RCC_PLLMul_x, x:[2,3,...16],最高是128M
    //HSE_SetSysClock(RCC_PLLMul_9);

    // 使用HSI时,SYSCLK = 4M * RCC_PLLMul_x, x:[2,3,...16],最高是64MH
    HSI_SetSysClock(RCC_PLLMul_16);

    // MCO 引脚初始化
    MCO_GPIO_Config();

    // 设置MCO引脚输出时钟,用示波器即可在PA8测量到输出的时钟信号,
    // 我们可以把PLLCLK/2作为MCO引脚的时钟来检测系统时钟是否配置准确
    // MCO引脚输出可以是HSE,HSI,PLLCLK/2,SYSCLK    
    // RCC_MCOConfig(RCC_MCO_HSE);                             
    // RCC_MCOConfig(RCC_MCO_HSI);                       
    // RCC_MCOConfig(RCC_MCO_PLLCLK_Div2);        
    RCC_MCOConfig(RCC_MCO_SYSCLK);              

    // LED 端口初始化
    LED_GPIO_Config();
    while (1)
    {
        LED1( ON );              // 亮
        Delay(0x0FFFFF);
        LED1( OFF );          // 灭 
        Delay(0x0FFFFF);        
    }
}


//  软件延时函数,使用不同的系统时钟,延时不一样
void Delay(__IO uint32_t nCount)    
{
    for(; nCount != 0; nCount--);
}

在主函数中,可以调用HSE_SetSysClock()或者HSI_SetSysClock()这两个函数把系统时钟设置成各种常用的时钟,然后通过MCO引脚监控, 或者通过LED闪烁的快慢体验不同的系统时钟对同一个软件延时函数的影响。

5. 小结

主要就是一个配置问题,可以参考时钟树来理解,比如我们以配置HSE为例:

  1. 开启HSE ,并等待 HSE 稳定

如何开启?答:使用库函数就行了呀

// 把RCC外设初始化成复位状态
RCC_DeInit(); // 库函数 RCC_DeInit()

//使能HSE,开启外部晶振,野火开发板用的是8M
RCC_HSEConfig(RCC_HSE_ON); // 库函数 RCC_HSEConfig()

// 等待 HSE 启动稳定
HSEStartUpStatus = RCC_WaitForHSEStartUp(); // 库函数 RCC_WaitForHSEStartUp()

照例我们还是解释一下新出现的库函数:

  1. RCC_DeInit()
  • 功能RCC_DeInit() 函数用于将 RCC(时钟控制器)外设的寄存器恢复到其默认复位状态。它会将 RCC 外设的所有设置重置为默认值,从而确保系统的时钟配置处于已知的初始状态。
  • 作用: 在对 RCC 进行任何新的配置之前,调用此函数可以避免之前设置可能对新的配置造成干扰,确保初始化过程的一致性和可靠性。
  1. RCC_HSEConfig(RCC_HSE_ON)
  • 功能RCC_HSEConfig() 函数用于配置外部高速晶振(HSE)。RCC_HSE_ON 参数表示使能 HSE,即启动外部晶振。
  • 参数:
    • RCC_HSE_ON: 启用 HSE,即外部晶振。
    • RCC_HSE_OFF: 禁用 HSE,即关闭外部晶振。
    • RCC_HSE_BYPASS: 使用外部晶振的旁路模式(如果有外部时钟源直接输入到 HSE 引脚)。
  • 作用: 在 STM32 微控制器中,外部晶振(HSE)提供了系统时钟源的一个选项,特别是在需要更高精度时钟源时使用。通过调用这个函数并传入 RCC_HSE_ON 参数,可以启动外部晶振,供系统时钟使用。
  1. RCC_WaitForHSEStartUp()
  • 功能RCC_WaitForHSEStartUp() 函数用于等待外部高速晶振(HSE)稳定并准备好。此函数会检查 HSE 是否已经成功启动并稳定,返回启动状态。
  • 返回值:
    • SUCCESS: 表示 HSE 已经成功启动并稳定。
    • ERROR: 表示 HSE 启动失败或没有稳定。
  • 作用: 外部晶振需要一定的时间才能稳定输出正确的时钟信号。通过调用此函数,可以确保在继续进行其他依赖 HSE 的配置之前,HSE 已经稳定工作。这对于确保系统的时钟源可靠性非常重要。

  1. 设置 AHB、APB2、APB1的预分频因子

参考时钟树设置预分频因子:

// 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);
  1. RCC_HCLKConfig(RCC_SYSCLK_Div1)
  • 功能: 设置 AHB 总线(HCLK)的时钟预分频因子。
  • 参数:
    • RCC_SYSCLK_Div1: 设置 HCLK 为 SYSCLK 的 1 倍,即没有分频。
    • 其他可能的参数如 RCC_SYSCLK_Div2RCC_SYSCLK_Div4, 等等,用于不同的分频设置。
  • 作用: 这个配置将 AHB 总线的时钟频率设置为系统时钟(SYSCLK)的频率。在这个例子中,RCC_SYSCLK_Div1 使得 HCLK 与 SYSCLK 相同,没有分频。
  1. RCC_PCLK2Config(RCC_HCLK_Div1)
  • 功能: 设置 APB2 总线(PCLK2)的时钟预分频因子。
  • 参数:
    • RCC_HCLK_Div1: 设置 PCLK2 为 HCLK 的 1 倍,即没有分频。
    • 其他可能的参数如 RCC_HCLK_Div2RCC_HCLK_Div4, 等等,用于不同的分频设置。
  • 作用: 这个配置将 APB2 总线的时钟频率设置为 AHB 总线(HCLK)的频率。在这个例子中,RCC_HCLK_Div1 使得 PCLK2 与 HCLK 相同,没有分频。
  1. RCC_PCLK1Config(RCC_HCLK_Div2)
  • 功能: 设置 APB1 总线(PCLK1)的时钟预分频因子。
  • 参数:
    • RCC_HCLK_Div2: 设置 PCLK1 为 HCLK 的 1/2,即分频因子为 2。
    • 其他可能的参数如 RCC_HCLK_Div1RCC_HCLK_Div4, 等等,用于不同的分频设置。
  • 作用: 这个配置将 APB1 总线的时钟频率设置为 AHB 总线(HCLK)的 1/2,即有一个分频因子。这是因为 STM32 微控制器中,APB1 外设的最大时钟频率通常受到限制,可能低于 AHB 总线的频率。

  1. 设置PLL的时钟来源,和PLL的倍频因子,设置各种频率主要就是在这里设置
// 设置PLL时钟来源为HSE,设置PLL倍频因子
// PLLCLK = 8MHz * pllmul
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, pllmul);
  • 功能:这段代码配置 PLL 的时钟源以及倍频因子,从而设置 PLL 输出时钟的频率。PLL 是用于生成高频时钟信号的一个重要模块,通常用于系统时钟(SYSCLK)的源。

  • 参数:

  1. RCC_PLLSource_HSE_Div1
  • 这指定了 PLL 时钟源为外部高速时钟(HSE,High-Speed External)。HSE 是一个外部晶振或晶体振荡器的输出频率。
  • Div1 表示将 HSE 时钟源的频率不做分频(即 HSE 输入直接作为 PLL 的输入时钟源)。
  1. pllmul
  • 这是 PLL 的倍频因子,它决定了 PLL 输出时钟的频率。通常,这个倍频因子可以在一个预定义的范围内设置,例如 2 到 16。

  • PLL 输出频率(PLLCLK)由公式计算:PLLCLK = HSE * pllmul

  • 作用:

  1. 设置 PLL 时钟源
    通过将 PLL 时钟源设置为 HSE,你选择了一个外部的高频晶振作为 PLL 的输入。这通常提供了一个稳定且高精度的时钟源。

  2. 配置 PLL 倍频因子
    倍频因子 pllmul 决定了 PLL 输出频率相对于 HSE 输入频率的倍数。这使得你可以根据需要生成所需的高频时钟,以满足系统的需求。


  1. 开启PLL,并等待PLL稳定
// 开启PLL 
RCC_PLLCmd(ENABLE); // 使用库函数 RCC_PLLCmd()
// 等待 PLL稳定
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{

}
  1. RCC_PLLCmd()

函数原型如下:

void RCC_PLLCmd(FunctionalState NewState);
  • 功能:启用或禁用 PLL(Phase-Locked Loop)。
  • 参数
    • NewState:设置为 ENABLE 或 DISABLE。 ENABLE 启用 PLL,DISABLE 禁用 PLL。
  • 描述:这个函数通过设置 RCC(Reset and Clock Control)寄存器中的 PLL 使能位来启动或停止 PLL 时钟源。
  1. RCC_GetFlagStatus()

函数原型如下:

FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);
  • 功能:获取指定 RCC 标志的状态。
  • 参数
    • RCC_FLAG:需要检查的标志类型,比如 RCC_FLAG_PLLRDY 表示 PLL 是否准备好了。
  • 返回值
    • 返回 SET 或 RESET,指示指定的标志是否已经设置。

  1. 把PLLCK切换为系统时钟SYSCLK
// 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // PLLCLK经过我们上面设置了,现在只需要把它作为系统时钟

RCC_SYSCLKConfig()

函数原型如下:

void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource);
  • 功能:设置系统时钟源(SYSCLK)。
  • 参数
    • RCC_SYSCLKSource:指定系统时钟源的常量。可以是以下几个值之一:
      • RCC_SYSCLKSource_HSI: 高速内部振荡器 (HSI) 时钟。
      • RCC_SYSCLKSource_HSE: 高速外部振荡器 (HSE) 时钟。
      • RCC_SYSCLKSource_PLLCLK: PLL 时钟。

  1. 读取时钟切换状态位,确保PLLCLK被选为系统时钟
// 读取时钟切换状态位,确保PLLCLK被选为系统时钟
// 库函数 RCC_GetSYSCLKSource() 用于读取系统时钟源
// 参数代表系统时钟源,0x08代表PLLCLK
while (RCC_GetSYSCLKSource() != 0x08)
{
      // 等待时钟切换完成
}
    // 系统时钟设置完成

RCC_GetSYSCLKSource()

函数原型如下:

uint8_t RCC_GetSYSCLKSource(void);
  • 功能:获取当前系统时钟源。
  • 返回值
    • 0x00: 表示系统时钟源为 HSI (高速内部振荡器)。
    • 0x04: 表示系统时钟源为 HSE (高速外部振荡器)。
    • 0x08: 表示系统时钟源为 PLL (相位锁定环)。
  • 描述:这个函数读取 RCC 寄存器中的 SYSCLK 配置位,返回当前系统时钟的源。

2024.8.20 第一次修订,后期不再维护

posted @ 2024-08-20 15:45  hazy1k  阅读(24)  评论(0编辑  收藏  举报