【正点原子STM32连载】 第十一章 STM32时钟配置 摘自【正点原子】STM32F103 战舰开发指南V1.2

news2024/10/6 20:26:53

1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html

第十一章 STM32时钟配置

MCU都是基于时序控制的一个系统。这一讲将结合《STM32F10xxx参考手册_V10(中文版).pdf》和《Cortex-M3权威指南》的知识,对STM32F1的整体架构作一个简单的介绍,帮助大家更全面、系统地认识STM32F1系统的主控结构。了解时钟系统在整个STM32系统的贯穿和驱动作用,学会设置STM32的系统时钟。
本章将分为如下几个小节:
11.1 认识时钟树
11.2 如何修改主频

11.1 认识时钟树
数字电路的知识告诉我们:任意复杂的电路控制系统都可以经由门电路组成的组合电路实现。回顾《第五章 STM32基础知识入门》的知识点,我们知道STM32内部也是由多种多样的电路模块组合在一起实现的。当一个电路越复杂,在达到正确的输出结果前,它可能因为延时会有一些短暂的中间状态,而这些中间状态有时会导致输出结果会有一个短暂的错误,这叫做电路中的“毛刺现象”,如果电路需要运行得足够快,那么这些错误状态会被其它电路作为输入采样,最终形成一系列的系统错误。为了解决这个问题,在单片机系统中,设计时以时序电路控制替代纯粹的组合电路,在每一级输出结果前对各个信号进行采样,从而使得电路中某些信号即使出现延时也可以保证各个信号的同步,可以避免电路中发生的“毛刺现象”,达到精确控制输出的效果。
由于时序电路的重要性,因此在MCU设计时就设计了专门用于控制时序的电路,在芯片设计中称为时钟树设计。由此设计出来的时钟,可以精确控制我们的单片机系统,这也是我们这节要展开分析的时钟分析。为什么是时钟树而不是时钟呢?一个MCU越复杂,时钟系统也会相应地变得复杂,如STM32F1的时钟系统比较复杂,不像简单的51单片机一个系统时钟就可以解决一切。对于STM32F1系列的芯片,正常工作的主频可以达到72Mhz,但并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及RTC只需要几十kHZ的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的MCU一般都是采取多时钟源的方法来解决这些问题。
STM32本身非常复杂,外设非常的多,为了保持低功耗工作,STM32的主控默认不开启这些外设功能。用户可以根据自己的需要决定STM32芯片要使用的功能,这个功能开关在STM32主控中也就是各个外设的时钟。
图11.1.1 STM32F1时钟系统图
如图11.1.1为一个简化的STM32F1时钟系统。图中已经把我们主要关注几处标注出来。A部分表示其它电路需要的输入源时钟信号;B为一个特殊的振荡电路“PLL”,由几个部分构成;C为我们重点需要关注的MCU内的主时钟“SYSCLK”;AHB预分频器将SYSCLK分频或不分频后分发给其它外设进行处理,包括到F部分的Cortex-M内核系统的时钟。D、E部分别为定时器等外设的时钟源APB1/APB2。G是STM32的时钟输出功能,其它部分等我们学习到再详细探讨。接下来我们来详细了解这些部分的功能。
11.1.1 时钟源
对于STM32F1,输入时钟源(Input Clock)主要包括HSI,HSE,LSI,LSE。其中,从时钟频率来分可以分为高速时钟源和低速时钟源,其中HSI、HSE高速时钟,LSI和LSE是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中HSE和LSE是外部时钟源;其他是内部时钟源,芯片上电即可产生,不需要借助外部电路。下面我们看看STM32的时钟源。
(1)2个外部时钟源:
高速外部振荡器HSE (High Speed External Clock signal)
外接石英/陶瓷谐振器,频率为4MHz~16MHz。本开发板使用的是8MHz。
低速外部振荡器LSE (Low Speed External Clock signal)
外接32.768kHz石英晶体,主要作用于RTC的时钟源。
(2)2个内部时钟源:
高速内部振荡器HSI(High Speed Internal Clock signal)
由内部RC 振荡器产生,频率为 8MHz。
低速内部振荡器LSI(Low Speed Internal Clock signal)
由内部RC振荡器产生,频率为40kHz,可作为独立看门狗的时钟源。
芯片上电时默认由内部的HSI时钟启动,如果用户进行了硬件和软件的配置,芯片才会根据用户配置调试尝试切换到对应的外部时钟源,所以同时了解这几个时钟源信号还是很有必要的。如何设置时钟的方法我们会在后文提到。
11.1.2 锁相环PLL
锁相环是自动控制系统中常用的一个反馈电路,在STM32主控中,锁相环的作用主要有两个部分:输入时钟净化和倍频。前者是利用锁相环电路的反馈机制实现,后者我们用于使芯片在更高且频率稳定的时钟下工作。
在STM32中,锁相环的输出也可以作为芯片系统的时钟源。根据图11.1.1的时钟结构,使用锁相环时只需要进行三个部分的配置。为了方便查看,截取了使用PLL作为系统时钟源的配置部分,如图11.1.2.1所示。
在这里插入图片描述

图11.1.2.1 PLL时钟配置图
图11.1.2.1借用了在CubeMX下用锁相环配置72MHz时钟的一个示例:
PLLXTPRE:HSE分频器作为PLL输入 (HSE divider for PLL entry)
即图11.1.2.1在标注为①的地方,它专门用于HSE,ST设计它有两种方式,并把它的控制功能放在RCC_CFGR寄存器中,我们引用如图11.1.2.2。
在这里插入图片描述

图11.1.2.2 PLLXTPRE设置选项值
从F103参考手册可知它的值有两个:一是2分频,另一种是1分频(不分频)。
经过HSE分频器处理后的输出振荡时钟信号比直接输入的时钟信号更稳定。
PLLSRC:PLL输入时钟源 (PLL entry clock source)
图中②表示的是PLL时钟源的选择器,同样的,参考F103参考手册:
在这里插入图片描述

图11.1.2.3 PLLSRC锁相环时钟源选择
它有两种可选择的输入源:设计为HSI的二分频时钟,另一个是A处的PLLXTPRE处理后的HSE信号。
PLLMUL:PLL倍频系数 (PLL multiplication factor)
图中③所表示的配置锁相环倍频系数,同样地可以查到在STM32F1系列中,ST设置它的有效倍频范围为2~16倍。
结合图11.1.2.1,要实现72MHz的主频率,我们通过选择HSE不分频作为PLL输入的时钟信号,即输入8Mhz,通过标号③选择倍频因子,可选择2-16倍频,我们选择9倍频,这样可以得到时钟信号为89=72MHz。
11.1.3 系统时钟SYSCLK
STM32的系统时钟SYSCLK为整个芯片提供了时序信号。我们已经大致知道STM32主控是时序电路链接起来的。对于相同的稳定运行的电路,时钟频率越高,指令的执行速度越快,单位时间能处理的功能越多。STM32的系统时钟是可配置的,在STM32F1系列中,它可以为HSI、PLLCLK、HSE中的一个,通过CFGR的位SW[1:0]设置。
讲解PLL作为系统时钟时,根据我们开发板的资源,可以把主频通过PLL设置为72MHz。仍使用PLL作为系统时钟源,如果使用HSI/2,那么可以得到最高主频8MHz/2
16=64MHz。从上面的图11.2.1时钟树图可知,AHB、APB1、APB2、内核时钟等时钟通过系统时钟分频得到。根据得到的这个系统时钟,下面我们结合外设来看一看各个外设时钟源。
在这里插入图片描述

图11.2.3.1 STM32F103系统时钟生成图
大家看图11.2.3.1 STM32F103系统时钟,标号C为系统时钟输入选择,可选时钟信号有外部高速时钟HSE(8M)、内部高速时钟HSI(8M)和经过倍频的PLL CLK(72M),选择PLL CLK作为系统时钟,此时系统时钟的频率为72MHz。系统时钟来到标号D的AHB预分频器,其中可选择的分频系数为1,2,4,8,16,32,64,128,256,我们选择不分频,所以AHB总线时钟达到最大的72MHz。
下面介绍一下由AHB总线时钟得到的时钟:
APB1总线时钟,由HCLK经过标号E的低速APB1预分频器得到,分频因子可以选择1,2,4,8,16,这里我们选择的是2分频,所以APB1总线时钟为36M。由于APB1是低速总线时钟,所以APB1总线最高频率为36MHz,片上低速的外设就挂载在该总线上,例如有看门狗定时器、定时器2/3/4/5/6/7、RTC时钟、USART2/3/4/5、SPI2(I2S2)与SPI3(I2S3)、I2C1与I2C2、CAN、USB设备和2个DAC。
APB2总线时钟,由HCLK经过标号F的高速APB2预分频器得到,分频因子可以选择1,2,4,8,16,这里我们选择的是1即不分频,所以APB2总线时钟频率为72M。与APB2高速总线链接的外设有外部中断与唤醒控制、7个通用目的输入/输出口(PA、PB、PC、PD、PE、PF和PG)、定时器1、定时器8、SPI1、USART1、3个ADC和内部温度传感器。其中标号G是ADC的预分频器在后面ADC实验中详细说明。
此外,AHB总线时钟直接作为SDIO、FSMC、AHB总线、Cortex内核、存储器和DMA的HCLK时钟,并作为Cortex内核自由运行时钟FCLK。
在这里插入图片描述

图11.2.3.2 USB、RTC、MCO相关时钟
标号H是USBCLK,是一个通用串行接口时钟,时钟来源于PLLCLK。STM32F103内置全速功能的USB外设,其串行接口引擎需要一个频率为48MHz的时钟源。该时钟源只能从 PLL输出端获取,可以选择为1.5分频或者1分频,也就是,当需要使用 USB模块时,PLL 必须使能,并且时钟频率配置为48MHz或72MHz。
标号I是MCO输出内部时钟,STM32 的一个时钟输出 IO(PA8),它可以选择一个时钟信号输出,可以选择为PLL输出的 2 分频、HSI、HSE、或者系统时钟。这个时钟可以用来给外部其他系统提供时钟源。
标号J是RTC定时器,其时钟源为HSE/128、LSE或LSI。
11.1.4 时钟信号输出MCO
STM32允许通过设置,通过MCO引脚输出一个稳定的时钟信号。在图11.1.1中标注为“G”的部分。以下四个时钟信号可被选作MCO时钟:
● SYSCLK
● HSI
● HSE
● 除2的PLL时钟
时钟的选择由时钟配置寄存器(RCC_CFGR)中的MCO[2:0]位控制。
我们可以通过MCO引脚来输出时钟信号,测试输出时钟的频率,或作为其它需要时钟信号的外部电路的时钟。
11.2 如何修改主频
STM32F103默认的情况下(比如:串口IAP时或者是未初始化时钟时),使用的是内部8M的HSI作为时钟源,所以不需要外部晶振也可以下载和运行代码的。
下面我们来讲解如何让STM32F103芯片在72MHZ的频率下工作,72MHz是官方推荐使用的最高的稳定时钟频率。而正点原子的STM32F103战舰开发板的外部高速晶振的频率就是8MHz,我们就是在这个晶振频率的基础上,通过各种倍频和分频得到72MHZ的系统工作频率。
11.2.1 STM32F1时钟系统配置
下面我们将分几步给大家讲解STM32F1时钟系统配置过程,这部分内容很重要,请大家认真阅读。
第1步:配置HSE_VALUE
讲解STM32F1xx_hal_conf.h文件的时候,我们知道需要宏定义HSE_VALUE匹配我们实际硬件的高速晶振频率(这里是8MHZ),代码中通过使用宏定义的方式来选择HSE_VALUE的值是25M或者8M,这里我们不去定义USE_STM3210C_EVAL这个宏或者全局变量即可,选择定义HSE_VALUE的值为8M。代码如下:

#if !defined (HSE_VALUE)
#if defined(USE_STM3210C_EVAL)
#define HSE_VALUE 25000000U 	/*!< Value of the External oscillator in Hz */
#else 
#define HSE_VALUE 8000000U 		/*!< Value of the External oscillator in Hz */ 
#endif 
#endif /* HSE_VALUE */2步:调用SystemInit函数
我们介绍启动文件的时候就知道,在系统启动之后,程序会先执行SystemInit函数,进行系统一些初始化配置。启动代码调用SystemInit函数如下:
Reset_Handler    PROC
                 EXPORT  Reset_Handler                    [WEAK]
                 IMPORT  SystemInit
                 IMPORT  __main

                 LDR     R0, =SystemInit
                 BLX     R0
                 LDR     R0, =__main
                 BX      R0
                 ENDP

下面我们来看看system_stm32f1xx.c文件下定义的SystemInit程序,源码在176行到188行,简化函数如下。

void SystemInit (void)
{
#if defined(STM32F100xE) || defined(STM32F101xE) || defined(STM32F101xG) || defined(STM32F103xE) || defined(STM32F103xG)
  #ifdef DATA_IN_ExtSRAM
    SystemInit_ExtMemCtl(); 
  #endif /* 配置扩展SRAM */
#endif 

  /* 配置中断向量表 */
#if defined(USER_VECT_TAB_ADDRESS)
  SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#endif /* USER_VECT_TAB_ADDRESS */
}

从上面代码可以看出,SystemInit主要做了如下两个方面工作:

  1. 外部存储器配置
  2. 中断向量表地址配置
    然而我们的代码中实际并没有定义DATA_IN_ExtSRAM和USER_VECT_TAB_ADDRESS这两个宏,实际上SystemInit对于正点原子的例程并没有起作用,但我们保留了这个接口。从而避免了去修改启动文件。另外,是可以把一些重要的初始化放到SystemInit这里,在main函数运行前就把重要的一些初始化配置好(如ST这里是在运行main函数前先把外部的SRAM初始化),这个我们一般用不到,直接到main函数中处理即可,但也有厂商(如RT-Thread)就采取了这样的做法,使得main函数更加简单,但对于初学者,我们暂时不建议这种用法。
    HAL库的SystemInit函数并没有任何时钟相关配置,所以后续的初始化步骤,我们还必须编写自己的时钟配置函数。
    第3步:在main函数里调用用户编写的时钟设置函数
    我们打开HAL库例程实验1跑马灯实验,看看我们在工程目录Drivers\SYSTEM分组下面定义的sys.c文件中的时钟设置函数sys_stm32_clock_init的内容:
/**
 * @brief       系统时钟初始化函数
 * @param       plln: PLL倍频系数(PLL倍频), 取值范围: 2~16
                  中断向量表位置在启动时已经在SystemInit()中初始化
 * @retval      无
 */
void sys_stm32_clock_init(uint32_t plln)
{
    HAL_StatusTypeDef ret = HAL_ERROR;
    RCC_OscInitTypeDef rcc_osc_init = {0};
    RCC_ClkInitTypeDef rcc_clk_init = {0};

    rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE;  	/* 选择要配置HSE */
    rcc_osc_init.HSEState = RCC_HSE_ON;                       	/* 打开HSE */
    rcc_osc_init.HSEPredivValue = RCC_HSE_PREDIV_DIV1;     	/* HSE预分频系数 */
    rcc_osc_init.PLL.PLLState = RCC_PLL_ON;                  	/* 打开PLL */
    rcc_osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSE;       	/* PLL时钟源选择HSE */
    rcc_osc_init.PLL.PLLMUL = plln;                             /* PLL倍频系数 */
    ret = HAL_RCC_OscConfig(&rcc_osc_init);                   	/* 初始化 */

    if (ret != HAL_OK)
    {
        while (1); /* 时钟初始化失败后,程序将可能无法正常执行,可以在这里加入自己的处理 */
    }

    /* 选中PLL作为系统时钟源并且配置HCLK,PCLK1和PCLK2*/
rcc_clk_init.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK 
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
    rcc_clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;/* 设置系统时钟来自PLL */
    rcc_clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1;         /* AHB分频系数为1 */
    rcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV2;          /* APB1分频系数为2 */
rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV1;          /* APB2分频系数为1 */
/* 同时设置FLASH延时周期为2WS,也就是3个CPU周期。 */
    ret = HAL_RCC_ClockConfig(&rcc_clk_init, FLASH_LATENCY_2);  

    if (ret != HAL_OK)
    {
        while (1);  /* 时钟初始化失败后,程序将可能无法正常执行,可以在这里加入自己的处理 */
    }
}

函数sys_stm32_clock_init就是用户的时钟系统配置函数,除了配置PLL相关参数确定SYSCLK值之外,还配置了AHB、APB1和APB2的分频系数,也就是确定了HCLK,PCLK1和PCLK2的时钟值。
我们首先来看看使用HAL库配置STM32F1时钟系统的一般步骤:

  1. 配置时钟源相关参数:调用函数HAL_RCC_OscConfig()。
  2. 配置系统时钟源以及SYSCLK、AHB、APB1和APB2的分频系数:调用函数HAL_RCC_ClockConfig()。
    下面我们详细讲解这个2个步骤。
    步骤1:配置时钟源相关参数,使能并选择HSE作为PLL时钟源,配置PLL1,我们调用的函数为HAL_RCC_OscConfig(),该函数在HAL库头文件STM32F1xx_hal_rcc.h中声明,在文件STM32F1xx_hal_rcc.c中定义。首先我们来看看该函数声明:
    HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct);
    该函数只有一个形参,就是结构体RCC_OscInitTypeDef类型指针。接下来我们看看结构体RCC_OscInitTypeDef的定义:
typedef struct
{
  uint32_t OscillatorType;         /* 需要选择配置的振荡器类型 */
  uint32_t HSEState;                /* HSE状态 */
uint32_t HSEPredivValue;         /* HSE预分频值 */
  uint32_t LSEState;                /* LSE状态 */
  uint32_t HSIState;                /* HIS状态 */
  uint32_t HSICalibrationValue;   /* HIS校准值 */
  uint32_t LSIState;                /* LSI状态 */
  RCC_PLLInitTypeDef PLL;          /* PLL配置 */
}RCC_OscInitTypeDef;
该结构体前面几个参数主要是用来选择配置的振荡器类型。比如我们要开启HSE,那么我们会设置OscillatorType的值为RCC_OSCILLATORTYPE_HSE,然后设置HSEState的值为RCC_HSE_ON开启HSE。对于其他时钟源:HIS、LSI、LSE,配置方法类似。
RCC_OscInitTypeDef这个结构体还有一个很重要的成员变量是PLL,它是结构体RCC_PLLInitTypeDef类型。它的作用是配置PLL相关参数,我们来看看它的定义:
typedef struct
{
  uint32_t PLLState;     /* PLL状态 */
  uint32_t PLLSource;    /* PLL时钟源 */
  uint32_t PLLMUL;        /* PLL倍频系数M */
}RCC_PLLInitTypeDef;

从RCC_PLLInitTypeDef;结构体的定义很容易看出该结构体主要用来设置PLL时钟源以及相关分频倍频参数。这个结构体的定义的相关内容请结合时钟树中红色框的内容一起理解。
接下来我们看看我们的时钟初始化函数sys_stm32_clock_init中的配置内容:

/* 使能HSE,并选择HSE作为PLL时钟源,配置PLLMUL */
RCC_OscInitTypeDef rcc_osc_init = {0};
rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE; 	/* 使能HSE */
rcc_osc_init.HSEState = RCC_HSE_ON;                    	/* 打开HSE */
rcc_osc_init.HSEPredivValue = RCC_HSE_PREDIV_DIV1;  	/* HSE预分频 */
rcc_osc_init.PLL.PLLState = RCC_PLL_ON;          	  	/* 打开PLL */
rcc_osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSE;      	/* PLL时钟源为HSE */
rcc_osc_init.PLL.PLLMUL = plln;                         	/* 主PLL倍频因子 */
ret = HAL_RCC_OscConfig(&rcc_osc_init);               	/* 初始化 */

通过函数的该段程序,我们开启了HSE时钟源,同时选择PLL时钟源为HSE,然后把sys_stm32_clock_init的形参直接设置作为PLL的参数M的值,这样就达到了设置PLL时钟源相关参数的目的。
设置好PLL时钟源参数之后,也就是确定了PLL的时钟频率,然后到我们的步骤2。
步骤2:配置系统时钟源,以及SYSCLK、AHB、APB1和APB2相关参数,用函数HAL_RCC_ClockConfig(),声明如下:

HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, 
uint32_t FLatency);
该函数有两个形参,第一个形参RCC_ClkInitStruct是结构体RCC_ClkInitTypeDef类型指针变量,用于设置SYSCLK时钟源以及SYSCLK、AHB、APB1和APB2的分频系数。第二个形参FLatency用于设置FLASH延迟。
RCC_ClkInitTypeDef结构体类型定义比较简单,我们来看看其定义:
typedef struct
{
  uint32_t ClockType;            		/* 要配置的时钟 */
  uint32_t SYSCLKSource;          	/* 系统时钟源 */
  uint32_t AHBCLKDivider;         	/* AHB分频系数 */
  uint32_t APB1CLKDivider;        	/* APB1分频系数 */
  uint32_t APB2CLKDivider; 			/* APB2分频系数 */
}RCC_ClkInitTypeDef;
我们在sys_stm32_clock_init函数中的实际应用配置内容如下:
/****************** 具体配置*************************/
/*选中PLL作为系统时钟源并且配置HCLK,PCLK1和PCLK2*/
/*设置系统时钟时钟源为PLL*/
/*AHB分频系数为1*/
/*APB1分频系数为2*/
/*APB2分频系数为1*/
/*同时设置FLASH延时周期为2WS,也就是3个CPU周期。*/
/***************************************************/
rcc_clk_init.ClockType = (RCC_CLOCKTYPE_SYSCLK | 
RCC_CLOCKTYPE_HCLK | 
RCC_CLOCKTYPE_PCLK1 | 
RCC_CLOCKTYPE_PCLK2);
rcc_clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
rcc_clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1;       
rcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV2;         
rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV1;         
ret = HAL_RCC_ClockConfig(&rcc_clk_init, FLASH_LATENCY_2);  

sys_stm32_clock_init函数中的RCC_ClkInitTypeDef结构体配置内容:
第一个参数ClockType配置表示我们要配置的是SYSCLK、HCLK、PCLK1和PCLK四个时钟。
第二个参数SYSCLKSource配置选择系统时钟源为PLL。
第三个参数AHBCLKDivider配置AHB分频系数为1。
第四个参数APB1CLKDivider配置APB1分频系数为2。
第五个参数APB2CLKDivider配置APB2分频系数为1。
根据我们在mian函数中调用sys_stm32_clock_init(RCC_PLL_MUL9)时设置的形参数值,我们可以计算出,PLL时钟为PLLCLK = HSE * 9 = 8MHz * 9 = 72MHz。
同时我们选择系统时钟源为PLL,所以系统时钟SYSCLK=72MHz。AHB分频系数为1,故频率为HCLK=SYSCLK/1=72MHz。APB1分频系数为2,故其频率为PCLK1=HCLK/2=36MHz。APB2分频系数为1,故其频率为PCLK2=HCLK/1=72MHz。我们总结一下通过调用函数sys_stm32_clock_init(RCC_PLL_MUL9)之后的关键时钟频率值:

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

最后我们来看看函数HAL_RCC_ClockConfig 第二个入口参数FLatency的含义,为了使FLASH读写正确(因为72Mhz的时钟比Flash的操作速度24Mhz要快得多,操作速度不匹配容易导致Flash操作失败),所以需要设置延时时间。对于STM32F1系列,FLASH延迟配置参数值是通过下表11.2.1.1来确定的,具体可以参考《STM32F10xxx闪存编程参考手册》3寄存器说明/3.1闪存访问控制寄存器。

表11.2.1.1 FLASH推荐的等待状态和编程延迟数
从上可以看出,我们设置值为FLASH_LATENCY_2,也就是2WS,也就是3个CPU周期,为什么呢?因为经过上面的配置之后,系统时钟频率达到了最高的72MHz,对应的就是两个等待状态,所以选择FLASH_LATENCY_2。
时钟系统配置相关知识就给大家讲解到这里。
11.2.2 STM32F1时钟使能和配置
上一节我们讲解了时钟系统配置步骤。在配置好时钟系统之后,如果我们要使用某些外设,例如GPIO,ADC等,我们还要使能这些外设时钟。这里大家必须注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32的外设时钟使能是在RCC相关寄存器中配置的。因为RCC相关寄存器非常多,有兴趣的同学可以直接打开《STM32F10xxx参考手册_V10(中文版).pdf》6.3小节查看所有RCC相关寄存器的配置。接下来我们来讲解通过STM32F1的HAL库使能外设时钟的方法。
在STM32F1的HAL库中,外设时钟使能操作都是在RCC相关固件库文件头文件STM32F1xx_hal_rcc.h定义的。大家打开STM32F1xx_hal_rcc.h头文件可以看到文件中除了少数几个函数声明之外大部分都是宏定义标识符。外设时钟使能在HAL库中都是通过宏定义标识符来实现的。首先,我们来看看GPIOA的外设时钟使能宏定义标识符:

#define __HAL_RCC_GPIOA_CLK_ENABLE()   do { \
                                __IO uint32_t tmpreg; \
                                SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);\
                                tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);\
                                UNUSED(tmpreg); \
                              } while(0U)

这段代码主要是定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),它的核心操作是通过下面这行代码实现的:
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);
这行代码的作用是,设置寄存器RCC->APB2ENR的相关位为1,至于是哪个位,是由宏定义标识符RCC_APB2ENR_IOPAEN的值决定的,而它的值为:

#define RCC_APB2ENR_IOPAEN_Pos               (0U)
#define RCC_APB2ENR_IOPAEN_Msk               (0x1UL << RCC_APB2ENR_IOPAEN_Pos)  
#define RCC_APB2ENR_IOPAEN                    RCC_APB2ENR_IOPAEN_Msk

上面三行代码很容易计算出来RCC_APB2ENR_IOPAEN= (0x00000001<<2),因此上面代码的作用是设置寄存器RCC->APB2ENR寄存器的位2为1。我们可以从STM32F1的参考手册中搜索APB2ENR寄存器定义,位2的作用是用来使用GPIOA时钟。APB2ENR寄存器的位2描述如下:
位0 IOPAEN:IO端A时钟使能(I/O port A clock enable)
由软件置‘1’或清‘0’
0:IO端口A时钟关闭
1:IO端口A时钟开启
那么我们只需要在我们的用户程序中调用宏定义标识符就可以实现GPIOA时钟使能。使用方法为:
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能GPIOA时钟 */
对于其他外设,同样都是在STM32F1xx_hal_rcc.h头文件中定义,大家只需要找到相关宏定义标识符即可,这里我们列出几个常用使能外设时钟的宏定义标识符使用方法:

__HAL_RCC_DMA1_CLK_ENABLE();    	/* 使能DMA1时钟 */
__HAL_RCC_USART2_CLK_ENABLE();  	/* 使能串口2时钟 */ 
__HAL_RCC_TIM1_CLK_ENABLE();    	/* 使能TIM1时钟 */

我们使用外设的时候需要使能外设时钟,如果我们不需要使用某个外设,同样我们可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。我们同样以GPIOA为例,宏定义标识符为:
#define __HAL_RCC_GPIOA_CLK_DISABLE() (RCC->APB2ENR) &= ~ (RCC_APB2ENR_GPIOAEN)
同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE()的作用是设置RCC->APB2ENR寄存器的位2为0,也就是禁止GPIOA时钟。具体使用方法我们这里就不做过多讲解,我们这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:

__HAL_RCC_DMA1_CLK_DISABLE();   	/* 禁止DMA1时钟 */
__HAL_RCC_USART2_CLK_DISABLE(); 	/* 禁止串口2时钟 */
__HAL_RCC_TIM1_CLK_DISABLE();   	/* 禁止TIM1时钟 */

关于STM32F1的外设时钟使能和禁止方法我们就给大家讲解到这里。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/548336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

log4net在Asp.net MVC4中的使用

1、安装log4net插件 新建Asp.net MVC4项目&#xff0c;并在Nuget控制台输入命令&#xff0c;或者直接搜索log4net在线安装&#xff0c;安装log4net >Install-Package log4net -Version 2.0.15 2、配置web.config文件 <?xml version"1.0" encoding"ut…

小试牛刀:应用深度强化学习优化文本摘要思路及在裁判文书摘要上的实践效果

一、引言 近期&#xff0c;随着大模型的出现&#xff0c;强化学习再一次的引起了本人的兴趣&#xff0c;本文将应用深度强化学习来优化文本摘要模型&#xff0c;使生成的摘要更加的流畅。在此之前&#xff0c;大家都采用了很多种方式训练摘要系统&#xff0c;例如&#xff1a;…

MySQL的主从实战

MySQL的主从实战 1、Mysql主从的必要性 访问量不断增减&#xff0c;Mysql服务器的压力增大&#xff1b;就需要对Mysql进行优化与改造&#xff1b; 实现Mysql的高可用MySQL的主从复制搭建 主从搭建的目的就是实现数据库冗余备份同步主服务器和Slave服务器&#xff0c;一旦Mas…

Vivado综合属性系列之七 DONT TOUCH

目录 一、前言 二、DONT TOUCH ​ ​2.1 属性说明 ​ ​2.2 属性用法 ​ ​2.3 工程代码 ​ ​2.4 参考资料 一、前言 ​ ​设计中经常会遇到一些信号&#xff0c;模块等被综合工具优化&#xff0c;而实际这些部分确是我们所需要的&#xff0c;针对这种情况&a…

哨兵机制原理详解

文章目录 初始化 Sentinel三个定时任务&#xff08;重要&#xff09;INFO任务订阅/发布任务心跳任务 Redis节点下线判断主观下线判断客观下线判断 Sentinel Leader 选举故障转移过程整体过程Master 选择算法修改从服务器的复制目标将旧的主服务器变为从服务器 节点上线原Redis节…

如何用Nginx实现对国家/城市以及指定IP的访问限制?

1.前言 在【如何用Nginx代理MySQL连接&#xff0c;并限制可访问IP】一文中&#xff0c;我们实现了通过Nginx代理MySQL连接&#xff0c;并限制了指定IP才能通过Nginx进行连接&#xff0c;以提高数据安全性。 该场景适用于根据具体的IP地址来进行访问限制&#xff0c;假如我们要…

synchronized优化原理

文章目录 一、Monitor1.1 Monitor结构 二、轻量级锁三、锁膨胀四、自旋优化五、偏向锁 一、Monitor Monitor的工作原理也是synchronized底层原理 每个Java对象都可以关联一个Monitor对象&#xff0c;如果使用synchronized给对象上锁之后&#xff0c;该对象头的MarkWord中就被设…

怎样从“点点点”进阶到自动化测试?

为什么要学习自动化测试 在讨论这个问题之前&#xff0c;先来聊一下测试人员的职业发展路线&#xff0c;无非就是两条&#xff0c;技术路线和管理路线&#xff0c;技术路线一般就是功能测试&#xff08;60%&#xff09;-->自动化测试&#xff08;25%&#xff09;-->测试…

fio引发的一些问题

fio引发的一些问题 奇怪的255扇区在nvme驱动中插入打印语句直接编译模块加载源码编译内核 查找内核源码 奇怪的255扇区 由于块设备驱动项目需要测试读写速度&#xff0c;故使用fio工具&#xff0c;没想着深入了解&#xff0c;简单测个速就可以 使用tldr命令得到测试磁盘读写的…

linux内核篇-文件系统(硬盘、虚拟文件系统、文件缓存)

文件系统的意义 之前说的都是在进程在物理内存保存的数据&#xff0c;内存就像一个纸箱子&#xff0c;仅仅是一个暂存数据的地方&#xff0c;而且空间有限。如果我们想要进程结束之后&#xff0c;数据依然能够保存下来&#xff0c;就不能只保存在内存里&#xff0c;而是应该保存…

Nacos-04-@RefreshScope自动刷新原理

Nacos动态刷新原理 Nacos做配置中心的时候&#xff0c;配置数据的交互模式是有服务端push推送的&#xff0c;还是客户端pull拉取的&#xff1f; 短轮询 不管服务端的配置是否发生变化&#xff0c;不停发起请求去获取配置&#xff0c;比如支付订单场景中前端JS不断轮询订单支…

hadoop启动,缺少RM的进程:Error starting ResourceManager【已解决】

Error starting ResourceManager【已解决】 现象解决思路报错内容解决总结 现象 Hadoop启动后 执行jps 查看进程&#xff0c;缺少了 ResourceManager 解决思路 start-all.sh分别会有五个日志产生 缺少哪个进程&#xff0c;就去看谁的日志 报错内容 resourcemanager的log文…

基于 Linux 下的生产者消费者模型

目录 传统艺能&#x1f60e;概念&#x1f618;特点&#x1f60d;优点&#x1f601;基于阻塞队列的生产者消费者模型&#x1f923;模拟实现&#x1f602;基于计算任务的生产者消费者模型&#x1f44c; 传统艺能&#x1f60e; 小编是双非本科大二菜鸟不赘述&#xff0c;欢迎米娜…

chatgpt赋能Python-python3_date

Python 3 Date介绍 Python 3是一种非常流行的编程语言&#xff0c;其中涉及到日期处理的功能非常强大。Python 3支持处理日期、时间和时间刻度&#xff0c;因此可以在各种情况下使用它来管理日期。 日期格式 Python 3支持多种日期格式&#xff0c;如下所示&#xff1a; “Y…

不怕得罪人地推荐这9本黑客书籍

[利益声明] 1、这9本都和我有些关系或缘分&#xff0c;也是我至少过了一遍的&#xff0c;虽然并没都仔细推敲&#xff0c;但是这些书&#xff0c;我还是不得不点个赞。 2、其中一本是我和 xisigr 写的:-)我并不觉得在这不能推荐&#xff0c;因为这本书毕竟卖得很好。 然后&am…

torch.nn.functional.normalize参数说明

torch.nn.functional.normalize参数说明 函数定义参数及功能官方说明三维数据实例解释参数dim0参数dim1参数dim2参数dim-1 参考博文及感谢 函数定义 torch.nn.functional.normalize(input, p2.0, dim1, eps1e-12, outNone) # type: (Tensor, float, int, float, Optional[Tens…

chatgpt赋能Python-python3_9怎么安装jieba库

Python3.9怎么安装jieba库 随着大数据时代的到来&#xff0c;中文分词是一个愈发重要的问题。而jieba是一个基于Python的中文分词工具包&#xff0c;具有高速、易用、解耦的特点&#xff0c;广受开发者的青睐。本文将介绍如何在Python3.9环境下安装jieba库。 什么是jieba库 …

微服务: Seata AT 分布式事务以及配置方式(上篇)

目录 前言简介: 1. 安装seata-at -> 1.1 先看版本, 全局搜一下 -> 1.2 版本说明 alibaba/spring-cloud-alibaba Wiki -> 1.3 选择seata-at版本 -> 1.4 下载后按照下图进行创建文件 ---> 1.4.0 先在nacos创建命名空间seata ---> 1.4.1 registry.conf…

Chrome 的骑士盾,谷歌 Security Princess 访谈

童话故事里的公主都有一种需要被保护的感觉&#xff0c;就像马里奥大叔在这么多年来都要在库巴手上拯救出碧姬公主一样。不过在谷歌的这位 Security Princess 却手执盾牌&#xff0c;守护着大家的 Chrome 浏览器免受恶意程序攻击。小编这次就乘着世界网络安全日的机会&#xff…

微信小程序-生命周期

为什么今天突然总结一下微信小程序的生命周期呢&#xff1f;因为突然发现这个知识点忘得有点干净。所以今天就看一下微信小程序的生命周期是怎么个事吧&#xff01; 目录 生命周期 生命周期的分类 生命周期函数的作用 生命周期函数的分类 生命周期是指一个对象从创建->…