超级版主
 
- 积分
- 5892
- 金钱
- 5892
- 注册时间
- 2019-5-8
- 在线时间
- 1634 小时
|
|
第十章 STM32时钟系统
1)实验平台:正点原子Mini STM32H562开发板
2)章节摘自【正点原子】STM32H562开发指南 V1.0
3)购买链接: https://detail.tmall.com/item.htm?id=753606659612
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h562_mini.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子STM32开发板技术交流群:414052738
STM32H5时钟系统的知识在《STM32H5xx参考手册_V1(英文版).pdf》第八章复位和时钟控制章节有较详细的讲解。这里我们对STM32H5的整体架构作一个简单的介绍,帮助大家更全面、系统地认识STM32H5系统的主控结构。了解时钟系统在整个STM32系统的贯穿和驱动作用,学会设置STM32的系统时钟。
本章将分为如下几个小节:
10.1 认识时钟树
10.2 如何修改主频
10.1 认识时钟树
数字电路的知识告诉我们:任意复杂的电路控制系统都可以经由门电路组成的组合电路实现。回顾第五章的知识点,我们知道STM32内部也是由多种多样的电路模块组合在一起实现的。当一个电路越复杂,在达到正确的输出结果前,它可能因为延时会有一些短暂的中间状态,而这些中间状态有时会导致输出结果会有一个短暂的错误,这叫做电路中的“毛刺现象”,如果电路需要运行得足够快,那么这些错误状态会被其它电路作为输入采样,最终形成一系列的系统错误。为了解决这个问题,在单片机系统中,设计时以时序电路控制替代纯粹的组合电路,在每一级输出结果前对各个信号进行采样,从而使得电路中某些信号即使出现延时也可以保证各个信号的同步,可以避免电路中发生的“毛刺现象”,达到精确控制输出的效果。
由于时序电路的重要性,因此在MCU设计时就设计了专门用于控制时序的电路,在芯片设计中称为时钟树设计。由此设计出来的时钟,可以精确控制我们的单片机系统,这也是我们这节要展开分析的时钟分析。为什么是时钟树而不是时钟呢?一个MCU越复杂,时钟系统也会相应地变得复杂,如STM32H5的时钟系统比较复杂,不像简单的51单片机一个系统时钟就可以解决一切。对于STM32H5系列的芯片,正常工作的主频可以达到480MHz,但并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及RTC只需要几十KHz的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的MCU一般都是采取多时钟源的方法来解决这些问题。
STM32内部非常复杂,外设也非常多,为了更低的功耗,STM32默认不开启这些外设功能。用户可以根据自己的需要决定STM32芯片要使用哪些外设,从而再开启该外设的时钟,否则该外设不工作。
下面来看一下STM32H5时钟系统图。STM32H5时钟系统图看起来有点多且复杂,但是分开几部分各个理解其实没有那么难了。
图10.1.1 STM32H5时钟系统图
10.1.1 时钟源
对于STM32H5有六个时钟源可供使用,可分为外部时钟源和内部时钟源。具体如图10.1.1所示,在STM32H5的时钟系统图里,我们把这六个时钟源用橘黄色框框起来了。
(1)2个外部时钟源:
高速外部振荡器 (HSE)
支持4 MHz到50MHz 频率范围内的晶振。
低速外部振荡器 (LSE)
一般是32.768 kHz 晶振,可用于看门狗和RTC实时时钟。
2个外部时钟源都是芯片外部晶振产生的时钟频率,故而都有精度高的优点。
(2)4个内部时钟源:
高速内部振荡器 (HSI)
频率 64MHz 的高速RC振荡器,可用于系统时钟。
48 MHz RC振荡器 (HSI48)
频率48MHz,用于给特定的外设提供时钟,比如可以作为USB的时钟源。
低功耗内部振荡器 (CSI)
频率约是 4MHz,主要用于低功耗。
低速内部振荡器 (LSI)
频率约32 kHz的低速RC 振荡器,可用于RTC实时时钟、独立看门狗和自动唤醒。
10.1.2 锁相环PLL
STM32H5时钟系统图中看到红色框的部分,可以知道STM32H5有三个锁相环,分别是:PLL1、PLL2、PLL3。而且它们的时钟源只有一个,一起共用,但是进入到锁相环的时钟源分别经过三个不同的预分频器,这样我们就可以让锁相环有不同的输入时钟频率。
下面看到STM32H5时钟系统图标号①②③这三个组成部分:
①锁相环时钟源选择器
锁相环时钟源有三个:his_ck、csi_ck、hse_ck。我们一般选择hse_ck,即来自外部高速晶振,Mini STM32H562开发板外部高速晶振为8MHZ。
②PLL1\PLL2\PLL3时钟源预分频器
DIVM1、DIVM2和DIVM3分别是PLL1、PLL2和PLL3输入时钟的预分频系数,取值范围都是:1~63,请根据实际需要设置。
③PLL1\PLL2\PLL3锁相环
PLL1、PLL2和PLL3锁相环都是一个输入,三个输出。his_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):
假设外部晶振为8MHz,需要得到250MHz的pll1_p_ck频率,则可以设置:DIVM1=4,DIVN1=250,DIVP1=2即可。其它分频输出(pll1_q_ck/pll1_r_ck)计算方法与上式相似。我们把它们对应到STM32CubeMX工具的时钟系统图上理解就很直观了,如图10.1.2.1所示。
图10.1.2.1 STM32CubeMX对应的设置
关于时钟系统,大家如果看数据手册不太理解,结合STM32CubeMX的时钟系统配置界面理解会有很好的效果。
10.1.3 系统时钟SYSCLK
下面看到STM32H5时钟系统图标号④⑤这两个组成部分:
④sys_ck(即系统时钟)的时钟源有四个:hsi_ck、csi_ck、hse_ck和pll1_p_ck,系统默认是选择 hsi_ck(64Mhz)作为时钟源的。 当我们把 PLL1 配置好并且选择 pll1_p_ck 作为系统时钟的时钟源,则系统时钟可以得到 250MHz的时钟频率,从而得到最高性能。
⑤这部分是STM32H5的内核时钟和总线时钟等,总线时钟包括AHB、APB1、APB2、APB3的时钟。从上面的图10.2.1时钟树图可知,AHB、APB1、APB2、APB3、内核时钟等时钟通过系统时钟分频得到。该分频器是AHB预分频器,可选择的分频系数为1,2,4,8,16,32,64,128,256,512,我们选择不分频,所以AHB总线时钟达到最大的250MHz。
我们把它们对应到STM32CubeMX工具的时钟系统图上理解就很直观了,如图10.1.3.1所示:
图10.2.3.1 STM32H562系统时钟生成图
从上图可以知道,经过配置,AHB、APB1、APB2、APB3和内核时钟的频率都是250MHz。
如果参考手册对应外设章节没有额外说明,那么挂载在AHB、APB1、APB2、APB3总线上的外设时钟源就是该总线的时钟。但是也有例外的,比如定时器时钟源,分为两种情况:
1、如果APB预分频器为1,挂载在该总线上的定时器时钟源频率等于APB的频率;
2、否则,等于APB频率的两倍(×2)。
定时器的时钟源的详细说明,请看定时器章节的教程。
11.1.4 MCO时钟输出
下面看到STM32H5时钟系统图标号⑥这个组成部分:
MCO时钟输出,其作用是为外部器件提供时钟,STM32H562有两个MCO。
MCO1\MCO2时钟源选择器:
MCO1(外部器件的输出时钟1)时钟源有五个:his_ck、lse_ck、hse_ck、pll1_q_ck和hsi48_ker_ck。
MCO2(外部器件的输出时钟2)时钟源有六个:sys_ck、pll2_p_ck、hse_ck、pll1_p_ck、csi_ck和lsi_ker_ck。
MCO1\MCO2时钟分频器:
MCO1PRE是MCO1的预分频器,取值范围:1到15。
MCO2PRE是MCO2的预分频器,取值范围:1到15。
MCO1\MCO2时钟输出引脚:
MCO1、MCO2两个时钟输出引脚给外部器件提供时钟源(分别由PA8和PC9复用功能实现),每个引脚可以选择一个时钟源,通过RCC时钟配置寄存器 (RCC_CFGR1)进行配置。
关于STM32H5xx时钟的详细介绍,请参考《STM32H5xx参考手册_V1(英文版).pdf》第11章,有不明白的地方,可以对照手册仔细研究。具体哪个外设是连接在哪个时钟总线上,以及对应的时钟总线最高主频是多少,请参考STM32H562VIT6.pdf数据手册(路径:A盘→7,硬件资料→2,芯片资料→STM32H562VIT6.pdf)。
通过以上内容的介绍,我们知道STM32H562的时钟设计的比较复杂,各个时钟基本都是可控的,任何外设都有对应的时钟控制开关,这样的设计,对降低功耗是非常有用的,不用的外设不开启时钟,就可以大大降低其功耗。
11.2 如何修改主频
STM32H562默认的情况下(比如:串口IAP时或者是未初始化时钟时),使用的是内部64MHz的HSI作为时钟源,所以不需要外部晶振也可以下载和运行代码的。
下面我们来讲解如何让STM32H562芯片在250MHz的频率下工作,250MHz是官方推荐使用的最高的稳定时钟频率。正点原子的Mini STM32H562开发板的外部高速晶振的频率是8MHz,我们就是在这个晶振频率的基础上,通过各种倍频和分频得到250MHZ的系统工作频率。
11.2.1 STM32H5时钟系统配置
下面我们将分几步给大家讲解STM32H5时钟系统配置过程,这部分内容很重要,请大家认真阅读。
第1步:配置HSE_VALUE
讲解STM32H5xx_hal_conf.h文件的时候,我们知道需要宏定义HSE_VALUE匹配我们实际硬件的高速晶振频率(这里是8MHz),代码如下:
- #if !defined (HSE_VALUE)
- #define HSE_VALUE ((uint32_t)8000000) /* 外部高速振荡器的值,单位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_stm32h5xx.c文件下定义的SysteMinit程序,源码在199行到277行,简化函数如下。
- void SystemInit(void)
- {
- uint32_t reg_opsr;
- /* FPU settings ------------------------------------------------------------*/
- #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
- SCB->CPACR |= ((3UL << 20U)|(3UL << 22U)); /* 设置CP10和CP11为全访问 */
- #endif
- /* 将RCC时钟配置复位为默认复位状态 */
- /* 设置 HSION bit */
- RCC->CR = RCC_CR_HSION;
- /* 复位 CFGR 寄存器 */
- RCC->CFGR1 = 0U;
- RCC->CFGR2 = 0U;
- /* 复位 HSEON, HSECSSON, HSEBYP, HSEEXT, HSIDIV, HSIKERON, CSION,
- CSIKERON, HSI48 and PLLxON 位 */
- /* 代码部分太长,省略了 */
- /* 复位 PLLxCFGR 寄存器等 */
- /* 代码部分太长,省略了 */
- /* 配置向量表位置添加偏移地址 */
- #ifdef VECT_TAB_SRAM
- SCB->VTOR = SRAM1_BASE | VECT_TAB_OFFSET; /* 内部SRAM中的矢量表重定位 */
- #else
- SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* 在内部FLASH矢量表重定位 */
- #endif /* VECT_TAB_SRAM */
- /* 检查OPSR寄存器,以验证是否有正在进行的交换或选项字节更新被重置中断 */
- /* 代码部分太长,省略了 */
- }
复制代码 从上面代码可以看出,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 时钟设置函数
- * [url=home.php?mod=space&uid=271674]@param[/url] plln: PLL1倍频系数(PLL倍频), 取值范围: 4~512.
- * @param pllm: PLL1预分频系数(进PLL之前的分频), 取值范围: 1~63.
- * @param pllp: PLL1的p分频系数(PLL之后的分频), 分频后作为系统时钟,
- 取值范围: 2~128.(且必须是2的倍数)
- * @param pllq: PLL1的q分频系数(PLL之后的分频), 取值范围: 1~128.
- * @note
- *
- * Fvco: VCO频率
- * Fsys: 系统时钟频率, 也是PLL1的p分频输出时钟频率
- * Fq: PLL1的q分频输出时钟频率
- * Fs: PLL输入时钟频率, 可以是HSI, CSI, HSE等.
- * Fvco = Fs * (plln / pllm);
- * Fsys = Fvco / pllp = Fs * (plln / (pllm * pllp));
- * Fq = Fvco / pllq = Fs * (plln / (pllm * pllq));
- *
- * 外部晶振为 8M的时候, 推荐值: plln = 250, pllm = 4, pllp = 2, pllq = 2
- * 得到:Fvco = 8 * (250 / 4) = 500Mhz
- * Fsys = pll1_p_ck = 500 / 2 = 250Mhz
- * Fq = pll1_q_ck = 500 / 2 = 250Mhz
- *
- * H563默认需要配置的频率如下:
- * CPU频率(rcc_c_ck) = SYSCLK = 250Mhz
- * AHB1/2/3(rcc_hclk1/2/3) = 250Mhz
- * APB1/2/3(rcc_pclk1/2/3) = 250Mhz
- *
- * @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};
- /* 配置主稳压器输出的电压级别,VOS = 0, Scale0, 1.35V内核电压,使用最高主频250MHz */
- __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);
- while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
- /* 配置PLL1时钟 */
- rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE; /* 时钟源为HSE */
- rcc_osc_init.HSEState = RCC_HSE_ON; /* 打开HSE */
- rcc_osc_init.PLL.PLLState = RCC_PLL_ON; /* 打开PLL1 */
- rcc_osc_init.PLL.PLLSource = RCC_PLL1_SOURCE_HSE; /* PLL1 时钟源选择HSE */
- rcc_osc_init.PLL.PLLM = pllm; /* PLL1 预分频系数(PLL之前的分频) */
- rcc_osc_init.PLL.PLLN = plln; /* PLL1 N倍频系数 */
- rcc_osc_init.PLL.PLLP = pllp; /* PLL1 P分频系数 */
- rcc_osc_init.PLL.PLLQ = pllq; /* PLL1 Q分频系数 */
- rcc_osc_init.PLL.PLLR = 2; /* PLL1 R分频系数 */
- rcc_osc_init.PLL.PLLRGE = RCC_PLL1_VCIRANGE_1;
- rcc_osc_init.PLL.PLLVCOSEL = RCC_PLL1_VCORANGE_WIDE;
- rcc_osc_init.PLL.PLLFRACN = 0;
- ret = HAL_RCC_OscConfig(&rcc_osc_init); /* 初始化 */
- if (ret != HAL_OK)
- {
- return 1;
- }
- /*
- * 配置系统时钟来源和各总线时钟预分频系数
- * 配置系统时钟为250MHz
- * 配置AHB总线时钟为250Mhz,对应AHB1、AHB2和AHB3总线
- * 配置APB1总线时钟为250MHz
- * 配置APB2总线时钟为250MHz
- * 配置APB3总线时钟为250MHz
- */
- rcc_clk_init.ClockType = RCC_CLOCKTYPE_SYSCLK
- |RCC_CLOCKTYPE_HCLK
- |RCC_CLOCKTYPE_PCLK1
- |RCC_CLOCKTYPE_PCLK2
- |RCC_CLOCKTYPE_PCLK3;
- rcc_clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
- rcc_clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1;
- rcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV1;
- rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV1;
- rcc_clk_init.APB3CLKDivider = RCC_HCLK_DIV1;
- ret = HAL_RCC_ClockConfig(&rcc_clk_init, FLASH_LATENCY_5);
- if (ret != HAL_OK)
- {
- return 1;
- }
- return 0;
- }
复制代码 函数sys_stm32_clock_init就是用户的时钟系统配置函数,除了配置PLL相关参数确定SYSCLK值之外,还配置了AHB、APB1、APB2、APB3总线时钟的预分频系数。我们首先来看看使用HAL库配置STM32H5时钟系统的一般步骤:
1) 设置调压器输出电压级别:调用函数__HAL_PWR_VOLTAGESCALING_CONFIG()。
2) 配置时钟源相关参数:调用函数HAL_RCC_OscConfig()。
3) 选择系统时钟SYSCLK的时钟来源以及配置AHB、APB1、APB2、APB3总线时钟的预分频系数,调用HAL_RCC_ClockConfig()函数。
下面我们详细讲解这个3个步骤。
步骤1:调压器输出电压级别VOS,它是由PWR控制寄存器PWR_VOSCR的位5:4来确定的:
- 位5:4 VOS[1:0]
- 00:电压调节3(默认)
- 01:电压调节2
- 10:电压调节1
- 11:电压调节0
复制代码 电压调节级别数值越小工作频率越高,所以如果我们要配置H750的主频为250MHz,那么我们需要配置调压器输出电压调节级别VOS为级别0,详细的表格参考在步骤3会讲解。所以函数sys_stm32_clock_init中步骤1源码如下:
- /* 配置主稳压器输出的电压级别,VOS = 0, Scale0, 1.35V内核电压,使用最高主频250MHz */
- __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);
复制代码 步骤2:配置时钟源和锁相环PPL1的相关参数,使能并选择HSE作为PLL时钟源,我们调用的函数为HAL_RCC_OscConfig(),该函数在HAL库头文件STM32H5xx_hal_rcc.h中声明,在文件STM32H5xx_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; /* HSI分频系数 */
- uint32_t HSICalibrationValue; /* HSI校准系数 */
- uint32_t LSIState; /* LSI状态 */
- uint32_t CSIState; /* CSI状态 */
- uint32_t CSICalibrationValue; /* CSI校准系数 */
- uint32_t HSI48State; /* HSI48状态 */
- RCC_PLLInitTypeDef PLL; /* PLL配置结构体 */
- } RCC_OscInitTypeDef;
复制代码 该结构体前面几个参数主要是用来选择配置的振荡器类型。比如我们要开启HSE,那么我们会设置OscillatorType的值为RCC_OSCILLATORTYPE_HSE,然后设置HSEState的值为RCC_HSE_ON开启HSE。对于其他时钟源:HIS、LSI、LSE、CSI和HSI48,配置方法类似。
RCC_OscInitTypeDef这个结构体还有一个很重要的成员变量是PLL,它是结构体RCC_PLLInitTypeDef类型。它的作用是配置PLL相关参数,我们来看看它的定义:
- typedef struct
- {
- uint32_t PLLState; /* PLL1状态 */
- uint32_t PLLSource; /* PLL1时钟源 */
- uint32_t PLLM; /* PLL1分频系数M */
- uint32_t PLLN; /* PLL1倍频系数N */
- uint32_t PLLP; /* PLL1分频系数P */
- uint32_t PLLQ; /* PLL1分频系数Q */
- uint32_t PLLR; /* PLL1分频系数R */
- uint32_t PLLRGE; /* PLL1时钟输入范围 */
- uint32_t PLLVCOSEL; /* PLL1时钟输出范围 */
- uint32_t PLLFRACN; /* PLL1 VCO乘数因子的小数部分 */
- }RCC_PLLInitTypeDef;
复制代码 从RCC_PLLInitTypeDef结构体的定义很容易看出该结构体主要用来设置PLL时钟源以及相关分频倍频参数。
接下来看看系统时钟初始化函数sys_stm32_clock_init中的配置内容:
- /* 配置PLL1时钟 */
- rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE; /* 时钟源为HSE */
- rcc_osc_init.HSEState = RCC_HSE_ON; /* 打开HSE */
- rcc_osc_init.PLL.PLLState = RCC_PLL_ON; /* 打开PLL1 */
- rcc_osc_init.PLL.PLLSource = RCC_PLL1_SOURCE_HSE; /* PLL1 时钟源选择HSE */
- rcc_osc_init.PLL.PLLM = pllm; /* PLL1 预分频系数(PLL之前的分频) */
- rcc_osc_init.PLL.PLLN = plln; /* PLL1 N倍频系数 */
- rcc_osc_init.PLL.PLLP = pllp; /* PLL1 P分频系数 */
- rcc_osc_init.PLL.PLLQ = pllq; /* PLL1 Q分频系数 */
- rcc_osc_init.PLL.PLLR = 2; /* PLL1 R分频系数 */
- rcc_osc_init.PLL.PLLRGE = RCC_PLL1_VCIRANGE_1;
- rcc_osc_init.PLL.PLLVCOSEL = RCC_PLL1_VCORANGE_WIDE;
- rcc_osc_init.PLL.PLLFRACN = 0;
- ret = HAL_RCC_OscConfig(&rcc_osc_init); /* 初始化 */
- if (ret != HAL_OK)
- {
- return 1;
- }
复制代码 通过函数的该段程序,我们开启了HSE时钟源,同时选择PLL时钟源为HSE,然后把sys_stm32_clock_init的4个形式参数直接设置作为PLL的参数N、M、P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。
设置好时钟源和PLL1的相关参数后,就完成了步骤2的内容。
步骤3:选择系统时钟SYSCLK的时钟来源以及配置AHB、APB1、APB2、APB3总线时钟的预分频系数,用函数HAL_RCC_ClockConfig(),声明如下:
- HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct,
- uint32_t FLatency);
复制代码 该函数有两个形参,第一个形参RCC_ClkInitStruct是RCC_ClkInitTypeDef结构体类型指针变量,用于设置SYSCLK时钟源以及SYSCLK、AHB、APB1、APB2、APB3的分频系数。第二个形参FLatency用于设置FLASH延迟。
RCC_ClkInitTypeDef结构体类型定义比较简单,我们来看看其定义:
- typedef struct
- {
- uint32_t ClockType; /* 要配置的时钟 */
- uint32_t SYSCLKSource; /* 系统时钟源 */
- uint32_t AHBCLKDivider; /* AHB分频系数 */
- uint32_t APB1CLKDivider; /* APB1分频系数 */
- uint32_t APB2CLKDivider; /* APB2分频系数 */
- uint32_t APB3CLKDivider; /* APB3分频系数 */
- }RCC_ClkInitTypeDef;
复制代码 我们在sys_stm32_clock_init函数中的实际应用配置内容如下:
- /*
- * 配置系统时钟来源和各总线时钟预分频系数
- * 配置系统时钟为250MHz
- * 配置AHB总线时钟为250Mhz,对应AHB1、AHB2和AHB3总线
- * 配置APB1总线时钟为250MHz
- * 配置APB2总线时钟为250MHz
- * 配置APB3总线时钟为250MHz
- */
- rcc_clk_init.ClockType = RCC_CLOCKTYPE_SYSCLK
- |RCC_CLOCKTYPE_HCLK
- |RCC_CLOCKTYPE_PCLK1
- |RCC_CLOCKTYPE_PCLK2
- |RCC_CLOCKTYPE_PCLK3;
- rcc_clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
- rcc_clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1;
- rcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV1;
- rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV1;
- rcc_clk_init.APB3CLKDivider = RCC_HCLK_DIV1;
- ret = HAL_RCC_ClockConfig(&rcc_clk_init, FLASH_LATENCY_5);
- if (ret != HAL_OK)
- {
- return 1;
- }
复制代码 sys_stm32_clock_init函数中的RCC_ClkInitTypeDef结构体配置内容:
第一个成员ClockType配置表示我们要配置到的时钟,把所以配置到的时钟的宏都赋值给该成员,这里我们包含了5个宏定义。
第二个成员SYSCLKSource配置选择系统时钟源为PLL。
第三个成员AHBCLKDivider配置AHB总线时钟预分频系数为1。
第四个成员APB1CLKDivider配置APB1总线时钟预分频系数为1。
第五个成员APB2CLKDivider配置APB2总线时钟预分频系数为1。
第六个成员APB3CLKDivider配置APB3总线时钟预分频系数为1。
根据我们在mian函数中调用sys_stm32_clock_init(250, 4, 2, 2)时设置的形参数值,代入PLL输出频率的计算公式可得:
因为我们选择系统时钟源为PLL(pll1_p_ck),所以系统时钟SYSCLK=250MHz。再结合各个总线时钟的预分频系数,总结一下调用函数sys_stm32_clock_init(250, 4, 2, 2)之后,关键的时钟频率值如下:(HCLK代表AHB1~3总线上的时钟)
- sys_ck(系统时钟) = 250MHz
- AHB总线时钟(HCLK= sys_cpre_ck /1) = 250MHz
- APB1总线时钟(rcc_pclk1=HCLK/1) = 250MHz
- APB2总线时钟(rcc_pclk2=HCLK/1) = 250MHz
- APB3总线时钟 (rcc_pclk3=HCLK/1) = 250MHz
复制代码 最后我们来看看函数HAL_RCC_ClockConfig第二个入口参数FLatency的含义,对于STM32H5系列,FLASH延迟配置参数值是通过下表来确定的:
表11.2.1.1 FLASH推荐的等待状态和编程延迟数
由于前面我们已经配置好系统时钟为250MHz,即CPU时钟为250MHz,所以根据表11.2.1.1需设置等待周期为5WS,也就是6个CPU周期,即第二个形参设置值:
- ret = HAL_RCC_ClockConfig(&rcc_clk_init_handle, FLASH_LATENCY_5);
复制代码 这样FLASH的读写稳定就得到了保障。注:表11.2.1.1摘抄于《STM32H5xx参考手册_V1(英文版).pdf》第240页。
时钟系统配置相关知识就给大家讲解到这里。
11.2.2 STM32H5时钟使能和配置
上一节我们讲解了时钟系统配置步骤。在配置好时钟系统之后,如果我们要使用某些外设,例如GPIO,ADC等,我们还要使能这些外设时钟。这里大家必须注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32的外设时钟使能是在RCC相关寄存器中配置的。因为RCC相关寄存器非常多,有兴趣的同学可以直接打开《STM32H5xx参考手册_V1(英文版).pdf》 11章查看所有RCC相关寄存器的配置。接下来我们来讲解通过STM32H5 的HAL库使能外设时钟的方法。
在STM32H5的HAL库中,外设时钟使能操作都是在RCC相关固件库文件头文件stm32h5xx_hal_rcc.h定义的。大家打开stm32h5xx_hal_rcc.h头文件可以看到文件中除了少数几个函数声明之外大部分都是宏定义标识符。外设时钟使能在HAL库中都是通过宏定义标识符来实现的。首先,我们来看看GPIOA的外设时钟使能宏定义标识符:
- #define __HAL_RCC_GPIOA_CLK_ENABLE() do { \
- __IO uint32_t tmpreg; \
- SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
- * Delay after an RCC peripheral clock enabling */ \
- 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)/*0x00000001 */
- #define RCC_AHB2ENR_GPIOAEN RCC_AHB2ENR_GPIOAEN_Msk
复制代码 上面三行代码很容易计算出来RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器RCC->AHB2ENR寄存器的最低位为1。我们可以从STM32H5的参考手册中搜索AHB2ENR寄存器定义,最低位的作用是用来使用GPIOA时钟。AHB2ENR寄存器的位0描述如下:
- 位0 GPIOAEN:GPIOA 外设时钟使能
- 由软件置1和清零
- 0:禁止IO端口A时钟(复位后的默认值)
- 1:使能IO端口A时钟
复制代码 那么我们只需要在我们的用户程序中调用宏定义标识符就可以实现GPIOA时钟使能。使用方法为:
- __HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能GPIOA时钟 */
复制代码 对于其他外设,同样都是在STM32H5xx_hal_rcc.h头文件中定义,大家只需要找到相关宏定义标识符即可,这里我们列出几个常用使能外设时钟的宏定义标识符使用方法:
- __HAL_RCC_OSPI1_CLK_ENABLE(); /* 使能OSPI1时钟 */
- __HAL_RCC_USART2_CLK_ENABLE(); /* 使能串口2时钟 */
- __HAL_RCC_TIM1_CLK_ENABLE(); /* 使能TIM1时钟 */
复制代码 我们使用外设的时候需要使能外设时钟,如果我们不需要使用某个外设,同样我们可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。我们同样以GPIOA为例,宏定义标识符为:
- #define __HAL_RCC_GPIOA_CLK_DISABLE()
- CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码 同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE()的作用是设置RCC->AHB2ENR寄存器的最低位为0,也就是禁止GPIOA时钟。具体使用方法我们这里就不做过多讲解,我们这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:
- __HAL_RCC_ OSPI1_CLK_DISABLE(); /* 禁止OSPI1时钟 */
- __HAL_RCC_USART2_CLK_DISABLE(); /* 禁止串口2时钟 */
- __HAL_RCC_TIM1_CLK_DISABLE(); /* 禁止TIM1时钟 */
复制代码 关于STM32H5的外设时钟使能和禁止方法我们就给大家讲解到这里。 |
|