03. STM32的时钟系统

一、STM32F4的时钟树

STM32F4时钟系统图

  其中,A 部分表示 输入时钟源,可分为 外部时钟源内部时钟源;B 为 锁相环 “PLL”;C 为 系统时钟源选择器,此项决定了 MCU 的系统主时钟 “SYSCLK” 的大小;AHB 预分频器将 SYSCLK 分频或不分频后分发给其它外设进行处理,包括到 D 部分的 Cortex-M 内核系统的时钟 和使能单元。E 为 定时器以及其它外设的时钟源 APB1/APB2。F 是 STM32 的 MCO 时钟输出功能

STM32F4时钟简图

二、时钟源

  对于 STM32F4,输入时钟源(Input Clock)主要包括 HSIHSELSILSE。其中,从时钟频率来分可以分为 高速时钟源低速时钟源,其中 HSIHSE高速时钟LSILSE低速时钟。从来源可分为 外部时钟源内部时钟源外部时钟源 就是从 外部通过接晶振 的方式获取时钟源,其中 HSELSE外部时钟源HSILSI 是内部时钟源,芯片上电即可产生,不需要借助外部电路。

  • 高速外部振荡器 HSE(High Speed External Clock signal)
    • 外接石英/陶瓷谐振器,频率为 4MHz~26MHz。开发板使用的是 8MHz。
  • 低速外部振荡器 LSE(Low Speed External Clock signal)
    • 外接 32.768kHz 石英晶体,主要作用于 RTC 的时钟源。
  • 高速内部振荡器 HSI(High Speed Internal Clock signal)
    • 由内部 RC 振荡器产生,频率为 16MHz。
  • 低速内部振荡器 LSI(Low Speed Internal Clock signal)
    • 由内部 RC 振荡器产生,频率为 32kHz,可作为独立看门狗的时钟源。

芯片上电时默认由内部的 HSI 时钟启动,如果用户进行了硬件和软件的配置,芯片才会根据用户配置调试尝试切换到对应的外部时钟源。

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

三、锁相环PLL

  锁相环是自动控制系统中常用的一个反馈电路,在 STM32 主控中,锁相环的作用主要有两个部分:输入时钟净化倍频。前者是利用锁相环电路的反馈机制实现,后者我们用于使芯片在更高且频率稳定的时钟下工作。

  在 STM32 中,锁相环的输出也可以作为芯片系统的时钟源。根据图 11.1.1 的时钟结构,使用锁相环时只需要进行三个部分的配置。为了方便查看,截取了使用 PLL 作为系统时钟源的配置部分,如图 11.1.2.1 所示。

PLL时钟配置图

【1】、PLL Source Mux:PLL 时钟源选择器

  图中标号 ① 表示的是 PLL 时钟源的选择器,由寄存器 RCC_PLLCFGR 的 bit22 位进行控制,关于该寄存器的描述可参考《STM32F4xx 参考手册_V4(中文版).pdf》第 116 页,如下图所示:

PLLSRC锁相环时钟源选择

寄存器 RCC_PLLCFGR 的 bit22 位有两种可选择的输入源:一个是内部时钟 HSI 信号,另一个是外部时钟 HSE 信号。

【2】、PLLM:HSE 分频器作为 PLL 输入 (HSE divider for PLL entry)

  标号 ② 的地方,主 PLL 输入时钟的分频系数,并把它的控制功能放在 RCC_PLLCFGR 寄存器中。

PLLXTPRE设置选项值

【3】、PLLMUL:PLL 倍频系数 (PLL multiplication factor)

  图中 ③ 所表示的配置锁相环倍频系数,同样地可以查到在 STM32F4 系列中,ST 设置它的有效倍频范围为 2~16 倍。结合时钟树,要实现 168MHz 的主频率,我们通过选择 HSE 分频作为 PLL 输入的时钟信号,输入 8Mhz,8 分频,即 1MHz,通过标号 ③ 选择倍频因子,我们选择 336 倍频,这样可以得到时钟信号为 1*336=336MHz,然后经过 2 分频,得到 168MHz。

四、系统时钟SYSCLK

  STM32 的系统时钟 SYSCLK 为整个芯片提供了时序信号。对于相同的稳定运行的电路,时钟频率越高,指令的执行速度越快,单位时间能处理的功能越多。STM32 的系统时钟是可配置的,在 STM32F4 系列中,它可以为 HSI、PLLCLK、HSE 中的一个,通过 CFGR 的位 SW[1:0] 设置。

STM32F407系统时钟生成图

  标号 ④ 为系统时钟输入源选择,可选时钟信号有外部高速时钟 HSE(8M)、内部高速时钟 HSI(16M)和经过倍频的 PLL CLK(168M)。这里我们选择 PLL CLK 作为系统时钟,此时系统时钟的频率为 168MHz。系统时钟来到标号 ⑤ 的 AHB 预分频器,其中可选择的分频系数为 1,2,4,8,16,32,64,128,256,512,我们选择不分频,所以 AHB 总线时钟达到最大的 168MHz。

  APB1 总线时钟,由 HCLK 经过标号 ⑥ 的低速 APB1 预分频器得到,分频因子可以选择 1,2,4,8,16,这里我们选择的是 4 分频,所以 APB1 总线时钟为 42M。由于 APB1 是低速总线时钟,APB1 总线最高频率为 42MHz,片上低速的外设就挂载在该总线上,例如有看门狗定时器、定时器 2/3/4/5/6/7、RTC 时钟、USART2/3/4/5、SPI2(I2S2)与 SPI3(I2S3)、I2C1~3、CAN 和 2 个 DAC。

  APB2 总线时钟,由 HCLK 经过标号 ⑦ 的高速 APB2 预分频器得到,分频因子可以选择 1,2,4,8,16,这里我们选择的是 2 分频,所以 APB2 总线时钟频率为 84M。与 APB2 高速总线连接的外设有定时器 1/8/9/10/11、SPI1、USART1 和 USART6、3 个 ADC 和 SDIO 接口。

  其中标号 ⑧ 决定了定时器时钟频率,该位由硬件自动设置,分为两种情况:

  • 如果 APB 预分频器为 1,定时器时钟频率等于 APB 域的频率
  • 否则,等于 APB 域的频率的两倍(×2)

  此外,AHB 总线时钟直接作为 GPIO(A\B\C\D\E\F\G\H\I)、以太网、DCMI、FSMC、AHB总线、Cortex 内核、存储器和 DMA 的 HCLK 时钟,并作为 Cortex 内核自由运行时钟 FCLK。

RTC相关时钟

  标号 ⑨ 是 RTC 时钟,其时钟源有三个途径:HSE/x(x = 2~31)、LSE 或 LSI。

五、时钟信号输出MCO

MACO相关时钟

  标号 ⑩ 是 MCO 时钟输出,其作用是为外部器件提供时钟。STM32 允许通过设置,通过 MCO 引脚输出一个稳定的时钟信号。可以从上图看到 STM32F4 时钟系统图标号 ⑩、⑪、⑫ 三个部分:

  • ⑫MCO1\MCO2 时钟源选择器

    • MCO1(外部器件的输出时钟 1)时钟源有四个:LSE、HSE、HSI 和 PLLCLK。
    • MCO2(外部器件的输出时钟 2)时钟源有四个:SYSCLK、PLLI2SCLK、HSE 和 PLLCLK。
  • ⑪MCO1\MCO2 时钟分频器

    • MCO1 和 MCO2 的预分频器,取值范围均为:1 到 5。
  • ⑩MCO1\MCO2 时钟输出引脚

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

六、修改时钟主频

  STM32F407 默认的情况下,使用的是内部 8M 的 HSI 作为时钟源,所以不需要外部晶振也可以下载和运行代码的。STM32F407 芯片在 168MHz 的频率下工作,168MHz 是官方推荐使用的最高的稳定时钟频率。而开发板的外部高速晶振的频率就是 8MHz,我们就是在这个晶振频率的基础上,通过各种倍频和分频得到 168MHz 的系统工作频率。

【1】、配置 HSE_VALUE

  stm32f4xx_hal_conf.h 文件 84 行左右,有个 HSE_VALUE 参数,这个参数表示我们的外部高速晶振的频率。这个参数请务必根据我们板子外部焊接的晶振频率来修改,官方默认是 25M。这里,我们使用的开发板,晶振频率为 8MHz。

#if !defined (HSE_VALUE) 
    /* 外部高速振荡器的值,单位 HZ */
    #define HSE_VALUE ((uint32_t)8000000) 
#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_stm32f4xx.c 文件下定义的 SystemInit() 程序,源码在 168 行到 183 行左右。

void SystemInit(void)
{
    /* FPU settings ------------------------------------------------------------*/
    #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
        /* set CP10 and CP11 Full Access */
        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)
        /* Vector Table Relocation in Internal SRAM */
        SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; 
    #endif /* USER_VECT_TAB_ADDRESS */
}

  从上面代码可以看出,SystemInit() 函数主要做了如下两个方面工作:

  • 外部存储器配置
  • 中断向量表地址配置

【3】、在 main() 函数里调用用户编写的时钟设置函数

  我们可以调用函数 HAL_RCC_OscConfig() 来配置 时钟源相关参数,它的函数说明如下:

HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef  *RCC_OscInitStruct);

  该函数只有一个形参,就是结构体 RCC_OscInitTypeDef 类型指针。它的定义如下:

typedef struct
{
    uint32_t OscillatorType;        // 需要配置的振荡器
    uint32_t HSEState;              // HSE状态
    uint32_t LSEState;              // LSE状态
    uint32_t HSIState;              // HSI状态
    uint32_t HSICalibrationValue;   // HSI校准值,范围0x00~0x1F
    uint32_t LSIState;              // LSI状态
    RCC_PLLInitTypeDef PLL;         // PLL初始化结构体
}RCC_OscInitTypeDef;

  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
}RCC_PLLInitTypeDef;

  然后,我们可以调用函数 HAL_RCC_ClockConfig() 配置系统时钟源,以及 SYSCLK、AHB、APB1 和 APB2 相关参数,它的函数说明如下:

HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef  *RCC_ClkInitStruct, uint32_t FLatency);

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

 RCC_ClkInitTypeDef 结构体类型定义如下:

typedef struct
{
    uint32_t ClockType;             // 要配置的时钟(SYSCLK/HCLK/PCLK1/PCLK2)
    uint32_t SYSCLKSource;          // 系统时钟源
    uint32_t AHBCLKDivider;         // AHB时钟预分频系数
    uint32_t APB1CLKDivider;        // APB1时钟预分频系数
    uint32_t APB2CLKDivider;        // APB2时钟预分频系数
}RCC_ClkInitTypeDef;

  第二个参数 FLatency 用于设置 FLASH 延迟,为了使 FLASH 读写正确(因为 168Mhz 的时钟比 Flash 的操作速度 24Mhz 要快得多,操作速度不匹配容易导致 Flash 操作失败),所以需要设置延时时间。对于 STM32F4 系列,FLASH 延迟配置参数值是通过下表来确定的:

CPU时钟频率对应的等待周期

七、自定义时钟配置函数

  首先配置了时钟源相关参数,我们开启了 HSE 时钟源,同时选择 PLL 时钟源为 HSE,然后把system_stm32_clock_init() 函数的形参直接设置作为 PLL 的参数的值,这样就达到了设置 PLL 时钟源相关参数的目的。

  设置好 PLL 时钟源参数之后,也就是确定了 PLL 的时钟频率,然后我们要配置系统时钟源,以及 SYSCLK、AHB、APB1 和 APB2 相关参数。

  根据我们在 main() 函数中调用 System_Clock_Init(8, 336, 2, 7) 时设置的形参数值,我们可以计算出,PLL 时钟为 PLLCLK = HSE * 336 / 8 / 2 = 168MHz。同时我们选择系统时钟源为 PLL,所以系统时钟 SYSCLK=168MHz。AHB 分频系数为 1,故频率为 HCLK=SYSCLK/1=168MHz 。 APB1 分频系数为 4,故其频率为 PCLK1=HCLK/4=42MHz。APB2 分频系数为 2,故其频率为 PCLK2=HCLK/2=84MHz。

/**
 * @brief 时钟设置函数
 * 
 * @param pllm: 主PLL和音频PLL预分频系数(进PLL之前的分频), 取值范围:2 ~ 63
 * @param plln: 主PLL倍频系数(PLL倍频), 取值范围: 64 ~ 432
 * @param pllp: 主PLL的p分频系数(PLL之后的分频),分频后作为系统时钟,取值范围: [2, 4, 6, 8](仅限这4个值)
 * @param pllq: 主PLL的q分频系数(PLL之后的分频),取值范围: 2 ~ 15
 * @return uint8_t 错误代码: 0,成功;1,错误;
 * 
 * @note 
 *      外部晶振为 8M的时候, 推荐值:  pllm = 8, plln = 336, pllp = 2, pllq = 7.
 *      F(vco): F(hse) / pllm * plln = 8 / 8 * 336 = 336Mhz
 *      F(PLL48CK) = F(vco) / pllq = F(hse) / pllm * plln / pllq = 336 / 7 = 48Mhz
 *      F(SYSCLK) = F(PLLCLK) = F(vco) / pllp = F(hse) / pllm * plln / pllp = 336 / 2 = 168Mhz
 *      F(AHB1/2/3) = F(SYSCLK) = 168Mhz
 *      F(HCLK) = F(AHB1/2/3) = 168Mhz
 *      F(APB1) = pll_p_ck / 4 = 42Mhz
 *      F(APB2) = pll_p_ck / 2 = 84Mhz
 */
uint8_t System_Clock_Init(uint32_t pllm, uint32_t plln, uint32_t pllp, uint32_t pllq)
{
    HAL_StatusTypeDef result = HAL_OK;
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    __HAL_RCC_PWR_CLK_ENABLE();                                                 // 使能PWR时钟

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

    // 使能HSE,并选择HSE作为PLL时钟源,配置PLL1,开启USB时钟
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;                  // 时钟源为HSE
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                    // 打开HSE

    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                                // 打开PLL
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;                        // PLL时钟源选择HSE

    RCC_OscInitStruct.PLL.PLLM = pllm;
    RCC_OscInitStruct.PLL.PLLN = plln;
    RCC_OscInitStruct.PLL.PLLP = pllp;
    RCC_OscInitStruct.PLL.PLLQ = pllq;

    result = HAL_RCC_OscConfig(&RCC_OscInitStruct);                             // 初始化RCC
    if(result != HAL_OK)
    {
        // 时钟初始化失败,可以在这里加入自己的处理
        return 1;
    }

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

    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;                   // 设置系统时钟时钟源为PLL
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                          // AHB分频系数为1
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;                           // APB1分频系数为4
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;                           // APB2分频系数为2

    result = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);          // 同时设置FLASH延时周期为5WS,也就是6个CPU周期
    if(result != HAL_OK)
    {
        // 时钟初始化失败,可以在这里加入自己的处理
        return 1;
    }
  
    // STM32F405x/407x/415x/417x Z版本的器件支持预取功能
    if (HAL_GetREVID() == 0x1001)
    {
        __HAL_FLASH_PREFETCH_BUFFER_ENABLE();                                   // 使能flash预取
    }

    return 0;
}

posted @ 2023-10-25 19:43  星光映梦  阅读(73)  评论(0编辑  收藏  举报