STM32的时钟配置——时钟树解析

此文章由于讲得较详细因此篇幅较长,请带着一点耐心去读,相信会有收获!

STM32为什么要有复杂的时钟系统

首先STM32 本身非常复杂,外设非常的多,但是并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及 RTC 只需要几十 k 的时钟源即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的 MCU 一般都是采取多时钟源的方法来解决这些问题。

详解STM32时钟系统

下图来自STM32CubeMX工具的时钟配置界面:
下图即是标准库默认8M外部时钟,系统时钟72M的配置情况
在这里插入图片描述
下图来自STM32F1的中文版datasheet的时钟系统章节:
STM32的时钟系统确实是很复杂,不仅有倍频,分频,还有一系列的外设时钟开关。倍频是考虑到了电磁兼容性,如果外部直接提供一个72MHz的晶振,太高的震荡频率会给电路板的制作带来一定的难度。分频则是因为STM32既有高速外设,也有低速外设,各外设的工作频率不相同,需要分开来管理。最后,每个外设时钟还有自己独立的开关(在图上可以看到,在外设时钟之前需要经过一个与门,这就是它们的开关)在我们不使用该外设时,需要把时钟关闭以减少STM32的功耗。
在这里插入图片描述

STM32有几个时钟源

STM32有以下4个时钟源(标号对应上图中的蓝色数字标号):
①高速外部时钟(HSE):以外部晶振作时钟源,晶振频率可取范围为4~16MHz,我们一般采用8MHz的晶振。
②低速外部时钟(LSE):以外部晶振作时钟源,主要提供给实时时钟模块,所以一般采用32.768KHz。
③高速内部时钟(HSI): 由内部RC振荡器产生,频率为8MHz,但不稳定。
④低速内部时钟(LSI):由内部RC振荡器产生,也主要提供给实时时钟模块,频率大约为40KHz。

有些资料把PLL也作为一个时钟源,事实上PLL 为锁相环倍频输出,也是由HSI或者HSE倍频得来的,其时钟输入源可选择为 HSI/2、HSE 或者 HSE/2。倍频可选择为2~16 倍,但是其输出频率最大不得超过 72MHz(仅针对STM32F103)

时钟在STM32内部最终是供给四大块,图中用红色椭圆圈出——USB的48MHz时钟、系统时钟SYSCLK、实时时钟模块RTC、独立看门狗的时钟IWDGCLK。其中最主要的,也是最大头是系统时钟SYSCLK,它可以是内部或外部高速时钟直接接过来,也可以内、外部高速时钟是PLL倍频后提供的,系统时钟再分别供给Cortex内核、SDIO、AHB总线、DMA、APB1、APB2等。

我们通常是采用外部8MHz高速时钟(HSE),所以着重说HSE。我们以前面的GPIO上的时钟为例,由ST的Datasheet可知,GPIO是在APB2高速外设总线上的,图中绿色的线就是时钟的流程,我们一步步地来看。

8MHz外部晶体(或晶振)输入后,先经过一个开关PLLXTPRE(HSE divider for PLL entry),此开关决定对HSE进行2分频再输入到PLL或直接到PLL。我们选择不分频。

这样时钟又到了第二个开关PLLSRC(PLL entry clock source),此开关决定PLL的时钟来源,是内部高速时钟二分频的时钟还是PLLXTPRE的输出。我们选择后者,这时的时钟在进入PLL前还是8MHz,因为在PLLXTPRE我们没有分频。

到了PLL倍频器,由PLLMUL决定倍频系统数,可以选择2~16倍频输出,但记住,PLL输出频率最高72MHz,所以我们选择9倍频,这样PLL输出就是最高72MHz的PLLCLK时钟了。这时的PLLCLK为USB提供时钟。

开关SW来决定SYSCLK的时钟来源,前面已经提到,这里我们由PLLCLK做为SYSCLK的来源,这样系统时钟SYSCLK就是72MHz了。

在供给外设前,先经过AHB预分频,我们选择不分频;在供给GPIO前,还要再经过APB2预分频,因为APB2为高速外设,所以我们选择不分频,这样GPIO的时钟就是72MHz了。注意,低速外设APB1最高频率为36MHz,所以在使用APB1的外设时,要注意设置好分频系统。还要注意,要使用外设,先要对外设时钟进行使能,见图中黄色云形框。这是因为STM32采用了低功耗的设计,对不使用的外设,其时钟不使能,以达到降低功耗的效果。

USB 的时钟USBCLK(图中红色椭圆标出)是来自 PLL 时钟源。 STM32 中有一个全速功能的 USB 模块,其串行接口引擎需要一个频率为 48MHz 的时钟源。该时钟源只能从 PLL 输出端获取,可以选择为 1.5 分频或者 1 分频,也就是,当需要使用 USB模块时,PLL 必须使能,并且时钟频率配置为 48MHz 或 72MHz。

RTC 时钟源RTCCLK(图中红色椭圆标出),从图上可以看出,RTC 的时钟源可以选择 LSI,LSE,以及HSE 的 128 分频。

独立看门狗时钟源只能由40KHz的LSI提供。

SYSCLK 通过 AHB 分频器分频后送给各模块使用。这些模块包括:
①AHB 总线、内核、内存和 DMA 使用的 HCLK 时钟。

②通过 8 分频后送给 Cortex 的系统定时器时钟,也就是 systick 了。

③直接送给 Cortex 的空闲运行时钟 FCLK。

④送给 APB1 分频器。APB1 分频器输出一路供 APB1 外设使用(PCLK1,最大频率 36MHz),另一路送给定时器(Timer)2、3、4 倍频器使用。

⑤送给 APB2 分频器。APB2 分频器分频输出一路供 APB2 外设使用(PCLK2,最大频率 72MHz),另一路送给定时器(Timer)1 倍频器使用。

其中需要理解的是 APB1 和 APB2 的区别,APB1 上面连接的是低速外设,包括电源接口、备份接口、CAN、USB、I2C1、I2C2、UART2、UART3 等等,APB2 上面连接的是高速外设包括 UART1、SPI1、Timer1、ADC1、ADC2、所有普通 IO 口(PA~PE)、第二功能 IO 口等。

关于时钟输出

MCO 是 microcontroller clock output 的缩写,是微控制器时钟输出引脚,在 STM32 F1系列中 由 PA8 复用所得,主要作用是可以对外提供时钟,相当于一个有源晶振。 如图左下角的咖啡色方框里面,MCO 的时钟来源可以是: PLLCLK/2、 HSI、 HSE、 SYSCLK,具体选哪个由时钟配置寄存器CFGR 的位 26-24: MCO[2:0]决定。 除了对外提供时钟这个作用之外, 我们还可以通过示波器监控 MCO 引脚的时钟输出来验证我们的系统时钟配置是否正确。

软件配置时钟

暂时只针对F1标准库
在stm32的启动文件startup_stm32f10x_hd.s中,会发现有这么一块用汇编写的代码。

      Reset_Handler   PROC
            EXPORT  Reset_Handler             [WEAK]
            IMPORT  __main
            IMPORT  SystemInit
            LDR     R0, =SystemInit
            BLX     R0               
            LDR     R0, =__main
            BX      R0
            ENDP

从这里我们可以看到,我们的程序在进入到main函数之前,先要执行systeminit,跳转到这个函数的定义。里面的代码是对寄存器直接进行操作,原因是尽可能提高执行效率,以便尽快使MCU进入正常工作所需的时钟。
下面给出简化了的SystemInit 函数源码,假定预定义了STM32F10X_HD

void SystemInit (void)
{
    
    
  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
  /* 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();

#ifdef VECT_TAB_SRAM
  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif 
}

这里我们主要关注SetSysClock函数,这里配置了系统时钟

static void SetSysClock(void)
{
    
    
#ifdef SYSCLK_FREQ_HSE
  SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
  SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
  SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
  SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
  SetSysClockTo56();  
#elif defined SYSCLK_FREQ_72MHz
  SetSysClockTo72();
#endif
 
 /* If none of the define above is enabled, the HSI is used as System clock
    source (default after reset) */ 
}

正常情况下,通常我们都是需要配置成72MHz运行

static void SetSysClockTo72(void)
{
    
    
	__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
	// ① 使能 HSE,并等待 HSE 稳定
	RCC->CR |= ((uint32_t)RCC_CR_HSEON);
	
	// 等待 HSE 启动稳定,并做超时处理
	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;
	}
	// HSE 启动成功,则继续往下处理
	if (HSEStatus == (uint32_t)0x01) {
    
    
		//-----------------------------------------------------------
		// 使能 FLASH 预存取缓冲区 */
		FLASH->ACR |= FLASH_ACR_PRFTBE;
		
		// SYSCLK 周期与闪存访问时间的比例设置,这里统一设置成 2
		// 设置成 2 的时候, SYSCLK 低于 48M 也可以工作,如果设置成 0 或者 1 的时候,
		// 如果配置的 SYSCLK 超出了范围的话,则会进入硬件错误,程序就死了
		// 0: 0 < SYSCLK <= 24M
		// 1: 24< SYSCLK <= 48M
		// 2: 48< SYSCLK <= 72M */
		FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
		FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
		//------------------------------------------------------------
		
		// ② 设置 AHB、 APB2、 APB1 预分频因子
		// 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 时钟来源,设置 PLL 倍频因子, 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);
		
		// ④ 使能 PLL
		RCC->CR |= RCC_CR_PLLON;
		
		// ⑤ 等待 PLL 稳定
		while ((RCC->CR & RCC_CR_PLLRDY) == 0) {
    
    
		}
		
		// ⑥ 选择 PLL 作为系统时钟来源
		RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
		RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
		
		// ⑦ 读取时钟切换状态位,确保 PLLCLK 被选为系统时钟
		while ((RCC->CFGR&(uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08){
    
    
		}
	} else {
    
    // 如果 HSE 启动失败,用户可以在这里添加错误代码出来
	}
}

SystemInit()函数运行完成后的状态:(注意以上程序默认是8M外部晶振的情况)
SYSCLK(系统时钟)=72MHz

AHB 总线时钟(使用 SYSCLK) =72MHz

APB1 总线时钟(PCLK1) =36MHz

APB2 总线时钟(PCLK2) =72MHz

PLL 时钟 =72MHz

初始化之后可以通过变量SystemCoreClock获取系统变量。如果 SYSCLK=72MHz,那么变量SystemCoreClock=72000000。

参考鸣谢:
http://m.elecfans.com/article/606451.html
http://blog.chinaunix.net/uid-24219701-id-4081961.html
https://blog.csdn.net/jiangnaxing8498/article/details/77196424

猜你喜欢

转载自blog.csdn.net/weixin_44788542/article/details/111646936