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即可跳转到函数定义处, 如下图所示:

注意:使用该功能时请确保路径中没有过多的中文,否则将跳转失败。
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的配置,其实就来源于时钟树:

图中展示的/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) 对应的等待周期数,内容如下:

探索者系列设置的电压为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中进行操作,对任意一种芯片都是生效的,即最本质的时钟配置。

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