Cubemx与HAL库系列教程|系统时钟配置详解及源码分析

STM32时钟系统简介

STM32种类繁多,时钟系统也不尽相同,但基本的还是大差不差,今日小飞哥就F1系列的MCU简单聊一聊STM32的时钟系统

1、时钟种类介绍:

先来看一看时钟树图,包含了整个系统的始终来源及各个外设的始终来源

STM32 有5个时钟源:HSI、HSE、LSI、LSE、PLL。

接下来,各个时钟的含义听小飞哥白话白话,自己看ST手册也可以哒

  • 1.1 HSI时钟(内部高速时钟)

HSI时钟信号由内部8MHz的RC振荡器产生,可直接作为系统时钟或在2分频后作为PLL输入。

HSI RC振荡器能够在不需要任何外部器件的条件下提供系统时钟。它的启动时间比HSE晶体振荡器短。然而,即使在校准之后它的时钟频率精度仍较差。

校准

制造工艺决定了不同芯片的RC振荡器频率会不同,这就是为什么每个芯片的HSI时钟频率在出厂前已经被ST校准到1%(25°C)的原因。系统复位时,工厂校准值被装载到时钟控制寄存器的HSICAL[7:0]位。 如果用户的应用基于不同的电压或环境温度,这将会影响RC振荡器的精度。可以通过时钟控制寄存器里的HSITRIM[4:0]位来调整HSI频率。

  • 1.2 HSE时钟(外部高速时钟)

高速外部时钟信号(HSE)由以下两种时钟源产生:

● HSE外部晶体/陶瓷谐振器

● HSE用户外部时钟

为了减少时钟输出的失真和缩短启动稳定时间,晶体/陶瓷谐振器和负载电容器必须尽可能地靠近振荡器引脚。负载电容值必须根据所选择的振荡器来调整。

  • 1.3 PLL

内部PLL可以用来倍频HSI RC的输出时钟或HSE晶体输出时钟。PLL的设置(选择HIS振荡器除2或HSE振荡器为PLL的输入时钟,和选择倍频因子)必须在其被激活前完成。一旦PLL被激活,这些参数就不能被改动。

如果PLL中断在时钟中断寄存器里被允许,当PLL准备就绪时,可产生中断申请。 如果需要在应用中使用USB接口,PLL必须被设置为输出48或72MHZ时钟,用于提供48MHz的USBCLK时钟。

  • 1.4 LSE时钟(外部低速时钟)

LSE晶体是一个32.768kHz的低速外部晶体或陶瓷谐振器。它为实时时钟或者其他定时功能提供一个低功耗且精确的时钟源。

在这个模式里必须提供一个32.768kHz频率的外部时钟源。你可以通过设置在备份域控制寄存器(RCC_BDCR)里的LSEBYP和LSEON位来选择这个模式。具有50%占空比的外部时钟信号(方波、正弦波或三角波)必须连到OSC32_IN引脚,同时保证OSC32_OUT引脚悬空。

  • 1.5 LSI时钟(内部低速时钟)

LSI RC担当一个低功耗时钟源的角色,它可以在停机和待机模式下保持运行,为独立看门狗和自动唤醒单元提供时钟。LSI时钟频率大约40kHz(在30kHz和60kHz之间)。

以上介绍来自STM32手册,总共分为5大时钟源。

2、系统时钟源选择及配置

先来看看cubemx中的时钟树图,可以一目了然的看到整个时钟架构,还是非常的nice的

要注意的是,使用内部高速时钟的话,最大能配置到64MHZ,使用外部高速时钟的话,能配置到72MHZ

了解了基本的时钟架构之后,我们要怎么样配置得到自己想要的系统频率呢?

接下来分别介绍内部高速时钟和外部高速时钟的配置

  • 2.1 内部高速时钟作为时钟输入

使用内部高速时钟的话,就不需要关注外部硬件了,可以看到内部高速时钟有3个去向,其中2个去向是可以到我们的系统时钟

sysclk后面,我们可以看到还有很多的,HCLK,PCLK,等等,这些又是什么含义呢?

系统时钟SYSCLK最大频率为72MHz(64MHZ),它是供STM32中绝大部分部件工作的时钟源。系统时钟可由PLL、HSI或者HSE提供输出,并且它通过AHB分频器分频后送给各模块使用,AHB分频器可选择1、2、4、8、16、64、128、256、512分频。其中AHB分频器输出的时钟送给5大模块使用:

①、送给AHB总线、内核、内存和DMA使用的HCLK时钟。

②、分频后送给STM32芯片的系统定时器时钟(Systick=Sysclk/8=9Mhz)

③、直接送给Cortex的自由运行时钟(free running clock)FCLK。【ARMJISHU注:FCLK 为处理器的自由振荡的处理器时钟,用来采样中断和为调试模块计时。在处理器休眠时,通过FCLK 保证可以采样到中断和跟踪休眠事件。 Cortex-M3内核的“自由运行时钟(free running clock)”FCLK。“自由”表现在它不来自系统时钟HCLK,因此在系统时钟停止时FCLK 也继续运行。FCLK和HCLK 互相同步。FCLK 是一个自由振荡的HCLK。FCLK 和HCLK 应该互相平衡,保证进入Cortex-M3 时的延迟相同。】

④、送给APB1分频器。APB1分频器可选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给定时器(Timer)2、3、4倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2、3、4使用。

⑤、送给APB2分频器。APB2分频器可选择1、2、4、8、16分频, 其输出一路供APB2外设使用(PCLK2,最大频率72MHz),另一路送给定时器(Timer)1倍频器使用。该倍频器可选择1或者2倍频,时钟输出 供定时器1使用。另外,APB2分频器还有一路输出供ADC分频器使用,分频后送给ADC模块使用。ADC分频器可选择为2、4、6、8分频。

以上提到3种时钟Fclk、Hclk和Pclk,简单解释如下:

Fclk为供给CPU内核的时钟信号,我们所说的cpu主频为XXXXMHz,就是指的这个时钟信号,相应的,1/Fclk即为cpu时钟周期;

Hclk为优秀的高性能总线(AHB bus peripherals)供给时钟信号(AHB为advanced high-performance bus) HCLK :AHB总线时钟,由系统时钟SYSCLK 分频得到,一般不分频,等于系统时钟,HCLK是高速外设时钟,是给外部设备的,比如内存,flash。

Pclk为优秀的高性能外设总线(APB bus peripherals)供给时钟信号(其中APB为advanced peripherals bus)。

以上介绍让大家对各个时钟有个清晰的认识,接下来言归正传,该如何在cubemx中配置呢?

当使用PLL锁相环的时候,最大系统时钟能倍频至最大64MHZ,倍频系数2-16,可以根据自己的需要选择,设置不同的主频

直接使用HSI的话,系统时钟设置为8MHZ

后面的这些外设时钟需要我们根据自己的需要调整分频系数

至此,使用内部时钟的配置就完了,接下来介绍使用外部时钟:

  • 2.2 外部高速时钟作为时钟输入

使用外部时钟,首先我们硬件上必须设计有外部晶振或者外部输入源,我们一般设计使用外部晶振

外部时钟源(HSE旁路)模式:

该模式下必须提供外部时钟。用户通过设置时钟控制寄存器中的HSEBYP和HSEON位来选择这一模式。外部时钟信号(50%占空比的方波、正弦波或三角波)必须连到SOC_IN引脚,此时OSC_OUT引脚对外呈高阻态。

外部晶体/陶瓷谐振器(HSE晶体)模式:

这种模式用得比较常见,HSE晶体可以为系统提供较为精确的时钟源。在时钟控制寄存器RCC_CR中的HSERDY位用来指示高速外部振荡器是否稳定。在启动时,直到这一位被硬件置’1’,时钟才被释放出来。HSE晶体可以通过设置时钟控制寄存器里RCC_CR中的HSEON位被启动和关闭。

选中之后,MCU的对应引脚会被使用,也即是我们的硬件设计对应的引脚

同样的,HSE的时钟也有不同的路线可以到系统时钟,直接通向SYSCLK的话,就是外部晶振频率,4-16MHZ,走PLL这条路线的话,选择就变得丰富起来

使用外部晶振,最大主频可以达到72MHZ

结合cubemx,对时钟进行配置,对新手了解MCU时钟结构还是非常有好的,配置也是非常的简单,省却了去了解一大堆的寄存器。

系统时钟配置源码分析

系统时钟初始化的参数主要封装在两个结构体里面:

跟晶体状态相关的:

/**
  * @brief  RCC Internal/External Oscillator (HSE, HSI, LSE and LSI) configuration structure definition
  */
typedef struct
{
  uint32_t OscillatorType;       /*!< The oscillators to be configured.
                                       This parameter can be a value of @ref RCC_Oscillator_Type */

#if defined(STM32F105xC) || defined(STM32F107xC)
  uint32_t Prediv1Source;       /*!<  The Prediv1 source value.
                                       This parameter can be a value of @ref RCCEx_Prediv1_Source */
#endif /* STM32F105xC || STM32F107xC */

  uint32_t HSEState;              /*!< The new state of the HSE.
                                       This parameter can be a value of @ref RCC_HSE_Config */

  uint32_t HSEPredivValue;       /*!<  The Prediv1 factor value (named PREDIV1 or PLLXTPRE in RM)
                                       This parameter can be a value of @ref RCCEx_Prediv1_Factor */

  uint32_t LSEState;              /*!<  The new state of the LSE.
                                        This parameter can be a value of @ref RCC_LSE_Config */

  uint32_t HSIState;              /*!< The new state of the HSI.
                                       This parameter can be a value of @ref RCC_HSI_Config */

  uint32_t HSICalibrationValue;   /*!< The HSI calibration trimming value (default is RCC_HSICALIBRATION_DEFAULT).
                                       This parameter must be a number between Min_Data = 0x00 and Max_Data = 0x1F */

  uint32_t LSIState;              /*!<  The new state of the LSI.
                                        This parameter can be a value of @ref RCC_LSI_Config */

  RCC_PLLInitTypeDef PLL;         /*!< PLL structure parameters */

#if defined(STM32F105xC) || defined(STM32F107xC)
  RCC_PLL2InitTypeDef PLL2;         /*!< PLL2 structure parameters */
#endif /* STM32F105xC || STM32F107xC */
} RCC_OscInitTypeDef;

跟时钟源、分频相关的

/**
  * @brief  RCC System, AHB and APB busses clock configuration structure definition
  */
typedef struct
{
  uint32_t ClockType;             /*!< The clock to be configured.
                                       This parameter can be a value of @ref RCC_System_Clock_Type */

  uint32_t SYSCLKSource;          /*!< The clock source (SYSCLKS) used as system clock.
                                       This parameter can be a value of @ref RCC_System_Clock_Source */

  uint32_t AHBCLKDivider;         /*!< The AHB clock (HCLK) divider. This clock is derived from the system clock (SYSCLK).
                                       This parameter can be a value of @ref RCC_AHB_Clock_Source */

  uint32_t APB1CLKDivider;        /*!< The APB1 clock (PCLK1) divider. This clock is derived from the AHB clock (HCLK).
                                       This parameter can be a value of @ref RCC_APB1_APB2_Clock_Source */

  uint32_t APB2CLKDivider;        /*!< The APB2 clock (PCLK2) divider. This clock is derived from the AHB clock (HCLK).
                                       This parameter can be a value of @ref RCC_APB1_APB2_Clock_Source */
} RCC_ClkInitTypeDef;

配置代码如下:

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;//时钟源为HSE
  RCC_OscInitStruct.HSEState = RCC_HSE_ON; //打开HSE
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV2;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;//打开PLL
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;//PLL时钟源选择HSE
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

配置参数可以对照cubemx中时钟树图分析分析

跟时钟相关的宏定义基本都在STM32XXX_RCC.h中,就不再多列举了

在HAL_RCC_OscConfig,函数中对HSE的配置如下,贴出一部分代码,其他时钟源配置类似,贴了一些中文注释:

/* Check the parameters */
  assert_param(IS_RCC_OSCILLATORTYPE(RCC_OscInitStruct->OscillatorType));
  /*------------------------------- HSE Configuration ------------------------*/ 
  if(((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSE) == RCC_OSCILLATORTYPE_HSE)
  {
    /* Check the parameters */
    assert_param(IS_RCC_HSE(RCC_OscInitStruct->HSEState));
    /* When the HSE is used as system clock or clock source for PLL in these cases HSE will not disabled */
   //判断HSE是否作为系统时钟,或者作为PLL时钟的来源
   
   
  // RCC_CFGR_SWS_HSE=0x00000004
  //__HAL_RCC_GET_SYSCLK_SOURCE函数,获取CFGR位 3:2 SWS: 系统时钟切换状态 (System clock switch status)
   //判断3:2位是否为01,含义:01: HSE 振荡器用作系统时钟,这两个位为只读
   //也就是判断此时系统时钟或者主PLL时钟是否已经设置为HSE
   
   //RCC_CFGR_SWS_PLL=0x00000008 ,判断CFGR的第3为是否为1
    if((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_HSE)                                                                     ||\
      ((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_PLL) && ((RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) == RCC_PLLCFGR_PLLSRC_HSE)))
    {   //PLL作为系统时钟,  RCC_PLLCFGR_PLLSRC=0x00400000,第22位,是否选择:1:选择 HSE 振荡器时钟作为 PLL 和 PLLI2S 时钟输入
  
  
  //如果HSE作为系统时钟来源,或者作为PLL时钟来源的话
      if((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) && (RCC_OscInitStruct->HSEState == RCC_HSE_OFF))
      {
    //此时HSE已经打开了,或HSE没有使能,这里的任何一种情况都会导致失败
        return HAL_ERROR;
      }
    }
    else  //否则的话,系统的时钟还没有进行初始化
    {
      /* Reset HSEON and HSEBYP bits before configuring the HSE --------------*/
  
  //对RCC->CFGR 寄存器的23:16 清零,也就是复位HSEON(关闭振荡器) 和重置就绪位
      __HAL_RCC_HSE_CONFIG(RCC_HSE_OFF);
      
  //获取当前系统时间戳,用于判断关闭HSE是否超时
      /* Get Start Tick*/
      tickstart = HAL_GetTick();
      
      /* Wait till HSE is disabled */  
      while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)  //等待HSE关闭
      {
        if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)
        {
          return HAL_TIMEOUT;  //超过最大时间为关闭HSE则,出错
        }       
      }
      
      /* Set the new HSE configuration ---------------------------------------*/
   //重新设置HSE,这个值来自于结构体,我们使用就需要使能,通过设置就可以对RCC->CFGR 寄存器的23:16,写此值
      __HAL_RCC_HSE_CONFIG(RCC_OscInitStruct->HSEState);
      
      /* Check the HSE State */
   //再一次进行确认,我们是否设置了打开HSE
      if((RCC_OscInitStruct->HSEState) != RCC_HSE_OFF)
      {
    //进入说明,我们的确是打开了
        /* Get Start Tick*/
    //获取当前系统时间戳
        tickstart = HAL_GetTick();
      
        /* Wait till HSE is ready */ 
   //获取HSE就绪标志位,未就绪就等待
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)
        {
          if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)
          {
            return HAL_TIMEOUT;
          } 
        }
      }
      else //否则没有打开HSE的开关咯
      {
        /* Get Start Tick*/
        tickstart = HAL_GetTick();
         
        /* Wait till HSE is bypassed or disabled */
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)
        {
          if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE) //HSE_TIMEOUT_VALUE=5000
          {
            return HAL_TIMEOUT;
          } 
        }
      }
    }
  }

摘出来这段,就是等待时钟相关标志位置位,保证系统处于稳定的状态

/* Set the new HSE configuration ---------------------------------------*/
      __HAL_RCC_HSE_CONFIG(RCC_OscInitStruct->HSEState);


      /* Check the HSE State */
      if (RCC_OscInitStruct->HSEState != RCC_HSE_OFF)
      {
        /* Get Start Tick */
        tickstart = HAL_GetTick();

        /* Wait till HSE is ready */
        while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)
        {
          if ((HAL_GetTick() - tickstart) > HSE_TIMEOUT_VALUE)
          {
            return HAL_TIMEOUT;
          }
        }
      }
      else
      {
        /* Get Start Tick */
        tickstart = HAL_GetTick();

        /* Wait till HSE is disabled */
        while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)
        {
          if ((HAL_GetTick() - tickstart) > HSE_TIMEOUT_VALUE)
          {
            return HAL_TIMEOUT;
          }
        }
      }

关于时钟的配置设计的寄存器是非常多的,小飞哥刚开始学习的时候,有寄存器版本和库函数版本,当时一看寄存器真精简啊,后来发现库函数用着好简单...后来就放弃了寄存器版本...

今天的分享就要OVER了,仅仅进行了比较粗略的介绍,希望对各位小伙伴有一些帮助,有疑问的可以+小飞哥好友,一起交流学习

 专辑|RT-Thread实战笔记

 专辑|cubemx与HAL库系列教程

☞ 如何制定通讯协议及如何解析协议数据

☞ 10分钟教你玩转freemodbus

  • 8
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
嵌入式系统设计是指将计算机系统集成到其他设备或系统中,以实现特定功能。而基于STM32CubeMXHAL库的嵌入式系统设计是指利用ST公司的STM32系列微控制器,结合STM32CubeMX工具和HAL库(Hardware Abstraction Layer),进行软硬件设计和开发的过程。 STM32CubeMX是ST公司推出的一款嵌入式系统设计工具,它提供了图形化的界面,可以帮助开发者快速配置STM32微控制器的引脚、时钟、外设等参数,生成初始化代码和项目文件,使整个开发过程更加简化和快捷。 HAL库STM32系列微控制器的硬件抽象层库,提供了一组封装了底层硬件操作的API接口,使开发者能够更加方便地进行外设控制和数据处理等操作。开发者可以根据具体的需求,选择需要的API接口,编写相应的代码,完成对硬件的控制和操作。 在使用STM32CubeMXHAL库进行嵌入式系统设计时,可以通过STM32CubeMX工具进行硬件的初始化和配置,生成相应的代码和项目文件。然后,在IDE(集成开发环境)中使用HAL库提供的API接口,编写代码进行软件的开发和应用程序的编写。通过这种方式,开发者可以在短时间内快速完成嵌入式系统设计,提高开发效率和产品质量。 总而言之,基于STM32CubeMXHAL库的嵌入式系统设计提供了一种快速、简化的嵌入式软硬件开发方法,使开发者能够更加高效地进行嵌入式应用的设计与开发。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小飞哥玩嵌入式

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值