STM32F413 HAL固件库(二)-时钟与RCC

ARM架构。细琢磨有一种很奇妙的感觉。

基础概念

时钟是单片机运行的基础,时钟信号推动单片机内各个部分执行相应的指令。时钟系统就是CPU的脉搏,其重要性不言而喻。STM32外设非常多,但我们实际使用时只会用到几个有限外设。使用时,任何外设都需要时钟才能启动,但并不是所有外设都需要系统时钟那么高的频率。有些外设高速,有些外设低速,如果都用高速时钟,势必会造成浪费,并且功耗很大,同时抗电磁干扰能力也更弱。所以较为复杂的MCU都是采用多时钟源的方法来兼容不同速度的外设,也就有了STM32的时钟系统和时钟树。这样能显著降低芯片耗能

此外,为了降低功耗,STM32所有的外设时钟初始设置都为disable。当我们用到哪个外设,只需打开对应外设的时钟就可以了,其他的依然还是disable的状态。这就是为什么不管你配置什么功能都需要先打开对应的时钟的原因。

下图为时钟系统图:

1.png

乍一看很吓人,但我们化整为零,就会很简单。系统时钟在相对中间的位置,那左边的部分就是设置系统时钟使用的时钟源。在STM32F413上,有三种不同的时钟源驱动系统时钟

  • HSE振荡器时钟

    高速外部时钟信号(HSE)有2个时钟源:HSE外部晶振/陶瓷谐振器和HSE外部用户时钟。HSE的特点是精度非常高。如果我们使用HSE或者HSE经过PLL倍频之后的时钟作为系统时钟SYSCLK, 当HSE故障时候,不仅HSE会被关闭,PLL也会被关闭,此时高速的内部时钟时钟信号HSI会作为备用的系统时钟, 直到HSE恢复正常。

  • HSI振荡器时钟

    HSI 时钟信号由内部16 MHz RC振荡器生成,可直接用作系统时钟,或者用作PLL输入。HSI RC振荡器的优点是成本较低(无需使用外部组件)。此外,其启动速度也要比HSE晶振快,但即使校准后,其精度也不及外部晶振或陶瓷谐振器。

  • 主PLL(PLL)时钟

    STM32F413有两个PLL。PLL的主要作用是对HSI、HSE时钟进行倍频,然后把时钟输出到各个功能部件。主PLL由HSE或HSI振荡器提供时钟信号,并具有两个不同的输出时钟:

    • 第一个输出PLLP用于生成高速系统时钟(最高达 100 MHz);
    • 第二个输出PLLQ用于生成USB OTG FS(48 MHz)、RNG和SDIO(≤ 50 MHz)时钟。

    2.png

由上图可知,时钟信号进入PLL之前首先会经过一个分频系数为M的分频器,随后经过N倍的倍频器之后,再次经过三个系数分别为P、Q、R的分频器之后,输出最终的 PLL 时钟信号(分别为 PLLP、PLLQ信号)。例如外部晶振选择为8MHz,同时设置相应的分频器M=1,倍频器倍频系数N=20,分频器分频系数P=2,那么主PLL生成的第一个输出高速时钟为:

\[\frac {8MHz * N} {M * P}=\frac {8MHz * 20} {1 * 2}=80MHz\]

器件有两个次级时钟源:

  • 32kHz低速内部RC(LSI RC),该RC用于驱动独立看门狗,也可选择提供给RTC用于停机/待机模式下的自动唤醒;
  • 32.768kHz低速外部晶振(LSE 晶振),用于驱动RTC时钟(RTCCLK)。从下图可以看出,RTC的时钟源可以选择LSI,LSE,以及HSE分频后的时钟。HSE分频系数为2~31。

    3.png

系统时钟SYSCLK的右边,则是系统时钟通过AHB预分频器,给相对应的外设设置相对应的时钟频率。SYSCLK系统时钟来源有三个方面:HSI,HSE和PLL。在我们实际应用中,因为对时钟速度要求都比较高,所以一般情况下,都是采用PLL作为SYSCLK时钟源。根据前面的计算公式,就可以算出系统的SYSCLK是多少。

4.png

SYSCLK经过AHB分频器后,输出时钟给5大模块使用:

  • 送给AHB总线、内核、内存、DMA使用的HCLK时钟;
  • 通过8分频送给系统定时器的定时时钟(SysTick)。通过对SysTick控制与状态寄存器的设置,也可选择HCLK时钟作为SysTick时钟
  • 直接送给Cortex的空闲时钟PCLK;
  • 送给APB1分频器,可选择1、2、4、8、16分频。其输出一路供APB1外设使用(PCLK1,最大频率50M),另一路送给APB1定时器(timer)2、3、4倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2、3、4使用。(低速外设);
  • 送给APB2分频器,可选择1、2、4、8、16分频,其输出一路供APB2外设使用(PCLK2,最大频率72M),另一路送给APB2定时器(Timer)倍频器使用。该倍频器可选择1或者2倍频。另外还有一路供给DFSDM。(高速外设)。

以下时钟不是由系统时钟提供的:

  • 来自于专用PLL输出(PLL48CLK)的USB OTG FS时钟(48 MHz)和SDIO时钟(≤ 48 MHz);
  • I2S时钟;

    要实现高品质的音频性能,可通过专用PLL(PLLI2S)或映射到I2S_CKIN引脚的外部时钟提供I2S时钟。

  • I2CFMP1时钟,该时钟可由HSI、SYSCLK或APB1时钟提供。

5.png

综上,整幅图从左到右可以简单理解为:各个时钟源——>系统时钟来源的设置——>各外设时钟的设置

6.png

STM32还提供时钟输出功能,可以选择一个时钟信号输出到MCO脚上。

寄存器操作

程序存储器、数据存储器、寄存器和I/O端口排列在同一个线性(即地址连续)的4 GB地址空间内(统一编址),各字节按小端格式在存储器中编码。一个存储字单元中编号最低的字节被视为该字的最低有效字节,而编号最高的字节被视为最高有效字节。可寻址的存储空间分为8个主要块,每个块为512MB。

寄存器映射方式如下:

7.png

而RCC寄存器组的每个层级基地址宏定义如下,都在stm32f413xx.h中:

#define PERIPH_BASE           0x40000000UL /*!< Peripheral base address in the alias region                                */
#define APB1PERIPH_BASE       PERIPH_BASE
#define AHB1PERIPH_BASE       (PERIPH_BASE + 0x00020000UL)
#define RCC_BASE              (AHB1PERIPH_BASE + 0x3800UL)

#define RCC                 ((RCC_TypeDef *) RCC_BASE)
typedef struct
{
  __IO uint32_t CR;            /*!< RCC clock control register,                                  Address offset: 0x00 */
  __IO uint32_t PLLCFGR;       /*!< RCC PLL configuration register,                              Address offset: 0x04 */
  __IO uint32_t CFGR;          /*!< RCC clock configuration register,                            Address offset: 0x08 */
  __IO uint32_t CIR;           /*!< RCC clock interrupt register,                                Address offset: 0x0C */
  __IO uint32_t AHB1RSTR;      /*!< RCC AHB1 peripheral reset register,                          Address offset: 0x10 */
  __IO uint32_t AHB2RSTR;      /*!< RCC AHB2 peripheral reset register,                          Address offset: 0x14 */
  __IO uint32_t AHB3RSTR;      /*!< RCC AHB3 peripheral reset register,                          Address offset: 0x18 */
  uint32_t      RESERVED0;     /*!< Reserved, 0x1C                                                                    */
  __IO uint32_t APB1RSTR;      /*!< RCC APB1 peripheral reset register,                          Address offset: 0x20 */
  __IO uint32_t APB2RSTR;      /*!< RCC APB2 peripheral reset register,                          Address offset: 0x24 */
  uint32_t      RESERVED1[2];  /*!< Reserved, 0x28-0x2C                                                               */
  __IO uint32_t AHB1ENR;       /*!< RCC AHB1 peripheral clock register,                          Address offset: 0x30 */
  __IO uint32_t AHB2ENR;       /*!< RCC AHB2 peripheral clock register,                          Address offset: 0x34 */
  __IO uint32_t AHB3ENR;       /*!< RCC AHB3 peripheral clock register,                          Address offset: 0x38 */
  uint32_t      RESERVED2;     /*!< Reserved, 0x3C                                                                    */
  __IO uint32_t APB1ENR;       /*!< RCC APB1 peripheral clock enable register,                   Address offset: 0x40 */
  __IO uint32_t APB2ENR;       /*!< RCC APB2 peripheral clock enable register,                   Address offset: 0x44 */
  uint32_t      RESERVED3[2];  /*!< Reserved, 0x48-0x4C                                                               */
  __IO uint32_t AHB1LPENR;     /*!< RCC AHB1 peripheral clock enable in low power mode register, Address offset: 0x50 */
  __IO uint32_t AHB2LPENR;     /*!< RCC AHB2 peripheral clock enable in low power mode register, Address offset: 0x54 */
  __IO uint32_t AHB3LPENR;     /*!< RCC AHB3 peripheral clock enable in low power mode register, Address offset: 0x58 */
  uint32_t      RESERVED4;     /*!< Reserved, 0x5C                                                                    */
  __IO uint32_t APB1LPENR;     /*!< RCC APB1 peripheral clock enable in low power mode register, Address offset: 0x60 */
  __IO uint32_t APB2LPENR;     /*!< RCC APB2 peripheral clock enable in low power mode register, Address offset: 0x64 */
  uint32_t      RESERVED5[2];  /*!< Reserved, 0x68-0x6C                                                               */
  __IO uint32_t BDCR;          /*!< RCC Backup domain control register,                          Address offset: 0x70 */
  __IO uint32_t CSR;           /*!< RCC clock control & status register,                         Address offset: 0x74 */
  uint32_t      RESERVED6[2];  /*!< Reserved, 0x78-0x7C                                                               */
  __IO uint32_t SSCGR;         /*!< RCC spread spectrum clock generation register,               Address offset: 0x80 */
  __IO uint32_t PLLI2SCFGR;    /*!< RCC PLLI2S configuration register,                           Address offset: 0x84 */
  uint32_t      RESERVED7;     /*!< Reserved, 0x84                                                                    */
  __IO uint32_t DCKCFGR;       /*!< RCC Dedicated Clocks configuration register,                 Address offset: 0x8C */
  __IO uint32_t CKGATENR;      /*!< RCC Clocks Gated ENable Register,                            Address offset: 0x90 */
  __IO uint32_t DCKCFGR2;      /*!< RCC Dedicated Clocks configuration register 2,               Address offset: 0x94 */
} RCC_TypeDef;

从外设基地址开始,逐层封装,到RCC寄存器基地址处时,定义一个结构体指针量,将寄存器地址指向RCC基地址,结构体内部的存储空间大小刚好按照真实的偏移量定义,这样就只需要对结构体内的元素进行读写即可达到操控寄存器的目的。

结构体内变量前修饰着__IO,这是一种宏,它的定义在core_cm4.h中:

#define     __IO    volatile             /*!< Defines 'read / write' permissions */

volatile限定符说明所修饰的对象是变化的,每次应该从变量对应的地址处访问,而不从高速缓存中访问。使用volatile的目的是因为该结构体中的成员变量对应的是寄存器,而寄存器在程序运行过程中状态随时都可能改变,这样它对应的bit也会改变。不加修饰的话,CPU可能会从高速缓存中取成员变量,这样就造成了实际的值与取到的值不一致。

至此,RCC寄存器组的地址、名称和作用我们就都理解了。接下来就是如何用HAL API配置时钟。

在最开始,单片机上电启动时,会执行system_stm32f4xx.c,里面有对时钟对初始配置:

#if !defined  (HSE_VALUE) 
  #define HSE_VALUE    ((uint32_t)8000000) /*!< Default value of the External oscillator in Hz */
#endif /* HSE_VALUE */

#if !defined  (HSI_VALUE)
  #define HSI_VALUE    ((uint32_t)16000000) /*!< Value of the Internal oscillator in Hz*/
#endif /* HSI_VALUE */
uint32_t SystemCoreClock = 16000000;
const uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9};
const uint8_t APBPrescTable[8]  = {0, 0, 0, 0, 1, 2, 3, 4};

SystemInit函数进行初始化配置:

void SystemInit(void)
{
  /* FPU settings ------------------------------------------------------------*/
  #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10 and CP11 Full Access */
  #endif
  /* Reset the RCC clock configuration to the default reset state ------------*/
  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001;

  /* Reset CFGR register */
  RCC->CFGR = 0x00000000;

  /* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

  /* Reset PLLCFGR register */
  RCC->PLLCFGR = 0x24003010;

  /* Reset HSEBYP bit */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

  /* Disable all interrupts */
  RCC->CIR = 0x00000000;
  
#if defined (DATA_IN_ExtPSRAM)
  SystemInit_ExtMemCtl(); 
#endif /* DATA_IN_ExtPSRAM */

  /* Configure the Vector Table location add offset address ------------------*/
#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
}

上述代码进行了FPU设置,复位RCC时钟配置(默认开启MSI)和配置了中断向量表地址。除此之外并没有进行时钟配置,所以这部分需要我们自己编写。

下面是样例函数:

/**
  * @brief  System Clock Configuration
  *         The system Clock is configured as follow : 
  *            System Clock source            = PLL (HSE)
  *            SYSCLK(Hz)                     = 100000000
  *            HCLK(Hz)                       = 100000000
  *            AHB Prescaler                  = 1
  *            APB1 Prescaler                 = 2
  *            APB2 Prescaler                 = 1
  *            HSE Frequency(Hz)              = 8000000
  *            PLL_M                          = 8
  *            PLL_N                          = 200
  *            PLL_P                          = 2
  *            PLL_Q                          = 7
  *            PLL_R                          = 2
  *            VDD(V)                         = 3.3
  *            Main regulator output voltage  = Scale1 mode
  *            Flash Latency(WS)              = 3
  * @param  None
  * @retval None
  */
static void SystemClock_Config(void)
{
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
  RCC_OscInitTypeDef RCC_OscInitStruct;
  HAL_StatusTypeDef ret = HAL_OK;

  /* Enable Power Control clock */
  __HAL_RCC_PWR_CLK_ENABLE();

  /* The voltage scaling allows optimizing the power consumption when the device is 
     clocked below the maximum system frequency, to update the voltage scaling value 
     regarding system frequency refer to product datasheet.  */
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /* Enable HSE Oscillator and activate PLL with HSE as source */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 8;
  RCC_OscInitStruct.PLL.PLLN = 200;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 7;
  RCC_OscInitStruct.PLL.PLLR = 2;
  ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);
  
  if(ret != HAL_OK)
  {
    while(1) { ; } 
  }

  /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 
     clocks dividers */
  RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | 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_DIV1;  
  ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3);
  if(ret != HAL_OK)
  {
    while(1) { ; }  
  }
}

样例表示了我们配置时钟的一般流程:

  • 使能PWR电源控制时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE()
  • 设置调压器输出电压级别:调用函数__HAL_PWR_VOLTAGESCALING_CONFIG()
  • 配置时钟源相关参数:HAL_RCC_OscConfig()
  • 配置系统时钟源以及AHB,APB1和APB2的分频系数:调用函数HAL_RCC_ClockConfig()

第一步的使能函数是一个宏,定义在stm32f4xx_hal_rcc.h里:

#define __HAL_RCC_PWR_CLK_ENABLE()     do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->APB1ENR, RCC_APB1ENR_PWREN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_PWREN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)

SET_BITREAD_BIT也是宏,定义在stm32f4xx.h中,作为寄存器操作的函数,设置相关位为1:

#define SET_BIT(REG, BIT)     ((REG) |= (BIT))
#define READ_BIT(REG, BIT)    ((REG) & (BIT))

而内部的操作数,定义在stm32f413xx.h中。具体寄存器操作参见手册:

#define RCC_APB1ENR_PWREN_Pos              (28U)                               
#define RCC_APB1ENR_PWREN_Msk              (0x1UL << RCC_APB1ENR_PWREN_Pos)     /*!< 0x10000000 */
#define RCC_APB1ENR_PWREN                  RCC_APB1ENR_PWREN_Msk

之所以要使能PWR时钟,是因为第二步是电源控制相关配置,所以需要使能。

UNUSED也是宏,定义在stm32f4xx_hal_def.h中,用来规避gcc的警告:

#define UNUSED(X) (void)X

第二步设置输出电压级别的目的是便于器件工作时性能与功耗平衡。定义是在stm32f4xx_hal_pwr_ex.h中。不再细说。以下函数是条件编译中的一个分支:

#define __HAL_PWR_VOLTAGESCALING_CONFIG(__REGULATOR__) do {                                                     \
                                                            __IO uint32_t tmpreg = 0x00U;                        \
                                                            MODIFY_REG(PWR->CR, PWR_CR_VOS, (__REGULATOR__));   \
                                                            /* Delay after an RCC peripheral clock enabling */  \
                                                            tmpreg = READ_BIT(PWR->CR, PWR_CR_VOS);             \
                                                            UNUSED(tmpreg);                                     \
                                                          } while(0U)
#endif /* STM32F405xx || STM32F407xx || STM32F415xx || STM32F417xx */

具体参数如下图:

8.png

下面配置时钟源是非常重要的环节。代码中是将结构体变量传入HAL_RCC_OscConfig()函数中。该函数定义在stm32f4xx_hal_rcc.c中:

__weak HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef  *RCC_OscInitStruct)
{
  uint32_t tickstart, pll_config;

  /* Check Null pointer */
  if(RCC_OscInitStruct == NULL)
  {
    return HAL_ERROR;
  }

  /* 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 */
    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)))
    {
      if((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) && (RCC_OscInitStruct->HSEState == RCC_HSE_OFF))
      {
        return HAL_ERROR;
      }
    }
    else
    {
      /* 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 bypassed or disabled */
        while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)
        {
          if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)
          {
            return HAL_TIMEOUT;
          }
        }
      }
    }
  }

先不分析这函数干了什么,先关注传入的结构体。RCC_OscInitStruct的结构体定义是在stm32f4xx_hal_rcc.h中:

typedef struct
{
  uint32_t OscillatorType;       /*!< The oscillators to be configured.
                                      This parameter can be a value of @ref RCC_Oscillator_Type                   */

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

  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                                                    */
}RCC_OscInitTypeDef;

前面几个参数是用来选择配置的振荡器类型。比如开启HSE,那么我们会设置OscillatorType的值为RCC_OSCILLATORTYPE_HSE,然后设置HSEState的值为RCC_HSE_ON来开启HSE。其他时钟源配置方法类似。之后设置PLL参数,它也是个结构体,定义在stm32f4xx_hal_rcc_ex.h中:

typedef struct
{
  uint32_t PLLState;   /*!< The new state of the PLL.
                            This parameter can be a value of @ref RCC_PLL_Config                      */

  uint32_t PLLSource;  /*!< RCC_PLLSource: PLL entry clock source.
                            This parameter must be a value of @ref RCC_PLL_Clock_Source               */

  uint32_t PLLM;       /*!< PLLM: Division factor for PLL VCO input clock.
                            This parameter must be a number between Min_Data = 0 and Max_Data = 63    */

  uint32_t PLLN;       /*!< PLLN: Multiplication factor for PLL VCO output clock.
                            This parameter must be a number between Min_Data = 50 and Max_Data = 432 
                            except for STM32F411xE devices where the Min_Data = 192 */

  uint32_t PLLP;       /*!< PLLP: Division factor for main system clock (SYSCLK).
                            This parameter must be a value of @ref RCC_PLLP_Clock_Divider             */

  uint32_t PLLQ;       /*!< PLLQ: Division factor for OTG FS, SDIO and RNG clocks.
                            This parameter must be a number between Min_Data = 2 and Max_Data = 15    */
#if defined(STM32F410Tx) || defined(STM32F410Cx) || defined(STM32F410Rx) || defined(STM32F446xx) || defined(STM32F469xx) ||\
    defined(STM32F479xx) || defined(STM32F412Zx) || defined(STM32F412Vx) || defined(STM32F412Rx) || defined(STM32F412Cx) ||\
    defined(STM32F413xx) || defined(STM32F423xx)
  uint32_t PLLR;       /*!< PLLR: PLL division factor for I2S, SAI, SYSTEM, SPDIFRX clocks.
                            This parameter is only available in STM32F410xx/STM32F446xx/STM32F469xx/STM32F479xx
                            and STM32F412Zx/STM32F412Vx/STM32F412Rx/STM32F412Cx/STM32F413xx/STM32F423xx devices. 
                            This parameter must be a number between Min_Data = 2 and Max_Data = 7     */
#endif /* STM32F410xx || STM32F446xx || STM32F469xx || STM32F479xx || STM32F412Zx || STM32F412Vx || STM32F412Rx || STM32F412Cx || STM32F413xx || STM32F423xx */ 
}RCC_PLLInitTypeDef;

注释已经说的很明白,现在回到配置中:

	RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;  //时钟源为HSE
	RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS;   
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;    
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;   //时钟源为HSE
  RCC_OscInitStruct.PLL.PLLM = 8;
  RCC_OscInitStruct.PLL.PLLN = 200;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 7;
  RCC_OscInitStruct.PLL.PLLR = 2;

以上,时钟源相关参数设置完成。接下来是设置系统时钟,调用了HAL_RCC_ClockConfig()函数。它的声明是在stm32f4xx_hal_rcc.h,定义是在c文件:

HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency);

第一个参数是结构体,和之前的类似,后一个参数FLatency,这个是延迟相关的参数:

9.png

前者结构体的定义是在stm32f4xx_hal_rcc.h中:

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;

很清晰明了。自此时钟设置完毕。之后在主函数中调用即可。