1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#
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文件中,通过枚举列出了大部分的外设,如下所示:
/**
/**
/**
/**
图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的外设时钟了。