《STM32H7R7开发指南 V1.1 》第十章 STM32时钟系统

第十章 STM32时钟系统


       MCU都是基于时序控制的一个系统。这一讲将结合《STM32H7Rx参考手册_V6(英文版).pdf》的知识,对STM32H7RS的整体架构做一个简单的介绍,帮助大家更全面、系统地认识STM32H7RS系统的主控结构。了解时钟系统在整个STM32系统的贯穿和驱动作用,学会设置STM32的系统时钟。

       本章将分为如下几个小节:

       10.1 认识时钟树

       10.2 如何修改主频


        10.1 认识时钟树

       数字电路的知识告诉我们:任意复杂的电路控制系统都可以经由门电路组成的组合电路实现。回顾《第五章 STM32基础知识入门》的知识点,我们知道STM32内部也是由多种多样的电路模块组合在一起实现的。当一个电路越复杂,在达到正确的输出结果前,它可能因为延时会有一些短暂的中间状态,而这些中间状态有时会导致输出结果会有一个短暂的错误,这叫做电路中的“毛刺现象”,如果电路需要运行得足够快,那么这些错误状态会被其它电路作为输入采样,最终形成一系列的系统错误。为了解决这个问题,在单片机系统中,设计时以时序电路控制替代纯粹的组合电路,在每一级输出结果前对各个信号进行采样,从而使得电路中某些信号即使出现延时也可以保证各个信号的同步,可以避免电路中发生的“毛刺现象”,达到精确控制输出的效果。

       由于时序电路的重要性,因此在MCU设计时就设计了专门用于控制时序的电路,在芯片设计中称为时钟树设计。由此设计出来的时钟,可以精确控制我们的单片机系统,这也是我们这节要展开分析的时钟分析。为什么是时钟树而不是时钟呢?一个MCU越复杂,时钟系统也会相应地变得复杂,如STM32H7R7的时钟系统比较复杂,不像简单的51单片机一个系统时钟就可以解决一切。对于STM32H7R7系列的芯片,正常工作的主频可以达到600Mhz,但并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及RTC只需要几十kHZ的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的MCU一般都是采取多时钟源的方法来解决这些问题。

       STM32本身非常复杂,外设非常的多,为了保持低功耗工作,STM32的主控默认不开启这些外设功能。用户可以根据自己的需要决定STM32芯片要使用的功能,这个功能开关在STM32主控中也就是各个外设的时钟。

       下面来看一下STM32H7RS时钟系统图。STM32H7RS时钟系统图看起来有点多且复杂,但是分开几部分各个理解其实没有那么难了。


图10.1.1 STM32H7RS时钟系统图


       10.1.1 时钟源

       对于STM32H7RS有六个时钟源可供使用,可分为外部时钟源和内部时钟源。具体如图10.1.1所示,在STM32H7RS的时钟系统图里,我们把这六个时钟源标记成橘黄色。


       (1)2个外部时钟源:

       l 高速外部振荡器 (HSE)

       支持 4 MHz 到 48 MHz 频率范围内的晶振。

       l 低速外部振荡器 (LSE)

       一般是32.768 kHz 晶振,可用于看门狗和RTC实时时钟。

       2个外部时钟源都是芯片外部晶振产生的时钟频率,故而都有精度高的优点。


       (2)4个内部时钟源:

       l 高速内部振荡器 (HSI)

         频率 64MHz 的高速RC振荡器,可用于系统时钟。

       l 48 MHz RC振荡器 (HSI48)

       频率48MHz,用于给特定的外设提供时钟,比如配合CRS可以作为USB的时钟源。

       l 低功耗内部振荡器 (CSI)

       频率约是 4MHz,主要用于低功耗。

       l 低速内部振荡器 (LSI)

       频率约32 kHz的低速RC 振荡器,可用于RTC实时时钟、独立看门狗和自动唤醒。


       10.1.2 锁相环PLL

       STM32H7RS时钟系统图中看到红色框的部分,可以知道STM32H7RS有三个锁相环,分别是:PLL1、PLL2、PLL3。而且它们的时钟源只有一个,一起共用,但是进入到锁相环的时钟源分别经过三个不同的预分频器,这样我们就可以让锁相环有不同的输入时钟频率。

       下面看到STM32H7时钟系统图标号①②③这三个组成部分:


       l ①锁相环时钟源选择器

       锁相环时钟源有三个:hsi_ck、csi_ck、hse_ck。我们一般选择hse_ck,即来自外部高速晶振,正点原子 STM32H7R7外部高速晶振为24MHZ。


       l ②PLL1\PLL2\PLL3时钟源预分频器

       DIVM1、DIVM2和DIVM3分别是PLL1、PLL2和PLL3输入时钟的预分频系数,取值范围都是:1~63,请根据实际需要设置。


       l ③PLL1\PLL2\PLL3锁相环

       PLL1、PLL2和PLL3锁相环都是一个输入,三个输出。hsi_ck、csi_ck、hse_ck三个时钟源中的一个经过DIVM预分频器处理作为锁相环的输入时钟源。PLL1三个输出分别是:pll1_p_ck、pll1_q_ck和pll1_r_ck,PLL2和PLL3同理。

       下面以PLL1为例介绍一下锁相环内部框图,PLL2和PLL3同理。

       DIVN1是PLL1中VCO的倍频系数,其取值范围是:4~512。

       DIVP1是pll1_p_ck的预分频系数,取值范围是:2、4、6…128(必须是偶数)。

       DIVQ1是pll1_q_ck的预分频系数,取值范围是:1~128。

       DIVR1是pll1_r_ck的预分频系数,取值范围是:1~128。

       FRACN1是PLL1中VCO的倍频系数的小数部分,它和DIVN1一起组成PLL1的倍频系数,但是我们一般情况下都是不需要用到小数倍频的,所以小数部分我们不讨论。

       这里以pll1_p_ck为例,简单介绍下PLL输出频率的计算公式(时钟PLL输入频率为hse_ck):



       假设外部晶振为24Mhz,需要得到600Mhz的pll1_p_ck频率,则可以设置:DIVM1=6,DIVN1=300,DIVP1=2即可。其他分频输出(pll1_q_ck/pll1_r_ck)计算方法与上式相似。我们把它们对应到STM32CubeMX工具的时钟系统图上理解就很直观了,如图10.1.2.1所示。


图10.1.2.1 STM32CubeMX对应的设置


       关于时钟系统,大家如果看数据手册不太理解,结合STM32CubeMX的时钟系统配置界面理解会有很好的效果。


       10.1.3 系统时钟SYSCLK

       下面看到STM32H7RS时钟系统图标号④⑤两个组成部分:


       l ④sys_ck时钟源选择器

       sys_ck(即系统时钟)的时钟源有四个:hsi_ck、csi_ck、hse_ck和pll1_p_ck,系统默认是选择hsi_ck(64Mhz)作为时钟源的。当我们把PLL1配置好并且选择pll1_p_ck作为系统时钟的时钟源,则系统时钟可以得到600MHZ的时钟频率,从而得到最高性能。


       l ⑤系统时钟生成和使能单元

       这部分是STM32H7R7的内核时钟和总线时钟等,总线时钟包括AHB1,2,3,4,5和APB1,APB2,APB4,APB5的时钟。从上面的图10.1.1时钟树图可知,AHB1,2,3,4,5、内核时钟等通过系统时钟分频得到。我们选择不分频,所以总线时钟达到最大的300MHz。

       我们可以把它们对应到STM32CubeMX工具上的时钟系统图上理解,如图10.1.3.2所示。


图10.1.3.2 对应STM32CubeMX配置图


       从上图可以知道,经过配置,AHB1,2,3,4和AHB5的频率都是300MHz。

       APB1,APB2,APB4和APB5总线的频率都是150MHz。

       如果参考手册对应外设章节没有额外说明,那么挂载在这些总线上的外设时钟源就是该总线的时钟。但是也有例外的,比如定时器时钟源,分为两种情况:

       1、如果APB预分频器为1,挂载在该总线上的定时器时钟源频率等于APB的频率;

       2、否则,等于APB频率的两倍(×2)。

       定时器的时钟源的详细说明,请看定时器章节的教程。


       10.1.4 MCO时钟输出

       MCO时钟输出,其作用是为外部器件提供时钟,STM32H7R7有两个MCO。下面看到STM32H7R7时钟系统图标号⑦⑧⑨三个部分:

       l ⑦MCO1\MCO2时钟源选择器

       MCO1(外部器件的输出时钟1)时钟源有五个:hsi_ck、lse_ck、hse_ck、pll1_q_ck和hsi48_ck。

       MCO2(外部器件的输出时钟2)时钟源有六个:sys_ck、pll2_p_ck、hse_ck、pll1_p_ck、csi_ck和lsi_ck。


       l ⑧MCO1\MCO2时钟分频器

       MCO1PRE是MCO1的预分频器,取值范围:1到15。

       MCO2PRE是MCO2的预分频器,取值范围:1到15。


       l ⑨MCO1\MCO2时钟输出引脚

       MCO1、MCO2两个时钟输出引脚给外部器件提供时钟源(分别由PA8和PC9复用功能实现),每个引脚可以选择一个时钟源,通过RCC时钟配置寄存器 (RCC_CFGR)进行配置。

       关于STM32H7RSxx时钟的详细介绍,请参考《STM32H7Rx参考手册_V6(英文版).pdf》第8.5节,有不明白的地方,可以对照手册仔细研究。具体哪个外设是连接在哪个时钟总线上,以及对应的时钟总线最高主频是多少,请参考STM32H7R7L8H6H.pdf数据手册(路径:A盘→7,硬件资料→2,芯片资料→ STM32H7R7L8H6H.pdf)。

       通过以上内容的介绍,我们知道STM32H7R7的时钟设计的比较复杂,各个时钟基本都是可控的,任何外设都有对应的时钟控制开关,这样的设计,对降低功耗是非常有用的,不用的外设不开启时钟,就可以大大降低其功耗。


        10.2 如何修改主频

       STM32H7R7默认的情况下(比如:串口IAP时或者是未初始化时钟时),使用的是内部64M的HSI作为时钟源,所以不需要外部晶振也可以下载和运行代码的。

       下面我们来讲解如何让STM32H7R7芯片在600MHZ的频率下工作,这是官方推荐使用的最高的稳定时钟频率。正点原子的 STM32H7R7开发板的外部高速晶振的频率是24MHZ,我们就是在这个晶振频率的基础上,通过各种倍频和分频得到600MHZ的系统工作频率。


       10.2.1 STM32H7R7时钟系统配置

       下面我们将分几步给大家讲解STM32H7R7时钟系统配置过程,这部分内容很重要,请大家认真阅读。


       第1步:配置HSE_VALUE

       讲解stm32h7rsxx_hal_conf.h文件的时候,我们知道需要宏定义HSE_VALUE匹配我们实际硬件的高速晶振频率(这里是24MHZ),代码如下:

#if !defined  (HSE_VALUE)
#define HSE_VALUE    24000000U /* 外部高速振荡器的值,单位HZ */
#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_stm32h7rsxx.c文件下定义的SystemInit程序,源码在143行到152行,简化函数如下。

void SystemInit(void)
{
  /* Configure the Vector Table location -----------------------------------*/
  SCB->VTOR = INTVECT_START;
 
  /* FPU settings ----------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
  SCB->CPACR |= ((3UL << 20U)|(3UL << 22U));/* set CP10 and CP11 Full Access */
#endif
}

       从上面代码可以看出,SystemInit主要做了如下三个方面工作:

       1) 设置FPU 

       2) 复位RCC时钟配置为默认复位值(默认开启HSI)

       3) 配置中断向量表地址

       HAL库的SystemInit函数并没有像标准库的SystemInit函数一样进行时钟的初始化配置。HAL库的SystemInit函数除了打开HSI之外,没有任何时钟相关配置,所以使用HAL库,我们必须编写自己的时钟配置函数。


       第3步:在main函数里调用用户编写的时钟设置函数

       我们打开HAL库例程实验1跑马灯实验,看看我们在工程目录Drivers\SYSTEM分组下面定义的sys.c文件中的时钟设置函数sys_stm32_clock_init的内容:

/**
 * @brief   配置时钟
 * @note    当外部晶振频率为24MHz时,推荐值:plln=300 pllm=6 pllp=2
 * @param   plln: PLL1倍频系数(范围:8~420)
 * @param   pllm: PLL1预分频系数(范围:1~63)
 * @param   pllp: PLL1 P输出预分频系数(范围:2~128(偶数))
 * @retval  配置结果
 * @arg     0: 成功
 * @arg     1: 失败
 */
uint8_t sys_stm32_clock_init(uint32_t plln, uint32_t pllm, uint32_t pllp)
{
    RCC_ClkInitTypeDef rcc_clk_init_struct = {0};
    
    /* 配置系统电源 */
    if (HAL_PWREx_ConfigSupply(PWR_DIRECT_SMPS_SUPPLY) != HAL_OK)
    {
        return 1;
    }
    
    /* 使能XSPIM接口 */
    HAL_PWREx_EnableXSPIM1();
    HAL_PWREx_EnableXSPIM2();
    
    /* 开启XSPI1、XSPI2 I/O的高速低压(HSLV)模式
     * !!!特别注意!!!
     * 1、当对应I/O电源电压高于2.7V时不能开启HSLV,否则可能损坏硬件。
     * 2、仅当Flash选项字节中对应XSPI1_HSLV、XSPI2_HSLV或VDDIO_HSLV被设置为1时,
软件上的HSLV使能配置才会生效。
     */
    __HAL_RCC_SBS_CLK_ENABLE();
    HAL_SBS_EnableIOSpeedOptimize(SBS_IO_XSPI1_HSLV);
    HAL_SBS_EnableIOSpeedOptimize(SBS_IO_XSPI2_HSLV);
    
    /* 配置内部调节器输出电压 */
    if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE0)!= HAL_OK)
    {
        return 1;
    }
    
    /* 使能VDD33USB电压电平检测器
     * 注意
     * 使用USB HS、USB FS或GPIOM端口时,需先使能VDD33USB电压电平检测器
     */
    if (HAL_PWREx_EnableUSBVoltageDetector() != HAL_OK)
    {
        return 1;
    }
    
    /* 使能AHB-SRAM时钟 */
    __HAL_RCC_SRAM1_CLK_ENABLE();
    __HAL_RCC_SRAM2_CLK_ENABLE();
    
    /* 配置PLL */
rcc_osc_init_struct.OscillatorType = RCC_OSCILLATORTYPE_HSE |
RCC_OSCILLATORTYPE_LSI | RCC_OSCILLATORTYPE_HSI48;
    rcc_osc_init_struct.HSEState = RCC_HSE_ON;
    rcc_osc_init_struct.LSIState = RCC_LSI_ON;
    rcc_osc_init_struct.HSI48State = RCC_HSI48_ON;
    rcc_osc_init_struct.PLL1.PLLState = RCC_PLL_ON;
    rcc_osc_init_struct.PLL1.PLLSource = RCC_PLLSOURCE_HSE;
    rcc_osc_init_struct.PLL1.PLLM = pllm;
    rcc_osc_init_struct.PLL1.PLLN = plln;
    rcc_osc_init_struct.PLL1.PLLP = pllp;
    rcc_osc_init_struct.PLL1.PLLQ = 2;
    rcc_osc_init_struct.PLL1.PLLR = 2;
    rcc_osc_init_struct.PLL1.PLLS = 2;
    rcc_osc_init_struct.PLL1.PLLT = 2;
    rcc_osc_init_struct.PLL1.PLLFractional = 0;
    rcc_osc_init_struct.PLL2.PLLState = RCC_PLL_ON;
    rcc_osc_init_struct.PLL2.PLLSource = RCC_PLLSOURCE_HSE;
    rcc_osc_init_struct.PLL2.PLLM = 6;
    rcc_osc_init_struct.PLL2.PLLN = 300;
    rcc_osc_init_struct.PLL2.PLLP = 2;
    rcc_osc_init_struct.PLL2.PLLQ = 12;
    rcc_osc_init_struct.PLL2.PLLR = 6;
    rcc_osc_init_struct.PLL2.PLLS = 3;
    rcc_osc_init_struct.PLL2.PLLT = 6;
    rcc_osc_init_struct.PLL2.PLLFractional = 0;
    rcc_osc_init_struct.PLL3.PLLState = RCC_PLL_ON;
    rcc_osc_init_struct.PLL3.PLLSource = RCC_PLLSOURCE_HSE;
    rcc_osc_init_struct.PLL3.PLLM = 6;
    rcc_osc_init_struct.PLL3.PLLN = 300;
    rcc_osc_init_struct.PLL3.PLLP = 2;
    rcc_osc_init_struct.PLL3.PLLQ = 50;
    rcc_osc_init_struct.PLL3.PLLR = 2;
    rcc_osc_init_struct.PLL3.PLLS = 12;
    rcc_osc_init_struct.PLL3.PLLT = 2;
    rcc_osc_init_struct.PLL3.PLLFractional = 0;
    if (HAL_RCC_OscConfig(&rcc_osc_init_struct) != HAL_OK)
    {
        return 1;
    }
    
    /* 配置CPU、AHB和APB总线时钟 */
rcc_clk_init_struct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |
 RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2 | RCC_CLOCKTYPE_PCLK4 | 
RCC_CLOCKTYPE_PCLK5;
    rcc_clk_init_struct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    rcc_clk_init_struct.SYSCLKDivider = RCC_SYSCLK_DIV1;
    rcc_clk_init_struct.AHBCLKDivider = RCC_HCLK_DIV2;
    rcc_clk_init_struct.APB1CLKDivider = RCC_APB1_DIV2;
    rcc_clk_init_struct.APB2CLKDivider = RCC_APB2_DIV2;
    rcc_clk_init_struct.APB4CLKDivider = RCC_APB4_DIV2;
    rcc_clk_init_struct.APB5CLKDivider = RCC_APB5_DIV2;
    if (HAL_RCC_ClockConfig(&rcc_clk_init_struct, FLASH_LATENCY_7) != HAL_OK)
    {
        return 1;
    }
    
    /* 使能XSPI1 NOR Flash内存映射 */
    sys_xspi1_enable_memmapmode();
    sys_memory_mapped_flag = 1;
 
    return 0;
}

       函数sys_stm32_clock_init就是用户的时钟系统配置函数,除了配置PLL相关参数确定SYSCLK值之外,还配置了AHB、APB1、APB2、APB4和APB5总线时钟的分频系数。

       我们首先来看看使用HAL库配置STM32H7R7时钟系统的一般步骤:

       1) 设置调压器输出电压级别:调用函数HAL_PWREx_ControlVoltageScaling ()。

       2) 配置时钟源相关参数:调用函数HAL_RCC_OscConfig()。

       3) 选择系统时钟SYSCLK的时钟来源以及配置AHB、APB1、APB2、APB4和APB5总线时钟的预分频系数,调用HAL_RCC_ClockConfig()函数。

       下面我们详细讲解这个3个步骤。

       步骤1:调压器输出电压级别VOS,它是由PWR控制寄存器CSR4的位0来确定的:

位0 VOS[0]
0:VOS低电平(默认值)
1:VOS高电平

       电压调节级别数值越小工作频率越高,所以如果我们要配置H7R7的主频为600MHz,那么我们需要配置调压器输出电压调节级别VOS为级别0。 

       步骤2:配置时钟源和锁相环PPL1的相关参数,使能并选择HSE作为PLL时钟源,我们调用的函数为HAL_RCC_OscConfig(),该函数在HAL库头文件stm32h7rsxx_hal_rcc.h中声明,在文件stm32h7rsxx_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 LSEState;                 /* LSE状态 */
  uint32_t HSIState;                 /* HSI状态 */
  uint32_t HSIDiv;                     /* HIS分频因素 */
  uint32_t HSICalibrationValue;        /* HIS校准值 */
  uint32_t LSIState;                 /* LSI状态 */
  uint32_t HSI48State;               /* HSI48的状态 */
  uint32_t CSIState;                 /* CSI状态 */
  RCC_PLLInitTypeDef PLL1;           /* PLL1结构体 */
  RCC_PLLInitTypeDef PLL2;           /* PLL2结构体 */
  RCC_PLLInitTypeDef PLL3;           /* PLL3结构体 */
}RCC_OscInitTypeDef;

       该结构体前面几个参数主要是用来选择配置的振荡器类型。比如我们要开启HSE,那么我们会设置OscillatorType的值为RCC_OSCILLATORTYPE_HSE,然后设置HSEState的值为RCC_HSE_ON开启HSE。对于其他时钟源:HSI、LSI、LSE、CSI和HSI48,配置方法类似,这里我们还开启了HSI48的时钟。

       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 */
  uint32_t PLLS;         /* PLL分频系数S */
  uint32_t PLLT;         /* PLL分频系数T */
  uint32_t PLLFractional;  /* PLL锁相环倍增系数 */
}RCC_PLLInitTypeDef;

       从RCC_PLLInitTypeDef结构体的定义很容易看出该结构体主要用来设置PLL时钟源以及相关分频倍频参数。这个结构体定义的相关内容请结合图 10.1.1时钟树中红色框部分内容一起理解。

       设置好时钟源和PLL1的相关参数后,就完成了步骤2的内容。

       步骤3:选择系统时钟SYSCLK的时钟来源以及配置AHB、APB1、APB2、APB4和APB5总线时钟的预分频系数,用函数HAL_RCC_ClockConfig(),声明如下:

HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, 
uint32_t FLatency);

       该函数有两个形参,第一个形参RCC_ClkInitStruct是RCC_ClkInitTypeDef结构体类型指针变量,用于设置SYSCLK时钟源以及SYSCLK、AHB、APB1、APB2、APB4和APB5的分频系数。第二个形参FLatency用于设置FLASH延迟。

       RCC_ClkInitTypeDef结构体类型定义比较简单,我们来看看其定义:

typedef struct
{
  uint32_t ClockType;              /* 要配置的时钟 */
  uint32_t SYSCLKSource;           /* 系统时钟源 */
  uint32_t SYSCLKDivider;          /* SYSCLK分频系数 */
  uint32_t AHBCLKDivider;          /* AHB分频系数 */
  uint32_t APB1CLKDivider;         /* APB1分频系数 */
  uint32_t APB2CLKDivider;         /* APB2分频系数 */
  uint32_t APB4CLKDivider;         /* APB4分频系数 */
  uint32_t APB5CLKDivider;         /* APB5分频系数 */
}RCC_ClkInitTypeDef;

       根据我们在mian函数中调用sys_stm32_clock_init(300, 6, 2)时设置的形参数值,代入PLL输出频率的计算公式可得:



       因为我们选择系统时钟源为PLL(pll1_p_ck),所以系统时钟SYSCLK=600MHz。再结合各个总线时钟的预分频系数,总结一下调用函数sys_stm32_clock_init(300, 6, 2)之后,关键的时钟频率值如下:(HCLK代表AHB1~5总线上的时钟)

sys_ck(系统时钟)                 = 600MHz
sys_ck分频后的时钟           = 600MHz
AHB总线时钟           = 300MHz
APB1总线时钟                = 150MHz
APB2总线时钟                = 150MHz
APB4总线时钟                     = 150MHz
APB5总线时钟           = 150MHz

       最后我们来看看函数HAL_RCC_ClockConfig第二个入口参数FLatency的含义,对于STM32H7R7系列,FLASH延迟配置参数值是通过下表来确定的:


表10.2.1.1 FLASH推荐的等待状态和编程延迟数


       下面我们看看我们在sys_stm32_clock_init中调用函数HAL_RCC_ClockConfig的时候,根据表10.2.1.1需设置等待周期为7,也就是8个CPU周期,第二个形参设置值:

HAL_RCC_ClockConfig(&rcc_clk_init_struct, FLASH_LATENCY_7);

       这样FLASH的读写稳定就得到了保障。

       时钟系统配置相关知识就给大家讲解到这里。


       10.2.2 STM32H7RS时钟使能和配置

       上一节我们讲解了时钟系统配置步骤。在配置好时钟系统之后,如果我们要使用某些外设,例如GPIO,ADC等,我们还要使能这些外设时钟。这里大家必须注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32的外设时钟使能是在RCC相关寄存器中配置的。因为RCC相关寄存器非常多,有兴趣的同学可以直接打开《STM32H7Rx参考手册_V6(英文版).pdf》 7.8小节查看所有RCC相关寄存器的配置。接下来我们来讲解通过STM32H7RS的HAL库使能外设时钟的方法。

       在STM32H7RS的HAL库中,外设时钟使能操作都是在RCC相关固件库文件头文件stm32h7rsxx_hal_rcc.h定义的。大家打开stm32h7rsxx_hal_rcc.h头文件可以看到文件中除了少数几个函数声明之外大部分都是宏定义标识符。外设时钟使能在HAL库中都是通过宏定义标识符来实现的。首先,我们来看看GPIOA的外设时钟使能宏定义标识符:

#define __HAL_RCC_GPIOA_CLK_ENABLE()   do { \
                          __IO uint32_t tmpreg; \
                          SET_BIT(RCC->AHB4ENR, RCC_AHB4ENR_GPIOAEN);\
                          /* Delay after an RCC peripheral clock enabling */ \
                          tmpreg = READ_BIT(RCC->AHB4ENR,RCC_AHB4ENR_GPIOAEN);\
                          UNUSED(tmpreg); \
                            } while(0)

       这段代码主要是定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),它的核心操作是通过下面这行代码实现的:

SET_BIT(RCC->AHB4ENR, RCC_AHB4ENR_GPIOAEN);

       这行代码的作用是,设置寄存器RCC->AHB4ENR的相关位为1,至于是哪个位,是由宏定义标识符RCC_AHB4ENR_GPIOAEN的值决定的,而它的值为:

#define RCC_AHB4ENR_GPIOAEN_Pos              (0U)
#define RCC_AHB4ENR_GPIOAEN_Msk              (0x1UL << RCC_AHB4ENR_GPIOAEN_Pos)  
#define RCC_AHB4ENR_GPIOAEN                   RCC_AHB4ENR_GPIOAEN_Msk

       上面三行代码很容易计算出来RCC_AHB4ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器RCC->AHB4ENR寄存器的最低位为1。我们可以从STM32H7RS的参考手册中搜索AHB4ENR寄存器定义,最低位的作用是用来使用GPIOA时钟。AHB4ENR寄存器的位0描述如下:

位0  GPIOAEN:GPIOA 外设时钟使能
    由软件置1和清零
  0:禁止IO端口A时钟(复位后的默认值)
1:使能IO端口A时钟

       那么我们只需要在我们的用户程序中调用宏定义标识符就可以实现GPIOA时钟使能。使用方法为:

__HAL_RCC_GPIOA_CLK_ENABLE();      /* 使能GPIOA时钟 */

       对于其他外设,同样都是在stm32h7rsxx_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->AHB4ENR) &= ~(RCC_AHB4ENR_GPIOAEN)

       同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE()的作用是设置RCC->AHB4ENR寄存器的最低位为0,也就是禁止GPIOA时钟。具体使用方法我们这里就不做过多讲解,我们这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:

__HAL_RCC_DMA1_CLK_DISABLE();     /* 禁止DMA1时钟 */
__HAL_RCC_USART2_CLK_DISABLE();  /* 禁止串口2时钟 */
__HAL_RCC_TIM1_CLK_DISABLE();    /* 禁止TIM1时钟 */

       关于STM32H7RS的外设时钟使能和禁止方法我们就给大家讲解到这里。


请使用浏览器的分享功能分享到微信等