26.3.10 ZD-EP39 [系统时钟] F4系列系统时钟初始化函数简介

1 系统时钟初始化函数介绍

正点原子提供的系统时钟初始化函数存放在Drivers/STSTEM/sys.c文件下,具体通过sys_stm32_clock_init()函数实现,该函数下的调用的两个主要函数为HAL_RCC_OscConfig()和HAL_RCC_ClockConfig(),这两个函数的主要作用如下:

  • HAL_RCC_OscConfig():振荡器(Oscillator)配置函数,核心作用是启用 / 禁用 STM32 的内部 / 外部振荡器(HSI/HSE/LSI/LSE),配置锁相环(PLL)的倍频 / 分频参数,生成稳定的原始时钟源。
  • HAL_RCC_ClockConfig():系统时钟配置函数,核心作用是将HAL_RCC_OscConfig()配置好的时钟源(如 PLL 输出)选为系统时钟(SYSCLK),并配置 AHB/APB1/APB2 总线的分频系数,最终将时钟分配到内核和外设。

下面来详细展开一下这两个函数:


2 配置函数解析

先简单说一下如何在工程中溯源一个函数定义

在我们找到对应函数后,双击它会自动框选出函数,此时我们按下F12即可跳转到函数定义处, 如下图所示:

F12代表的功能为Go To Definition Of (void),即跳转到函数的定义处
注意:使用该功能时请确保路径中没有过多的中文,否则将跳转失败。

2.1 HAL_RCC_OscConfig()

通过跳转功能我们可以知道该函数的原型为:

HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef  *RCC_OscInitStruct)

HAL_RCC_OscConfig()函数原型

在这里我们逐个进行解析:

  • HAL_StatusTypeDef:HAL库状态配置,跳转可知对应定义下的HAL库状态有OK,ERROR,BUSY,TIMEOUT四种状态:
typedef enum 
{
  HAL_OK       = 0x00U,
  HAL_ERROR    = 0x01U,
  HAL_BUSY     = 0x02U,
  HAL_TIMEOUT  = 0x03U
} HAL_StatusTypeDef;

HAL_StatusTypeDef的四种状态

  • RCC_OscInitTypeDef:振荡器初始化配置,跳转可得以下结构体:
typedef struct
{
  uint32_t OscillatorType;
  uint32_t HSEState;
  uint32_t LSEState;
  uint32_t HSIState;
  uint32_t HSICalibrationValue;/*default is RCC_HSICALIBRATION_DEFAULT*/
  uint32_t LSIState;
}RCC_OscInitTypeDef;

RCC_OscInitTypeDef结构体

从上到下分别为振荡器类型、HSE(外部高速振荡器)状态、LSE(外部低速振荡器)状态、HSI(内部高速振荡器)状态、HSI校准微调值、LSI(内部低速振荡器)状态以及结构体为PLL锁相环初始化配置的PLL结构,其中PLL结构体又如下图所示:

typedef struct
{
  uint32_t PLLState;
  uint32_t PLLSource;
  uint32_t PLLM;
  uint32_t PLLN;
  uint32_t PLLP;
  uint32_t PLLQ;
#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;
#endif /* STM32F410xx || STM32F446xx || STM32F469xx || STM32F479xx || STM32F412Zx || STM32F412Vx || STM32F412Rx || STM32F412Cx || STM32F413xx || STM32F423xx */ 
}RCC_PLLInitTypeDef;

RCC_PLLInitTypeDef结构体

从上到下分别为PLL状态、PLL来源、PLL分/倍频系数M,N,P,Q(其中另有一个R隶属于F4的其他系列,此处的F407系列仅有MNPQ)。

对于M,N,P,Q的配置,其实就来源于时钟树:

F4时钟树简图

图中展示的/M,*N,/P,/Q与结构体中的M,N,P,Q是对应的。

该函数与接下来的HAL_RCC_ClockConfig()函数配置过程见后。

2.2 HAL_RCC_ClockConfig()

同样的,该函数原型为:

HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency);

其中HAL_StatusTypeDef同上

  • RCC_ClkInitTypeDef:时钟初始化配置,跳转可得以下结构体:
typedef struct
{
  uint32_t ClockType;
  uint32_t SYSCLKSource; 
  uint32_t AHBCLKDivider;
  uint32_t APB1CLKDivider;
  uint32_t APB2CLKDivider;
}RCC_ClkInitTypeDef;

RCC_ClkInitTypeDef结构体

从上到下分别为要配置的时钟类型、系统时钟来源、总线AHB时钟预分频系数、总线APB1时钟预分频系数、总线APB2时钟预分频系数。

  • FLatency:FLASH等待时间周期,查找后输入内容定义如下:
#if defined(STM32F405xx) || defined(STM32F415xx) || defined(STM32F407xx) || defined(STM32F417xx) ||\
    defined(STM32F401xC) || defined(STM32F401xE) || defined(STM32F410Tx) || defined(STM32F410Cx) ||\
    defined(STM32F410Rx) || defined(STM32F411xE) || defined(STM32F412Zx) || defined(STM32F412Vx) ||\
    defined(STM32F412Rx) || defined(STM32F412Cx) || defined(STM32F413xx) || defined(STM32F423xx)
     
#define FLASH_LATENCY_0                FLASH_ACR_LATENCY_0WS   /*!< FLASH Zero Latency cycle      */
#define FLASH_LATENCY_1                FLASH_ACR_LATENCY_1WS   /*!< FLASH One Latency cycle       */
#define FLASH_LATENCY_2                FLASH_ACR_LATENCY_2WS   /*!< FLASH Two Latency cycles      */
#define FLASH_LATENCY_3                FLASH_ACR_LATENCY_3WS   /*!< FLASH Three Latency cycles    */
#define FLASH_LATENCY_4                FLASH_ACR_LATENCY_4WS   /*!< FLASH Four Latency cycles     */
#define FLASH_LATENCY_5                FLASH_ACR_LATENCY_5WS   /*!< FLASH Five Latency cycles     */
#define FLASH_LATENCY_6                FLASH_ACR_LATENCY_6WS   /*!< FLASH Six Latency cycles      */
#define FLASH_LATENCY_7                FLASH_ACR_LATENCY_7WS   /*!< FLASH Seven Latency cycles    */
#endif /* STM32F40xxx || STM32F41xxx || STM32F401xx || STM32F410xx || STM32F411xE || STM32F412Zx || STM32F412Vx || STM32F412Rx || STM32F412Cx ||
          STM32F413xx || STM32F423xx */

FLASH_LATENCY等待周期定义

对应该值的定义我们要在STM32F4xx参考手册中找到表 7. CPU 时钟频率 (HCLK) 对应的等待周期数,内容如下:

CPU 时钟频率 (HCLK) 对应的等待周期数

探索者系列设置的电压为3.3V,且设置的最高时钟频率为168MHz,在对应处我们找到其等待的周期为6个CPU周期(5WS处理周期+1等待周期),因此我们设置的FLantency值为FLASH_LANTENCY_5。

关于为什么会有等待周期,可以参考26.2.11 ADD [结构基础/数电基础] MOS管、与或非门结构解释中关于总线通信的内容。

下面我们来看具体的配置过程。


3 系统时钟初始化配置过程

我们首先看到整个system_stm32_clock_init()函数是怎么定义的:

uint8_t sys_stm32_clock_init(uint32_t plln, uint32_t pllm, uint32_t pllp, uint32_t pllq)
{
    HAL_StatusTypeDef ret = HAL_OK;
    RCC_OscInitTypeDef rcc_osc_init = {0};
    RCC_ClkInitTypeDef rcc_clk_init = {0};

    __HAL_RCC_PWR_CLK_ENABLE();                                         /* 使能PWR时钟 */

    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);      /* 设置调压器输出电压级别,以便在器件未以最大频率工作 */

    /* 使能HSE,并选择HSE作为PLL时钟源,配置PLL1,开启USB时钟 */
    rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE;        /* 时钟源为HSE */
    rcc_osc_init.HSEState = RCC_HSE_ON;                          /* 打开HSE */
    rcc_osc_init.PLL.PLLState = RCC_PLL_ON;                      /* 打开PLL */
    rcc_osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSE;              /* PLL时钟源选择HSE */
    rcc_osc_init.PLL.PLLN = plln;
    rcc_osc_init.PLL.PLLM = pllm;
    rcc_osc_init.PLL.PLLP = pllp;
    rcc_osc_init.PLL.PLLQ = pllq;
    ret = HAL_RCC_OscConfig(&rcc_osc_init);                      /* 初始化RCC */
    if(ret != HAL_OK)
    {
        return 1;                                                /* 时钟初始化失败,可以在这里加入自己的处理 */
    }

    /* 选中PLL作为系统时钟源并且配置HCLK,PCLK1和PCLK2 */
    rcc_clk_init.ClockType = ( RCC_CLOCKTYPE_SYSCLK \
                                    | RCC_CLOCKTYPE_HCLK \
                                    | RCC_CLOCKTYPE_PCLK1 \
                                    | RCC_CLOCKTYPE_PCLK2);

    rcc_clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;         /* 设置系统时钟时钟源为PLL */
    rcc_clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1;                /* AHB分频系数为1 */
    rcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV4;                 /* APB1分频系数为4 */
    rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV2;                 /* APB2分频系数为2 */
    ret = HAL_RCC_ClockConfig(&rcc_clk_init, FLASH_LATENCY_5);   /* 同时设置FLASH延时周期为5WS,也就是6个CPU周期 */
    if(ret != HAL_OK)
    {
        return 1;                                                /* 时钟初始化失败 */
    }
    
    /* STM32F405x/407x/415x/417x Z版本的器件支持预取功能 */
    if (HAL_GetREVID() == 0x1001)
    {
        __HAL_FLASH_PREFETCH_BUFFER_ENABLE();                    /* 使能flash预取 */
    }
    return 0;
}

首先,该函数的输入内容为plln,pllm,pllp,pllq,即PLL相关分/倍频系数。

其次,该函数定义了HAL库配置状态ret并初始化为HAL_OK,定义了两个配置结构体rcc_osc_init和rcc_clk_init并初始化为 { 0 },随后使能了PWR(Power电源)的时钟并配置的电源的电压级别(以适配对应的频率)。

注意:在使用任何一个外设前都要使能该外设与其对应的时钟,否则该外设将无法运行。

之后进行了Osc的相关配置:将振荡器源定义为HSE并使能了HSE,使能了PLL并将其来源定义为HSE,随后定义了相关分/倍频系数为输入内容。

在完成Osc相关配置后调用了HAL_RCC_OscConfig()函数,输入内容即为前面定义的rcc_osc_init(注意:HAL_RCC_OscConfig()输入的形参为指针类型,这里应输入的是rcc_osc_init的地址即&rcc_osc_init)并将输出内容赋值给ret,若输出的ret不为OK则直接返回值,初始化失败。

完成Osc相关配置后进入到Clk的相关配置。

首先定义了要配置的时钟,即所有的四个时钟(SYS,AHB,APB1,APB2,通过 | 或运算符进行定义)

随后定义了系统时钟来源,根据时钟树简图可知即PLL,并依次定义了AHB,APB1,APB2的预分频系数,最后也将rcc_clk_init重新带回HAL_RCC_ClockConfig()函数,设定FLASH等待周期为5,将输出的内容赋值给ret,进行同样操作。

最后启用了STM32F405x/407x/415x/417x Z版本的器件支持预取功能,能够提升FLASH的性能。

由此就完成了系统时钟的初始化。


4 总结

本质上这一章的步骤就是把在CubeMX上配置的时钟树直接在sys.c中进行操作,对任意一种芯片都是生效的,即最本质的时钟配置。

CubeMX时钟树配置

可以看到,我们对各个结构体的操作本质上就是该时钟树中的各个节点。

欢迎订阅 Subscribe to 沿江路右转Turn Right at Yanjiang Rd.

期待您的精彩评论 Looking forward to your wonderful comments.
[email protected]
订阅 Subscribe
赣ICP备2026002696号