STM32 HAL库时钟系统详解

目录

一、认识时钟树

1、什么是时钟

2、认识时钟树(F4)

2.1、时钟源的认识

2.2、STM32F4时钟树

2.3、系统时钟来源

2.4、总线时钟来源

二、配置系统时钟

1、系统时钟配置步骤

2、外设时钟使能和失能

3、编写系统时钟配置函数(sys_stm32_clock_init)

3.1、开启电源管理时钟

3.2、设置调压器输出电压级别

3.3、选择时钟源并配置 PLL

3.4、选择系统时钟源并配置总线分频器

3.5、设置指令预取

4、完整配置代码


一、认识时钟树

1、什么是时钟

简单来说,时钟是具有周期性的脉冲信号,最常用的是占空比50%的方波

时钟是单片机的脉搏,搞懂时钟走向及关系,对单片机使用至关重要!

2、认识时钟树(F4)

2.1、时钟源的认识

H:high 高

L:low 低

S:speed 速度

I:internal 内部

E:external 外部

2.2、STM32F4时钟树

STM32F4时钟树简图

STM32F407xx数据手册时钟图

2.3、系统时钟来源

系统时钟(SYSCLK)可以有三个来源:内部高速振荡器(SHI)、外部高速振荡器(HSE)和 PLL 锁相环,一般配置为 PLL 锁相环

HSE外部高速时钟先经过 M 分频,进入 PLL 锁相环,在 PLL 锁相环中经过 N 倍频(由于倍频后的时钟不稳定,需要VCO来稳定倍频后的频率)得到稳定的超高速时钟,这个时钟如果经过 P 分频就可以得到系统时钟(SYSCLK),这个时钟如果经过 Q 分频就可以得到其他外设时钟(一般设为 48MHz 的全速 USB 外设时钟)

2.4、总线时钟来源

系统时钟经过 AHB 分频得到 AHB 总线时钟内核时钟存储器时钟和 DMA 时钟。AHB 时钟(HCLK)经过 APB1 分频得到 APB1 外设总线时钟,经过 APB2 分频得到 APB2 外设总线时钟,经过 1/8 分频得到系统滴答定时器时钟(SysTick)

其中,挂载在 APBx 总线上的定时器会根据 APBx 的分频系数是否等于 1 来决定是否进行 2 倍频

if (APBx分频系数 == 1)
    倍频系数 = 1;
else
    倍频系数 = 2;

二、配置系统时钟

1、系统时钟配置步骤

(1)配置 HSE_VALUE:告诉 HAL 库外部晶振频率,stm32f4xx_hal_conf.h

(2)调用 SystemInit():在启动文件中调用,在 system_stm32f4xx.c 定义

(3)选择时钟源,配置 PLL:通过 HAL_RCC_OscConfig() 函数设置

(4)选择系统时钟源,配置总线分频器:通过 HAL_RCC_ClockConfig() 函数设置

(3)+(4)=  sys_stm32_clock_init()

2、外设时钟使能和失能

3、编写系统时钟配置函数(sys_stm32_clock_init)

3.1、开启电源管理时钟

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

3.2、设置调压器输出电压级别

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

设置调压器输出电压级别为 0 时,HCLK频率最大可设为 144MHz,调压器输出电压级别为 1 时, HCLK 频率最大可设为 168MHz

3.3、选择时钟源并配置 PLL

通过 HAL_RCC_OscConfig() 函数来选择时钟源并配置 PLL

详细配置代码

HAL_StatusTypeDef ret = HAL_OK;
RCC_OscInitTypeDef rcc_osc_init = {0};

/* 使能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;                                                /* 时钟初始化失败,可以在这里加入自己的处理 */
}

3.4、选择系统时钟源并配置总线分频器

通过 HAL_RCC_ClockConfig() 函数来选择系统时钟源并配置总线分频器

详细配置代码

RCC_ClkInitTypeDef rcc_clk_init = {0};

/* 选中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;                                                /* 时钟初始化失败 */
}

这里初始化需要同时设置 FLASH 延时等待周期数,根据 芯片手册 —> 嵌入式Flash —> 读接口 来确定

根据芯片工作电压为 3.3V,HCLK 设置为 168MHz,可以确定等待周期数为 5

3.5、设置指令预取

官方手册介绍

每个 Flash 读操作可读取 128 位,可以是 4 行 32 位指令,也可以是 8 行 16 位指令,具体取决于烧写在 Flash 中的程序。因此对于顺序执行的代码,至少需要 4 个 CPU 周期来执行前一次读取的 128 位指令行。在 CPU 请求当前指令行时,可使用 I-Code 总线的预取操作读取 Flash 中的下一个连续存放的 128 位指令行。可将 FLASH_ACR 寄存器中的 PRFTEN 位置 1,来使能预取功能。当访问 Flash 至少需要一个等待周期时,此功能非常有用。

下图为需要 3 WS(3 个等待周期)访问 Flash 时连续 32 位指令的执行过程,图中分别介绍了使用和不使用预取操作两种情况。

处理非顺序执行的代码(有分支)时,指令可能并不存在于当前使用的或预取的指令行中。这种情况下,CPU 等待时间至少等于等待周期数。

总的来说,当芯片支持指令预取时,设置指令预取会使 Flash 读取指令速度更快!!

在 sys_stm32_clock_init 中配置

/* STM32F405x/407x/415x/417x Z版本的器件支持预取功能 */
if (HAL_GetREVID() == 0x1001)
{
    __HAL_FLASH_PREFETCH_BUFFER_ENABLE();                    /* 使能flash预取 */
}

也可以在 HAL_Init() 函数中配置

#if (PREFETCH_ENABLE != 0U)
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */

PREFETCH_ENABLE 在 stm32f4xx_hal_conf.h 文件中定义,默认为 0U(关闭)

#define  PREFETCH_ENABLE              0U /* The prefetch will be enabled in SystemClock_Config(), depending on the used 
                                            STM32F405/415/07/417 device: RevA (prefetch must be off) or RevZ (prefetch can be on/off) */

为了防止重复开启指令预取,可以在 sys_stm32_clock_init 中先判断再配置

/* STM32F405x/407x/415x/417x Z版本的器件支持预取功能 */
#if (PREFETCH_ENABLE == 0U)
if (HAL_GetREVID() == 0x1001)
{
    __HAL_FLASH_PREFETCH_BUFFER_ENABLE();                    /* 使能flash预取 */
}
#endif

4、完整配置代码

/**
 * @brief       时钟设置函数
 * @param       plln: 主PLL倍频系数(PLL倍频), 取值范围: 64~432.
 * @param       pllm: 主PLL和音频PLL预分频系数(进PLL之前的分频), 取值范围: 2~63.
 * @param       pllp: 主PLL的p分频系数(PLL之后的分频), 分频后作为系统时钟, 取值范围: 2, 4, 6, 8.(仅限这4个值)
 * @param       pllq: 主PLL的q分频系数(PLL之后的分频), 取值范围: 2~15.
 * @note
 *
 *              Fvco: VCO频率
 *              Fsys: 系统时钟频率, 也是主PLL的p分频输出时钟频率
 *              Fq:   主PLL的q分频输出时钟频率
 *              Fs:   主PLL输入时钟频率, 可以是HSI, HSE等.
 *              Fvco = Fs * (plln / pllm);
 *              Fsys = Fvco / pllp = Fs * (plln / (pllm * pllp));
 *              Fq   = Fvco / pllq = Fs * (plln / (pllm * pllq));
 *
 *              外部晶振为 8M的时候, 推荐值: plln = 336, pllm = 8, pllp = 2, pllq = 7.
 *              得到:Fvco = 8 * (336 / 8) = 336Mhz
 *                   Fsys = pll_p_ck = 336 / 2 = 168Mhz
 *                   Fq   = pll_q_ck = 336 / 7 = 48Mhz
 *
 *              F407默认需要配置的频率如下:
 *              CPU频率(HCLK) = pll_p_ck = 168Mhz
 *              AHB1/2/3(rcc_hclk1/2/3) = 168Mhz
 *              APB1(rcc_pclk1) = pll_p_ck / 4 = 42Mhz
 *              APB1(rcc_pclk2) = pll_p_ck / 2 = 84Mhz
 *
 * @retval      错误代码: 0, 成功; 1, 错误;
 */
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 (PREFETCH_ENABLE == 0U)
    if (HAL_GetREVID() == 0x1001)
    {
        __HAL_FLASH_PREFETCH_BUFFER_ENABLE();                    /* 使能flash预取 */
    }
#endif

    return 0;
}

作者:HZU_Puzzle

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 HAL库时钟系统详解

发表评论