OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 3245|回复: 0

《STM32F407 探索者开发指南》第十一章 STM32时钟系统

[复制链接]

1148

主题

1160

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4946
金钱
4946
注册时间
2019-5-8
在线时间
1256 小时
发表于 2023-7-1 10:28:11 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-7-1 10:40 编辑

第十一章 STM32时钟系统

1)实验平台:正点原子探索者STM32F407开发板

2) 章节摘自【正点原子】STM32F407开发指南 V1.1


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f407_explorerV3.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)STM32技术交流QQ群:151941872

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

STM32F4时钟系统的知识在《STM32F4xx参考手册_V18(英文版).pdf》第六章复位和时钟控制章节有较详细的讲解。这里我们对STM32F4的整体架构作一个简单的介绍,帮助大家更全面、系统地认识STM32F4系统的主控结构。了解时钟系统在整个STM32系统的贯穿和驱动作用,学会设置STM32的系统时钟。
本章将分为如下几个小节:
11.1 认识时钟树
11.2 如何修改主频

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

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

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

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

如图11.1.1为一个简化的STM32F4时钟系统。图中已经把我们主要关注几处标注出来。A部分表示输入时钟源,可分为外部时钟源和内部时钟源;B为锁相环“PLL”;C为我们重点需要关注的系统时钟源选择器,此项决定了MCU的系统主时钟“SYSCLK”的大小;AHB预分频器将SYSCLK分频或不分频后分发给其它外设进行处理,包括到D部分的Cortex-M内核系统的时钟和使能单元。E为定时器以及其它外设的时钟源APB1/APB2。F是STM32的MCO时钟输出功能,其它部分等我们学习到再详细探讨。接下来我们来详细介绍下这部分的功能。

11.1.1 时钟源
对于STM32F4,输入时钟源(Input Clock)主要包括HSI,HSE,LSI,LSE。其中,从时钟频率来分可以分为高速时钟源和低速时钟源,其中HSI、HSE高速时钟,LSI和LSE是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中HSE和LSE是外部时钟源;其他是内部时钟源,芯片上电即可产生,不需要借助外部电路。下面我们看看STM32的时钟源。

(1)2个外部时钟源:
l  高速外部振荡器HSE (High Speed External Clock signal)
外接石英/陶瓷谐振器,频率为4MHz~26MHz。本开发板使用的是8MHz。

l  低速外部振荡器LSE (Low Speed External Clock signal)
外接32.768kHz石英晶体,主要作用于RTC的时钟源。

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

(2)2个内部时钟源:
l  高速内部振荡器HSI(High Speed Internal Clock signal)
由内部RC 振荡器产生,频率为 16MHz。

l  低速内部振荡器LSI(Low Speed Internal Clock signal)
由内部RC振荡器产生,频率为32kHz,可作为独立看门狗的时钟源。

芯片上电时默认由内部的HSI时钟启动,如果用户进行了硬件和软件的配置,芯片才会根据用户配置调试尝试切换到对应的外部时钟源,所以同时了解这几个时钟源信号还是很有必要的。如何设置时钟的方法我们会在后文提到。

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

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

图11.1.2.1借用了在CubeMX下用锁相环配置168MHz时钟的一个示例:
u  PLL Source Mux:PLL时钟源选择器
图中标号①表示的是PLL时钟源的选择器,由寄存器RCC_PLLCFGR的bit22位进行控制,关于该寄存器的描述可参考《STM32F4xx参考手册_V4(中文版).pdf》第116页,如下图所示:     
image005.png
图11.1.2.3 PLLSRC锁相环时钟源选择

它有两种可选择的输入源:一个是内部时钟HSI信号,另一个是外部时钟HSE信号。

u  PLLM:HSE分频器作为PLL输入(HSE divider for PLL entry)

即图11.1.2.1在标号②的地方,主PLL输入时钟的分频系数,并把它的控制功能放在RCC_PLLCFGR寄存器中,我们引用如图11.1.2.2。     
image007.png
图11.1.2.2 PLLXTPRE设置选项值

从F407参考手册可得到它的值范围是:0~63。

u  PLLMUL:PLL倍频系数(PLL multiplication factor)

图中③所表示的配置锁相环倍频系数,同样地可以查到在STM32F4系列中,ST设置它的有效倍频范围为2~16倍。

结合图11.1.2.1,要实现168MHz的主频率,我们通过选择HSE分频作为PLL输入的时钟信号,输入8Mhz,8分频,即1MHz,通过标号③选择倍频因子,我们选择336倍频,这样可以得到时钟信号为1*336=336MHz,然后经过2分频,得到168MHz。

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

讲解PLL作为系统时钟时,根据我们开发板的资源,可以把主频通过PLL设置为168MHz。从上面的图11.2.1时钟树图可知,AHB、APB1、APB2、内核时钟等时钟通过系统时钟分频得到。根据得到的这个系统时钟,下面我们结合外设来看一看各个外设时钟源。   
image011.png
图11.2.3.1 STM32F407系统时钟生成图

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

下面介绍一下由AHB总线时钟得到的时钟:

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接口。其中标号⑧决定了定时器时钟频率,该位由硬件自动设置,分为两种情况:

1、 如果APB预分频器为1,定时器时钟频率等于APB域的频率;

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

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

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

11.1.4 时钟信号输出MCO          
image015.png
图11.1.4.1 MACO相关时钟

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

l ⑫MCO1\MCO2时钟源选择器
MCO1(外部器件的输出时钟1)时钟源有四个:LSE、HSE、HSI和PLLCLK。
MCO2(外部器件的输出时钟2)时钟源有四个:SYSCLK、PLLI2SCLK、HSE和PLLCLK。

l  ⑪MCO1\MCO2时钟分频器
MCO1和MCO2的预分频器,取值范围均为:1到5。

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

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

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

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

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

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

1步:配置HSE_VALUE
讲解STM32F4xx_hal_conf.h文件的时候,我们知道需要宏定义HSE_VALUE匹配我们实际硬件的高速晶振频率(这里是8MHz),代码中通过使用宏定义的方式来选择HSE_VALUE的值是25M或者8M选择定义HSE_VALUE的值为8M。代码如下:
  1. #if !defined (HSE_VALUE)
  2. #define HSE_VALUE 8000000U     /*!<Value of the External oscillator in Hz */
  3. #endif
  4. #endif /* HSE_VALUE */
复制代码
2步:调用SystemInit函数
我们介绍启动文件的时候就知道,在系统启动之后,程序会先执行SystemInit函数,进行系统一些初始化配置。启动代码调用SystemInit函数如下:
  1. Reset_Handler   PROC
  2.                 EXPORT  Reset_Handler                    [WEAK]
  3.                 IMPORT  SystemInit
  4.                 IMPORT  __main
  5.                  LDR     R0, =SystemInit
  6.                 BLX     R0
  7.                 LDR     R0, =__main
  8.                 BX      R0
  9.                 ENDP
复制代码
下面我们来看看system_stm32f4xx.c文件下定义的SystemInit程序,源码在168行到183行,函数如下。
  1. void SystemInit(void)
  2. {
  3.   /* FPU settings------------------------------------------------------------*/
  4.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
  5.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10and CP11 Full Access */
  6.   #endif
  7. #if defined (DATA_IN_ExtSRAM) || defined(DATA_IN_ExtSDRAM)
  8. SystemInit_ExtMemCtl();
  9. #endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
  10.   /* Configure the Vector Table location-------------------------------------*/
  11. #if defined(USER_VECT_TAB_ADDRESS)
  12.   SCB->VTOR = VECT_TAB_BASE_ADDRESS|VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
  13. #endif /* USER_VECT_TAB_ADDRESS */
  14. }
复制代码
从上面代码可以看出,SystemInit主要做了如下两个方面工作:
1) 外部存储器配置
2) 中断向量表地址配置

然而我们的代码中实际并没有定义DATA_IN_ExtSRAM和USER_VECT_TAB_ADDRESS这两个宏,实际上SystemInit对于正点原子的例程并没有起作用,但我们保留了这个接口。从而避免了去修改启动文件。另外,是可以把一些重要的初始化放到SystemInit这里,在main函数运行前就把重要的一些初始化配置好(如ST这里是在运行main函数前先把外部的SRAM初始化),这个我们一般用不到,直接到main函数中处理即可,但也有厂商(如RT-Thread)就采取了这样的做法,使得main函数更加简单,但对于初学者,我们暂时不建议这种用法。

HAL库的SystemInit函数并没有任何时钟相关配置,所以后续的初始化步骤,我们还必须编写自己的时钟配置函数。

3步:在main函数里调用用户编写的时钟设置函数
我们打开HAL库例程实验1跑马灯实验,看看我们在工程目录Drivers\SYSTEM分组下面定义的sys.c文件中的时钟设置函数sys_stm32_clock_init的内容:
  1. /**
  2. *@brief       时钟设置函数
  3. *@param       plln: 主PLL倍频系数(PLL倍频), 取值范围: 64~432.
  4. *@param       pllm: 主PLL和音频PLL预分频系数(进PLL之前的分频), 取值范围: 2~63.
  5. *@param       pllp: 主PLL的p分频系数(PLL之后的分频), 分频后作为系统时钟, 取值范围: 2,
  6.            4, 6, 8.(仅限这4个值)
  7. *@param       pllq: 主PLL的q分频系数(PLL之后的分频), 取值范围: 2~15.
  8. *@note
  9. *
  10. *             Fvco: VCO频率
  11. *             Fsys: 系统时钟频率, 也是主PLL的p分频输出时钟频率
  12. *             Fq:   主PLL的q分频输出时钟频率
  13. *             Fs:   主PLL输入时钟频率, 可以是HSI, HSE等.
  14. *             Fvco = Fs * (plln / pllm);
  15. *             Fsys = Fvco / pllp = Fs * (plln / (pllm * pllp));
  16. *             Fq   = Fvco / pllq = Fs * (plln /(pllm * pllq));
  17. *
  18. *             外部晶振为 8M的时候, 推荐值: plln =336, pllm = 8, pllp = 2, pllq = 7.
  19. *             得到:Fvco = 8 *(336 / 8) = 336Mhz
  20. *                   Fsys = pll_p_ck = 336 / 2 =168Mhz
  21. *                   Fq   = pll_q_ck = 336 / 7 = 48Mhz
  22. *
  23. *             F407默认需要配置的频率如下:
  24. *             CPU频率(HCLK) =pll_p_ck = 168Mhz
  25. *             AHB1/2/3(rcc_hclk1/2/3) = 168Mhz
  26. *             APB1(rcc_pclk1) = pll_p_ck / 4 = 42Mhz
  27. *             APB1(rcc_pclk2) = pll_p_ck / 2 = 84Mhz
  28. *
  29. *@retval     错误代码: 0, 成功; 1, 错误;
  30. */
  31. uint8_t sys_stm32_clock_init(uint32_t plln, uint32_t pllm, uint32_t pllp, uint32_t pllq)
  32. {
  33.    HAL_StatusTypeDef ret = HAL_OK;
  34.    RCC_OscInitTypeDef rcc_osc_init = {0};
  35. RCC_ClkInitTypeDef rcc_clk_init = {0};
  36.    __HAL_RCC_PWR_CLK_ENABLE();                             /* 使能PWR时钟 */
  37.    
  38. /* 下面这个设置用来设置调压器输出电压级别,以便在器件以最大频率工作时使性能与功耗实现平衡 */
  39.     /* 设置调压器输出电压级别,以便在器件未以最大频率工作 */
  40.    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  41.     /* 使能HSE,并选择HSE作为PLL时钟源,配置PLL1,开启USB时钟 */
  42.    rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  43.    rcc_osc_init.HSEState = RCC_HSE_ON;                  /* 打开HSE */
  44.    rcc_osc_init.PLL.PLLState = RCC_PLL_ON;              /* 打开PLL */
  45.    rcc_osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSE;    /* PLL时钟源选择HSE */
  46.    rcc_osc_init.PLL.PLLN = plln;
  47.    rcc_osc_init.PLL.PLLM = pllm;
  48.    rcc_osc_init.PLL.PLLP = pllp;
  49.    rcc_osc_init.PLL.PLLQ = pllq;
  50.     ret=HAL_RCC_OscConfig(&rcc_osc_init_handle);      /* 初始化RCC */
  51.     if(ret != HAL_OK)
  52.     {
  53.        return 1;    /* 时钟初始化失败,可以在这里加入自己的处理 */
  54.     }
  55.     /* 选中PLL作为系统时钟源并且配置HCLK,PCLK1和PCLK2*/
  56.    rcc_clk_init.ClockType = (RCC_CLOCKTYPE_SYSCLK \
  57.                                     | RCC_CLOCKTYPE_HCLK\
  58.                                     |RCC_CLOCKTYPE_PCLK1 \
  59.                                     |RCC_CLOCKTYPE_PCLK2);
  60.     /* 设置系统时钟时钟源为PLL */
  61.    rcc_clk_init.SYSCLKSource   =RCC_SYSCLKSOURCE_PLLCLK;
  62.    rcc_clk_init.AHBCLKDivider  = RCC_SYSCLK_DIV1;      /* AHB分频系数为1 */
  63.    rcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV4;        /* APB1分频系数为4 */
  64.    rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV2;        /* APB2分频系数为2 */
  65.     /* 同时设置FLASH延时周期为5WS,也就是6个CPU周期 */
  66.     ret=HAL_RCC_ClockConfig(&rcc_clk_init_handle, FLASH_LATENCY_5);
  67.     if(ret != HAL_OK)
  68.     {
  69.        return 1;    /* 时钟初始化失败 */
  70.     }
  71.    
  72.     /* STM32F405x/407x/415x/417x Z版本的器件支持预取功能 */
  73.     if (HAL_GetREVID() == 0x1001)
  74.     {
  75.        __HAL_FLASH_PREFETCH_BUFFER_ENABLE();            /* 使能flash预取 */
  76.     }
  77.     return 0;
  78. }
复制代码
函数sys_stm32_clock_init就是用户的时钟系统配置函数,除了配置PLL相关参数确定SYSCLK值之外,还配置了AHB、APB1和APB2的分频系数,也就是确定了HCLK,PCLK1和PCLK2的时钟值。

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

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

2) 配置系统时钟源以及SYSCLK、AHB、APB1和APB2的分频系数:调用函数HAL_RCC_ClockConfig()。

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

步骤1:配置时钟源相关参数,使能并选择HSE作为PLL时钟源,配置PLL1,我们调用的函数为HAL_RCC_OscConfig(),该函数在HAL库头文件STM32F4xx_hal_rcc.h中声明,在文件STM32F4xx_hal_rcc.c中定义。首先我们来看看该函数声明:
  1. HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct);
复制代码
该函数只有一个形参,就是结构体RCC_OscInitTypeDef类型指针。接下来我们看看结构体RCC_OscInitTypeDef的定义:
  1. typedef struct
  2. {
  3.   uint32_t OscillatorType;         /* 需要选择配置的振荡器类型 */
  4.   uint32_t HSEState;                /* HSE状态 */
  5.   uint32_t LSEState;                /* LSE状态 */
  6.   uint32_t HSIState;                /* HSI状态 */
  7.   uint32_tHSICalibrationValue;   /* HSI校准值 */
  8.   uint32_t LSIState;                /* LSI状态 */
  9. RCC_PLLInitTypeDef PLL;          /* PLL配置 */
  10. }RCC_OscInitTypeDef;
复制代码
该结构体前面几个参数主要是用来选择配置的振荡器类型。比如我们要开启HSE,那么我们会设置OscillatorType的值为RCC_OSCILLATORTYPE_HSE,然后设置HSEState的值为RCC_HSE_ON开启HSE。对于其他时钟源:HSI、LSI、LSE,配置方法类似。
RCC_OscInitTypeDef这个结构体还有一个很重要的成员变量是PLL,它是结构体RCC_PLLInitTypeDef类型。它的作用是配置PLL相关参数,我们来看看它的定义:
  1. typedef struct
  2. {
  3.   uint32_t PLLState;    /* PLL状态 */
  4.   uint32_t PLLSource;   /* PLL时钟源 */
  5.   uint32_t PLLM;         /* PLL倍频系数M */
  6. uint32_t PLLN;        /* PLL倍频系数N */
  7. uint32_t PLLP;        /* PLL倍频系数P */
  8. uint32_t PLLQ;        /* PLL倍频系数Q */
  9. }RCC_PLLInitTypeDef;
复制代码
从RCC_PLLInitTypeDef结构体的定义很容易看出该结构体主要用来设置PLL时钟源以及相关分频倍频参数。这个结构体的定义的相关内容请结合时钟树中红色框的内容一起理解。

接下来我们看看我们的时钟初始化函数sys_stm32_clock_init中的配置内容:
  1.     /* 使能HSE,并选择HSE作为PLL时钟源,配置PLL1,开启USB时钟 */
  2.    rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  3.    rcc_osc_init.HSEState = RCC_HSE_ON;                /* 打开HSE */
  4.    rcc_osc_init.PLL.PLLState = RCC_PLL_ON;           /* 打开PLL */
  5.    rcc_osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSE; /* PLL时钟源选择HSE */
  6.    rcc_osc_init.PLL.PLLN = plln;
  7.    rcc_osc_init.PLL.PLLM = pllm;
  8.    rcc_osc_init.PLL.PLLP = pllp;
  9.    rcc_osc_init.PLL.PLLQ = pllq;
复制代码
通过函数的该段程序,我们开启了HSE时钟源,同时选择PLL时钟源为HSE,然后把sys_stm32_clock_init的形参直接设置作为PLL的参数M的值,这样就达到了设置PLL时钟源相关参数的目的。

设置好PLL时钟源参数之后,也就是确定了PLL的时钟频率,然后到我们的步骤2。

步骤2:配置系统时钟源,以及SYSCLK、AHB、APB1和APB2相关参数,用函数HAL_RCC_ClockConfig(),声明如下:
  1. HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct,
  2. uint32_t FLatency);
复制代码
该函数有两个形参,第一个形参RCC_ClkInitStruct是结构体RCC_ClkInitTypeDef类型指针变量,用于设置SYSCLK时钟源以及SYSCLK、AHB、APB1和APB2的分频系数。第二个形参FLatency用于设置FLASH延迟。

RCC_ClkInitTypeDef结构体类型定义比较简单,我们来看看其定义:
  1. typedef struct
  2. {
  3.   uint32_t ClockType;                /* 要配置的时钟 */
  4.   uint32_t SYSCLKSource;             /* 系统时钟源 */
  5.   uint32_t AHBCLKDivider;            /* AHB分频系数 */
  6.   uint32_t APB1CLKDivider;           /* APB1分频系数 */
  7.   uint32_t APB2CLKDivider;          /* APB2分频系数 */
  8. }RCC_ClkInitTypeDef;
复制代码
我们在sys_stm32_clock_init函数中的实际应用配置内容如下:
  1.     /* 选中PLL作为系统时钟源并且配置HCLK,PCLK1和PCLK2*/
  2.    rcc_clk_init.ClockType = (RCC_CLOCKTYPE_SYSCLK \
  3.                                     | RCC_CLOCKTYPE_HCLK\
  4.                                     |RCC_CLOCKTYPE_PCLK1 \
  5.                                     |RCC_CLOCKTYPE_PCLK2);
  6.     /* 设置系统时钟时钟源为PLL */
  7.    rcc_clk_init.SYSCLKSource   =RCC_SYSCLKSOURCE_PLLCLK;
  8.    rcc_clk_init.AHBCLKDivider  = RCC_SYSCLK_DIV1;   /* AHB分频系数为1 */
  9.    rcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV4;     /* APB1分频系数为4 */
  10.    rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV2;     /* APB2分频系数为2 */
复制代码
sys_stm32_clock_init函数中的RCC_ClkInitTypeDef结构体配置内容:

第一个参数ClockType配置表示我们要配置的是SYSCLK、HCLK、PCLK1和PCLK四个时钟。

第二个参数SYSCLKSource配置选择系统时钟源为PLLCLK。

第三个参数AHBCLKDivider配置AHB分频系数为1。

第四个参数APB1CLKDivider配置APB1分频系数为4。

第五个参数APB2CLKDivider配置APB2分频系数为2。

根据我们在mian函数中调用sys_stm32_clock_init(336,8, 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。我们总结一下通过调用函数sys_stm32_clock_init(336,8, 2, 7)之后的关键时钟频率值:
         SYSCLK(系统时钟)                            =168MHz
         PLL主时钟                                  =168MHz
         AHB总线时钟(HCLK=SYSCLK/1)        =168MHz
         APB1总线时钟(PCLK1=HCLK/2)           =42MHz
         APB2总线时钟(PCLK2=HCLK/1)           =84MHz
最后我们来看看函数HAL_RCC_ClockConfig 第二个入口参数FLatency的含义,为了使FLASH读写正确(因为168Mhz的时钟比Flash的操作速度24Mhz要快得多,操作速度不匹配容易导致Flash操作失败),所以需要设置延时时间。对于STM32F4系列,FLASH延迟配置参数值是通过下表11.2.1.1来确定的:     
image017.png
表11.2.1.1 CPU时钟频率对应的等待周期

由于前面我们已经配置好系统时钟为168M,即CPU时钟为168M,所以根据表11.2.1.1需设置等待周期为5WS,也就是6个CPU周期,即第二个形参设置值:
  1.     ret=HAL_RCC_ClockConfig(&rcc_clk_init_handle, FLASH_LATENCY_5);
复制代码
这样FLASH的读写稳定就得到了保障。注:表11.2.1.1摘抄于《STM32F4xx参考手册_V4(中文版).pdf》第61页。

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

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

在STM32F4的HAL库中,外设时钟使能操作都是在RCC相关固件库文件头文件STM32F4xx_hal_rcc.h定义的。大家打开STM32F4xx_hal_rcc.h头文件可以看到文件中除了少数几个函数声明之外大部分都是宏定义标识符。外设时钟使能在HAL库中都是通过宏定义标识符来实现的。首先,我们来看看GPIOA的外设时钟使能宏定义标识符:
  1. #define __HAL_RCC_GPIOA_CLK_ENABLE()  do { \
  2.                             __IO uint32_t tmpreg = 0x00U; \
  3.                             SET_BIT(RCC->AHB1ENR,RCC_AHB1ENR_GPIOAEN);\
  4.                            /* Delay after an RCC peripheral clockenabling */ \
  5.                             tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
  6.                             UNUSED(tmpreg); \
  7.                             } while(0U)
复制代码
这段代码主要是定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),它的核心操作是通过下面这行代码实现的:
  1. SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);
复制代码
这行代码的作用是,设置寄存器RCC-> AHB1ENR的相关位为1,至于是哪个位,是由宏定义标识符RCC_AHB1ENR_GPIOAEN的值决定的,而它的值为:
  1. #define RCC_AHB1ENR_GPIOAEN_Pos             (0U)                                
  2. #define RCC_AHB1ENR_GPIOAEN_Msk             (0x1UL <<RCC_AHB1ENR_GPIOAEN_Pos)   
  3. #define RCC_AHB1ENR_GPIOAEN                  RCC_AHB1ENR_GPIOAEN_Msk<span style="font-family: Tahoma; font-size: medium; background-color: rgb(255, 255, 255);">    </span>
复制代码
上面三行代码很容易计算出来RCC_AHB1ENR_GPIOAEN=0x00000001,因此上面代码的作用是设置寄存器RCC-> AHB1ENR寄存器的bit0为1。我们可以从STM32F4的参考手册中搜索AHB1ENR寄存器定义,位0的作用是用来使用GPIOA时钟。AHB1ENR寄存器的位0描述如下:
位0  GPIOAEN:GPIOA 外设时钟使能
由软件置1和清零
0:禁止IO端口A时钟(复位后的默认值)   
1:使能IO端口A时钟
那么我们只需要在我们的用户程序中调用宏定义标识符就可以实现GPIOA时钟使能。使用方法为:
  1. __HAL_RCC_GPIOA_CLK_ENABLE();       /* 使能GPIOA时钟 */
复制代码
对于其他外设,同样都是在STM32F4xx_hal_rcc.h头文件中定义,大家只需要找到相关宏定义标识符即可,这里我们列出几个常用使能外设时钟的宏定义标识符使用方法:
  1. __HAL_RCC_DMA1_CLK_ENABLE();        /* 使能DMA1时钟 */
  2. __HAL_RCC_USART2_CLK_ENABLE();     /* 使能串口2时钟 */
  3. __HAL_RCC_TIM1_CLK_ENABLE();        /* 使能TIM1时钟 */
复制代码
我们使用外设的时候需要使能外设时钟,如果我们不需要使用某个外设,同样我们可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。我们同样以GPIOA为例,宏定义标识符为:
#define__HAL_RCC_GPIOA_CLK_DISABLE() (RCC->APB2ENR) &= ~ (RCC_APB2ENR_GPIOAEN)
同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE()的作用是设置RCC->AHB1ENR寄存器的位0为0,也就是禁止GPIOA时钟。具体使用方法我们这里就不做过多讲解,我们这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:
  1. __HAL_RCC_DMA1_CLK_DISABLE();       /* 禁止DMA1时钟 */
  2. __HAL_RCC_USART2_CLK_DISABLE();    /* 禁止串口2时钟 */
  3. __HAL_RCC_TIM1_CLK_DISABLE();       /* 禁止TIM1时钟 */
复制代码
关于STM32F4的外设时钟使能和禁止方法我们就给大家讲解到这里。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-4-18 06:58

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表