(六)STM32时钟系统RCC——使用HSE/HSI配置时钟

1. STM32 时钟系统介绍

1.1 STM32 时钟来源

        时钟系统相当于 CPU 的脉搏,只有时钟起效了,CPU 才可以跑起来。所有时钟对于 CPU 也是至关重要的。但 STM32 的时钟系统是比较复杂的,51单片机只有一个系统时钟,而 STM32 却有多个时钟源,为什么需要这么多的时钟源呢?

        首先, STM32 本身就非常复杂,外设非常多,但是并不是所有的外设都需要系统时钟这么高的时钟频率,比如,看门狗以及 RTC 只需要几十 KHz的时钟频率即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的 MCU 一般都是采取多时钟源的方法来解决这些问题,不同的资源用不同的时钟源来降低功耗。首先让我们来看看 STM32 的时钟系统图吧:

        在 STM32 中,有五个时钟源,为 ①HSI、②HSE、③LSI、④LSE、⑤PLL。

        从时钟频率来分可以分为高速时钟源和低速时钟源,在这 5 个中 ①HSI,②HSE 以及 ⑤PLL 是高速时钟,③LSI 和 ④LSE 是低速时钟。

        从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中 HSE 和 LSE 是外部时钟源,其他的是内部时钟源。

        下面我们看看 STM32 的 5 个时钟源,我们讲解顺序是按图中红圈标示的顺序:

①、HSI 是高速内部时钟,RC 振荡器,频率为 8MHz。
②、HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围4MHz~16MHz。我们的开发板接的是 8M 的晶振。
③、LSI 是低速内部时钟,RC 振荡器,频率为 40kHz。独立看门狗的时钟源只能是 LSI,同
时 LSI 还可以作为 RTC 的时钟源。
④、LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
⑤、PLL 为锁相环倍频输出,其时钟输入源可选择为 HSI/2、HSE 或者 HSE/2。倍频可选择为2~16 倍,但是其输出频率最大不得超过 72MHz。

1.2 STM32 时钟源的供给

        上面我们简要概括了 STM32 的时钟源,那么这 5 个时钟源是怎么给各个外设以及系统提
供时钟的呢?这里我们将一一讲解。我们还是从图的下方讲解起吧,因为下方比较简单。
        图中我们用 A~E 标示我们要讲解的地方。

A. MCO 时钟来源    

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

我们找到配置 MCO 的函数:

点击 RCC_MCOConfig()函数,该函数可以在 stm32固件库 中 stm32f10x_rcc.h 文件中找到,进去可以看到:

再次点击 CFGR_BYTE4_ADDRESS,去查看它的定义:

得知这就是配置 MCO 的输出是哪个时钟源的寄存器地址。

那是如何确定下这个地址的呢?

根据上图在 STM32 中文参考手册中,查找中输入 MCO :

        找到配置 MCO 的寄存器,时钟配置寄存器(RCC_CFGR)的24、25、26 位;而该寄存器的地址是怎么算的呢? 该寄存器的地址是在 RCC 的基地址进行偏移得到的。

        比如我们参考 STM32 中文参考手册(第2章 存储器和总线架构(2.3 存储器映像))可知,跟 RCC 有关的寄存器地址范围是 0x4002 1000 - 0x4002 13FF,则RCC的基地址是 0x4002 1000,根据上面 时钟配置寄存器 的 偏移地址:0x04,可知 时钟配置寄存器 的地址是 0x4002 1000 + 0x04 = 0x4002 1004。

        STM32 的寄存器都是32位,4字节的存储单元。一个地址对应的是一个字节的存储单元,因此找到配置 MCO 的时钟源的地址就是 0x4002 1007 ,这在 stm32 的库文件已经给宏定义好了。

        找到地址后,我们就可以往这个地址的内存单元中写入数据,来控制MCO的输出是哪个时钟源。 

 可以看到,写100(二进制) 表示MCO 输出的是系统时钟

我们点击 RCC_MCO_SYSCLK,可以看到宏定义,是十六进制的 0x04,就是 0100:

        这就是如何配置 MCO 的输出来源,个人编写代码是比较容易的,但要知道,其实程序最终还是操作寄存器来完成各种外设功能。 

B. RTC 时钟来源

        这里是 RTC 时钟源,从图上可以看出,RTC 的时钟源可以选择 LSI,LSE,以及HSE 的 128 分频。  


C. USB 时钟源

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

D. SYSCLK 系统时钟源

        D 处就是 STM32 的系统时钟 SYSCLK,它是供 STM32 中绝大部分部件工作的时钟源。系统时钟可选择为 PLL 输出、HSI 或者 HSE。系统时钟最大频率为 72MHz,当然你也可以超频,不过一般情况为了系统稳定性是没有必要冒风险去超频的。

E. 外设时钟源

        这里的 E 处是指其他所有外设了。从时钟图上可以看出,其他所有外设的时钟最终来源都是 SYSCLK。SYSCLK 通过 AHB 分频器分频后送给各模块使用。这些模块包括:

  • ①、AHB 总线、内核、内存和 DMA 使用的 HCLK 时钟。
  • ②、通过 8 分频后送给 Cortex 的系统定时器时钟,也就是 systick 了。
  • ③、直接送给 Cortex 的空闲运行时钟 FCLK。
  • ④、送给 APB1 分频器。APB1 分频器输出一路供 APB1 外设使用(PCLK1,最大
  • 频率 36MHz),另一路送给定时器(Timer)2、3、4 、5、6、7倍频器使用。
  • ⑤、送给 APB2 分频器。APB2 分频器分频输出一路供 APB2 外设使用(PCLK2,
  • 最大频率 72MHz),另一路送给定时器(Timer)1 、8倍频器使用。

1.3 APB1 和 APB2 的区别        

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

        APB2 上面连接的是高速外设包括 UART1、SPI1、Timer1、ADC1、ADC2、所有普通 IO 口(PA~PE)、第二功能 IO 口等。居宁老师的《稀里糊涂玩 STM32》资料里面教大家的记忆方法是 2>1, APB2 下面所挂的外设的时钟要比 APB1 的高。


        在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1
外设、APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。后面我们讲解
实例的时候回讲解到时钟使能的方法。

        STM32 时钟系统的配置除了初始化的时候在 system_stm32f10x.c 中的 SystemInit()函数中外,其他的配置主要在 stm32f10x_rcc.c 文件中,里面有很多时钟设置函数,可以打开这个文件浏览一下,基本上看看函数的名称就知道这个函数的作用。在设置时钟的时候,一定要仔细参考 STM32 的时钟图,做到心中有数。

        这里需要指明一下,对于系统时钟,默认情况下是在 SystemInit 函数的 SetSysClock()函数中间判断的,而设置是通过宏定义设置的。我们可以看看 SetSysClock()函数体:

在 system_stm32f10x.c 文件中,可以看到,通过宏定义判断,进行宏定义时钟频率,

这段代码就是通过宏定义判断系统时钟该设置为多大的频率,系统默认宏定义是 72MHz, 

而我们在魔术棒里就进行了全局的宏定义操作,但根据上边的宏定义判断,这里不输入 STM32F10X_HD,好像也什么问题。除非你的芯片是 STM32F10X_LD_VL 系列的芯片,在魔术棒里进行全局宏定义,系统时钟就是 SYSCLK_FREQ_24MHz 。

再在 SystemInit() 函数中找到 SetSysClock() 函数,点击进去,可以看到这个函数就是对系统时钟进行配置。

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
}

通过前边的宏定义,再次判断,应该进入哪个系统时钟函数去。

这里总结以下,SystemInit()函数中设置的系统时钟大小:

SYSCLK(系统时钟)             =72MHz
AHB 总线时钟(使用 SYSCLK) =72MHz
APB1 总线时钟(PCLK1)          =36MHz
APB2 总线时钟(PCLK2)          =72MHz
PLL 时钟                                  =72MHz

2. 端口复用和重映射

 2.1 端口复用功能

        STM32 有很多的内置外设,这些外设的外部引脚都是与 GPIO 复用的。也就是说,一个 GPIO如果可以复用为内置外设的功能引脚,那么当这个 GPIO 作为内置外设使用的时候,就叫做复用也就是说,一个 IO 口不仅可以作为普通的 IO 口来用,还可以是其他外设的引脚,当这个引脚用作外设时,就叫做复用,简单来说就是片内外设占用了IO,就叫做复用
        这部分知识在《STM32 中文参考手册 V10》的 P109,P116~P121 有详细的讲解哪些 GPIO 管脚是可以复用为哪些内置外设的。
        MCU 都有串口,STM32 有好几个串口。比如说 STM32F103ZET6 有 5 个串口,可以查手册知道,串口 1 的引脚对应的 IO 为 PA9,PA10。PA9,PA10 默认功能是 GPIO,所以当PA9,PA10 引脚作为串口 1 的 TX,RX 引脚使用的时候,那就是端口复用

复用端口初始化有几个步骤:
1) GPIO 端口时钟使能。要使用到端口复用,当然要使能端口的时钟了。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

2) 复用的外设时钟使能。比如你要将端口 PA9,PA10 复用为串口,所以要使能串口时钟。复用什么片内外设,当然也要打开该外设的时钟。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

3) 端口模式配置。 在 IO 复用位内置外设功能引脚的时候,必须设置 GPIO 端口的模式,至于
在复用功能下 GPIO 的模式是怎么对应的,这个可以查看手册《STM32 中文参考手册 V10》
P110 的表格“8.1.11 外设的 GPIO 配置”。也就是说,外设在用 GPIO 口时,片内外设有不同的工作模式,对于不同的工作模式,GPIO 有不同的配置,至于怎么配置,看参考手册里的说明。这里我们拿 Usart1 举例:

        从表格中可以看出,我们要配置全双工的串口 1,那么 TX 管脚需要配置为推挽复用输出,RX 管脚配置为浮空输入或者带上拉输入

//USART1_TX PA.9 复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX PA.10 浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);

我们在使用复用功能的是时候,最少要使能 2 个时钟:
1) GPIO 时钟使能
2) 复用的外设时钟使能,同时要初始化 GPIO 以及复用外设功能

2.2 端口重映射

        为了使不同器件封装的外设 IO 功能数量达到最优,可以把一些复用功能重新映射到其他一些引脚上。STM32 中有很多内置外设的输入输出引脚都具有重映射(remap)的功能。我们知道每个内置外设都有若干个输入输出引脚,一般这些引脚的输出端口都是固定不变的,为了让设计工程师可以更好地安排引脚的走向和功能,在 STM32 中引入了外设引脚重映射的概念,即一个外设的引脚除了具有默认的端口外,还可以通过设置重映射寄存器的方式,把这个外设的引脚映射到其它的端口。
        简单的讲就是把管脚的外设功能映射到另一个管脚,但不是可以随便映射的,具体对应关系《STM32 中文参考手册 V10》的 P116 页“8.3 复用功能和调试配置”有讲解。这里我们同样拿串口 1 为例来讲解。

        注意:端口重映射和端口复用不是一个概念,复用是这个 GPIO,除了做默认的普通 IO 口外,还可以承担一部分外设引脚的功能。但,重映射是原来这个 IO 口可以复用片内外设的功能,但为了走线方便,可以将这个片内外设的功能 重映射到其他的 IO口上,简单说就是,把筷子比作IO口,IO口的默认功能就是 用筷子吃饭;端口复用可以认为是,把片上外设看作一个人,这个人拿筷子敲架子鼓,筷子干了原不属于它的功能,这叫做端口复用;重映射,相当于这个人本来是可以用这双筷子的,但由于各种原因,他没用这双筷子,用了另一双筷子,这叫做重映射。

        上图是截取的中文参考手册中的重映射表,从表中可以看出,默认情况下,串口 1 复用的时候的引脚位 PA9,PA10,同时我们可以将 TX 和 RX 重新映射到管脚 PB6 和 PB7 上面去。
        所以重映射我们同样要使能复用功能的时候讲解的 2 个时钟外,还要使能 AFIO 功能时钟,然后要调用重映射函数。详细步骤为:

1)使能 GPIO 时钟使能、复用的外设时钟使能:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

2)使能 AFIO 功能时钟:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

3)开启重映射:

GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);

        这样就将串口的 TX 和 RX 重映射到管脚 PB6 和 PB7 上面了。至于有哪些功能可以重映射,大家除了查看中文参考手册之外,还可以从 GPIO_PinRemapConfig 函数入手查看第一个入口参数的取值范围可以得知。在 stm32f10x_gpio.h 文件中定义了取值范围为下面宏定义的标识符,这里我们贴一小部分:

#define GPIO_Remap_SPI1 ((uint32_t)0x00000001)
#define GPIO_Remap_I2C1 ((uint32_t)0x00000002)
#define GPIO_Remap_USART1 ((uint32_t)0x00000004)
#define GPIO_Remap_USART2 ((uint32_t)0x00000008)
#define GPIO_PartialRemap_USART3 ((uint32_t)0x00140010)
#define GPIO_FullRemap_USART3 ((uint32_t)0x00140030)

        从上面可以看出,USART1 只有一种重映射,而对于 USART3,存在部分重映射完全重映射

        所谓部分重映射就是部分管脚和默认的是一样的,而部分管脚是重新映射到其他管脚。

        而完全重映射就是所有管脚都重新映射到其他管脚。看看手册中的 USART3 重映射表:

        部分重映射就是 PB10,PB11,PB12 重映射到 PC10,PC11,PC12 上。而 PB13 和 PB14 和没有重映射,跟原来的IO口是一样的,都是 USART3_CTS 和 USART3_RTS 对应管脚。我们要使用 USART3 的部分重映射,我们调用函数方法为:

GPIO_PinRemapConfig(GPIO_PartialRemap_USART3, ENABLE);

        完全重映射就是将这两个脚重新映射到 PD11 和 PD12 上去。USART相关的引脚都重映射过去。我们要使用 USART3 的完全重映射,我们调用函数方法为:

GPIO_PinRemapConfig(GPIO_FullRemap_USART3, ENABLE);

3. 需要参考的资料

3.1 存储器映像

《STM32中文参考手册_V10》

2.3 存储器映像

表1 列出了所用 STM32F10xxx中片内外设的起始地址:

3.2 复用功能外设的GPIO配置

《STM32中文参考手册_V10》

8.1.11 外设的 GPIO 配置

很多,参考手册

8.3 复用功能 IO 和 调试配置

主要是复用重映射功能

4. 修改 STM32 的系统时钟 代码

main.c

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

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

    /* 使用HSE(外部晶振提供时钟,8M或者12M,自行配置,一般是8M)时,
     * SYSCLK = 8M * RCC_PLLMul_x, x:[2,3,...16],最高是128M
     */
    HSE_SetSysClock(RCC_PLLMul_9);  //8M * 9 = 72MHz

    /* 使用HSI(内部的RC振荡提供时钟)时,SYSCLK = 4M * RCC_PLLMul_x, x:[2,3,...16],最高是64MH */
//    HSI_SetSysClock(RCC_PLLMul_16);  //4M * 16 = 64MHz

    /* 配置MCO引脚:PA8 对外提供时钟,最高频率不能超过IO口的翻转频率50MHZ
     * MCO 时钟来源可以是:PLLCLK/2 ,HSI,HSE,SYSCLK
     */

    /* 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_init();      //初始化LED
    delay_init();    //初始化延时函数

    while (1)
    {
        // 亮,单个反斜杠在C语言中是续行符语法,表示下一行跟这一行是同一行代码,斜杠后只能直接回车
        LED0_R = 0;
        \
        delay_ms(1000);
        LED0_R = 1;           // 灭
        delay_ms(1000);
    }
}

 clkconfig.c

#include "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();

/*-----------------1、开启HSE ,并等待 HSE 稳定-------------------*/
	
    //使能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);
		
/*-----------------2、设置 AHB、APB2、APB1的预分频因子-------------------*/

        // AHB预分频因子设置为1分频,HCLK = SYSCLK
        RCC_HCLKConfig(RCC_SYSCLK_Div1);

        // APB2预分频因子设置为1分频,PCLK2 = HCLK
        RCC_PCLK2Config(RCC_HCLK_Div1);

        // APB1预分频因子设置为2分频,PCLK1 = HCLK/2
        RCC_PCLK1Config(RCC_HCLK_Div2);

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

        // 设置PLL时钟来源为HSE,设置PLL倍频因子
        // PLLCLK = 8MHz * pllmul
        RCC_PLLConfig(RCC_PLLSource_HSE_Div1, pllmul);
		
/*-----------------4、开启PLL,并等待PLL稳定------------------------------*/

        // 开启PLL
        RCC_PLLCmd(ENABLE);

        // 等待 PLL稳定
        while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
        {
        }

/*-----------------5、把PLLCK切换到系统时钟SYSCLK上-----------------------*/
        RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

/*-----------------6、读取时钟切换状态位,确保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 = 72MHZ
 *       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();

/*-----------------1、开启HSI ,并等待 HSI 稳定-------------------*/
    //使能HSI
    RCC_HSICmd(ENABLE);

    // 等待 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);
		
/*-----------------2、设置 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);

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

        // 设置PLL时钟来源为HSE,设置PLL倍频因子
        // PLLCLK = 4MHz * pllmul
        RCC_PLLConfig(RCC_PLLSource_HSI_Div2, pllmul);

/*-----------------4、开启PLL,并等待PLL稳定--------------------------*/

        RCC_PLLCmd(ENABLE);
        // 等待 PLL稳定
        while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
        {
        }

/*-----------------5、把PLLCK切换到系统时钟SYSCLK上-------------------*/
        RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

/*-----------------6、读取时钟切换状态位,确保PLLCLK被选为系统时钟-----*/
        while (RCC_GetSYSCLKSource() != 0x08)
        {
        }
    }
    else
    {
        // 如果HSI开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理
        // 当HSE开启失败或者故障的时候,单片机会自动把HSI设置为系统时钟,
        // HSI是内部的高速时钟,8MHZ
        while (1)
        {
        }
    }
}


使用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倍频作为系统时钟,这是通常的做法

使用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]
举例:User_SetSysClock(RCC_PLLMul_9);  则设置系统时钟为:4MHZ * 9 = 36MHZ
          User_SetSysClock(RCC_PLLMul_16); 则设置系统时钟为:4MHZ * 16 = 64MHZ
HSI作为时钟来源,经过PLL倍频作为系统时钟,这是在HSE故障的时候才使用的方法
HSI会因为温度等原因会有漂移,不稳定,一般不会用HSI作为时钟来源,除非是迫不得已的情况
如果HSI要作为PLL时钟的来源的话,必须二分频之后才可以,即HSI/2,而PLL倍频因子最大只能是16,所以当使用HSI的时候,SYSCLK最大只能是4M*16=64M

 如上的代码中,注释中已经说明很清楚了,但 HSE_SetSysClock() 函数以及 HSI_SetSysClock()函数,需要自己编写代码。这个代码系列,如果需要,点赞,关注,私信我,这一块的代码以及及其明细的注释代码发给你以及整个工程文件。

        上边的代码,可以通过 HSE_SetSysClock() 函数以及 HSI_SetSysClock()函数 修改系统时钟,这个在注释里也说的很清楚了,然后在 while 循环里点个灯,加延时,修改系统时钟,就可以很明显的看出,delay 函数的延时就不对了。自己动手试试。

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值