架构图
思考重点
- 本文的目的是使用HSE外部晶振来配置系统时钟
- 参考手册中的时钟树如何理解
- 理解开发版初始化过程中对系统时钟的操作
- 如何自行变更系统时钟
配置时钟源
在开发版STM32F429,以HSE, HSI, PLL作为主要的系统时钟的信号来源,并拥有LSI, LSE低速内外部信号时钟源,两者频率分别为32, 32.768kHz
时钟源相当于节拍器的功能,藉由稳定的信号源输出,可以有效配置出单位时间内系统的运算次数。下面探讨三个主要的系统时钟来源的功能与特色:
- HSE
- 高速外部时钟信号
- 可由震盪器(oscillator)/晶振(crystal)提供信号源
- 选择震盪器为信号源需要接上引脚OSC_OUT, OSC_IN
- 频率: 4MHz~25MHz
- HSI
- 高速内部时钟信号
- 内部晶振提供信号源
- 当HSE故障将会自动切成HSI直到HSE恢復正常
- 频率: 16MHz
- PLL
- 锁向环
- 主要目的是对时钟信号源进行分频以及倍频处理,并将结果输出给相关外设
- 开发版具有两个PLL,分别是主PLL和I2S专用PLLI2S
- 官方建议最高运行频率为180MHz
- 主PLL
- 用于PLLCLK系统时钟
- 用于USB OTG FS, RNG, SDIO时钟(48MHz)
- PLLI2S
- 用于I2S的时钟
时钟树
上图为reference manual中对设置系统时钟的描述,也就是着名的时钟树框图,由于我们的目标是配製系统时间SYSCLK,因此可以把主线任务从上图的红框部分拆分成以下的流程图,这样对接下来的程式范例也好理解
从图中我们可以发现配置系统时间过程中有两大重点
- 选择PLL时钟源
- 选择系统时钟源
而系统时间通常是使用HSE, HSI, PLL三者之一,可以透过配置暂存器来选择,一般来说系统时间会使用PLL倍频之后的结果
PLL倍频
由于外部时钟来源的频率不够大,开发版需要透过PLL锁向环,将输入时钟倍频成适合的系统频率。因此PLL的处理过程围绕在将时钟源切分成约1MHz后再进行放大,我们先来看看PLL系统时间的运算公式:
[(HSE/HSI)/分频因子M] * 倍频因子N / PLLCLK分频因子P
操作流程
- 选择时钟输入来源: HSE(25MHz)或HSI(16MHz)
- 设置分频因子M: 时钟源/M的结果务必在1~2MHz。M的范围: 2~63
- 设置倍频因子N : STM32F42xxx, STM32F43xxx系列频率范围: 192~432MHz
- 设置PLLCLK分频因子P: P可以是2、4、6、8
举例来说,我们选择25MHz的外部震盪器时钟源,透过将M配置成25,把V时钟输入结果配置成1MHz,然后把N设为360,将输出结果放大成360MHz,最后设置P为2,输出180MHz的PLL系统时钟源
另外假如PLL时钟来源选择HSE,当HSE发生问题时,系统会自动将时钟源切换成HSI
AHB外设时钟HCLK
当我们选择PLL作为系统时钟来源后,首先会输出到高速外设汇流排AHB,同样的我们可以透过软体控制暂存器的AHB分频因子,分频因子可以设置为1, 2, 4, 8, 16, 64, 128, 256, 512。几乎所有周边外设都使用AHB的系统时间频率
APB2外设时钟PCLK2
APB2汇流排时钟经由AHB时钟分频得到,可以透过软体操作暂存器的APB2分频因子,分频因子可以设置为1, 2, 4, 8, 16。需要注意APB2时钟频率不可以超过90MHz
APB1外设时钟PCLK1
APB1汇流排时钟经由AHB时钟分频得到,可以透过软体操作暂存器的APB1分频因子,分频因子可以设置为1, 2, 4, 8, 16。需要注意APB1时钟频率不可以超过45MHz
暂存器介绍
在接下来的系统时钟配置环节主要涉及以下几个暂存器,详细可以参考reference manual
RCC_CR
- PLLRDY: 主PLL锁向环是否被开启
- PLLON: 开启PLL锁向环
- CSSON: 开启检测外部震盪器是否稳定
- HSEBYP: 当HSE发生问题时bypass给其他时钟信号源
- HSERDY: 由硬体判断HSE是否被开启
- HSEON: 开启HSE时钟源
- HSION: 开启HSI时钟源
RCC_PLLCFGR
- PLLP: 配置分频因子P
- PLLN: 配置分频因子N
- PLLM: 配置分频因子M
RCC_CFGR
- PPRE2: 配置APB2分频因子
- PPRE1: 配置APB1分频因子
- HPRE: 配置AHB分频因子
- SWS: 等待硬体切换系统时钟来源
- SW: 选择系统时钟来源
SetSysClock()函式
关于开发版初始化的主要写在函式SystemInit()
当中,系统时钟的配置当然也不例外。在SystemInit()
我们可以找到SetSysClock()
函式,我们上一小节介绍的诸如PLL时钟设定都在这个函式中完成,以下大致介绍SetSysClock()
:
- 目的: 初始化系统时间
- 主要操作:
- 开启HSE
- 配置内部电压调节器
- 配置AHB/APB1/APB2
- 配置PLL
- 开启PLL
- 开启Over-drive模式
- 配置Flash接口暂存器
- 将PLL配置成系统时间
static void SetSysClock(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* 开启HSE,操作RCC_CR的HSEON位*/
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* 在指定时间内等待硬体将HSERDY置位成1 */
do
{
HSEStatus = RCC->CR