第十一章 STM32时钟系统
STM32G4时钟系统的知识在《STM32G4xx参考手册_V7(英文版).pdf》第七章复位和时钟控制章节有较详细的讲解。这里我们对STM32G4的整体架构作一个简单的介绍,帮助大家更全面、系统地认识STM32G4系统的主控结构、了解时钟系统在整个STM32系统的贯穿和驱动作用,学会设置STM32的系统时钟。
本章将分为如下几个小节:
11.1 认识时钟树
11.2 如何修改主频
11.1 认识时钟树
数字电路的知识告诉我们:任意复杂的电路控制系统都可以经由门电路组成的组合电路实现。回顾《第五章 STM32基础知识入门》的知识点,我们知道STM32内部也是由多种多样的电路模块组合在一起实现的。当一个电路越复杂,在达到正确的输出结果前,它可能因为延时会有一些短暂的中间状态,而这些中间状态有时会导致输出结果会有一个短暂的错误,这叫做电路中的“毛刺现象”,如果电路需要运行得足够快,那么这些错误状态会被其它电路作为输入采样,最终形成一系列的系统错误。为了解决这个问题,在单片机系统中,设计时以时序电路控制替代纯粹的组合电路,在每一级输出结果前对各个信号进行采样,从而使得电路中某些信号即使出现延时也可以保证各个信号的同步,可以避免电路中发生的“毛刺现象”,达到精确控制输出的效果。
由于时序电路的重要性,因此在MCU设计时就设计了专门用于控制时序的电路,在芯片设计中称为时钟树设计。由此设计出来的时钟,可以精确控制我们的单片机系统,这也是我们这节要展开分析的时钟分析。为什么是时钟树而不是时钟呢?一个MCU越复杂,时钟系统也会相应地变得复杂,如STM32G4的时钟系统比较复杂,不像简单的51单片机一个系统时钟就可以解决一切。对于STM32G4系列的芯片,正常工作的主频可以达到170Mhz,但并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及RTC只需要几十Khz的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的MCU一般都是采取多时钟源的方法来解决这些问题。
STM32本身非常复杂,外设非常的多,为了保持低功耗工作,STM32的主控默认不开启这些外设功能。用户可以根据自己的需要决定STM32芯片要使用的功能,这个功能开关在STM32主控中也就是各个外设的时钟。
下面来看一下STM32G4时钟系统图。STM32G4时钟系统图看起来有点多且复杂,但是分开几部分各个理解其实没有那么难了。

图11.1.1 STM32G4时钟系统图
我们将时钟系统分为:时钟源,锁相环PLL,系统时钟SYSCLK,时钟信号输出MCO四个部分展开介绍。
11.1.1 时钟源
对于STM32G4,输入时钟源(Input Clock)主要包括HSI,HSE,LSI,LSE。其中,从时钟频率来分可以分为高速时钟源和低速时钟源,其中HSI、HSE高速时钟,LSI和LSE是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中HSE和LSE是外部时钟源;其他是内部时钟源,芯片上电即可产生,不需要借助外部电路。下面我们看看STM32的时钟源。
(1)2个外部时钟源:
l 高速外部振荡器HSE (High Speed External Clock signal)
外接石英/陶瓷谐振器,频率为4MHz~48MHz。本开发板使用的是8MHz。
l 低速外部振荡器LSE (Low Speed External Clock signal)
外接32.768kHz石英晶体,主要作用于RTC的时钟源。
(2)2个内部时钟源:
l 高速内部振荡器HSI(High Speed Internal Clock signal)
由内部RC 振荡器产生,频率为 16MHz。
l 低速内部振荡器LSI(Low Speed Internal Clock signal)
由内部RC振荡器产生,频率为32kHz,可作为独立看门狗的时钟源。
芯片上电时默认由内部的HSI时钟启动,如果用户进行了硬件和软件的配置,芯片才会根据用户配置调试尝试切换到对应的外部时钟源,所以同时了解这几个时钟源信号还是很有必要的。如何设置时钟的方法我们会在后文提到。
11.1.2 锁相环PLL
锁相环是自动控制系统中常用的一个反馈电路,在STM32主控中,锁相环的作用主要有两个部分:输入时钟净化和倍频。前者是利用锁相环电路的反馈机制实现,后者我们用于使芯片在更高且频率稳定的时钟下工作。
在STM32中,锁相环的输出也可以作为芯片系统的时钟源。根据图11.1.1的时钟结构,使用锁相环时只需要进行三个部分的配置。为了方便查看,截取了使用PLL作为系统时钟源的配置部分,如图11.1.2.1所示:

图11.1.2.1 PLL时钟配置图
PLL Source Mux:PLL时钟源选择器
图中标号①表示的是PLL时钟源的选择器,同样的,参考G474参考手册:

图11.1.2.2 PLLSRC锁相环时钟源选择
它有两种可选择的输入源:一个是内部时钟HSI信号,另一个是外部时钟HSE信号。
PLLM:HSE分频器作为PLL输入 (HSE divider for PLL entry)
即图11.1.2.1在标号②的地方,主PLL输入时钟的分频系数,并把它的控制功能放在RCC_PLLCFGR寄存器中,具体描述如图11.1.2.3所示:

图11.1.2.3 PLLM设置选项值
从G474参考手册可知它的值有16个:0~15。
PLLMUL:PLL倍频系数 (PLL multiplication factor)
图中③所表示的配置锁相环倍频系数,结合图11.1.2.1,要实现170MHz的主频率,我们通过选择HSE分频作为PLL输入的时钟信号,输入8Mhz,2分频,即4MHz,通过标号③选择倍频因子,我们选择85倍频,这样可以得到时钟信号为4*85=340MHz,然后经过2分频,得到170MHz。
11.1.3 系统时钟SYSCLK
STM32的系统时钟SYSCLK为整个芯片提供了时序信号。我们已经大致知道STM32主控是时序电路链接起来的。对于相同的稳定运行的电路,时钟频率越高,指令的执行速度越快,单位时间能处理的功能越多。STM32的系统时钟是可配置的,在STM32G4系列中,它可以为HSI、PLLCLK、HSE中的一个,通过CFGR的位SW[1:0]设置。
讲解PLL作为系统时钟时,根据我们开发板的资源,可以把主频通过PLL设置为170MHz。从上面的图11.1.1时钟树图可知,AHB、APB1、APB2、内核时钟等时钟通过系统时钟分频得到。根据得到的这个系统时钟,下面我们结合外设来看一看各个外设时钟源:

图11.1.3.1 STM32G474系统时钟生成图
大家看图11.1.3.1 STM32G474系统时钟,标号④为系统时钟输入选择,可选时钟信号有外部高速时钟HSE(8M)、内部高速时钟HSI(16M)和经过倍频的PLL CLK(170M)。这里我们选择PLL CLK作为系统时钟,此时系统时钟的频率为170MHz。系统时钟来到标号⑤的AHB预分频器,其中可选择的分频系数为1,2,4,8,16,32,64,128,256,512,我们选择不分频,所以AHB总线时钟达到最大的170MHz。
下面介绍一下由AHB总线时钟得到的时钟:
APB1总线时钟,由HCLK经过标号⑥的低速APB1预分频器得到,分频因子可以选择1,2,4,8,16,这里我们选择的是1,不分频,所以APB1总线时钟为170M。
APB2总线时钟,由HCLK经过标号⑦的高速APB2预分频器得到,分频因子可以选择1,2,4,8,16,这里我们选择的是1,不分频,所以APB2总线时钟频率为170M。其中标号⑧决定了定时器时钟频率,该位由硬件自动设置,分为两种情况:
1、 如果APB预分频器为1,定时器时钟频率等于APB域的频率;
2、 否则,等于APB域的频率的两倍(×2)。
此外,AHB总线时钟还直接作为GPIO(A\B\C\D\E\F\G)、FSMC、AHB总线、Cortex内核、存储器和DMA的HCLK时钟,并作为Cortex内核自由运行时钟FCLK。
11.1.4 RTC时钟、MCO时钟
① RTC时钟框图如下图所示:

图11.1.4.1 RTC相关时钟
图11.1.4.1标号⑨是RTC定时器,其时钟源为HSE/32、LSE或LSI。
② MCO时钟框图如下图所示:

图11.1.4.2 MCO相关时钟
图11.1.4.2标号⑩是MCO的钟源选择器,MCO是STM32 的一个时钟输出IO。STM32允许通过设置,让MCO引脚输出一个稳定的时钟信号。
MCO(外部器件的输出时钟)时钟源有四个:LSE、LSI、HSE、HSI 16、PLLCLK、SYSCLK、HSI48。
时钟源的选择由时钟配置寄存器(RCC_CFGR)中的MCOSEL[3:0]位控制。MCO的预分频器取值可选:1、2、4、8和16,由时钟配置寄存器(RCC_CFGR)中的MCOPRE[2:0]位控制。
我们可以通过MCO引脚来输出时钟信号,测试输出时钟的频率,或作为其它外部电路的时钟源。
11.2 如何修改主频
STM32G474默认的情况下(比如:串口IAP时或者是未初始化时钟时),使用的是内部16M的HSI作为时钟源,所以不需要外部晶振也可以下载和运行代码的。
下面我们来讲解如何让STM32G474芯片在170MHz的频率下工作,170MHz是官方推荐使用的最高且稳定的时钟频率。而正点原子的DMG474的外部高速晶振的频率就是8MHz,我们就是在这个晶振频率的基础上,通过各种倍频和分频得到170MHz的系统工作频率。
11.2.1 STM32G4时钟系统配置
下面我们将分几步给大家讲解STM32G4时钟系统配置过程,这部分内容很重要,请大家认真阅读。
第1步:配置HSE_VALUE
讲解stm32g4xx_hal_conf.h文件的时候,我们知道需要宏定义HSE_VALUE匹配我们实际硬件的高速晶振频率(这里是8MHz),代码中通过使用宏定义的方式来选择HSE_VALUE的值是25M或者8M选择定义HSE_VALUE的值为8M。代码如下:
#if !defined (HSE_VALUE) #define HSE_VALUE (8000000UL) /*Value of the External oscillator in Hz */ #endif #endif /* HSE_VALUE */
第2步:调用SystemInit函数
我们介绍启动文件的时候就知道,在系统启动之后,程序会先执行SystemInit函数,进行系统一些初始化配置。启动代码调用SystemInit函数如下:
Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT SystemInit IMPORT __main LDR R0, =SystemInit BLX R0 LDR R0, =__main BX R0 ENDP
下面我们来看看system_stm32g4xx.c文件下定义的SystemInit程序,源码在179行到190行,函数如下:
void SystemInit(void)
{
/* FPU settings */
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));
#endif
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* Configure the Vector Table location */
#if defined(USER_VECT_TAB_ADDRESS)
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET;
#endif /* USER_VECT_TAB_ADDRESS */
}
从上面代码可以看出,SystemInit主要做了如下两个方面工作:
1) 外部存储器配置;
2) 中断向量表地址配置。
然而我们的代码中实际并没有定义DATA_IN_ExtSRAM和USER_VECT_TAB_ADDRESS这两个宏,实际上SystemInit对于正点原子的例程并没有起作用,但我们保留了这个接口。从而避免了去修改启动文件。另外,是可以把一些重要的初始化放到SystemInit这里,在main函数运行前就把重要的一些初始化配置好(如ST这里是在运行main函数前先把外部的SRAM初始化),这个我们一般用不到,直接到main函数中处理即可,但也有厂商(如RT-Thread)就采取了这样的做法,使得main函数更加简单,但对于初学者,我们暂时不建议这种用法。
HAL库的SystemInit函数并没有任何时钟相关配置,所以后续的初始化步骤,我们还必须编写自己的时钟配置函数。
第3步:在main函数里调用用户编写的时钟设置函数
我们打开HAL库例程实验1跑马灯实验,看看我们在工程目录Drivers\SYSTEM分组下面定义的sys.c文件中的时钟设置函数sys_stm32_clock_init的内容:
/**
* @brief 时钟设置函数
* @param plln: 主PLL倍频系数(PLL倍频), 取值范围: 8~127.
* @param pllm: 主PLL和音频PLL预分频系数(进PLL之前的分频), 取值范围: 1~16.
* @param pllr: 主PLL的r分频系数(PLL之后的分频), 分频后作为系统时钟,
取值范围: 2, 4, 6, 8.(仅限这4个值)
* @param pllp: 主PLL的p分频系数(PLL之后的分频), 取值范围: 2~31.
* @param pllq: 主PLL的q分频系数(PLL之后的分频),
取值范围: 2, 4, 6, 8.(仅限这4个值)
* @note
* Fvco: VCO频率
* Fsys: 系统时钟频率, 也是主PLL的p分频输出时钟频率
* Fq: 主PLL的q分频输出时钟频率
* Fs: 主PLL输入时钟频率, 可以是HSI, HSE等.
* Fvco = Fs * (plln / pllm);
* Fsys = Fvco / pllr = Fs * (plln / (pllm * pllr));
* Fq = Fvco / pllq = Fs * (plln / (pllm * pllq));
*
* 外部晶振为8M的时候, 推荐值:
plln = 85, pllm = 2, pllr = 2, pllp = 4, pllq = 8.
* 得到:Fvco = 8 * (85 / 2) = 340Mhz
* Fsys = pll_p_ck = 340 / 2 = 170Mhz
* Fq = pll_q_ck = 340 / 8 = 42.5Mhz
*
* G474默认需要配置的频率如下:
* CPU频率(HCLK) = pll_p_ck = 170Mhz
* AHB1/2/3(rcc_hclk1/2/3) = 170Mhz
* APB1(rcc_pclk1) = pll_p_ck / 1 = 170Mhz
* APB1(rcc_pclk2) = pll_p_ck / 1 = 170Mhz
*
* @retval 错误代码: 0, 成功; 1, 错误;
*/
uint8_t sys_stm32_clock_init(uint32_t plln, uint32_t pllm,
uint32_t pllr,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_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST);
/* 使能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;
rcc_osc_init.PLL.PLLR = pllr;
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);
/* 设置系统时钟时钟源为PLL */
rcc_clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
rcc_clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1; /* AHB分频系数为1 */
rcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV1; /* APB1分频系数为1 */
rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV1; /* APB2分频系数为1 */
/* 同时设置FLASH延时周期为4WS,也就是5个CPU周期 */
ret = HAL_RCC_ClockConfig(&rcc_clk_init, FLASH_LATENCY_4);
if(ret != HAL_OK)
{
return 1; /* 时钟初始化失败 */
}
/* STM32G4x Z版本的器件支持预取功能 */
if (HAL_GetREVID() == 0x1001)
{
__HAL_FLASH_PREFETCH_BUFFER_ENABLE(); /* 使能flash预取 */
}
return 0;
}
函数sys_stm32_clock_init就是用户的时钟系统配置函数,除了配置PLL相关参数确定SYSCLK值之外,还配置了AHB、APB1和APB2的分频系数,也就是确定了HCLK,PCLK1和PCLK2的时钟值。
我们首先来看看使用HAL库配置STM32G4时钟系统的一般步骤:
1) 配置时钟源相关参数:调用函数HAL_RCC_OscConfig()。
2) 配置系统时钟源以及SYSCLK、AHB、APB1和APB2的分频系数:调用函数HAL_RCC_ClockConfig()。
下面我们详细讲解这个2个步骤。
步骤1:配置时钟源相关参数,使能并选择HSE作为PLL时钟源,配置PLL1,我们调用的函数为HAL_RCC_OscConfig(),该函数在HAL库头文件stm32g4xx_hal_rcc.h中声明,在文件stm32g4xx_hal_rcc.c中定义。首先我们来看看该函数声明:
HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct);
该函数只有一个形参,就是结构体RCC_OscInitTypeDef类型指针。接下来我们看看结构体RCC_OscInitTypeDef的定义:
typedef struct
{
uint32_t OscillatorType; /* 需要选择配置的振荡器类型 */
uint32_t HSEState; /* HSE状态 */
uint32_t HSEPredivValue; /* HSE预分频值 */
uint32_t LSEState; /* LSE状态 */
uint32_t HSIState; /* HSI状态 */
uint32_t HSICalibrationValue; /* HSI校准值 */
uint32_t LSIState; /* LSI状态 */
uint32_t HSI48State; /* HSI48状态 */
RCC_PLLInitTypeDef PLL; /* PLL配置 */
}RCC_OscInitTypeDef;
该结构体前面几个参数主要是用来选择配置的振荡器类型。比如我们要开启HSE,那么我们会设置OscillatorType的值为RCC_OSCILLATORTYPE_HSE,然后设置HSEState的值为RCC_HSE_ON开启HSE。对于其他时钟源:HSI、LSI、LSE,配置方法类似。
RCC_OscInitTypeDef这个结构体还有一个很重要的成员变量是PLL,它是结构体RCC_PLLInitTypeDef类型。它的作用是配置PLL相关参数,我们来看看它的定义:
typedef struct
{
uint32_t PLLState; /* PLL状态 */
uint32_t PLLSource; /* PLL时钟源 */
uint32_t PLLM; /* PLL倍频系数M */
uint32_t PLLN; /* PLL倍频系数N */
uint32_t PLLP; /* PLL倍频系数P */
uint32_t PLLQ; /* PLL倍频系数Q */
uint32_t PLLR; /* PLL倍频系数R */
}RCC_PLLInitTypeDef;
从RCC_PLLInitTypeDef;结构体的定义很容易看出该结构体主要用来设置PLL时钟源以及相关分频倍频参数。这个结构体的定义的相关内容请结合时钟树中红色框的内容一起理解。
接下来,我们看看时钟初始化函数sys_stm32_clock_init中的配置内容:
/* 使能HSE,并选择HSE作为PLL时钟源,配置PLL1,开启USB时钟 */ rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_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; rcc_osc_init.PLL.PLLR = pllr;
通过函数的该段程序,我们开启了HSE时钟源,同时选择PLL时钟源为HSE,然后把sys_stm32_clock_init的形参直接设置为PLL的参数值,这样就达到了设置PLL时钟源相关参数的目的。
设置好PLL时钟源参数之后,也就是确定了PLL的时钟频率,然后到我们的步骤2。
步骤2:配置系统时钟源,以及SYSCLK、AHB、APB1和APB2相关参数,用函数HAL_RCC_ClockConfig(),声明如下:
HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency);
该函数有两个形参,第一个形参RCC_ClkInitStruct是结构体RCC_ClkInitTypeDef类型指针变量,用于设置SYSCLK时钟源以及SYSCLK、AHB、APB1和APB2的分频系数。第二个形参FLatency用于设置FLASH延迟。
RCC_ClkInitTypeDef结构体类型定义比较简单,我们来看看其定义:
typedef struct
{
uint32_t ClockType; /* 要配置的时钟 */
uint32_t SYSCLKSource; /* 系统时钟源 */
uint32_t AHBCLKDivider; /* AHB分频系数 */
uint32_t APB1CLKDivider; /* APB1分频系数 */
uint32_t APB2CLKDivider; /* APB2分频系数 */
}RCC_ClkInitTypeDef;
我们在sys_stm32_clock_init函数中的实际应用配置内容如下:
/* 选中PLL作为系统时钟源并且配置HCLK,PCLK1和PCLK2*/ rcc_clk_init.ClockType = ( RCC_CLOCKTYPE_SYSCLK \ | RCC_CLOCKTYPE_HCLK \ | RCC_CLOCKTYPE_PCLK1 \ | RCC_CLOCKTYPE_PCLK2); /* 设置系统时钟时钟源为PLL */ rcc_clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; rcc_clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1; /* AHB分频系数为1 */ rcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV1; /* APB1分频系数为1 */ rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV1; /* APB2分频系数为1 */
sys_stm32_clock_init函数中的RCC_ClkInitTypeDef结构体配置内容:
第一个参数ClockType配置表示我们要配置的是SYSCLK、HCLK、PCLK1和PCLK四个时钟。
第二个参数SYSCLKSource配置选择系统时钟源为PLLCLK。
第三个参数AHBCLKDivider配置AHB分频系数为1。
第四个参数APB1CLKDivider配置APB1分频系数为1。
第五个参数APB2CLKDivider配置APB2分频系数为1。
根据我们在mian函数中调用sys_stm32_clock_init(85, 2, 2, 4, 8)时设置的形参数值,我们可以计算出,PLL时钟为PLLCLK = HSE * 85 / 2 / 2 = 170MHz。
同时我们选择系统时钟源为PLL,所以系统时钟SYSCLK=170MHz。AHB分频系数为1,
所以频率为HCLK = SYSCLK/1 = 170MHz。APB1分频系数为1,故其频率为PCLK1 = HCLK/1 = 170MHz。APB2分频系数为1,故其频率为PCLK2 = HCLK/1 = 170MHz。我们总结一下通过调用函数sys_stm32_clock_init(85, 2, 2, 4, 8)之后的关键时钟频率值:
SYSCLK(系统时钟) =170MHz PLL主时钟 =170MHz AHB总线时钟(HCLK=SYSCLK/1) =170MHz APB1总线时钟(PCLK1=HCLK/1) =170MHz APB2总线时钟(PCLK2=HCLK/1) =170MHz
最后我们来看看函数HAL_RCC_ClockConfig 第二个入口参数FLatency的含义,为了使FLASH读写正确(因为170Mhz的时钟比Flash的操作速度24Mhz要快得多,操作速度不匹配容易导致Flash操作失败),所以需要设置延时时间。对于STM32G4系列,FLASH延迟配置参数值是通过下表来确定的:

表11.2.1.1 CPU时钟频率对应的等待周期
由于前面我们已经配置好系统时钟为170M,即CPU时钟为170M,所以根据表11.2.1.1需设置等待周期为4WS,也就是5个CPU周期,即第二个形参设置值:
ret = HAL_RCC_ClockConfig(&rcc_clk_init, FLASH_LATENCY_4);
这样FLASH的读写稳定就得到了保障。注:表11.2.1.1摘抄于《STM32G4xx参考手册_V7(英文版).pdf》第98页。
时钟系统配置相关知识就给大家讲解到这里。
1 1.2.2 STM32 G4 时钟使能和配置
上一节我们讲解了时钟系统配置步骤。在配置好时钟系统之后,如果我们要使用某些外设,例如GPIO,ADC等,我们还要使能这些外设时钟。这里大家必须注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32的外设时钟使能是在RCC相关寄存器中配置的。因为RCC相关寄存器非常多,有兴趣的同学可以直接打开《STM32G4xx参考手册_V7(英文版).pdf》7.4小节查看所有RCC相关寄存器的配置。接下来我们来讲解通过STM32G4 的HAL库使能外设时钟的方法。
在STM32G4的HAL库中,外设时钟使能操作都是在RCC相关固件库文件头文件STM32G4xx_hal_rcc.h定义的。大家打开STM32G4xx_hal_rcc.h头文件可以看到文件中除了少数几个函数声明之外大部分都是宏定义标识符。外设时钟使能在HAL库中都是通过宏定义标识符来实现的。首先,我们来看看GPIOA的外设时钟使能宏定义标识符:
#define __HAL_RCC_GPIOA_CLK_ENABLE() do { \
__IO uint32_t tmpreg; \
SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
UNUSED(tmpreg); \
} while(0)
这段代码主要是定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),它的核心操作是通过下面这行代码实现的:
SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
这行代码的作用是,设置寄存器RCC->AHB2ENR的相关位为1,至于是哪个位,是由宏定义标识符RCC_AHB2ENR_GPIOAEN的值决定的,而它的值为:
#define RCC_AHB2ENR_GPIOAEN_Pos (0U) #define RCC_AHB2ENR_GPIOAEN_Msk (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos) #define RCC_AHB2ENR_GPIOAEN RCC_AHB2ENR_GPIOAEN_Msk
上面三行代码很容易计算出来 RCC_AHB2ENR_ GPIO AEN = (0x00000001<<0) ,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的位 0 为 1 。我们可以从 STM32 G4 的参考手册中搜索 AHB2ENR 寄存器定义,位 0 的作用是用来设置 GPIOA 时钟。 AHB2ENR 寄存器的位 0 描述如下:

图11.2.2.1 AHB2ENR寄存器的位0描述
那么我们只需要在我们的用户程序中调用宏定义标识符就可以实现GPIOA时钟使能。使用方法为:
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能GPIOA时钟 */
对于其他外设,同样都是在STM32G4xx_hal_rcc.h头文件中定义,大家只需要找到相关宏定义标识符即可,这里我们列出几个常用使能外设时钟的宏定义标识符使用方法:
__HAL_RCC_DMA1_CLK_ENABLE(); /* 使能DMA1时钟 */ __HAL_RCC_USART2_CLK_ENABLE(); /* 使能串口2时钟 */ __HAL_RCC_TIM1_CLK_ENABLE(); /* 使能TIM1时钟 */
我们使用外设的时候需要使能外设时钟,如果我们不需要使用某个外设,同样我们可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。我们同样以GPIOA为例,宏定义标识符为:
#define __HAL_RCC_GPIOA_CLK_DISABLE() (RCC->AHB2ENR &= ~(RCC_AHB2ENR_GPIOAEN))
同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE()的作用是设置RCC->AHB2ENR寄存器的位2为0,也就是禁止GPIOA时钟。具体使用方法我们这里就不做过多讲解,我们这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:
__HAL_RCC_DMA1_CLK_DISABLE(); /* 禁止DMA1时钟 */ __HAL_RCC_USART2_CLK_DISABLE(); /* 禁止串口2时钟 */ __HAL_RCC_TIM1_CLK_DISABLE(); /* 禁止TIM1时钟 */
关于STM32G4的外设时钟使能和禁止方法我们就给大家讲解到这里。