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