【正点原子STM32连载】第八章 APM32F407时钟系统介绍 摘自【正点原子】APM32F407最小系统板使用指南

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

第八章 APM32F407时钟系统介绍

MCU都是基于时序控制的系统,本章将为APM32F407的时钟系统作一个简单的介绍,帮助读者更全面、系统地认识APM32F407的时钟系统结构,并掌握APM32F407的时钟配置。
本章分为如下几个小节:
8.1 认识时钟树
8.2 配置系统主频
8.3 开启和关闭外设时钟

8.1 认识时钟树
从数字电路的知识可以知道:任意复杂的电路控制系统都可以经由门电路组成的组合电路实现。APM32内部也是由多种多样的电路模块组合在一起实现的。若一个电路越复杂,在达到正确的输出结果前,它可能因为延时会有一些短暂的中间状态,而这些中间状态有时会导致输出结果会有一个短暂的错误,这叫做电路中的“毛刺现象”,如果电路需要运行得足够快,那么这些错误状态会被其它电路作为输入采样,最终形成一系列的系统错误。为了解决这个问题,在单片机系统中,设计时以时序电路控制替代纯粹的组合电路,在每一级输出结果前对各个信号进行采样,从而使得电路中某些信号即使出现延时也可以保证各个信号的同步,可以避免电路中发生的“毛刺现象”,达到精确控制输出的效果。
由于时序电路的重要性,因此在MCU设计时就设计了专门用于控制时序的电路,在芯片设计中称为时钟树设计。由此设计出来的时钟,可以精确控制单片机系统,这也是本小节要展开分析的。为什么是时钟树而不是时钟呢?一个MCU越复杂,时钟系统也会相应的变得复杂,如APM32F407的时钟系统就比较复杂,不像简单的51单片机,一个系统时钟就可以解决一切。对于APM32F407系列的芯片,正常工作的主频可以达到168MHz,但并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及RTC只需要几十KHz的时钟频率即可工作。同一个电路,时钟频率越快功耗就越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的MCU一般都是采取多时钟的方法来解决这些问题。
APM32本身相对复杂,外设资源非常丰富,为了保证低功耗,APM32上电或复位后默认不开启这些外设功能,即不为这些外设提供时钟。用户可以根据自己的需要决定APM32芯片要使用的功能,即为这些外设提供时钟,同时也要配置时钟源和时钟的频率。
第八章 APM32F407时钟系统介绍

MCU都是基于时序控制的系统,本章将为APM32F407的时钟系统作一个简单的介绍,帮助读者更全面、系统地认识APM32F407的时钟系统结构,并掌握APM32F407的时钟配置。
本章分为如下几个小节:
8.1 认识时钟树
8.2 配置系统主频
8.3 开启和关闭外设时钟

8.1 认识时钟树
从数字电路的知识可以知道:任意复杂的电路控制系统都可以经由门电路组成的组合电路实现。APM32内部也是由多种多样的电路模块组合在一起实现的。若一个电路越复杂,在达到正确的输出结果前,它可能因为延时会有一些短暂的中间状态,而这些中间状态有时会导致输出结果会有一个短暂的错误,这叫做电路中的“毛刺现象”,如果电路需要运行得足够快,那么这些错误状态会被其它电路作为输入采样,最终形成一系列的系统错误。为了解决这个问题,在单片机系统中,设计时以时序电路控制替代纯粹的组合电路,在每一级输出结果前对各个信号进行采样,从而使得电路中某些信号即使出现延时也可以保证各个信号的同步,可以避免电路中发生的“毛刺现象”,达到精确控制输出的效果。
由于时序电路的重要性,因此在MCU设计时就设计了专门用于控制时序的电路,在芯片设计中称为时钟树设计。由此设计出来的时钟,可以精确控制单片机系统,这也是本小节要展开分析的。为什么是时钟树而不是时钟呢?一个MCU越复杂,时钟系统也会相应的变得复杂,如APM32F407的时钟系统就比较复杂,不像简单的51单片机,一个系统时钟就可以解决一切。对于APM32F407系列的芯片,正常工作的主频可以达到168MHz,但并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及RTC只需要几十KHz的时钟频率即可工作。同一个电路,时钟频率越快功耗就越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的MCU一般都是采取多时钟的方法来解决这些问题。
APM32本身相对复杂,外设资源非常丰富,为了保证低功耗,APM32上电或复位后默认不开启这些外设功能,即不为这些外设提供时钟。用户可以根据自己的需要决定APM32芯片要使用的功能,即为这些外设提供时钟,同时也要配置时钟源和时钟的频率。

图8.1.1 APM32F407时钟树
上图就是APM32F407的时钟树框图
①:该部分是四个时钟源,分别为:LSI(内部低速时钟)、LSE(外部低速时钟)、HSI(内部高速时钟)、HSE(外部高速时钟),这四个时钟源是整个时钟树的“源头”。
②:该部分为PLL(锁相环),这一部分内部是较为复杂的模拟电路,主要用于对输入的时钟信号倍频后输出,输入的时钟源可以来自HSI或HSE。
③:该部分主要用于选择系统时钟的时钟源,从图中可以看出,该选择器的输入有HSE、HSI和PLL1,输出作用于SYSCLK,而SYSCLK通过AHB Prescaler分频后输出,该输出将直接或间接地作为AHB、APB1和APB2总线上外设的时钟源,其中FCLK就是MCU内核的时钟,这部分对整个系统的运行起着至关重要的作用。
④:AHB Prescaler分频后的时钟再通过APB1 Prescaler和APB2 Prescaler分频后分别作为APB1和APB2总线上外设的时钟。
8.1.1 时钟源
APM32F407有四个时钟源,分别为:LSI(内部低速时钟)、LSE(外部低速时钟)、HSI(内部高速时钟)、HSE(外部高速时钟)。按低速时钟源和高速时钟源可分为LSI、LSE和HSI、HSE,按内部时钟源和外部时钟源可分为LSI、HSI和LSE、HSE,其中内部时钟源LSI和HSI是MCU芯片内部的时钟源,芯片上电后即可输出时钟,无需借助外部电路。
①:外部高速时钟HSE(High Speed External Clock)
该时钟信号由外部晶体、陶瓷谐振振荡器或外部时钟源产生,频率范围为4MHz~26MHz,正点原子APM32F407最小系统板使用频率为8MHz的晶体振荡器产生该时钟信号。
②:外部低速时钟LSE(Low Speed External Clock)
该时钟信号由外部晶体、陶瓷谐振振荡器或外部时钟源产生,频率为32.768KHz,主要作为RTC的时钟源,正点原子APM32F407最小系统板使用频率为32.768KHz的晶体振荡器产生该时钟信号。
③:内部高速时钟HSI(High Speed Internal Clock)
该时钟信号由内部RC振荡器产生,频率为16MHz。
④:内部低速时钟LSI(Low Speed Internal Clock)
该时钟信号由内部RC振荡器产生,频率为20KHz~35KHz(受温度、电压影响),主要作为独立看门狗或RTC的时钟源。
MCU上电时默认会使用HSI作为系统时钟启动,只有完成了相关的时钟配置,MCU才会根据配置切换到对应的时钟源,因此同时了解这几个时钟源是很有必要的,下文将会有提到时钟配置方法的章节。
8.1.2 锁相环
锁相环(PLL)是自动控制系统中常用的一个反馈电路,在APM32主控中,锁相环主要有对输入时钟进行净化和倍频这两个作用,前者是利用锁相环电路的反馈机制实现的,后者是用于使APM32主控稳定地工作于更高的时钟频率。
在APM32中,锁相环的输出时钟可以配置为系统时钟的时钟源,如下图所示:

图8.1.2.1 PLL1输出时钟作为系统时钟示例
从上图中可以看出,HSE的时钟HSECLK被作为PLL的输入时钟后经过B分频输入PLL1,随后在PLL1中经过A倍频和C分频后作为PLL1CLK输出,PLL1CLK被作为SYSCLK。
上面过程中的配置基本可以由PLL1配置寄存器(RCM_PLL1CFG)和时钟配置寄存器(RCM_CFG)完成配置。

图8.1.2.2 PLL1配置寄存器PLLCLKS位
PLLCLKS位用于选择PLL的时钟源,当该位写入0时,使用HSI作为PLL的时钟源;当该位写入1时,使用HSE作为PLL的时钟源。对于示例,将该为配置为1,即使用频率为8MHz的HSE作为PLL的时钟源。

图8.1.2.3 PLL1配置寄存器PLLB位
PLLB位用于对输入PLL的时钟信号进行分频,可分频的范围为2~63。对于示例,将该位配置为8,即对输入的频率为8MHz的HSE进行8分频,那么输入PLL1的时钟频率为1MHz。

图8.1.2.4 PLL1配置寄存器PLL1A位
PLL1A用于对输入PLL1的时钟信号进行倍频,可倍频的范围为2~432。对于示例,将该位配置为336,即可将输入PLL1的1MHz时钟信号倍频为336MHz。

图8.1.2.5 PLL1配置寄存器PLL1C位
PLL1C用于对PLL1待输出的时信号进行分频,可分频的值为2分频、4分频、6分频和8分频。对于示例,将该位写入0,即对336MHz的待输出时钟信号进行2分频后输出168MHz的时钟信号。

图8.1.2.6 时钟配置寄存器SCLKSEL位
SCLKSEL位用于选择SYSCLK的时钟源,可配置HSICLK、HSECLK或PLL1CLK作为SYSCLK的时钟源。对于示例,将该位写入2,即可选择频率为168MHz的PLL1CLK作为SYSCLK时钟源。
8.1.3 系统时钟
APM32的系统时钟(SYSCLK)为整个芯片的绝大多数外设和内核提供时钟信号,对于相同稳定运行的系统,时钟信号的频率越高,系统运行的速度也就越快,单位时间能够处理的事务也就越多。在上一小节中,将频率为8MHz的HSE时钟信号通过PLL1倍频为168MHz的时钟信号作为SYSCLK的时钟源。SYSCLK时钟信号可直接或间接地作为AHB总线、APB1总线、APB2总线和内核的时钟源。

图8.1.3.1 各总线和内核时钟配置示例
①:AHB预分频器,可对SYSCLK进行分频,分频范围为1~512。对于示例,AHB预分频器选择不分频(/1),那么AHB总线、内核、APB1总线和APB2总线输入的时钟信号频率都为168MHz。
②:APB1预分频器,可对待输入APB1总线的时钟信号进行分频,分频范围为1~16,APB1总线的时钟频率最大值为42MHz。对于示例,APB1预分频器选择4,即对频率为168MHz的时钟信号进行4分频为42MHz后作为APB1总线的时钟频率。
③:APB2预分频器,可对待输入APB2总线的时钟信号进行分频,分频范围为1~16,APB2总线的时钟频率最大值为84MHz。对于示例,APB2预分频器选择2,即对频率为168MHz的时钟信号进行2分频为84MHz后作为APB2总线的时钟频率。
8.2 配置系统主频
APM32F407在默认情况下,使用频率为16MHz的HSI作为系统时钟的时钟源,因此无需外部晶振也能够正常烧录和运行程序。
在上一小节中,介绍了如何配置时钟树,以让内核在168MHz的主频下运行,168MHz是Geehy官方针对APM32F407推荐的最大工作频率。
本节将介绍如何在代码中具体的对APM32F407的时钟进行配置。本书配置实验例程都是在main()函数中调用sys_apm32_clock_init()对APM32F407的时钟进行配置的,如下所示:
int main(void)
{
/* 省略其他无关代码 */

sys_apm32_clock_init(336, 8, 2, 7);	/* 配置系统时钟 */

/* 省略其他无关代码 */

}
相信读者对sys_apm32_clock_init()函数传入的参数并不陌生,这就是在上一小节示例中配置PLL1配置寄存器中PLL1A、PLLB、PLL1C和PLLD各位的值。
sys_apm32_clock_init()函数的代码比较多,请读者在实验例程中查看本函数的具体实现,本小节中仅列出该函数的关键代码,如下所示:
uint8_t sys_apm32_clock_init( uint32_t pll1a,
uint32_t pllb,
uint32_t pll1c,
uint32_t plld)
{
/* 本函数的部分代码省略,仅列出关键代码 */

RCM_ConfigAHB(RCM_AHB_DIV_1);				/* 设置AHB时钟的预分频系数为1 */
RCM_ConfigAPB2(RCM_APB_DIV_2);				/* 设置APB2时钟的预分频系数为2 */
RCM_ConfigAPB1(RCM_APB_DIV_4);				/* 设置APB1时钟的预分频系数为4 */

RCM_ConfigPLL1(	RCM_PLLSEL_HSE,				/* 配置PLL1 */
					pllb,
					pll1a,
					(RCM_PLL_SYS_DIV_T)((pll1c >> 1) - 1),
					plld);
RCM_EnablePLL1();							/* 使能PLL1 */

RCM_ConfigSYSCLK(RCM_SYSCLK_SEL_PLL);		/* 选择PLL1CLK作为系统时钟 */

return 0;

}
跟上一小节介绍的一致,AHB、APB2和APB1总线的预分频器分别配置为不分频、2分频和4分频;根据函数的传入参数配置PLL1配置寄存器中PLL1A、PLLB、PLL1C和PLLD各位的值,并将PLL的时钟源配置为HSE;将PLL1CLK配置为SYSCLK的时钟源。上面列出的代码仅是一些关键代码,除了列出代码的操作外还需要一些额外的操作,例如使能HSE并等待HSE稳定等,建议读者打开本书配套的任意实验例程,在sys.c文件中详细地查看本函数的具体实现。
通过sys_apm32_clock_init()函数就能够非常方便地配置系统主频了,例如,在考虑对MCU的性能需求和功耗的场景中,可以适当降低MCU的主频,来达到性能和功耗的平衡。
8.3 开启和关闭外设时钟
在前面的章节中,分析了APM32F407的时钟系统和相关的配置步骤,但是在使用某些外设之前,还需要手动开启相应外设的时钟,这是因为MCU在上电或复位后会默认关闭大部分外设的时钟,以降低功耗,因此,在程序开发中,对于某些开启的时钟但不再使用的外设,也应将其时钟关闭。
APM32F407外设的时钟由RCM(Reset and Clock Management,复位与时钟管理)控制,RCM中控制外设时钟的寄存器被按总线进行分类,涉及了五个寄存器,如下图所示:

图8.3.1 RCM中控制外设时钟的寄存器
其中RCM_AHB1CLKEN、RCM_AHB2CLKEN和RCM_AHB3CLKEN均是用于控制AHB总线上外设的时钟的;RCM_APB1CLKEN用于控制APB1总线上外设的时钟;RCM_APB2CLKEN用于控制APB2总线上外设的时钟。
下面以GPIOA为例,介绍如何配置RMC中相应的寄存器来开启或关闭GPIOA的时钟。首先要确定GPIOA是在AHB总线、APB1总线还是APB2总线上,这里可以借助APM32F407的地址映射来判断,APM32F407的地址映射图,如下图所示:

图8.3.2 APM32F407地址映射图
从上图中可以看出,GPIOA~GPIOI都是AHB总线上的外设,那么就能够确定了GPIOA是位于AHB总线上的外设。
接下来据需要在RCM的RCM_AHB1CLKEN、RCM_AHB2CLKEN和RCM_AHB3CLKEN寄存器中找到控制GPIOA时钟的寄存器,这里有个小技巧,RCM_AHB1CLKEN、RCM_AHB2CLKEN和RCM_AHB3CLKEN这三个寄存器中控制外设时钟的位,基本都是按照外设映射地址的高低来排序的,因为GPIOA~GPIOI的映射地址在AHB总线上是最低的,因此基本可以判断用于控制GPIOA时钟的寄存器为RCM_AHB1CLKEN。在RCM_AHB1CLKEN寄存器中可以找到PAEN位,该位就是用于控制GPIOA外设时钟使能和禁止的,如下图所示:

图8.3.3 RCM_AHB1CLKEN寄存器PAEN位
由上图可知,往RCM_AHB1CLKEN寄存器的PAEN位写入1后,就能配置使能GPIOA外设的时钟,往RCM_AHB1CLKEN寄存器的PAEN位写入0后,就能配置禁止GPIOA外设的时钟。
在Geehy的标准库中寄存器提供了开启和关闭总线外设时钟的函数(函数的实现在apm32f407xx_rcm.c文件中,调用时需包含apm32f407xx_rcm.h的头文件),如下所示:
void RCM_EnableAHB1PeriphClock(uint32_t AHB1Periph);
void RCM_DisableAHB1PeriphClock(uint32_t AHB1Periph);
void RCM_EnableAHB2PeriphClock(uint32_t AHB2Periph);
void RCM_DisableAHB2PeriphClock(uint32_t AHB2Periph);
void RCM_EnableAPB1PeriphClock(uint32_t APB1Periph);
void RCM_DisableAPB1PeriphClock(uint32_t APB1Periph);
void RCM_EnableAPB2PeriphClock(uint32_t APB2Periph);
void RCM_DisableAPB2PeriphClock(uint32_t APB2Periph);
那这些函数需要传入那些参数呢?
在apm32f4xx_rcm.h文件中,通过枚举列出了大部分的外设,如下所示:
/**

  • @brief AHB1 peripheral
    /
    typedef enum
    {
    RCM_AHB1_PERIPH_GPIOA = BIT0, /
    !< Select GPIOA clock /
    RCM_AHB1_PERIPH_GPIOB = BIT1, /
    !< Select GPIOB clock /
    RCM_AHB1_PERIPH_GPIOC = BIT2, /
    !< Select GPIOC clock /
    RCM_AHB1_PERIPH_GPIOD = BIT3, /
    !< Select GPIOD clock /
    RCM_AHB1_PERIPH_GPIOE = BIT4, /
    !< Select GPIOE clock /
    RCM_AHB1_PERIPH_GPIOF = BIT5, /
    !< Select GPIOF clock /
    RCM_AHB1_PERIPH_GPIOG = BIT6, /
    !< Select GPIOG clock /
    RCM_AHB1_PERIPH_GPIOH = BIT7, /
    !< Select GPIOH clock /
    RCM_AHB1_PERIPH_GPIOI = BIT8, /
    !< Select GPIOI clock /
    RCM_AHB1_PERIPH_GPIOJ = BIT9, /
    !< Select GPIOJ clock /
    RCM_AHB1_PERIPH_GPIOK = BIT10, /
    !< Select GPIOK clock /
    RCM_AHB1_PERIPH_CRC = BIT12, /
    !< Select CRC clock /
    RCM_AHB1_PERIPH_FLITF = BIT15, /
    !< Select FLITF clock /
    RCM_AHB1_PERIPH_SRAM1 = BIT16, /
    !< Select SRAM1 clock /
    RCM_AHB1_PERIPH_SRAM2 = BIT17, /
    !< Select SRAM2 clock /
    RCM_AHB1_PERIPH_BKPSRAM = BIT18, /
    !< Select BKPSRAM clock /
    RCM_AHB1_PERIPH_SRAM3 = BIT19, /
    !< Select SRAM3 clock /
    RCM_AHB1_PERIPH_CCMDATARAMEN = BIT20, /
    !< Select CCMDATARAMEN clock /
    RCM_AHB1_PERIPH_DMA1 = BIT21, /
    !< Select DMA1 clock /
    RCM_AHB1_PERIPH_DMA2 = BIT22, /
    !< Select DMA2 clock /
    RCM_AHB1_PERIPH_ETH_MAC = BIT25, /
    !< Select ETH MAC clock /
    RCM_AHB1_PERIPH_ETH_MAC_Tx = BIT26, /
    !< Select ETH MAC TX clock /
    RCM_AHB1_PERIPH_ETH_MAC_Rx = BIT27, /
    !< Select ETH MAC RX clock /
    RCM_AHB1_PERIPH_ETH_MAC_PTP = BIT28, /
    !< Select ETH MAC PTP clock /
    RCM_AHB1_PERIPH_OTG_HS = BIT29, /
    !< Select OTG HS clock /
    RCM_AHB1_PERIPH_OTG_HS_ULPI = BIT30 /
    !< Select OTG HS ULPI clock */
    } RCM_AHB1_PERIPH_T;

/**

  • @brief AHB2 peripheral
    /
    typedef enum
    {
    RCM_AHB2_PERIPH_DCI = BIT0, /
    !< Select DCI clock /
    RCM_AHB2_PERIPH_FPU = BIT1, /
    !< Select FPU clock /
    RCM_AHB2_PERIPH_BN = BIT2, /
    !< Select BN clock /
    RCM_AHB2_PERIPH_SM = BIT3, /
    !< Select SM clock /
    RCM_AHB2_PERIPH_CRYP = BIT4, /
    !< Select CRYP clock /
    RCM_AHB2_PERIPH_HASH = BIT5, /
    !< Select HASH clock /
    RCM_AHB2_PERIPH_RNG = BIT6, /
    !< Select RNG clock /
    RCM_AHB2_PERIPH_OTG_FS = BIT7 /
    !< Select OTG FS clock */
    } RCM_AHB2_PERIPH_T;

/**

  • @brief APB1 peripheral
    /
    typedef enum
    {
    RCM_APB1_PERIPH_TMR2 = BIT0, /
    !< Select TMR2 clock /
    RCM_APB1_PERIPH_TMR3 = BIT1, /
    !< Select TMR3 clock /
    RCM_APB1_PERIPH_TMR4 = BIT2, /
    !< Select TMR4 clock /
    RCM_APB1_PERIPH_TMR5 = BIT3, /
    !< Select TMR5 clock /
    RCM_APB1_PERIPH_TMR6 = BIT4, /
    !< Select TMR6 clock /
    RCM_APB1_PERIPH_TMR7 = BIT5, /
    !< Select TMR7 clock /
    RCM_APB1_PERIPH_TMR12 = BIT6, /
    !< Select TMR12 clock /
    RCM_APB1_PERIPH_TMR13 = BIT7, /
    !< Select TMR13 clock /
    RCM_APB1_PERIPH_TMR14 = BIT8, /
    !< Select TMR14 clock /
    RCM_APB1_PERIPH_WWDT = BIT11, /
    !< Select WWDT clock /
    RCM_APB1_PERIPH_SPI2 = BIT14, /
    !< Select SPI2 clock /
    RCM_APB1_PERIPH_SPI3 = BIT15, /
    !< Select SPI3 clock /
    RCM_APB1_PERIPH_USART2 = BIT17, /
    !< Select USART2 clock /
    RCM_APB1_PERIPH_USART3 = BIT18, /
    !< Select USART3 clock /
    RCM_APB1_PERIPH_UART4 = BIT19, /
    !< Select UART4 clock /
    RCM_APB1_PERIPH_UART5 = BIT20, /
    !< Select UART5 clock /
    RCM_APB1_PERIPH_I2C1 = BIT21, /
    !< Select I2C1 clock /
    RCM_APB1_PERIPH_I2C2 = BIT22, /
    !< Select I2C2 clock /
    RCM_APB1_PERIPH_I2C3 = BIT23, /
    !< Select I2C3 clock /
    RCM_APB1_PERIPH_CAN1 = BIT25, /
    !< Select CAN1 clock /
    RCM_APB1_PERIPH_CAN2 = BIT26, /
    !< Select CAN2 clock /
    RCM_APB1_PERIPH_PMU = BIT28, /
    !< Select PMU clock /
    RCM_APB1_PERIPH_DAC = BIT29, /
    !< Select DAC clock /
    RCM_APB1_PERIPH_UART7 = BIT30, /
    !< Select UART7 clock /
    RCM_APB1_PERIPH_UART8 = (int32_t)BIT31/
    !< Select UART8 clock */
    } RCM_APB1_PERIPH_T;

/**

  • @brief APB2 peripheral
    /
    typedef enum
    {
    RCM_APB2_PERIPH_TMR1 = BIT0, /
    !< Select TMR1 clock /
    RCM_APB2_PERIPH_TMR8 = BIT1, /
    !< Select TMR8 clock /
    RCM_APB2_PERIPH_USART1 = BIT4, /
    !< Select USART1 clock /
    RCM_APB2_PERIPH_USART6 = BIT5, /
    !< Select USART6 clock /
    RCM_APB2_PERIPH_ADC = BIT8, /
    !< Select ADC clock /
    RCM_APB2_PERIPH_ADC1 = BIT8, /
    !< Select ADC1 clock /
    RCM_APB2_PERIPH_ADC2 = BIT9, /
    !< Select ADC2 clock /
    RCM_APB2_PERIPH_ADC3 = BIT10, /
    !< Select ADC3 clock /
    RCM_APB2_PERIPH_SDIO = BIT11, /
    !< Select SDIO clock /
    RCM_APB2_PERIPH_SPI1 = BIT12, /
    !< Select SPI1 clock /
    RCM_APB2_PERIPH_SPI4 = BIT13, /
    !< Select SPI4 clock /
    RCM_APB2_PERIPH_SYSCFG = BIT14, /
    !< Select SYSCFG clock /
    RCM_APB2_PERIPH_EXTIT = BIT15, /
    !< Select EXTIT clock /
    RCM_APB2_PERIPH_TMR9 = BIT16, /
    !< Select TMR9 clock /
    RCM_APB2_PERIPH_TMR10 = BIT17, /
    !< Select TMR10 clock /
    RCM_APB2_PERIPH_TMR11 = BIT18, /
    !< Select TMR11 clock /
    RCM_APB2_PERIPH_SPI5 = BIT20, /
    !< Select SPI5 clock /
    RCM_APB2_PERIPH_SPI6 = BIT21, /
    !< Select SPI6 clock /
    RCM_APB2_PERIPH_SAI1 = BIT22, /
    !< Select SAI1 clock /
    RCM_APB2_PERIPH_LTDC = BIT26 /
    !< Select LTDC clock */
    } RCM_APB2_PERIPH_T;
    因为GPIOA的外设时钟由RCM_AHB1CLKEN寄存器中的PAEN位控制,因此调用函数RCM_EnableAHB1PeriphClock()并传入参数RCM_AHB1_PERIPH_GPIOA就能够开启GPIOA的外设时钟了,同样的若要关闭GPIOA的外设时钟,则调用函数RCM_DisableAHB1PeriphClock()并传入参数RCM_AHB1_PERIPH_GPIOA即可。
    细心的读者可能会发现,怎么没有RCM_AHB3CLKEN寄存器相关的配置函数和枚举呢?虽然Geehy标准库中没有提供类似函数类配置RCM_AHB3CLKEN寄存器中的外设时钟,但也可以在程序代码中进行配置,并且RCM_AHB3CLKEN寄存器非常简单,RCM_AHB3CLKEN寄存器中的Bit1~Bit31都是无效的,只有Bit0用于控制EMMC外设时钟的开启和关闭,因此,在程序代码中直接往RCM_AHB3CLKEN寄存器的Bit0写入1或写入0,即可开启或关闭EMMC的外设时钟了。

【正点原子STM32连载】第八章 APM32F407时钟系统介绍 摘自【正点原子】APM32F407最小系统板使用指南_第1张图片

图8.1.1 APM32F407时钟树
上图就是APM32F407的时钟树框图
①:该部分是四个时钟源,分别为:LSI(内部低速时钟)、LSE(外部低速时钟)、HSI(内部高速时钟)、HSE(外部高速时钟),这四个时钟源是整个时钟树的“源头”。
②:该部分为PLL(锁相环),这一部分内部是较为复杂的模拟电路,主要用于对输入的时钟信号倍频后输出,输入的时钟源可以来自HSI或HSE。
③:该部分主要用于选择系统时钟的时钟源,从图中可以看出,该选择器的输入有HSE、HSI和PLL1,输出作用于SYSCLK,而SYSCLK通过AHB Prescaler分频后输出,该输出将直接或间接地作为AHB、APB1和APB2总线上外设的时钟源,其中FCLK就是MCU内核的时钟,这部分对整个系统的运行起着至关重要的作用。
④:AHB Prescaler分频后的时钟再通过APB1 Prescaler和APB2 Prescaler分频后分别作为APB1和APB2总线上外设的时钟。
8.1.1 时钟源
APM32F407有四个时钟源,分别为:LSI(内部低速时钟)、LSE(外部低速时钟)、HSI(内部高速时钟)、HSE(外部高速时钟)。按低速时钟源和高速时钟源可分为LSI、LSE和HSI、HSE,按内部时钟源和外部时钟源可分为LSI、HSI和LSE、HSE,其中内部时钟源LSI和HSI是MCU芯片内部的时钟源,芯片上电后即可输出时钟,无需借助外部电路。
①:外部高速时钟HSE(High Speed External Clock)
该时钟信号由外部晶体、陶瓷谐振振荡器或外部时钟源产生,频率范围为4MHz~26MHz,正点原子APM32F407最小系统板使用频率为8MHz的晶体振荡器产生该时钟信号。
②:外部低速时钟LSE(Low Speed External Clock)
该时钟信号由外部晶体、陶瓷谐振振荡器或外部时钟源产生,频率为32.768KHz,主要作为RTC的时钟源,正点原子APM32F407最小系统板使用频率为32.768KHz的晶体振荡器产生该时钟信号。
③:内部高速时钟HSI(High Speed Internal Clock)
该时钟信号由内部RC振荡器产生,频率为16MHz。
④:内部低速时钟LSI(Low Speed Internal Clock)
该时钟信号由内部RC振荡器产生,频率为20KHz~35KHz(受温度、电压影响),主要作为独立看门狗或RTC的时钟源。
MCU上电时默认会使用HSI作为系统时钟启动,只有完成了相关的时钟配置,MCU才会根据配置切换到对应的时钟源,因此同时了解这几个时钟源是很有必要的,下文将会有提到时钟配置方法的章节。
8.1.2 锁相环
锁相环(PLL)是自动控制系统中常用的一个反馈电路,在APM32主控中,锁相环主要有对输入时钟进行净化和倍频这两个作用,前者是利用锁相环电路的反馈机制实现的,后者是用于使APM32主控稳定地工作于更高的时钟频率。
在APM32中,锁相环的输出时钟可以配置为系统时钟的时钟源,如下图所示:
【正点原子STM32连载】第八章 APM32F407时钟系统介绍 摘自【正点原子】APM32F407最小系统板使用指南_第2张图片

图8.1.2.1 PLL1输出时钟作为系统时钟示例
从上图中可以看出,HSE的时钟HSECLK被作为PLL的输入时钟后经过B分频输入PLL1,随后在PLL1中经过A倍频和C分频后作为PLL1CLK输出,PLL1CLK被作为SYSCLK。
上面过程中的配置基本可以由PLL1配置寄存器(RCM_PLL1CFG)和时钟配置寄存器(RCM_CFG)完成配置。
【正点原子STM32连载】第八章 APM32F407时钟系统介绍 摘自【正点原子】APM32F407最小系统板使用指南_第3张图片

图8.1.2.2 PLL1配置寄存器PLLCLKS位
PLLCLKS位用于选择PLL的时钟源,当该位写入0时,使用HSI作为PLL的时钟源;当该位写入1时,使用HSE作为PLL的时钟源。对于示例,将该为配置为1,即使用频率为8MHz的HSE作为PLL的时钟源。
【正点原子STM32连载】第八章 APM32F407时钟系统介绍 摘自【正点原子】APM32F407最小系统板使用指南_第4张图片

图8.1.2.3 PLL1配置寄存器PLLB位
PLLB位用于对输入PLL的时钟信号进行分频,可分频的范围为2~63。对于示例,将该位配置为8,即对输入的频率为8MHz的HSE进行8分频,那么输入PLL1的时钟频率为1MHz。
【正点原子STM32连载】第八章 APM32F407时钟系统介绍 摘自【正点原子】APM32F407最小系统板使用指南_第5张图片

图8.1.2.4 PLL1配置寄存器PLL1A位
PLL1A用于对输入PLL1的时钟信号进行倍频,可倍频的范围为2~432。对于示例,将该位配置为336,即可将输入PLL1的1MHz时钟信号倍频为336MHz。
【正点原子STM32连载】第八章 APM32F407时钟系统介绍 摘自【正点原子】APM32F407最小系统板使用指南_第6张图片

图8.1.2.5 PLL1配置寄存器PLL1C位
PLL1C用于对PLL1待输出的时信号进行分频,可分频的值为2分频、4分频、6分频和8分频。对于示例,将该位写入0,即对336MHz的待输出时钟信号进行2分频后输出168MHz的时钟信号。
【正点原子STM32连载】第八章 APM32F407时钟系统介绍 摘自【正点原子】APM32F407最小系统板使用指南_第7张图片

图8.1.2.6 时钟配置寄存器SCLKSEL位
SCLKSEL位用于选择SYSCLK的时钟源,可配置HSICLK、HSECLK或PLL1CLK作为SYSCLK的时钟源。对于示例,将该位写入2,即可选择频率为168MHz的PLL1CLK作为SYSCLK时钟源。
8.1.3 系统时钟
APM32的系统时钟(SYSCLK)为整个芯片的绝大多数外设和内核提供时钟信号,对于相同稳定运行的系统,时钟信号的频率越高,系统运行的速度也就越快,单位时间能够处理的事务也就越多。在上一小节中,将频率为8MHz的HSE时钟信号通过PLL1倍频为168MHz的时钟信号作为SYSCLK的时钟源。SYSCLK时钟信号可直接或间接地作为AHB总线、APB1总线、APB2总线和内核的时钟源。
【正点原子STM32连载】第八章 APM32F407时钟系统介绍 摘自【正点原子】APM32F407最小系统板使用指南_第8张图片

图8.1.3.1 各总线和内核时钟配置示例
①:AHB预分频器,可对SYSCLK进行分频,分频范围为1~512。对于示例,AHB预分频器选择不分频(/1),那么AHB总线、内核、APB1总线和APB2总线输入的时钟信号频率都为168MHz。
②:APB1预分频器,可对待输入APB1总线的时钟信号进行分频,分频范围为1~16,APB1总线的时钟频率最大值为42MHz。对于示例,APB1预分频器选择4,即对频率为168MHz的时钟信号进行4分频为42MHz后作为APB1总线的时钟频率。
③:APB2预分频器,可对待输入APB2总线的时钟信号进行分频,分频范围为1~16,APB2总线的时钟频率最大值为84MHz。对于示例,APB2预分频器选择2,即对频率为168MHz的时钟信号进行2分频为84MHz后作为APB2总线的时钟频率。
8.2 配置系统主频
APM32F407在默认情况下,使用频率为16MHz的HSI作为系统时钟的时钟源,因此无需外部晶振也能够正常烧录和运行程序。
在上一小节中,介绍了如何配置时钟树,以让内核在168MHz的主频下运行,168MHz是Geehy官方针对APM32F407推荐的最大工作频率。
本节将介绍如何在代码中具体的对APM32F407的时钟进行配置。本书配置实验例程都是在main()函数中调用sys_apm32_clock_init()对APM32F407的时钟进行配置的,如下所示:

int main(void)
{
    /* 省略其他无关代码 */
    
    sys_apm32_clock_init(336, 8, 2, 7);	/* 配置系统时钟 */
    
    /* 省略其他无关代码 */
}

相信读者对sys_apm32_clock_init()函数传入的参数并不陌生,这就是在上一小节示例中配置PLL1配置寄存器中PLL1A、PLLB、PLL1C和PLLD各位的值。
sys_apm32_clock_init()函数的代码比较多,请读者在实验例程中查看本函数的具体实现,本小节中仅列出该函数的关键代码,如下所示:

uint8_t sys_apm32_clock_init(	uint32_t pll1a,
    								uint32_t pllb,
    								uint32_t pll1c,
    								uint32_t plld)
{
    /* 本函数的部分代码省略,仅列出关键代码 */
    
    RCM_ConfigAHB(RCM_AHB_DIV_1);				/* 设置AHB时钟的预分频系数为1 */
    RCM_ConfigAPB2(RCM_APB_DIV_2);				/* 设置APB2时钟的预分频系数为2 */
    RCM_ConfigAPB1(RCM_APB_DIV_4);				/* 设置APB1时钟的预分频系数为4 */
    
    RCM_ConfigPLL1(	RCM_PLLSEL_HSE,				/* 配置PLL1 */
    					pllb,
    					pll1a,
    					(RCM_PLL_SYS_DIV_T)((pll1c >> 1) - 1),
    					plld);
    RCM_EnablePLL1();							/* 使能PLL1 */
    
    RCM_ConfigSYSCLK(RCM_SYSCLK_SEL_PLL);		/* 选择PLL1CLK作为系统时钟 */
    
    return 0;
}

跟上一小节介绍的一致,AHB、APB2和APB1总线的预分频器分别配置为不分频、2分频和4分频;根据函数的传入参数配置PLL1配置寄存器中PLL1A、PLLB、PLL1C和PLLD各位的值,并将PLL的时钟源配置为HSE;将PLL1CLK配置为SYSCLK的时钟源。上面列出的代码仅是一些关键代码,除了列出代码的操作外还需要一些额外的操作,例如使能HSE并等待HSE稳定等,建议读者打开本书配套的任意实验例程,在sys.c文件中详细地查看本函数的具体实现。
通过sys_apm32_clock_init()函数就能够非常方便地配置系统主频了,例如,在考虑对MCU的性能需求和功耗的场景中,可以适当降低MCU的主频,来达到性能和功耗的平衡。
8.3 开启和关闭外设时钟
在前面的章节中,分析了APM32F407的时钟系统和相关的配置步骤,但是在使用某些外设之前,还需要手动开启相应外设的时钟,这是因为MCU在上电或复位后会默认关闭大部分外设的时钟,以降低功耗,因此,在程序开发中,对于某些开启的时钟但不再使用的外设,也应将其时钟关闭。
APM32F407外设的时钟由RCM(Reset and Clock Management,复位与时钟管理)控制,RCM中控制外设时钟的寄存器被按总线进行分类,涉及了五个寄存器,如下图所示:
【正点原子STM32连载】第八章 APM32F407时钟系统介绍 摘自【正点原子】APM32F407最小系统板使用指南_第9张图片

图8.3.1 RCM中控制外设时钟的寄存器
其中RCM_AHB1CLKEN、RCM_AHB2CLKEN和RCM_AHB3CLKEN均是用于控制AHB总线上外设的时钟的;RCM_APB1CLKEN用于控制APB1总线上外设的时钟;RCM_APB2CLKEN用于控制APB2总线上外设的时钟。
下面以GPIOA为例,介绍如何配置RMC中相应的寄存器来开启或关闭GPIOA的时钟。首先要确定GPIOA是在AHB总线、APB1总线还是APB2总线上,这里可以借助APM32F407的地址映射来判断,APM32F407的地址映射图,如下图所示:
【正点原子STM32连载】第八章 APM32F407时钟系统介绍 摘自【正点原子】APM32F407最小系统板使用指南_第10张图片

图8.3.2 APM32F407地址映射图
从上图中可以看出,GPIOA~GPIOI都是AHB总线上的外设,那么就能够确定了GPIOA是位于AHB总线上的外设。
接下来据需要在RCM的RCM_AHB1CLKEN、RCM_AHB2CLKEN和RCM_AHB3CLKEN寄存器中找到控制GPIOA时钟的寄存器,这里有个小技巧,RCM_AHB1CLKEN、RCM_AHB2CLKEN和RCM_AHB3CLKEN这三个寄存器中控制外设时钟的位,基本都是按照外设映射地址的高低来排序的,因为GPIOA~GPIOI的映射地址在AHB总线上是最低的,因此基本可以判断用于控制GPIOA时钟的寄存器为RCM_AHB1CLKEN。在RCM_AHB1CLKEN寄存器中可以找到PAEN位,该位就是用于控制GPIOA外设时钟使能和禁止的,如下图所示:
在这里插入图片描述

图8.3.3 RCM_AHB1CLKEN寄存器PAEN位
由上图可知,往RCM_AHB1CLKEN寄存器的PAEN位写入1后,就能配置使能GPIOA外设的时钟,往RCM_AHB1CLKEN寄存器的PAEN位写入0后,就能配置禁止GPIOA外设的时钟。
在Geehy的标准库中寄存器提供了开启和关闭总线外设时钟的函数(函数的实现在apm32f407xx_rcm.c文件中,调用时需包含apm32f407xx_rcm.h的头文件),如下所示:

void RCM_EnableAHB1PeriphClock(uint32_t AHB1Periph);
void RCM_DisableAHB1PeriphClock(uint32_t AHB1Periph);
void RCM_EnableAHB2PeriphClock(uint32_t AHB2Periph);
void RCM_DisableAHB2PeriphClock(uint32_t AHB2Periph);
void RCM_EnableAPB1PeriphClock(uint32_t APB1Periph);
void RCM_DisableAPB1PeriphClock(uint32_t APB1Periph);
void RCM_EnableAPB2PeriphClock(uint32_t APB2Periph);
void RCM_DisableAPB2PeriphClock(uint32_t APB2Periph);

那这些函数需要传入那些参数呢?
在apm32f4xx_rcm.h文件中,通过枚举列出了大部分的外设,如下所示:

/**
 * @brief AHB1 peripheral
 */
typedef enum
{
    RCM_AHB1_PERIPH_GPIOA			= BIT0,		/*!< Select GPIOA clock */
    RCM_AHB1_PERIPH_GPIOB			= BIT1,		/*!< Select GPIOB clock */
    RCM_AHB1_PERIPH_GPIOC			= BIT2,		/*!< Select GPIOC clock */
    RCM_AHB1_PERIPH_GPIOD			= BIT3,		/*!< Select GPIOD clock */
    RCM_AHB1_PERIPH_GPIOE			= BIT4,		/*!< Select GPIOE clock */
    RCM_AHB1_PERIPH_GPIOF			= BIT5,		/*!< Select GPIOF clock */
    RCM_AHB1_PERIPH_GPIOG			= BIT6,		/*!< Select GPIOG clock */
    RCM_AHB1_PERIPH_GPIOH			= BIT7,		/*!< Select GPIOH clock */
    RCM_AHB1_PERIPH_GPIOI			= BIT8,		/*!< Select GPIOI clock */
    RCM_AHB1_PERIPH_GPIOJ			= BIT9,		/*!< Select GPIOJ clock */
    RCM_AHB1_PERIPH_GPIOK			= BIT10,	/*!< Select GPIOK clock */
    RCM_AHB1_PERIPH_CRC				= BIT12,	/*!< Select CRC clock */
    RCM_AHB1_PERIPH_FLITF			= BIT15,	/*!< Select FLITF clock */
    RCM_AHB1_PERIPH_SRAM1			= BIT16,	/*!< Select SRAM1 clock */
    RCM_AHB1_PERIPH_SRAM2			= BIT17,	/*!< Select SRAM2 clock */
    RCM_AHB1_PERIPH_BKPSRAM			= BIT18,	/*!< Select BKPSRAM clock */
    RCM_AHB1_PERIPH_SRAM3			= BIT19,	/*!< Select SRAM3 clock */
    RCM_AHB1_PERIPH_CCMDATARAMEN	= BIT20,	/*!< Select CCMDATARAMEN clock */
    RCM_AHB1_PERIPH_DMA1			= BIT21,	/*!< Select DMA1 clock */
    RCM_AHB1_PERIPH_DMA2			= BIT22,	/*!< Select DMA2 clock */
    RCM_AHB1_PERIPH_ETH_MAC			= BIT25,	/*!< Select ETH MAC clock */
    RCM_AHB1_PERIPH_ETH_MAC_Tx		= BIT26,	/*!< Select ETH MAC TX clock */
    RCM_AHB1_PERIPH_ETH_MAC_Rx		= BIT27,	/*!< Select ETH MAC RX clock */
    RCM_AHB1_PERIPH_ETH_MAC_PTP		= BIT28,	/*!< Select ETH MAC PTP clock */
    RCM_AHB1_PERIPH_OTG_HS			= BIT29,	/*!< Select OTG HS clock */
    RCM_AHB1_PERIPH_OTG_HS_ULPI		= BIT30		/*!< Select OTG HS ULPI clock */
} RCM_AHB1_PERIPH_T;

/**
 * @brief AHB2 peripheral
 */
typedef enum
{
    RCM_AHB2_PERIPH_DCI				= BIT0,		/*!< Select DCI clock */
    RCM_AHB2_PERIPH_FPU				= BIT1,		/*!< Select FPU clock */
    RCM_AHB2_PERIPH_BN				= BIT2,		/*!< Select BN clock */
    RCM_AHB2_PERIPH_SM				= BIT3,		/*!< Select SM clock */
    RCM_AHB2_PERIPH_CRYP			= BIT4,		/*!< Select CRYP clock */
    RCM_AHB2_PERIPH_HASH			= BIT5,		/*!< Select HASH clock */
    RCM_AHB2_PERIPH_RNG				= BIT6,		/*!< Select RNG clock */
    RCM_AHB2_PERIPH_OTG_FS			= BIT7		/*!< Select OTG FS clock */
} RCM_AHB2_PERIPH_T;

/**
 * @brief APB1 peripheral
 */
typedef enum
{
    RCM_APB1_PERIPH_TMR2			= BIT0,			/*!< Select TMR2 clock */
    RCM_APB1_PERIPH_TMR3			= BIT1,			/*!< Select TMR3 clock */
    RCM_APB1_PERIPH_TMR4			= BIT2,			/*!< Select TMR4 clock */
    RCM_APB1_PERIPH_TMR5			= BIT3,			/*!< Select TMR5 clock */
    RCM_APB1_PERIPH_TMR6			= BIT4,			/*!< Select TMR6 clock */
    RCM_APB1_PERIPH_TMR7			= BIT5,			/*!< Select TMR7 clock */
    RCM_APB1_PERIPH_TMR12			= BIT6,			/*!< Select TMR12 clock */
    RCM_APB1_PERIPH_TMR13			= BIT7,			/*!< Select TMR13 clock */
    RCM_APB1_PERIPH_TMR14			= BIT8,			/*!< Select TMR14 clock */
    RCM_APB1_PERIPH_WWDT			= BIT11,		/*!< Select WWDT clock */
    RCM_APB1_PERIPH_SPI2			= BIT14,		/*!< Select SPI2 clock */
    RCM_APB1_PERIPH_SPI3			= BIT15,		/*!< Select SPI3 clock */
    RCM_APB1_PERIPH_USART2			= BIT17,		/*!< Select USART2 clock */
    RCM_APB1_PERIPH_USART3			= BIT18,		/*!< Select USART3 clock */
    RCM_APB1_PERIPH_UART4			= BIT19,		/*!< Select UART4 clock */
    RCM_APB1_PERIPH_UART5			= BIT20,		/*!< Select UART5 clock */
    RCM_APB1_PERIPH_I2C1			= BIT21,		/*!< Select I2C1 clock */
    RCM_APB1_PERIPH_I2C2			= BIT22,		/*!< Select I2C2 clock */
    RCM_APB1_PERIPH_I2C3			= BIT23,		/*!< Select I2C3 clock */
    RCM_APB1_PERIPH_CAN1			= BIT25,		/*!< Select CAN1 clock */
    RCM_APB1_PERIPH_CAN2			= BIT26,		/*!< Select CAN2 clock */
    RCM_APB1_PERIPH_PMU				= BIT28,		/*!< Select PMU clock */
    RCM_APB1_PERIPH_DAC				= BIT29,		/*!< Select DAC clock */
    RCM_APB1_PERIPH_UART7			= BIT30,		/*!< Select UART7 clock */
    RCM_APB1_PERIPH_UART8			= (int32_t)BIT31/*!< Select UART8 clock */
} RCM_APB1_PERIPH_T;

/**
 * @brief APB2 peripheral
 */
typedef enum
{
    RCM_APB2_PERIPH_TMR1			= BIT0,		/*!< Select TMR1 clock */
    RCM_APB2_PERIPH_TMR8			= BIT1,		/*!< Select TMR8 clock */
    RCM_APB2_PERIPH_USART1			= BIT4,		/*!< Select USART1 clock */
    RCM_APB2_PERIPH_USART6			= BIT5,		/*!< Select USART6 clock */
    RCM_APB2_PERIPH_ADC				= BIT8,		/*!< Select ADC clock */
    RCM_APB2_PERIPH_ADC1			= BIT8,		/*!< Select ADC1 clock */
    RCM_APB2_PERIPH_ADC2			= BIT9,		/*!< Select ADC2 clock */
    RCM_APB2_PERIPH_ADC3			= BIT10,	/*!< Select ADC3 clock */
    RCM_APB2_PERIPH_SDIO			= BIT11,	/*!< Select SDIO clock */
    RCM_APB2_PERIPH_SPI1			= BIT12,	/*!< Select SPI1 clock */
    RCM_APB2_PERIPH_SPI4			= BIT13,	/*!< Select SPI4 clock */
    RCM_APB2_PERIPH_SYSCFG			= BIT14,	/*!< Select SYSCFG clock */
    RCM_APB2_PERIPH_EXTIT			= BIT15,	/*!< Select EXTIT clock */
    RCM_APB2_PERIPH_TMR9			= BIT16,	/*!< Select TMR9 clock */
    RCM_APB2_PERIPH_TMR10			= BIT17,	/*!< Select TMR10 clock */
    RCM_APB2_PERIPH_TMR11			= BIT18,	/*!< Select TMR11 clock */
    RCM_APB2_PERIPH_SPI5			= BIT20,	/*!< Select SPI5 clock */
    RCM_APB2_PERIPH_SPI6			= BIT21,	/*!< Select SPI6 clock */
    RCM_APB2_PERIPH_SAI1			= BIT22,	/*!< Select SAI1 clock */
    RCM_APB2_PERIPH_LTDC			= BIT26		/*!< Select LTDC clock */
} RCM_APB2_PERIPH_T;

因为GPIOA的外设时钟由RCM_AHB1CLKEN寄存器中的PAEN位控制,因此调用函数RCM_EnableAHB1PeriphClock()并传入参数RCM_AHB1_PERIPH_GPIOA就能够开启GPIOA的外设时钟了,同样的若要关闭GPIOA的外设时钟,则调用函数RCM_DisableAHB1PeriphClock()并传入参数RCM_AHB1_PERIPH_GPIOA即可。
细心的读者可能会发现,怎么没有RCM_AHB3CLKEN寄存器相关的配置函数和枚举呢?虽然Geehy标准库中没有提供类似函数类配置RCM_AHB3CLKEN寄存器中的外设时钟,但也可以在程序代码中进行配置,并且RCM_AHB3CLKEN寄存器非常简单,RCM_AHB3CLKEN寄存器中的Bit1~Bit31都是无效的,只有Bit0用于控制EMMC外设时钟的开启和关闭,因此,在程序代码中直接往RCM_AHB3CLKEN寄存器的Bit0写入1或写入0,即可开启或关闭EMMC的外设时钟了。

你可能感兴趣的:(stm32,嵌入式硬件,单片机)