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中,会发现有这么一块用汇编写的代码。

 
  1. Reset_Handler PROC
  2. EXPORT Reset_Handler [WEAK]
  3. IMPORT __main
  4. IMPORT SystemInit
  5. LDR R0, =SystemInit
  6. BLX R0
  7. LDR R0, =__main
  8. BX R0
  9. ENDP

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

 
  1. void SystemInit (void)
  2. {
  3. /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
  4. /* Set HSION bit */
  5. RCC->CR |= (uint32_t)0x00000001;
  6. /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
  7. RCC->CFGR &= (uint32_t)0xF8FF0000;
  8. /* Reset HSEON, CSSON and PLLON bits */
  9. RCC->CR &= (uint32_t)0xFEF6FFFF;
  10. /* Reset HSEBYP bit */
  11. RCC->CR &= (uint32_t)0xFFFBFFFF;
  12. /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
  13. RCC->CFGR &= (uint32_t)0xFF80FFFF;
  14. /* Disable all interrupts and clear pending bits */
  15. RCC->CIR = 0x009F0000;
  16. /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
  17. /* Configure the Flash Latency cycles and enable prefetch buffer */
  18. SetSysClock();
  19. #ifdef VECT_TAB_SRAM
  20. SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
  21. #else
  22. SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
  23. #endif
  24. }

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

 
  1. static void SetSysClock(void)
  2. {
  3. #ifdef SYSCLK_FREQ_HSE
  4. SetSysClockToHSE();
  5. #elif defined SYSCLK_FREQ_24MHz
  6. SetSysClockTo24();
  7. #elif defined SYSCLK_FREQ_36MHz
  8. SetSysClockTo36();
  9. #elif defined SYSCLK_FREQ_48MHz
  10. SetSysClockTo48();
  11. #elif defined SYSCLK_FREQ_56MHz
  12. SetSysClockTo56();
  13. #elif defined SYSCLK_FREQ_72MHz
  14. SetSysClockTo72();
  15. #endif
  16. /* If none of the define above is enabled, the HSI is used as System clock source (default after reset) */
  17. }

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

 
  1. static void SetSysClockTo72(void)
  2. {
  3. __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  4. // ① 使能 HSE,并等待 HSE 稳定
  5. RCC->CR |= ((uint32_t)RCC_CR_HSEON);
  6. // 等待 HSE 启动稳定,并做超时处理
  7. do {
  8. HSEStatus = RCC->CR & RCC_CR_HSERDY;
  9. StartUpCounter++;
  10. } while ((HSEStatus == 0) &&(StartUpCounter !=HSE_STARTUP_TIMEOUT));
  11. if ((RCC->CR & RCC_CR_HSERDY) != RESET) {
  12. HSEStatus = (uint32_t)0x01;
  13. } else {
  14. HSEStatus = (uint32_t)0x00;
  15. }
  16. // HSE 启动成功,则继续往下处理
  17. if (HSEStatus == (uint32_t)0x01) {
  18. //-----------------------------------------------------------
  19. // 使能 FLASH 预存取缓冲区 */
  20. FLASH->ACR |= FLASH_ACR_PRFTBE;
  21. // SYSCLK 周期与闪存访问时间的比例设置,这里统一设置成 2
  22. // 设置成 2 的时候, SYSCLK 低于 48M 也可以工作,如果设置成 0 或者 1 的时候,
  23. // 如果配置的 SYSCLK 超出了范围的话,则会进入硬件错误,程序就死了
  24. // 0: 0 < SYSCLK <= 24M
  25. // 1: 24< SYSCLK <= 48M
  26. // 2: 48< SYSCLK <= 72M */
  27. FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
  28. FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
  29. //------------------------------------------------------------
  30. // ② 设置 AHB、 APB2、 APB1 预分频因子
  31. // HCLK = SYSCLK
  32. RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
  33. //PCLK2 = HCLK
  34. RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
  35. //PCLK1 = HCLK/2
  36. RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
  37. //③ 设置 PLL 时钟来源,设置 PLL 倍频因子, PLLCLK = HSE * 9 = 72 MHz
  38. RCC->CFGR &= (uint32_t)((uint32_t)
  39. ~(RCC_CFGR_PLLSRC
  40. | RCC_CFGR_PLLXTPRE
  41. | RCC_CFGR_PLLMULL));
  42. RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE
  43. | RCC_CFGR_PLLMULL9);
  44. // ④ 使能 PLL
  45. RCC->CR |= RCC_CR_PLLON;
  46. // ⑤ 等待 PLL 稳定
  47. while ((RCC->CR & RCC_CR_PLLRDY) == 0) {
  48. }
  49. // ⑥ 选择 PLL 作为系统时钟来源
  50. RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
  51. RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
  52. // ⑦ 读取时钟切换状态位,确保 PLLCLK 被选为系统时钟
  53. while ((RCC->CFGR&(uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08){
  54. }
  55. } else { // 如果 HSE 启动失败,用户可以在这里添加错误代码出来
  56. }
  57. }

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

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

APB1 总线时钟(PCLK1) =36MHz

APB2 总线时钟(PCLK2) =72MHz

PLL 时钟 =72MHz

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值