STM32
直播中

郑成枝

8年用户 1370经验值
私信 关注
[问答]

详解STM32时钟系统

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

STM32有几个时钟源呢?

回帖(1)

刘文娟

2021-11-5 09:45:22
  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。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分