**写在前面:**一个 MCU 越复杂,时钟系统也会相应地变得复杂,如 STM32F1 的时钟系统比较复杂,不像简单的 51 单片机一个系统时钟就 可以解决一切。对于 STM32F1 系列的芯片,其有多个时钟源,构成了一个庞大的是时钟树。本节我们将学习时钟树的相关的内容。
目录
[2.2 PLL锁相环](#2.2 PLL锁相环)
[2.4 APB1、APB2时钟](#2.4 APB1、APB2时钟)
[2.5 其他时钟](#2.5 其他时钟)
[3.2利用 HAL 库配置 STM32F1 时钟系统](#3.2利用 HAL 库配置 STM32F1 时钟系统)
一、简述时钟
时钟树简图:
1、时钟源
|--------------|-----------|-------|
| HSE(高速外部振荡器) | 4-16MHz | 晶体或陶瓷 |
| LSE(低速外部振荡器) | 32.768KHz | 晶体或陶瓷 |
| HSI(高速内部振荡器) | 8MHz | RC电路 |
| LSI(低速内部振荡器) | 40KHz | RC电路 |
对于 STM32F1,输入时钟源(Input Clock)主要包括 HSI,HSE,LSI,LSE。
从时钟频率来分可以分为高速时钟源和低速时钟源,其中 HSI、HSE 高速时钟,LSI 和 LSE 是低速时钟。
从时钟来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中 HSE 和 LSE 是外部时钟源;其他是内部时钟源,芯片上电即可产生,不需要 借助外部电路。
晶体时钟的特点是:成本高、稳定、精确,如果可以首选外部时钟;
2、PLL锁相环
此处锁相环的主要作用是倍频,对于相同的稳定运行的电路,时钟频率越高,指令的执行速度越快, 单位时间能处理的功能越多。对于STM32来说,其系统时钟不超过72MHz;而时钟源的时钟频率最多也就16MHz,无法达到芯片的要求,此时就需要倍频器将外部时钟频率进行倍频,再传给系统时钟;
3、系统时钟SYSCLK
STM32 的系统时钟 SYSCLK 为整个芯片提供了时序信号。系统时钟的信号来源分别为:HSE、HSI以及经过分频器的输出信号;AHB、APB1、APB2、内核时钟等时钟通过系统时钟分 频得到。
4、HCLK(AHB总线)
AHB总线将来自系统时钟的信号进行分频或不分频通过总线再分给其他外设、系统内核时钟或者APB1、APB2上的时钟;
5、LSI、LSE
低速内部RC(LSI RC)振荡器,可以用于驱动独立看门狗,或通过程序选择驱动 RTC;
LSE 振荡器时钟,也可以驱动 RTC;
二、时钟树详解
一个简化的 STM32F1 时钟系统。图中已经把我们主要关注几处标注出来。
A 部分表示其他电路需要的输入源时钟信号;
B 为一个特殊的振荡电路"PLL",由几个部分构成;
C 为我们重点需要关注的 MCU 内的注释中"SYSCLK";AHB 预分频器将 SYSCLK 分频或不 分频后分发给其他外设进行处理;
D 和 E 部分分 别为定时器等外设的时钟源 APB1/APB2;
F 部分的 Cortex-M 内核系统的时钟;
G 是 STM32 的时钟输出功能。
2.1时钟源
A部分为四个时钟源,高速外部振荡器(8MHz)、低速外部振荡器(32.768kHz) 石英晶体,主要作用于 RTC 的时钟源。高速内部振荡器 由内部 RC 振荡器产生,频率为 8MHz。低速内部振荡器由内部 RC 振荡器产生,频率为 40kHz,可作为独立看门狗的时钟源。
由高速振荡器输出两路信号,分别直接连接SYSLCK系统时钟,以及进入PLL锁相环;
2.2 PLL锁相环
PLL锁相环由三部分组成,分别为:PLLXTPRE、PLLSRC、PLLMUL;
PLLXTPRE:HSE分频器作为PLL输入,专门用于 HSE,0:HSE不分频;1:HSE2分频;
PLLSRC:PLL时钟源的选择器;0:HSI经2分频作为PLL时钟源;1:HSE作为PLL时钟源;
PLLMUL:用于配置锁相环倍频系数,ST 设置它的 有效倍频范围为 2~16 倍;
涉及寄存器为RCC_CFGR(第16-21位)
2.3系统时钟SYSCLK
SYSCLK 为整个芯片提供了时序信号。对于相同的稳定运行的电路,时钟频率越高,指令的执行速度越快, 单位时间能处理的功能越多。STM32 的系统时钟是可配置的,在STM32F1 系列中,它可以为 HSI、PLLCLK、HSE 中的一个。可选时钟信号有外部高速时钟 HSE(8M)、内部高速时钟 HSI(8M)和经过倍频的 PLL CLK(72M),选择 PLL CLK 作为系统时钟,此时系统时钟的频率为 72MHz。
涉及寄存器为RCC_CFGR 的位 SW[1:0]
2.4 APB1、APB2时钟
AHB、APB1、APB2、内核时钟等时钟通过系统时钟分频得到。
系统时钟信号来到的 AHB 预分频器,可以选择的分频系数为 1,2,4,8,16,32,64,128,256,也可以选择不分频;AHB 总线时 钟达到最大的 72MHz。
涉及的寄存器为RCC_CFGR 的位 HPRE[3:0]
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 和内部温度传感器。
涉及的寄存器为RCC_CFGR 的位 PPRE-PPRE2[2:0]
2.5 其他时钟
此外,AHB总线还与一些外设直接相连,作为外设的时钟,比如ADC,在传入ADC之前,还需要接入ADC预分频器;
涉及的寄存器为RCC_CFGR 的位ADCPRE[1:0]:
最后为时钟树的全速USB OTG预分频,以及为控制器时钟输出;
USBCLK,是一个通用串行接口时钟,时钟来源于 PLLCLK。STM32F103 内置 全速功能的 USB 外设,其串行接口引擎需要一个频率为 48MHz 的时钟源。该时钟源只能从 PLL 输出端获取,可以选择为 1.5 分频或者 1 分频,也就是,当需要使用 USB 模块时,PLL 必须使能,并且时钟频率配置为 48MHz 或 72MHz。
MCO 输出内部时钟,STM32 的一个时钟输出 IO(PA8),它可以选择一个时钟信号输出,可以选择为 PLL 输出的 2 分频、HSI、HSE、或者系统时钟。这个时钟可以用来给外部其他系统提供时钟源。
涉及的寄存器为RCC_CFGR 的位OTGFSPRE、MCO:
RTC 定时器,其时钟源为 HSE/128、LSE 或 LSI。时钟安全系统、自由运行时钟FCLK;
这些寄存器只是对于使用时钟是一个整体的设置,具体到某个外设的时钟,还需要对应的寄存器设置。
三、配置系统时钟
在STM32库函数中,封装了部分寄存器的功能函数,其中有:
时钟源、锁相环:HAK_RCC_OssConfig()
系统时钟、总线:HAK_RCC_ClockConfig()
使能外设时钟:HAK_RCC_PPP_CLK_ENABLE()
扩展外设时钟(RTC/ADC/USB):HAL_RCCCEX_PeriphCLKConfig()
3.1系统时钟配置步骤
1、配置HSE_VALVE;
2、调用System Init()函数;
3、选择时钟源,配置PLL;
4、选择系统时钟源,配置总线分频器;
5、配置扩展外设时钟;
其中,重要的是步骤345 ,其封装在时钟设置函数 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 */
函数 sys_stm32_clock_init 就是用户的时钟系统配置函数,除了配置 PLL 相关参数确定 SYSCLK 值之外,还配置了 AHB、APB1 和 APB2 的分频系数,也就是确定了 HCLK,PCLK1 和 PCLK2 的时钟值。至于为何要这样配置,我们在底下这节进行说明:
3.2利用 HAL 库配置 STM32F1 时钟系统
一般步骤:
**1) 配置时钟源相关参数:**调用函数 HAL_RCC_OscConfig()。
HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct);
RCC_OscInitTypeDef 的定义:
在3.1系统时钟配置步骤中, sys_stm32_clock_init就是对上述结构体内容的配置;通过函数的该段程序,我们开启了 HSE 时钟源,同时选择 PLL 时钟源为 HSE,然后把 sys_stm32_clock_init 的形参直接设置作为 PLL 的参数 M 的值,这样就达到了设置 PLL 时钟源相关参数的目的。
**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 结构体定义
同样,在3.1系统时钟配置步骤中, sys_stm32_clock_init对上述结构体内容的配置;
第二个参数 FLatency的含义,为了使 FLASH 读写正确(因为 72Mhz 的时钟比 Flash 的操作速度 24Mhz 要快得多,操作速度不匹配 容易导致 Flash 操作失败),所以需要设置延时时间。
3.3外设时钟使能
在配置好时钟系统之后,如果我们要使用某些外设, 例如 GPIO,ADC 等,我们还要使能这些外设时钟。这里大家必须注意,如果在使用外设之前 没有使能外设时钟,这个外设是不可能正常运行的。
在 STM32F1 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件 STM32F1xx_hal_rcc.h 定义的,首先,我们来看看 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 的值决定的。 RCC_APB2ENR_IOPAEN的值为2,位 2 的作用是用来使用 GPIOA 时钟,那么我们只需要在我们的用户程序中调用宏定义标识符就可以实现 GPIOA 时钟使能。使用方法为:
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能 GPIOA 时钟 *
对于其他外设,同样都是在 STM32F1xx_hal_rcc.h 头文件中定义,大家只需要找到相关宏定义标识符即可。
我们使用外设的时候需要使能外设时钟,如果我们不需要使用某个外设,同样我们可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。
例如:
#define __HAL_RCC_GPIOA_CLK_DISABLE();/*禁止 GPIOA 时钟 *
**总结:**本节我们学习了STM32的时钟树,从简图学习,再到整个时钟树的详解,包括其功能,涉及的相关寄存器,以及配置的系统时钟的方法步骤,再后面的学习中,我们将经常使用时钟树,因此在后面我们也会熟练的掌握!
创作不易,还请大家多多点赞支持!!!