目录
0. 《STM32单片机自学教程》专栏
17.1 STM32时钟树
17.1.1 时钟源
17.1.2 锁相环PLL
17.1.3 系统时钟
17.1.3.1 系统时钟SYSCLK
17.1.3.2 AHB/APB总线时钟
17.1.3.3 其他时钟
17.1.3.4 MCO 时钟输出
17.2 系统时钟库函数
17.3 系统时钟配置练习
0. 《STM32单片机自学教程》专栏
本文作为专栏《STM32单片机自学教程》专栏其中的一部分,返回专栏总纲,阅读所有文章,点击Link:
STM32单片机自学教程-[目录总纲]_stm32 学习-CSDN博客
RCC(Reset Clock Control) 复位和时钟控制,包括复位和时钟控制两部分内容,本章我们主要讲解时钟部分. 大家可以参考《STM32F10X-中文参考手册》中的RCC 章节,里面有更详细的解释。本章我们首先学习一下STM32的时钟树,然后通过学习系统时钟函数SetSysClockTo72()加深对系统时钟配置的理解,最后通过一个简单的实例来实践时钟配置的方法。
17.1 STM32时钟树
MCU都是基于时序控制的,时序系统对MCU非常重要,因此在MCU设计时就设计了专门用于控制时序的电路,在芯片设计中称为时钟树设计。由此设计出来的时钟,可以精确控制我们的单片机系统。一个MCU越复杂,时钟系统也会相应地变得复杂,如STM32F1的时钟系统比较复杂,不像简单的51单片机一个系统时钟就可以解决一切。对于STM32F1系列的芯片,正常工作的主频可以达到72Mhz,但并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及RTC只需要几十kHZ的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的MCU一般都是采取多时钟源的方法来解决这些问题。所以我们称为时钟树而不是时钟。
STM32非常复杂,外设多,为了实现低功耗的要求,STM32的主控默认不开启这些外设功能。用户可以根据需要决定STM32要开启的功能,这个功能开关在STM32主控中也就是各个外设的时钟。
图17.1-1 STM32时钟树
在上图中把主要常关注几处标注出来了。A部分表示其他电路需要的输入时钟信号源;B为一个特殊的振荡电路“PLL”;C-K为各类时钟;L是STM32的时钟输出功能。其他部分后续如有用到我们再详细探讨。接下来我们对各部分进行讲解:
17.1.1 时钟源
如图17.1-1中标识A的部分。对于STM32F1系列单片机,输入时钟源(InputClock)主要包括四种,即HSI,HSE,LSI,LSE。其中,从时钟频率来分可以分为高速时钟源和低速时钟源,其中HIS和HSE为高速时钟,LSI和LSE是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中HSE和LSE是外部时钟源;其他是内部时钟源,芯片上电即可产生,不需要借助外部电路。
①高速外部振荡器 HSE (High Speed External Clock signal)
HSE是高速的外部时钟信号,可以由有源晶振或者无源晶振提供,频率从4-16MHZ不等。当使用有源晶振时,时钟从OSC_IN引脚进入,OSC_OUT引脚悬空,当选用无源晶振时,时钟从OSC_IN和OSC_OUT进入,并且要配谐振电容。HSE 最常使用的就是8M 的无源晶振。
当确定PLL 时钟来源的时候,HSE 可以不分频或者2 分频,这个由时钟配置寄存器CFGR 的17位PLLXTPRE 设置,我们一般设置为HSE 不分频。
②低速外部振荡器 LSE (Low Speed External Clock signal)
外接 32.768kHz晶振,主要作用于 RTC 的时钟源。
③高速内部振荡器 HSI(High Speed Internal Clock signal)
HSI时钟信号由内部8MHz的RC振荡器产生,可直接作为系统时钟或在2分频后作为PLL输入。HSI的RC振荡器能够在不需要任何外部器件的条件下提供系统时钟。它的启动时间比HSE晶体振荡器短。但是由于即使在校准之后它的时钟频率精度仍较差。我们一般使用的HSE。
④低速内部振荡器 LSI(Low Speed Internal Clock signal)
LSI担当一个低功耗时钟源的角色,它可以在停机和待机模式下保持运行,为独立看门狗和自动唤醒单元提供时钟。LSI时钟频率大约40kHz(在30kHz和60kHz之间)。LSIRC可以通过控制/状态寄存器(RCC_CSR)里的LSION位来启动或关闭。
17.1.2 锁相环PLL
锁相环是自动控制系统中常用的一个反馈电路,在STM32主控中,锁相环的作用主要有是输入时钟净化和倍频。前者是利用锁相环电路的反馈机制实现,后者我们用于使芯片在更高且频率稳定的时钟下工作。在STM32中,锁相环的输出也可以作为芯片系统的时钟源。根据图17.1-1的时钟结构(图中标识为B的部分),使用锁相环时需要进行三个部分的配置。为了方便查看,截取了使用PLL作为系统时钟源的配置部分,如图17.1-2所示。
图17.1-2 PLL配置示意图
①PLLXTPRE:HSE 分频器作为 PLL 输入 (HSE divider for PLL entry)
即上图在标注为①的地方,它专门用于 HSE,ST 设计它有两种方式,并把它的控制功能放在 RCC_CFGR 寄存器中,说明如下图 17.1-3。我们由此得知,HSE输入PLL可以是 2 分频,或者是 1 分频(不分频)。经过 HSE 分频器处理后的输出振荡时钟信号比直接输入的时钟信号更稳定。
图17.1-3 PLLXTPRE 设置选项值
②PLLSRC:PLL 输入时钟源 (PLL entry clock source)
图中②表示的是 PLL 时钟源的选择器。参考 F103 参考手册,如下图17.1-4. 它有两种可选择的输入源:一个是HSI 的二分频时钟,另一个是 前面PLLXTPRE 处理后的 HSE 信号。
图17.1-4 PLLSRC 锁相环时钟源选择
③PLLMUL: PLL 倍频系数 (PLL multiplication factor)
图中③表示配置锁相环的倍频系数,倍频之后便是PLL 时钟PLLCLK。倍频因子可以是 2~16 倍。 如果我们要实现 72MHz 的主频率,我们通过选择 HSE 不分频作为 PLL 输入的时钟信号,即输入 8MHz,通过标号③选择倍频因子,可选择 2-16 倍频,我们选择 9 倍频,这样可以得到时钟信号为 8*9=72MHz。即PLL 时钟PLLCLK为72MHz,再下一步我们选为系统时钟后,系统时钟频率便为72MHz。
17.1.3 系统时钟
17.1.3.1 系统时钟SYSCLK
如图17.1-1中标识C的部分。STM32的系统时钟SYSCLK为整个芯片提供了时序信号。对单片机来说时钟频率越高,指令的执行速度越快,单位时间能处理的功能越多,但功耗也会越大。STM32的系统时钟是可配置的,在STM32F1系列中,系统时钟来源可以是:HSI、PLLCLK、HSE,通过CFGR的位SW[1:0]设置。
我们一般设置系统时钟:SYSCLK = PLLCLK = 72M。如果仍使用PLL作为系统时钟源,如果使用HSI/2,那么可以得到最高主频8MHz/2*16=64MHz。
17.1.3.2 AHB/APB总线时钟
从上面的图 17.1-1时钟树图可知,AHB、APB1、APB2、内核时钟等时钟通过系统时钟分频得到。
AHB 总线时钟HCLK
如图17.1-1中标识D的部分。系统时钟SYSCLK经过AHB预分频器分频之后得到时钟叫APB总线时钟,即HCLK,分频因子可以是[1,2,4,8,16,64,128,256,512],具体的由时钟配置寄存器CFGR的位7-4:HPRE[3:0]设置。片上大部分外设的时钟都是经过HCLK分频得到,至于AHB总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置。AHB的时钟我们一般设置为1分频,即HCLK=SYSCLK=72M。
APB1 总线时钟PCLK1
如图17.1-1中标识E的部分。APB1总线时钟PCLK1由HCLK经过低速APB预分频器得到,分频因子可以是[1,2,4,8,16],具体的由时钟配置寄存器CFGR的位10-8:PRRE1[2:0]决定。PCLK1属于低速的总线时钟,最高为36M,片上低速的外设就挂载到这条总线上,比如USART2/3/4/5、SPI2/3,I2C1/2等。至于APB1总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置。APB1这里我们设置为2分频,即PCLK1=HCLK/2=36M。
APB2 总线时钟PCLK2
如图17.1-1中标识F的部分。APB2总线时钟PCLK2由HCLK经过高速APB2预分频器得到,分频因子可以是[1,2,4,8,16],具体由时钟配置寄存器CFGR的位13-11:PPRE2[2:0]决定。PCLK2属于高速的总线时钟,片上高速的外设就挂载到这条总线上,比如全部的GPIO、US-ART1、SPI1等。至于APB2总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置。对于APB2的时钟我们一般设置为1分频,即PCLK2=HCLK=72M。
17.1.3.3 其他时钟
除了上面的时钟,STM32还有很多时钟,剩下的时钟部分我们讲解几个重要的。原理都是一样的,大家会看手册配置即可,也没必要全部介绍。
USB时钟
如图17.1-1中标识H的部分。USB时钟是由PLLCLK经过USB预分频器得到,分频因子可以是[1,1.5],具体的由时钟配置寄存器CFGR的位22:USBPRE配置。USB的时钟最高是48M,根据分频因子反推过来算,PLLCLK只能是48M或者是72Mhz。
一般我们设置PLLCLK=72M,USBCLK=48Mhz。USB对时钟要求比较高,所以PLLCLK只能是由HSE倍频得到,不能使用HSI倍频。
Cortex 系统时钟
如图17.1-1中标识I的部分。Cortex 系统时钟由HCLK 8 分频得到,等于9MHZ,Cortex 系统时钟用来驱动内核的系统定时器SysTick。SysTick 一般用于操作系统的时钟节拍,也可以用做普通的定时。
ADC时钟
如图17.1-1中标识G的部分。ADC时钟由PCLK2经过ADC预分频器得到,分频因子可以是[2,4,6,8],具体的由时钟配置寄存器CFGR的位15-14:ADCPRE[1:0]决定。ADC时钟最高只能是14MHz,反推PCLK2的时钟只能是:28Mhz、56Mhz、84Mhz、112Mhz,鉴于PCLK2最高是72Mhz,所以只能取28M和56Mhz。
RTC 时钟
如图17.1-1中标识J的部分。RTC时钟可由HSE/128分频得到,也可由低速外部时钟信号LSE提供,频率为32.768KHZ,也可由低速内部时钟信号LSI提供,具体选用哪个时钟由备份域控制寄存器BDCR的位9-8:RTCSEL[1:0]配置。
独立看门狗时钟
如图17.1-1中标识K的部分。独立看门狗的时钟只能由LSI提供.LSI是低速的内部时钟信号,频率为40KHZ。
17.1.3.4 MCO 时钟输出
MCO是Microcontroller Clock Output的缩写,是微控制器时钟输出引脚,在STM32F1系列中由PA8复用所得,主要作用是可以对外提供时钟,相当于一个有源晶振。MCO的时钟来源可以是:PLLCLK/2、HSI、HSE、SYSCLK,具体选哪个由时钟配置寄存器CFGR的位26-24:MCO[2:0]决定。除了对外提供时钟这个作用之外,我们还可以通过示波器监控MCO引脚的时钟输出来验证我们的系统时钟配置是否正确。
17.2 系统时钟库函数
在固件库文件system_stm32f10x.c 的最后便是设置系统时钟的库函数SetSysClockTo72,如下代码:
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/
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/2 */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
/* 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);
/* 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 */
}
}
上述代码把把互联型相关的代码删掉了(STM32F10X_CL部分),该函数是直接操作寄存器的,有关寄存器部分请参考数据手册的RCC的寄存器描述部分。从上面代码可以大致总结配置步骤如下:
1.使能 HSE,并等待 HSE稳定
2.设置 AHB、APB2、APB1预分频因子
3.设置 PLL时钟来源,设置PLL倍频因子
4.使能PLL
5.等待PLL稳定
6.选择 PLL作为系统时钟来源
7.读取时钟切换状态位,确保 PLLCLK 被选为系统时钟
如果我们要配置某个外设的时候,假如配置APB1,我们也可以根据时钟树结构,根据时钟来源选择,设置分频因子这个步骤去配置,如下图17.2-1.
图17.2-1 时钟设置流程示意
17.3 系统时钟配置练习
STM32F103 默认的情况下使用的是内部8M的HSI作为时钟源,初始化后使用HSE,然后HSE经过PLL倍频之后作为系统时钟。通常的配置是:HSE=8Mhz,PLL的倍频因子为9,系统时钟就设置成:SYSCLK=8Mhz*9=72Mhz。使用HSE,系统时钟SYSCLK最高是128M。我们的库函数,当程序来到main函数之前,启动文件statup_stm32f10x_hd.s已经调用SystemInit()函数把系统时钟初始化成72MHZ,SystemInit()在库文件system_stm32f10x.c中定义。如果我们想把系统时钟设置低一点或者超频的话,可以修改底层的库文件,但为了维持库的完整性,我们可以根据时钟树的流程自行写一个。
当HSE故障的时候,如果PLL的时钟来源是HSE,那么当HSE故障的时候,这个时候CSS系统会自动切换HSI作为系统时钟,此时SYSCLK=HSI=8M。STM32时钟树的CSS(Clock Security System,时钟安全系统)是一个重要的安全机制,旨在提高系统的稳定性和可靠性,如图17.2-1中红色虚线框中位置。
因为调用库函数都是使用HSE,如果用户对精度要求不高,不想用HSE(因为要配置外部电路),而是想用稳定性和精度都差的HSI,这样可以省去外围电路的配置。下面我们给出个使用HSI配置系统时钟例子供参考,方法和系统时钟配置类似。代码如下(摘自野火):
void HSI_SetSysClock(uint32_t pllmul)
{
__IO uint32_t HSIStartUpStatus = 0;
// 把RCC外设初始化成复位状态,这句是必须的
RCC_DeInit();
//使能HSI
RCC_HSICmd(ENABLE);
// 等待 HSI稳定
while (RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET)
{
}
// 读取 HSI 就绪状态
HSIStartUpStatus = RCC->CR & RCC_CR_HSIRDY;
// 只有 HSI就绪之后则继续往下执行
if (HSIStartUpStatus == RCC_CR_HSIRDY)
{
//----------------------------------------------------------------------//
// 使能FLASH 预存取缓冲区
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
// SYSCLK周期与闪存访问时间的比例设置,这里统一设置成2
// 设置成2的时候,SYSCLK低于48M也可以工作,如果设置成0或者1的时候,
// 如果配置的SYSCLK超出了范围的话,则会进入硬件错误,程序就死了
// 0:0 < SYSCLK <= 24M
// 1:24< SYSCLK <= 48M
// 2:48< SYSCLK <= 72M
FLASH_SetLatency(FLASH_Latency_2);
//----------------------------------------------------------------------//
// AHB预分频因子设置为1分频,HCLK = SYSCLK
RCC_HCLKConfig(RCC_SYSCLK_Div1);
// APB2预分频因子设置为1分频,PCLK2 = HCLK
RCC_PCLK2Config(RCC_HCLK_Div1);
// APB1预分频因子设置为1分频,PCLK1 = HCLK/2
RCC_PCLK1Config(RCC_HCLK_Div2);
//-----------------设置各种频率主要就是在这里设置-------------------//
// 设置PLL时钟来源为HSI,设置PLL倍频因子
// PLLCLK = 4MHz * pllmul
RCC_PLLConfig(RCC_PLLSource_HSI_Div2, pllmul);
//------------------------------------------------------------------//
// 开启PLL
RCC_PLLCmd(ENABLE);
// 等待 PLL稳定
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
// 当PLL稳定之后,把PLL时钟切换为系统时钟SYSCLK
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 读取时钟切换状态位,确保PLLCLK被选为系统时钟
while (RCC_GetSYSCLKSource() != 0x08)
{
}
}
else
{ // 如果HSI开启失败,那么程序就会来到这里,用户可在这里添加出错的代码处理
// 当HSE开启失败或者故障的时候,单片机会自动把HSI设置为系统时钟,
// HSI是内部的高速时钟,8MHZ
while (1)
{
}
}
}
参考资料:
【1】哔站江协科技STM32入门教程
【2】《STM32单片机原理与项目实战》刘龙、高照玲、田华著
【3】《ARM Cortex-M3嵌入式原理及应用》黄可亚著
【4】《STM32嵌入式微控制器快速上手》陈志旺著
【5】《STM32单片机应用与全案例实践》沈红卫等著
【6】《野火STM32开发指南》
【7】《正点原子STM32开发指南》