STM32时钟系统(万字讲解+程序演示说明)

目录

1. STM32 时钟树

2. 时钟配置函数

2.1 时钟初始化配置函数

2.2 时钟使能配置函数

3. 自定义系统时钟

4. 实验现象


本篇内容将向大家介绍 STM32 的时钟系统,重点分析STM32的时钟树,只要理解好时钟树,STM32 一切时钟的来龙去脉会非常清楚。

通过介绍 STM32 时钟配置过程,让大家学会如何修改系统时钟频率,最后会通过一个简单的 LED 闪烁程序来讲述如何自定义系统时钟

学习本章可以参考“STM32F1xx 中文参考手册”“复位和时钟控制(RCC)”章节内容

STM32F1xx中文参考手册 https://pan.baidu.com/s/1ciCrhw8JBB5Fv0JG8Ttqrw?pwd=543f 

本章分为如下几部分内容:

1. STM32 时钟树

时钟对于单片机来说是非常重要的,它为单片机工作提供一个稳定的机器周期从而使系统能够正常运行

时钟系统犹如人的心脏,一旦有问题整个系统就崩溃

 STM32 属于高级单片机,其内部有很多的外设,但不是所有外设都使用同一时钟频率工作,比如内部看门狗和 RTC,它只需 30 几 KHz 的时钟频率即可工作,所以内部时钟源就有多种选择

STM32 系统复位后首先进入 SystemInit 函数进行时钟的设置,将 STM32F1 系统时钟设置为 72MHz,然后进入主函数

系统时钟大小如何得来,其他外设的时钟又如何划分,这些问题都可以通过一张时钟树图找到答案,只要理解好时钟树,STM32 一切时钟的来龙去脉就会非常清楚

下面就来了解下时钟树,如下图所示:

我们把时钟树拆分逐个介绍:

在 STM32 时钟系统中,有 5 个重要的时钟源,分别是 LSI、LSE、HSI、HSE、PLL

按照时钟频率分可分为高速时钟源低速时钟源,在这 5 个中 HSI,HSE 以及 PLL 属于高速时钟,LSI 和 LSE 属于低速时钟

按照时钟来源可分为外部时钟源内部时钟源,外部时钟源就是在 STM32 晶振管脚处接入外部晶振的方式获取时钟源,其中 HSE 和 LSE 是外部时钟源,其他的是内部时钟源

下面按照上图中数字顺序来介绍:

(1)图标 1 HSI 是内部高速时钟,RC 振荡器,频率为 8MHz。可作为系统时钟或 PLL 锁相环的输入

(2)图标 2 HSE 是外部高速时钟,芯片的 23 和 24 引脚即为外部高速晶振管脚。可通过外接一个频率范围是 4-16MHz 的时钟或者晶振。HSE 可以作为系统时钟和 PLL 锁相环输入,还可以经过 128 分频后输入给 RTC。

(3)图标 3 LSI 是内部低速时钟,RC 振荡器,频率大约为 40K,可供独立看门狗和 RTC 使用,并且独立看门狗只能使用 LSI 时钟。

(4)图标 4 LSE 是外部低速时钟,通常在此管脚上外接一个 32.768KHz 的晶振,供 RTC使用

(5)图标 5 PLL 是锁相环,用于倍频输出,因为开发板外部高速晶振也只有 8M,C8T6芯片的最大时钟频率是 72M,因此可通过 PLL 锁相环来倍频。

从图标 5 中可以看到,PLL 时钟输入源可选择为 HSI/2、HSE 或者 HSE/2,时钟源经过 2-16 倍频后输入给 PLLCLK,如果系统时钟选择由 PLLCLK 提供,则 PLLCLK 最大值不要超过 72M。

那么它是怎么倍频产生 72MHz 系统时钟的呢?

我们看到在主 PLL 内有倍频器和分频器,如下图所示:

从上图可以看出,PLL 时钟源的输入信号要先经过一个 PLLMUL 倍频器,将HSE 或 HSI 倍频(2-16)后输入给 PLLCLK,如果系统时钟源 SYSCLK 选择 PLLCLK 作为它的来源,则最大值不能超过 72M

虽然可以做超频处理,但会打破系统的稳定性,这个是不划算的,假如 PLLSRC 的时钟来源由 HSE 提供,C8T6 开发板使用的 HSE 是 8M 晶振,经过 PLLMUL 9 倍频后可以输出 72M 时钟频率给 PLLCLK

总结:如果我们选择 HSE 是 PLL 的时钟源,PLL 是 SYSCLK 的时钟源,即 SYSCLK 为72MHz,这个也是我们库函数模板中 SystemInit 所配置的最终系统时钟

在上图时钟树图中我们把常用的时钟用字母框起来,按照它们顺序依次介绍它们是怎么给其他外设和系统提供时钟

(A)MCO 是 STM32 的一个时钟输出 IO(PA8),它可以选择一个时钟信号输出,可以选择为 PLL 输出的 2 分频、 HSI、 HSE 或者系统时钟。这个时钟可以用来给外部其他系统提供时钟源

(B)RTC 时钟。从图中线的流向可知,RTC 时钟来源可以是内部低速的 LSI 时钟,外部低速 LSE 时钟(32.768K),还可以通过 HSE 128 分频后得到

(C)USB 时钟。STM32 中有一个全速功能的 USB 模块,其串行接口引擎需要一个频率为 48MHz 的时钟源,该时钟源只能从 PLL 输出端获取,可以选择为1.5 分频或者 1 分频,也就是当需要使用 USB 模块时,PLL 必须使能,并且PLLCLK 时钟频率配置为 48MHz 或 72MHz

(D)SYSCLK 系统时钟。它是 STM32 中绝大部分部件工作的时钟源。它的时钟来源可以由 HSI、HSE、PLLCLK 提供,相信大家选择 STM32F1 这种高级芯片,都希望有一个比较大的时钟频率,因此选择 PLLCLK 作为系统时钟。PLLCLK 又是从 HSE 或 HSI 经过 PLL 倍频得到。根据前面 PLL 计算关系大家就可以算出系统时钟是多少。

(E)其他所有外设。从时钟图上可以看出,其他所有外设的时钟最终来源都是 SYSCLK。SYSCLK 通过 AHB 分频器分频后送给各模块使用

这些模块包括:


①、AHB 总线、内核、内存和 DMA 使用的 HCLK 时钟

②、通过 8 分频后送给 Cortex 系统定时器时钟,即 SysTick

③、直接送给 Cortex 的空闲运行时钟 FCLK

④、送给 APB1 分频器。 APB1 分频器输出一路供 APB1 外设使用(PCLK1,

最大频率 36MHz),另一路送给定时器(Timer)1、2 倍频使用

⑤、送给 APB2 分频器。 APB2 分频器分频输出一路供 APB2 外设使用

(PCLK2,最大频率 72MHz),另一路送给定时器(Timer)1 倍频器使用

⑥、送给 ADC 分频器。ADC 分频器经过 2、4、6、8 分频后送给 ADC1/2/3 使

用,ADC 最大频率为 14M

⑦、二分频后送给 SDIO 使用


其中需要理解的是 APB1 和 APB2 的区别,APB1 上面连接的是低速外设, 包括电源接口、备份接口、 CAN、 USB、 I2C1、 I2C2、 UART2、 UART3 等, APB2上面连接的是高速外设包括 UART1、 SPI1、 Timer1、 ADC1、 ADC2、GPIO 等

大家可以简单这样记忆:2>1,所以 APB2 的速度大于 APB1 的速度

在时钟树图中我们还可以得到一个重要信息,大多数有关时钟输出部分都有一个使能控制,比如 AHB 总线、APB1 外设、APB2 外设、内核时钟等

当需要使用某个时钟的时候一定要开启它的使能,否则将不工作。在前面我们介绍库函数点亮第一个 LED 实验的时候就使能了 GPIO 的外设时钟,如果不开启,LED 将不工作

2. 时钟配置函数

2.1 时钟初始化配置函数

我们知道 STM32 系统复位后首先进入 SystemInit 函数进行时钟的设置,然后进入主函数 main

那么我们就来看下 SystemInit()函数到底做了哪些操作,首先打开我们前面使用库函数编写的 LED 程序,在system_stm32f10x.c 文件中可以找到 SystemInit()函数,如果不想找的可以直接打开其头文件,通过前面教大家的快速进入函数的方法进入到 SystemInit()内

SystemInit()代码如下:

void SystemInit (void)
{

  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
  /* Set HSION bit */

  RCC->CR |= (uint32_t)0x00000001;

  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
 

#ifndef STM32F10X_CL
  RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
  RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif   
/* STM32F10X_CL */   
  /* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

  /* Reset HSEBYP bit */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

  /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
  RCC->CFGR &= (uint32_t)0xFF80FFFF;

#ifdef STM32F10X_CL
 
/* Reset PLL2ON and PLL3ON bits */
  RCC->CR &= (uint32_t)0xEBFFFFFF;

  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x00FF0000;

  /* Reset CFGR2 register */
  RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)

  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x009F0000;

  /* Reset CFGR2 register */
  RCC->CFGR2 = 0x00000000;      
#else

  /* Disable all interrupts and clear pending bits  */
  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 

  /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
  /* Configure the Flash Latency cycles and enable prefetch buffer */

  SetSysClock();

#ifdef VECT_TAB_SRAM
  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;  
/* Vector Table Relocation in Internal SRAM. */
#else
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;   
/* Vector Table Relocation in Internal FLASH. */
#endif 
}

SystemInit 函数开始通过条件编译,先复位 RCC 寄存器,同时通过设置 CR 寄存器的 HSI 时钟使能位来打开 HSI 时钟。默认情况下如果 CR 寄存器复位,是选择 HSI 作为系统时钟,这点大家可以查看 RCC->CR 寄存器相关位描述可以得知,当低两位配置为 00 的时候(复位之后),会选择 HSI 振荡器为系统时钟

也就是说,调用 SystemInit 函数之后,首先是选择 HSI 作为系统时钟,在设置完相关寄存器后才换成 HSE 作为系统时钟,接下来 SystemInit 函数内部会调用 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
}

在 system_stm32f10x.c 文件的开头就有对此宏定义,系统默认的宏定义是72MHz,如下:

#define SYSCLK_FREQ_72MHz 72000000

如果你要设置为 36MHz,只需要注释掉上面代码,然后加入下面代码即可:

#define SYSCLK_FREQ_36MHz 36000000 

根据该函数内部实现过程可知,直接调用 SetSysClockTo72()函数,此函数功能是将系统时钟 SYSCLK 设置为 72M,AHB 总线时钟设置为 72M,APB2 总线时钟设置为 72M,APB1 总线时钟设置为 36M,PLL 时钟设置为 72M

函数具体实现大家可以打开库函数查看,这里我们就不截取出来。如果 SystemInit 内实现过程

看不懂没有关系,大家只要知道 SystemInit 函数执行完,时钟大小设置如下:

SYSCLK(系统时钟) =72MHz

AHB 总线时钟(HCLK=SYSCLK) =72MHz

APB1 总线时钟(PCLK1=SYSCLK/2) =36MHz

APB2 总线时钟(PCLK2=SYSCLK/1) =72MHz

PLL 主时钟 =72MHz

这些时钟值大家要记住

2.2 时钟使能配置函数

当使用一个外设时,必须先使能它的时钟

那么怎么通过库函数使能时钟呢?如需了解寄存器配置时钟,可以参考《STM32F10x 中文参考手册》“复位和时钟控制(RCC)”章节内容,里面有详细寄存器的介绍

固件库已经把时钟相关寄存器的使能配置都封装好,放在 stm32f10x_rcc.c 和 stm32f10x_rcc.h 中。只需要打开 stm32f10x_rcc.h 文件,会发现有很多的宏定义和时钟使能函数的声明。这些时钟函数可大致分为三类

一类是外设时钟使能函数,一类是时钟源和倍频因子配置函数,还有一类是外设复位函数。当然还有几个获取时钟源配置的函数。下面就来简单介绍下这些函数的使用

首先我们看下时钟使能函数,时钟使能函数包括外设时钟使能和时钟源使能,外设时钟使能相关函数如下:

void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);

void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewStat

e);

void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewStat

e);

上面 3 个时钟使能函数也正是 STM32 的 3 条总线(这个在前面介绍存储器与寄存器章节讲过)。由于 STM32 的外设都是挂接在 AHB 和 APB 总线上的,所以要使能外设时钟,也就是使能对应外设所挂接的总线时钟。比如 GPIO 外设它是挂接在 APB2 总线上的,如果使用 GPIO 外设,就需要先调用:

void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewStat

e);

函数使能 APB2 时钟。有的朋友就会问:我怎么知道哪个外设挂接在哪个总线上呢?很简单,可以通过 STM32 中文参考手册查找,还可以在固件库 stm32f10x_rcc.h 文件中查找。其实这些知识在存储器与寄存器章节已经介绍,大家回过头看下即可。

外设时钟使能函数有两个形参,第一个是你所使用的外设所挂接的时钟,第二个是选择你用的外设时钟使能还是失能。比如我们要使能端口 GPIOB,那么第一个传递的参数是RCC_APB2Periph_GPIOB 宏,第二个传递的参数是 ENABLE 使能。从第一个参数名来看也非常好理解,RCC 表示复位和时钟控制器,APB2 表示GPIOB 是挂接在 APB2 总线上,Periph 表示外设,后面的 GPIOB 表示我们使能的是 GPIOB 端口。第二个参数 ENABLE 表示使能。假如使能 GPIOA 端口时钟,那么只需要修改第一个参数值即可,按照刚才介绍的名意义,可以无需查找即可写出RCC_APB2Periph_GPIOA。其他的外设初始化方法类似

下面我们介绍下时钟源使能函数,通过前面的讲解,知道 STM32 有 5 大类时钟源,这里我们只挑几个重要的时钟源使能函数介绍,如下:

void RCC_HSICmd(FunctionalState NewState);

void RCC_LSICmd(FunctionalState NewState);

void RCC_PLLCmd(FunctionalState NewState);

void RCC_RTCCLKCmd(FunctionalState NewState);

这些函数都是用来使能相应的时钟源,比如我们要使能 PLL 时钟,那么就调用 RCC_PLLCmd 函数,函数有一个形参,和前面外设时钟的第二个参数一样,如果为 ENABLE 表示使能,DISABLE 表示失能

我们再来介绍下另外一类时钟函数——时钟源和倍频因子配置函数。这类函数主要用来选择相应的时钟源和配置时钟倍频因子,比如系统时钟,它可以由HSE、HSI 或者 PLLCLK 作为它的时钟源,具体选择哪个,就是通过时钟源配置函数实现。比如我们设置 HSE 作为系统时钟源,那么调用的函数就是:

 RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE); //配置时钟源为 HSE

在前面也介绍了 APB1 的时钟频率是 HCLK 的 2 分频。那么可以调用下面这个函数来实现:

RCC_PCLK1Config(RCC_HCLK_Div2); //设置低速 APB1 时钟(PCLK1

时钟倍频因子配置函数主要用来修改系统的时钟频率。在本章最后一节我们会通过一个简单 LED 闪烁程序来说明修改倍频因子后时钟的变化,最后介绍下另外一类时钟函数——外设复位函数。其函数如下:

void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewStat

e);

void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewStat

e); 

在 STM32F10x 高容量的芯片中没有 RCC_AHBPeriphResetCmd 函数。这类函数与前面讲解的外设时钟使能函数用法一样,只不过外设时钟使能函数是用于使能外设时钟,而这类函数是用于外设复位,从函数名也可以区分出来

其他的函数大家可以自行查找其功能和用法

3. 自定义系统时钟

在时钟树的讲解中我们知道,通过修改 PLLMUL 中的倍系数值(2-16)可以改变系统的时钟频率。在库函数中也有对时钟倍频因子配置的函数,如下:

void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t RCC_PLLMul);

第一个参数是 PLL 时钟源选择,我们例程中采用的都是 HSE 作为 PLL 的时钟源,可以设置为 RCC_PLLSource_HSE_Div1/RCC_PLLSource_HSE_Div2。第二个参数就是倍频因子值(RCC_PLLMul_2~RCC_PLLMul_16)。

为了方便朋友们能够修改系统时钟,我们这里自定义一个系统时钟初始化函数,我们将函数放在对应实验程序的 main.c 中。具体代码如下:

/***************************************************************************

****

* 函 数 名 : RCC_HSE_Config

* 函数功能 : 自定义系统时钟,可以通过修改 PLL 时钟源和倍频系数实现时钟调整

* 输 入 : div:RCC_PLLSource_HSE_Div1/RCC_PLLSource_HSE_Div2

pllmRCC_PLLMul_2-RCC_PLLMul_16

* 输 出 : 无

****************************************************************************

***/

void RCC_HSE_Config(u32 div,u32 pllm) //自定义系统时间(可以修改时钟)

{

RCC_DeInit(); //将外设 RCC 寄存器重设为缺省值

RCC_HSEConfig(RCC_HSE_ON);//设置外部高速晶振(HSE

if(RCC_WaitForHSEStartUp()==SUCCESS) //等待 HSE 起振

{

RCC_HCLKConfig(RCC_SYSCLK_Div1);//设置 AHB 时钟(HCLK

RCC_PCLK1Config(RCC_HCLK_Div2);//设置低速 AHB 时钟(PCLK1

RCC_PCLK2Config(RCC_HCLK_Div1);//设置高速 AHB 时钟(PCLK2

RCC_PLLConfig(div,pllm);//设置 PLL 时钟源及倍频系数

RCC_PLLCmd(ENABLE);//使能或者失能 PLL

while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY)==RESET);//检查指定的 RCC 标志位设置与否,PLL 就绪

RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);//设置系统时钟(SYSCLK

while(RCC_GetSYSCLKSource()!=0x08);//返回用作系统时钟的时钟源,0x08PLL 作为系统时钟

}

}

函数具体实现过程在程序中已经注释,大家可以参考注释。在函数中设置倍频因子时,我们给他传递了形参中的变量,这样做的好处是当你调用此函数时,只需要修改传递给函数形参内的值即可修改系统时钟,无需修改函数内部程序。在未修改系统时钟时,系统初始化后的时钟是 72M,对应着此函数参数设置如下:

RCC_HSE_Config(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);

如果现在我们想让系统时钟为 36M,只需要将参数值修改即可,如下:

RCC_HSE_Config(RCC_PLLSource_HSE_Div2,RCC_PLLMul_9);

此时修改的是 div 这个参数值,此参数用来对 HSE 时钟分频系数设置,从时钟树可知,HSE 可以直接流入到 PLLSRC,还可以经过 2 分频后给 PLLSRC。它的取值为RCC_PLLSource_HSE_Div1 或 RCC_PLLSource_HSE_Div2。

最后我们可以通过一个 LED 指示灯闪烁速度来反映系统时钟修改后的效果。

主函数代码如下:

/*******************************************************************************
* 函 数 名         : RCC_HSE_Config
* 函数功能           : 自定义系统时钟,可以通过修改PLL时钟源和倍频系数实现时钟调整
* 输    入         : div:RCC_PLLSource_HSE_Div1/RCC_PLLSource_HSE_Div2
                     pllm:RCC_PLLMul_2-RCC_PLLMul_16
* 输    出         : 无
******************************************************************************

void delay(u32 i)
{
    while(i--);
}

/*******************************************************************************

* 函 数 名 : main

* 函数功能 : 主函数

* 输 入 : 无

* 输 出 : 无

*******************************************************************************/

int main()

{

RCC_HSE_Config(RCC_PLLSource_HSE_Div2,RCC_PLLMul_9);

//36M

LED_Init();

while(1)

{

GPIO_ResetBits(LED0_PORT,LED0_PIN);//点亮 LED

delay(6000000);

GPIO_SetBits(LED0_PORT,LED0_PIN);

delay(6000000);

}

}

如果将 div 原先的 1 值修改为 2,此时系统时钟即为 36M,相当于速度慢了一倍。LED 闪烁的速度也就慢了一倍。

注意:不要把 STM32 系统时钟设置超过 72M 使用,否则很容易崩溃

4. 实验现象

核心板中的 D2 指示灯闪烁速度明显变慢
视频审核还未通过,不过从总体来说,视频分为两个过程,D2指示灯会一亮一灭进行闪烁,如下图所示:
灭状态:

亮状态:

审核通过了,视频如下: 

STM32时钟树系统实验现象

  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值