STM32学习5 时钟系统

  • 一、STM32系统时钟概述
  • 二、STM32时钟源
  • 1. HSE
  • 2. HSI
  • 3. LSE
  • 4. LSI
  • 3. 时钟分频器
  • (1)AHB分频器
  • (2)APB分频器
  • (3)锁相环PLL(Phase-Locked Loop)分频器
  • 三、时钟树
  • 1. 时钟树
  • 2. 主时钟输出MCO(Main Clock Output)
  • 3. USB预分频器
  • 4. SYSCLK
  • 5. PLLCLK
  • 6. RTC时钟
  • 四、RCC寄存器
  • 五、SystemInit()初始化时钟分析
  • 1. SystemInit()
  • 2. SetSysClockTo72()
  • 六、STM32 时钟函数
  • 1. RCC 配置函数
  • (1)RCC_HSEConfig
  • (2)RCC_LSEConfig
  • (3)RCC_PLLConfig
  • RCC_PLLSource参数:
  • pllm 参数
  • (())RCC_MCOConfig
  • (5)RCC_SYSCLKConfig
  • (6)RCC_HCLKConfig
  • 2. PLL配置函数
  • 3. 时钟使能函数
  • (1)RCC_HSICmd
  • (2)RCC_LSICmd
  • (3)RCC_PLLCmd
  • (4)RCC_RTCCLKCmd
  • (5)RCC_AHBPeriphClockCmd
  • (6)RCC_APBxPeriphClockCmd
  • 七、系统时钟设置步骤
  • 1. 步骤说明
  • 2. 自定义系统时钟实现


一、STM32系统时钟概述

系统时钟是STM32微控制器中最重要的部分之一,它负责提供时序信号以驱动处理器核心、外设和其他系统模块的运行。

系统时钟通常由多个时钟源、时钟分频器和时钟树组成,这些组件共同构成了系统时钟的组成和层级结构。

二、STM32时钟源

时钟源用来产生系统时钟信号,STM32有以下内部时钟源和外部时钟源:

1. HSE

HSE(High-Speed External)是指高速外部时钟,使用外部晶振,常见频率有 8MHz、12MHz、16MHz等。

在STM32微控制器中,HSE通常被配置为系统的主时钟源,用于产生系统的主时钟信号。通过将HSE连接到微控制器的时钟输入引脚(通常是HSE引脚),可以将外部晶体振荡器的时钟信号输入到微控制器中。

2. HSI

HSI(High-Speed Internal)是指高速内部时钟,它由微控制器内部的8MHz RC振荡器提供,用于产生系统的主时钟信号。

HSI可直接作为系统时钟或2分频后作为PLL输入。

STM32学习5 时钟系统_系统时钟

3. LSE

LSE(Low-Speed External)是指低速外部时钟,频率32.768kHz。它为实时时钟或其它定时功能提供一个低功耗且精确的时钟源。

4. LSI

LSI(Low-Speed Internal)低速内部时钟,频率大约是 40kHz(30kHz~60kHz之间),它可以在停机或待机模式下运行,可供看门狗使用或自动唤醒使用。

STM32学习5 时钟系统_单片机_02

3. 时钟分频器

时钟分频器是用于将系统时钟频率分频为所需频率的模块。在STM32微控制器中,时钟分频器可以用于调整各个外设的时钟频率,以满足不同外设的工作要求。
时钟分频器有以下几种类型:

(1)AHB分频器

用于将系统主时钟(AHB 总线时钟)分频为用于驱动存储器和某些外设的时钟信号。
AHB是高性能系统总线,主AHB总线用于DMA、中断控制器等;子AHB总线连接到主AHB总线上,并连接到次要外设,如GPIO、SPI、I2C等。

(2)APB分频器

用于将 AHB 总线时钟分频为用于驱动低速外设的时钟信号。在STM32中,有两个APB总线,即APB1和APB2,它们分别驱动不同类型的外设。APB 分频器可以将 AHB 总线时钟分频为适合驱动这些外设的频率。

(3)锁相环PLL(Phase-Locked Loop)分频器

用于调整 PLL 时钟源的频率,以产生所需的系统时钟频率。通过调整 PLL 分频器的分频系数,可以实现将外部高频振荡器(HSE)的频率倍增到系统所需的主时钟频率。

三、时钟树

1. 时钟树

时钟树是指微控制器中各种时钟信号之间的关系和组织结构。

从《STM32F1xx中文参考手册》可以查询时钟树的图示:

STM32学习5 时钟系统_单片机_03

2. 主时钟输出MCO(Main Clock Output)

MCO 是 STM32微控制器中的主时钟输出功能。通过MCO功能,可以将系统的主时钟信号输出到特定的引脚,以供外部设备使用。

STM32微控制器通常提供一个或多个MCO引脚,用于输出主时钟信号。MCO引脚通常可配置为多种功能,如系统时钟(SYSCLK)、高速外部时钟(HSE)、PLL时钟等。

3. USB预分频器

USB预分频器的主要作用是根据输入时钟信号的频率,将其分频以生成USB所需的时钟信号。USB通信中的时钟信号要求精确,因此需要通过预分频器来保证时钟信号的稳定和准确性。

如果要在应用中使用USB接口,PLL必须被设置为输出48或72MHz时钟,用于提供48MHz的USBCLK时钟。

4. SYSCLK

SYSCLK是整个系统的主时钟信号,用于驱动CPU核心和大多数外设的时钟信号。

SYSCLK的源头通常来自于STM32微控制器中的晶体振荡器(HSE)或内部RC振荡器(HSI)。

SYSCLK 最大9倍频 * 8MHz 可以由PLLCLK提供72MHz频率。

5. PLLCLK

PLLCLK是指锁相环(PLL)产生的时钟信号,PLLCLK的频率是通过HSE或HSI的频率进行倍频得到的。
PLLCLK 可以通过相应的寄存器配置来设置PLL的工作参数,如倍频系数(PLLMUL)、分频系数(PLLDIV),以及时钟源的选择(HSE或HSI)。通过合适的配置,可以得到所需的PLLCLK频率。

6. RTC时钟

RTC时钟用于提供准确的实时时钟和日历功能,通常与外部32.768kHz晶体振荡器配合使用。

四、RCC寄存器

RCC(Reset and Clock Control)主要负责系统时钟的控制和管理,它提供了一系列的寄存器,用于配置和控制处理器的时钟源、时钟频率和时钟分频器,以及外设的时钟使能和配置。
在 《STM32F1xx 中文参考手册》中有RCC寄存器描述,本文不再缀述。

五、SystemInit()初始化时钟分析

系统复位后,HSI被选为系统时钟。
在STM32的启动代码中,通常会包含一个名为SystemInit()的函数,该函数用于初始化系统的时钟和其他重要的系统配置。

系统时钟初始化后频率:

  • SYSCLK=72MHz
  • AHB 总线时钟(HCLK=SYSCLK)=72MHz
  • APB1 总线时钟(PCLK1=SYSCLK/2)= 36MHz
  • APB2 总线时钟(PCLK2=SYSCLK/1)=72MHz
  • PLL 主时钟 = 72MHz

在本文对应的开源仓库里,可以查看 SystemInit()函数内容:

STM32学习5 时钟系统_系统时钟_04

这里对部分代码结合RCC寄存器进行分析:

1. SystemInit()

void SystemInit (void)
{
  /* 将RCC时钟配置重置为默认的复位状态(仅用于调试目的) */
  /* 设置HSI位 */
  RCC->CR |= (uint32_t)0x00000001;

  /* 复位SW、HPRE、PPRE1、PPRE2、ADCPRE和MCO位 */
#ifndef STM32F10X_CL
  RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
  RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */   
  
  /* 复位HSEON、CSSON和PLLON位 */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

  /* 复位HSEBYP位 */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

  /* 复位PLLSRC、PLLMUL和USBPRE/OTGFSPRE位 */
  RCC->CFGR &= (uint32_t)0xFF80FFFF;

#ifdef STM32F10X_CL
  /* 复位PLL2ON和PLL3ON位 */
  RCC->CR &= (uint32_t)0xEBFFFFFF;

  /* 禁用所有中断并清除挂起位 */
  RCC->CIR = 0x00FF0000;

  /* 复位CFGR2寄存器 */
  RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
  /* 禁用所有中断并清除挂起位 */
  RCC->CIR = 0x009F0000;

  /* 复位CFGR2寄存器 */
  RCC->CFGR2 = 0x00000000;      
#else
  /* 禁用所有中断并清除挂起位 */
  RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
    
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
  #ifdef DATA_IN_ExtSRAM
    SystemInit_ExtMemCtl(); 
  #endif /* DATA_IN_ExtSRAM */
#endif 

  /* 配置系统时钟频率、HCLK、PCLK2和PCLK1分频器 */
  /* 配置Flash延迟周期并使能预取缓冲区 */
  SetSysClock();

#ifdef VECT_TAB_SRAM
  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* 将向量表重定位到内部SRAM。*/
#else
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* 将向量表重定位到内部Flash。*/
#endif 
}

函数中调用 了 SetSysClock() 来设置系统时钟,跟踪这个函数:

static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
  SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
  SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
  SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
  SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
  SetSysClockTo56();  
#elif defined SYSCLK_FREQ_72MHz
  SetSysClockTo72();
#endif
 
 /* If none of the define above is enabled, the HSI is used as System clock
    source (default after reset) */ 
}

跳转到 SetSysClockTo72()。

2. SetSysClockTo72()

static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* SYSCLK、HCLK、PCLK2和PCLK1配置 ---------------------------*/    
  /* 使能HSE */    
  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 (HSEStatus == (uint32_t)0x01)
  {
    /* 使能预取缓冲区 */
    FLASH->ACR |= FLASH_ACR_PRFTBE;

    /* Flash 2等待状态 */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    

 
    /* HCLK = SYSCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /* PCLK2 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /* PCLK1 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

#ifdef STM32F10X_CL
    /* 配置PLL ------------------------------------------------------*/
    /* PLL2配置:PLL2CLK = (HSE / 5) * 8 = 40 MHz */
    /* PREDIV1配置: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);
  
    /* 使能PLL2 */
    RCC->CR |= RCC_CR_PLL2ON;
    /* 等待PLL2就绪 */
    while((RCC->CR & RCC_CR_PLL2RDY) == 0)
    {
    }
    
   
    /* PLL配置: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    
    /*  PLL配置: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;    

    /* 等待PLL作为系统时钟源 */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
  }
  else
  { 
    /* 如果HSE启动失败,应用程序将具有错误的时钟配置。
         用户可以在这里添加一些代码来处理此错误 */
  }
}

六、STM32 时钟函数

STM32 时钟函数定义在 stm32f10x_rcc.c 文件里。

1. RCC 配置函数

(1)RCC_HSEConfig

示例:

RCC_HSEConfig(ENABLE); // 启用外部高速时钟(HSE)
RCC_HSEConfig(DISABLE); // 禁用外部高速时钟(HSE)

(2)RCC_LSEConfig

(3)RCC_PLLConfig

用于配置 PLL 的输入时钟分频系数和倍频系数。其中参数 div 表示 PLL 的输入时钟分频系数,参数 pllm 表示 PLL 的倍频系数。

RCC_PLLSource参数:
  • RCC_PLLSource_HSI_Div2: PLL 输入时钟为 HSI / 2。
  • RCC_PLLSource_HSE_Div1: PLL 输入时钟为 HSE。
  • RCC_PLLSource_HSE_Div2: PLL 输入时钟为 HSE / 2。
  • RCC_PLLSource_HSE_Prediv: PLL 输入时钟为 HSE/PREDIV。
pllm 参数

pllm 的取值范围为 2 到 16。通过调整 pllm 参数,可以在一定范围内调整 PLL 的输出时钟频率。
例如,当 pllm 取值为 2 时,输出时钟频率将是输入时钟频率的 2 倍;
当 pllm 取值为 16 时,输出时钟频率将是输入时钟频率的 16 倍。

(())RCC_MCOConfig

(5)RCC_SYSCLKConfig

示例:

RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI); // 将HSI配置为系统时钟源
RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE); // 将HSE配置为系统时钟源
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // 将PLL输出配置为系统时钟源

(6)RCC_HCLKConfig

配置 AHB 总线时钟的分频系数,从而设置 HCLK(AHB 总线时钟)的频率。
示例:

RCC_HCLKConfig(RCC_SYSCLK_Div2); // 设置 HCLK 为 SYSCLK 的一半
  • RCC_PCLK1Config
  • RCC_PCLK2Config
  • RCC_RTCCLKConfig
  • RCC_ADCCLKConfig
  • RCC_USBCLKConfig

2. PLL配置函数

用于配置 PLL 的输入源和倍频系数。

void RCC_PLLConfig(uint32_t PLLSource, uint32_t PLLMul);

3. 时钟使能函数

(1)RCC_HSICmd

用于使能或禁用内部高速时钟(HSI)。

(2)RCC_LSICmd

用于使能或禁用内部低速时钟(LSI)。

(3)RCC_PLLCmd

用于使能或禁用PLL(Phase-Locked Loop)。

(4)RCC_RTCCLKCmd

用于使能或禁用实时时钟(RTC)时钟源。
示例:

RCC_RTCCLKCmd(ENABLE); // 启用RTC时钟源
RCC_RTCCLKCmd(DISABLE); // 禁用RTC时钟源

(5)RCC_AHBPeriphClockCmd

使能或禁用指定的AHB总线上的外设时钟。
示例:

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); // 启用GPIOA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, DISABLE); // 禁用GPIOA时钟

(6)RCC_APBxPeriphClockCmd

使能或禁用指定的APB1或APB2总线上的外设时钟。
示例:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 启用USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, DISABLE); // 禁用USART1时钟

七、系统时钟设置步骤

1. 步骤说明

  1. 选择时钟源
    首先需要确定系统时钟源,通常可以是内部时钟源(HSI 或 HSE)或外部时钟源(例如外部晶体振荡器)。
  2. 配置时钟源
    根据需要,使能并配置所选的时钟源。对于外部时钟源,需要配置外部晶体振荡器(HSE)或外部时钟输入(例如 HSE 或者 PLL)。对于内部时钟源,例如 HSI,可以直接使能。
  3. 配置 PLL(可选)
    如果需要更高的系统时钟频率,则可以配置 PLL 来实现时钟倍频。在这一步中,需要设置 PLL 的输入时钟源、倍频系数以及输出时钟频率。
  4. 配置时钟分频器
    配置 AHB、APB1 和 APB2 的分频系数,以确定这些总线的时钟频率。这些分频系数可以根据系统需求进行调整,以满足外设的时钟要求。
  5. 等待时钟稳定
    对于外部时钟源(如 HSE),需要等待其稳定后再切换到该时钟源。
  6. 切换系统时钟
    最后,将系统时钟切换到所选的时钟源。确保切换后,系统时钟以正确的频率运行,并且各个外设和总线的时钟也被正确配置。
  7. 配置 Flash 读取延迟
    根据新的系统时钟频率,配置 Flash 存储器的读取延迟以确保稳定的 Flash 访问速度。这一步对于高频率下的系统运行至关重要。

2. 自定义系统时钟实现

/**
  * @brief  配置外部高速时钟(HSE)和 PLL
  * @param  div: PLL输入时钟分频系数
  *         pllm: PLL倍频系数
  * @retval None
  */
void RCC_HSE_Config(uint32_t div, uint32_t pllm) {
    RCC_DeInit(); // 复位 RCC 寄存器到默认值

    // 使能外部高速时钟(HSE)
    RCC_HSEConfig(RCC_HSE_ON);

    // 等待外部时钟稳定
    if (RCC_WaitForHSEStartUp() == SUCCESS) {
        // 配置 AHB 时钟分频系数
        RCC_HCLKConfig(RCC_SYSCLK_Div1); // AHB 不分频

        // 配置 APB1 和 APB2 时钟分频系数
        RCC_PCLK1Config(RCC_HCLK_Div2); // 低速 APB1 分频为 HCLK/2 36M
        RCC_PCLK2Config(RCC_HCLK_Div1); // 高速 APB2 不分频 72M

        // 配置 PLL 输入时钟分频系数和倍频系数
        RCC_PLLConfig(div, pllm);

        // 使能 PLL
        RCC_PLLCmd(ENABLE);

        // 等待 PLL 就绪
        while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);

        // 将 PLL 作为系统时钟源
        RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

        // 等待系统时钟源稳定
        while (RCC_GetSYSCLKSource() != 0x08);
    }
}

完整代码参考开源仓库:
https://gitee.com/xundh/stm32_arm_learn/tree/master/lesson5