一、STM32的复杂的时钟系统

1.时钟源
在STM32芯片中,设计了复杂的时钟系统,我们称之为时钟树。多样时钟源的设计核心是节能:高速外设配高速时钟,低速外设配低速时钟,最大化降低芯片功耗。
(1)在STM32中,系统时钟可以选择3种时钟源作为驱动:
HSI (High Speed Internal oscillator,高速内部时钟)
HSE(High Speed External ,高速外部时钟)
PLL(Phase Locked Loop,锁相环/倍频器)
(2)另外还有两种二级时钟:
LSI时钟(Low Speed Internal,低速内部时钟)
LSE时钟(Low Speed External,低速外部时钟)
RC振荡器不太稳定,优先选择外部时钟HSE。
以下是对STM32的时钟源进行的汇总
|------------|--------|-------------------------|------------------------|----|-----------------------------------------------------------------------------------------------------------------|
| 时钟源 | 类型 | 频率范围 | 稳定性 | 功耗 | 主要用途 |
| HSI高速内部时钟 | 内部RC电路 | 8MHz(默认) | 差(±1%~±3%) | 低 | 1. 芯片上电默认时钟(无外部晶振时); 2. 应急备份、极简电路; 3. 调试阶段临时使用 |
| HSE高速外部时钟 | 外部晶振 | 4~16MHz (常用 MHz/12MHz) | 优(晶振精度 ±10ppm) | 略高 | 1. 系统主时钟首选; 2. 串口 / 定时器 / USB 等对时钟精度要求高的外设; 3. PLL 倍频的输入源 |
| PLL锁相环/倍频器 | 倍频器 | 最大 72MHz(F103系列) | 依赖输入源(HSE 输入则稳,HSI 则差) | 中 | 1. 系统核心时钟(SYSCLK)的最终来源;2. 倍频规则: - HSI 输入:PLL = HSI/2 × 倍频(如 8/2×16=64MHz); - HSE 输入:PLL = HSE × 倍频(如 8×9=72MHz) |
| LSI低速内部时钟 | 内部RC电路 | 40kHz 左右(典型) | 差 | 极低 | 1. 独立看门狗(IWDG)专用; 2. RTC 时钟备份(无 LSE 时) |
| LSE低速外部时钟 | 外部晶振 | 32.768kHz(固定) | 尤 | 低 | 1. RTC 实时时钟(精准计时); 2. 低功耗模式下的时钟源 |
LSE为什么是32768Hz的原因:因为它是2的15次方,很容易得到一秒钟的时长。
LSI的用法:
也可以给RTC使用--作为备用
给看门狗使用--看门狗一般采用LSI
1.1.外部时钟源
1.1.1.OSC_OUT/OSC_IN:振荡器引脚,外接高速晶振

HSE:高速外部时钟源,4-16MHz可选。常用8MHz
1.1.2.OSC32_IN/OSC32_OUT:低速晶振引脚,外接低速晶振

LSE:低速外部时钟源,常用32.768KHz

1.1.3.MCO:主时钟输出引脚,为片外外设提供时钟

1.2.内部时钟源
HSI:高速内部时钟源,固定为8MHz。使用RC振荡电路,精度较差。

LSI:低速内部时钟源,固定为40KHz。使用RC振荡电路,精度较差。

PLL:锁相环倍频器

SYSCLK:系统时钟

二、时钟的作用
时钟的作用主要有两方面
- 生成系统时钟(SYSCLK):作为芯片内部核心工作节拍,要求高频、高精度;
- 生成实时时钟(RTCCLK):用于外部时间感知,频率低、精度要求适中。
三、芯片启动时,系统时钟的时钟源切换:
芯片启动时,由于外部晶振时钟需要启动时间较长,因此芯片会先使用内部时钟,待外部时钟稳定后再切换到外部时钟。如下图:

先使用HSI后切换到HSE的过程,在芯片的启动代码中的SystemInit函数中有体现:
cpp
//startup_stm32f10x_hd.s汇编文件中的启动入口函数
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
SystemInit函数在startup_stm32f10x.c文件中实现
cpp
//下面的代码是基于STM32F103ZET6的初始化操作,可以看出,系统初始化时,第一步先使能了内置时钟HSI
//在执行完初始化操作的最后,通过SetSysClock();函数将时钟切换到外部时钟。
void SystemInit (void)
{
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
RCC->CFGR &= (uint32_t)0xF8FF0000;
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
}
SetSysClock();函数也是在startup_stm32f10x.c文件中实现
cpp
//代码节选
static void SetSysClock(void)
{
SetSysClockTo72();
}
SetSysClockTo72();函数也是在startup_stm32f10x.c文件中实现
cpp
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/
/* Enable HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
if (HSEStatus == (uint32_t)0x01)
{
/* Enable Prefetch Buffer */
FLASH->ACR |= FLASH_ACR_PRFTBE;
/* Flash 2 wait state */
FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
/* HCLK = SYSCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK2 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
/* PCLK1 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
#ifdef STM32F10X_CL
//条件编译不生效部分,略去
#else
/* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL));
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */
/* Enable PLL */
RCC->CR |= RCC_CR_PLLON;
/* Wait till PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* Wait till PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong clock
configuration. User can add here some code to deal with this error */
}
}
以上代码就是芯片启动时,系统时钟的启动过程。
三、总结
1.HSE时钟
高速外部时钟,由外部时钟源提供,目前几乎所有的STM32单片机的设计都是在外部接一个8MHz的晶振,经过PLL倍频(9倍频)后得到一个72MHz的系统时钟。系统默认设置就是这个时钟,在启动文件中可以看到相应的配置。

2.HSI时钟
HSI时钟信号由内部8MHz的RC振荡器产生,可以直接作为系统时钟或在2分频后作为PLL输入。HSI RC振荡器能够在不需要任何外部器件的条件下提供系统时钟。它的启动时间比HSE晶体振荡器短。然而,即使在校准之后他的时钟频率精度仍然较差。
3.PLL时钟
内部PLL用来倍频HSI RC和HSE晶体输出的时钟。PLL的设置必须在其被激活前完成。一旦PLL被激活,这些参数就不能被改动。如果PLL中断在时钟中断寄存器中被允许,当PLL准备就绪时,可产生中断申请。
PLL时钟一般都是对外部的8MHz的时钟信号经过9倍频后,得到72MHz的时钟频率,这是STM32F1系列允许的最高时钟频率。
4.LSE时钟
LSE晶体是一个32.768KHz的低速外部晶体或陶瓷谐振器。它为实时时钟或其他定时功能提供一个低功耗且精确的时钟源。
LSE是不能驱动系统时钟的。
5.LSI时钟
LSI RC担当一个低功耗时钟源的角色,它可以在停机和待机模式下保持运行,为独立看门狗和自动唤醒单元提供时钟。LSI时钟频率大约40KHz(在30~60KHz之间)。
LSI也不能驱动系统时钟。