嵌入式学习笔记——STM32的时钟树

时钟树

  • 前言
  • 时钟树
  • 时钟分类
  • 时钟树框图
  • LSI与LSE
  • HSI、HSE与PLL
  • 系统时钟的产生
  • 举例
  • AHB、APBx的时钟配置
  • 时钟树相关寄存器介绍
  • 1.时钟控制寄存器(RCC_CR)
  • 2.RCC PLL 配置寄存器 (RCC_PLLCFGR)
  • 3.RCC 时钟配置寄存器 (RCC_CFGR)
  • 4.RCC 时钟中断寄存器 (RCC_CIR)
  • 修改系统时钟配置为内部时钟
  • 代码流程
  • 编程代码
  • 验证
  • **使用NDK的Debug仿真查看STM32的系统频率的操作方式:**
  • 总结
  • 前言

    在之前的所有代码编程的过程中,似乎每次都绕不开一个叫做时钟使能的东西,当时我们是在数据手册上直接看其挂接在那条时钟线上的,那么STM32内部的时钟到底是怎么一个构型呢,本文来对此做一个介绍。

    时钟树

    老规矩,一个新的名词出现,首先需要搞清楚它是个啥,下图中对时钟树给出了定义,STM32的时钟树是由多个时钟源和时钟分频组成的,为STM32芯片提供各种时钟信号。也就是说,在使用STM32的时候,所有的频率和时钟都是通过时钟树产生的。

    时钟分类

    在之前的嵌入式学习笔记——STM32硬件基础知识中的晶振电路部分提到过一嘴,STM32的基础时钟源有4个HSI振荡器时钟 、HSE振荡器时钟 、LSI振荡器时钟 、LSE振荡器时钟。这是最底层的时钟来源。
    然后是上图中提到的PLL锁相环SYSCLK系统时钟,前面提到过,此系列使用的STM32F407VE的主频是168Mhz,这个主频对应的就是SYSCLK系统时钟,而PLL锁相环就是这个系统时钟的直接来源
    在硬件篇介绍的时候,我们知道,外部晶振(也就是外部高速时钟HSE)的频率是25MHZ内部振荡电路(对应内部高速时钟HSI)的频率是16Mhz,显然这两者都不能直接提供168MHZ的主频,那么系统时钟的168Mhz是怎么来的呢?
    其实就是将HSI或者HSE接入PLL,通过PLL倍频产生的,具体的产生过程在后面的结构框图和寄存器介绍中描述。
    系统时钟的来源:HSI振荡器时钟 HSE振荡器时钟 PLL锁相环
    两个次级时钟源:LSI振荡器时钟 LSE振荡器时钟
    H/L:高速/低速 SI/SE:内部/外部

    时钟树框图

    大致了解了时钟树的构成后,就可以来研究它的框图了,这是一张很复杂的图,还是按照之前的思路,对其进行一个拆分,把暂时不用的先排除,此图的下方红框里面的是一个I2S的音频处理专用的时钟,独立于其他的所有时钟,所以暂时不看它,绿框里面的是一个以太网的时钟,也是独立于其他所有模块,所以暂时也先不看,最下面的橙色框里的USB2.0时钟也是,这三个暂时都不需要看。

    简化下半部分后,如下图所示,橙色框内的MCO1和MCO2使用来做测试脚的,使用示波器进行测量,测量频率是否正常的,所以这部分暂时也可以不看,进一步简化。

    LSI与LSE

    我们先来看上半部分的内部低速时钟LSI和外部低速时钟LSE;根据框图,可以发现紫色框的内部低速时钟频率为32KHZ,有两个分支,一个是为独立看门狗提供时钟,另一个是为RTC提供时钟。
    而绿色的框是外部低速时钟,外接引脚OSC32_IN和OSC32_OUT就是接外部晶振的引脚,进来的频率是32.768KHZ进来后只有一个走向就是为RTC提供时钟。
    然后需要注意的是蓝色框的四选一数据选择器,一共有三个输入,前两个是上面的LSI和LSE,还有一个来自下方的HSE,中间经过了一个/2到31的分频器,也就是说看门狗智能由内部低速时钟提供时钟,RTC可以由LSI、LSE、或者是HSE经过分频后来提供时钟,具体使用哪一个需要我们在代码里面配置。

    HSI、HSE与PLL

    丢掉了下面,看完了上面,删除了测试接口,接下来就到了本文的重头戏了,百分之90的片上外设的时钟都与这部分有关系。
    首先,看图中的系统时钟(红色椭圆框),可以放发现,系统时钟也是通过一个选择器来选择的,输入源有三个,分别是来自橙色框的HSI(内部高速时钟),绿色框的HSE(外部高速时钟)以及蓝色框的PLL锁相环,也就是说,系统时钟可以有三个选择。
    这里有一个问题,上面也提到过,系统时钟的直接来演应该选择哪一个输入?
    由于内部高速时钟和外部高速时钟都无法提供168Mhz的频率,所以系统时钟的直接来源应该是PLLCLK,也就是说在配置的时候需要将系统时钟选择到锁相环上。

    系统时钟的产生

    那么问题来了,PLLCLK又是怎么产生168Mhz的频率的呢,我们放大这部分电路来做个解析,
    如下图所示:
    首先内部时钟HSI和外部时钟 HSE会来到绿色框的选择器,由我们编程控制具体选择HSI还是HSE;经过选择器选择后,会有一个分频器 “/M”一般这个M就是对应的多少M,
    举个栗子:我们选择外部高速时钟作为锁相环的输入,此时的外部时钟频率是8MHZ,那么此处的分配就会采用8分频,将频率分频为1Mhz输入,方便后面的计算。
    同样的,如果板子上没有外接晶振,只能选择内部高速时钟作为PLL的输入,此时内部高速时钟的频率是16Mhz,那么此处的分频系数就要配置为16,总之就是将对应输入的频率在此处转化为1Mhz,当然也可以是其他的值,只是1Mhz方便计算而已。

    再经过选择器和分频器后,就正式进入了PLL锁相环了,这里我们结合寄存器的描述来解析:
    首先1的位置就是VCO也就是经过分频后的1Mh频率,在其正下方有一个xN,这个xN是倍频的意思,此处的xN是需要我们编程时进行控制的,它的具体位置就是RCC PLL 配置寄存器 (RCC_PLLCFGR)的第6到14位,但是其写入值不是随意的,注意看下方的描述,我们写入的数据必须是192-432之间的值;

    经过倍频后,会来到3的位置,这里有一个/P,根据上面的经验,这个肯定也是一个分频器,那么它的配置有没有要求呢,还是来看看寄存器的相关描述:它是由RCC PLL 配置寄存器 (RCC_PLLCFGR)的第16和17位控制的,注意最低分频也是2分频。

    经过/P后就输出到SYSCLK的选择器了,至于下面的4和5暂时我们没有用上,所以这里不做介绍,想要了解的自己去中文编程手册查看。

    举例

    好了,到这里已经知道了系统频率是怎么产生的了,举两个例子吧,
    第一种,选择外部时钟作为PLL的输入,外部时钟是8MHZ,主频是168MHZ,那么有关PLL的配置流程应该是怎样的?

    //此处伪代码主要是走一下计算流程实际流程会比这个多几个步骤
    {
    	1.绿框选择器选择HSE作为输入;
    	2.橙框的分频系数设置为8分频,输出1Mhz的信号到VCO;
    	3.配置2号位置的倍频系数,系数要求是(192-433),由于后面还有个最低的2分频,所以此处要配置为336(168*2);
    	4.配置3号位置分频器为00,二分频,这样就实现了一个168MHZ的系统频率
    }
    

    第二种,没有外部接晶振电路,或者说板子的外接晶振电路挂逼了,此时应该如何配置系统的时钟为200MHZ(对于F407来说,200MHZ的频率算超频,如非特殊需求必要,不建议这么操作,会拉高功耗而且造成不稳定)

    //此处伪代码主要是走一下计算流程实际流程会比这个多几个步骤
    {
    	1.绿框选择器选择HSI作为输入;
    	2.橙框的分频系数设置为16分频,输出1Mhz的信号到VCO;(内部高速时做的频率为16MHZ)
    	3.配置2号位置的倍频系数,系数要求是(192-433),由于后面还有个最低的2分频,所以此处要配置为400(200*2);
    	4.配置3号位置分频器为00,二分频,这样就实现了一个200MHZ的系统频率
    }
    

    AHB、APBx的时钟配置

    上面一段主要是介绍了系统时钟的具体生成过程,这个时钟框图还剩下最后一块,就是我们前面配置时经常使用到的AHB、APB1、APB2这些。

    如上图所示,经左侧的选择器选择后,最终的SYSCLK的频率是168MHZ,紧接着往右就分成了两条路线一条向上直接是给到了以太网时钟,这个暂时用不上,可以暂时性忽略;另一路则是来到了AHB,下面有一个分频器,意思是可以配置AHB分频为1,2,4,8,16分频,这个具体的我们也看一眼,对应的寄存器描述,其具体的控制是由RCC 时钟配置寄存器 (RCC_CFGR)的第4到第7位来实现的。

    然后再往后走,又分成了两路,这次先看下面的这一路,可以看见首先来到了2位置的APBx的分频控制,关于它的具体配置也是有对应的寄存器位的,如下图:其中APB1占RCC 时钟配置寄存器 (RCC_CFGR)的10-12位
    APB2占RCC 时钟配置寄存器 (RCC_CFGR)的13-15位
    然后还有对应的要求,APB1不能超过42MHZ,APB2不能超过84Mhz。

    通过这个就可以为大多是外设提供所需的时钟了,需要特别注意的是下面还有个框3,里面是一个判断语句,如果APBx是1分频,则×1给到定时器,如果APBx不是1分频,则需要×2后再给到定时器

    最后,还有一个遗留的就是经过AHB后向上去的,那个最后是给内核提供时钟了,还有个(框4位置)是经过8分频后位系统时钟提供时钟,也就是下一篇会介绍的系统滴答(SYS_Tick),这个到下一篇再说。

    时钟树相关寄存器介绍

    整个时钟相关的寄存器都在中文编程手册的第6.3章,主要包括三大类:1.时钟配置寄存器部分 ;2.外设复位寄存器部分 3.外设使能寄存器部分;

    这里重点是如何配置时钟,所以需要使用到的是第一大类,时钟配置相关的寄存器。

    1.时钟控制寄存器(RCC_CR)

    写法:RCC->CR
    功能: 1.打开对应的时钟源 HIS HSE PLL;2.用以判断对应的时钟是否准备好。
    配置过程中需要使用到的具体位:
    位 0 HSION:内部高速时钟使能 (Internal high-speed clock enable)
    置1:打开HSI时钟
    置0:关闭HSI时钟
    写操作,时钟并不是立刻打开的
    位 1 HSIRDY:内部高速时钟就绪标志 (Internal high-speed clock ready flag)
    为1:表示HSI时钟开启成功
    为0:表示HSI时钟还没有开启成功
    读操作

    位 16 HSEON:HSE 时钟使能 (HSE clock enable)
    置1:打开HSE时钟
    置0: 关闭HSE时钟
    位 17 HSERDY:HSE 时钟就绪标志 (HSE clock ready flag)
    为1:表示HSE时钟开启成功
    为0:表示HSE时钟还没有开启成功

    位 24 PLLON:主 PLL (PLL) 使能 (Main PLL (PLL) enable)
    置1:打开锁相环
    置0:关闭锁相环
    位 25 PLLRDY:主 PLL (PLL) 时钟就绪标志 (Main PLL (PLL) clock ready flag)
    为1:表示PLL开启成功
    为0:表示PLL还没有开启成功
    PLL的锁相环具备写保护,在PLL锁相环运行状态的时候,不能进行相关配置
    其他位的相关介绍请参考手册。

    2.RCC PLL 配置寄存器 (RCC_PLLCFGR)

    这个寄存器在上面介绍PLL的流程的时候已经大致看过了,其作用就是配置PLL的各项参数,产生系统所需的168MHZ的频率。
    写法:RCC->PLLCFGR
    功能:配置PLL锁相环,注意:配置过程中需要先操作上一个寄存器关闭锁相环

    位 5:0 PLLM:主 PLL (PLL) 和音频 PLL (PLLI2S) 输入时钟的分频系数 (Division factor for the main PL(PLL) and audio PLL (PLLI2S) input clock)
    可以配置相应的分频
    写入值:2~63 可写入的值有限制

    位 14:6 PLLN:适用于 VCO 的主 PLL (PLL) 倍频系数 (Main PLL (PLL) multiplication factor for VCO)
    可以配置相应的倍频
    写入值:192~432 可写入的值有限制

    位 17:16 PLLP:适用于主系统时钟的主 PLL (PLL) 分频系数 (Main PLL (PLL) division factor for main system clock)
    可以配置相应的分频,对应的分频有自己的参数
    00:PLLP = 2
    01:PLLP = 4
    10:PLLP = 6
    11:PLLP = 8

    位 22 PLLSRC: 主 PLL(PLL) 和音频 PLL (PLLI2S) 输入时钟源 (Main PLL(PLL) and audio PLL
    (PLLI2S) entry clock source)
    由软件置 1 和清零,用于选择 PLL 和 PLLI2S 时钟源。此位只有在 PLL 和 PLLI2S 已禁止时
    才可写入。
    0:选择 HSI 时钟作为 PLL 和 PLLI2S 时钟输入
    1:选择 HSE 振荡器时钟作为 PLL 和 PLLI2S 时钟输
    此位就是上面框图中的绿色选择框,选择HSI或者是HSE作为PLL的输入。

    3.RCC 时钟配置寄存器 (RCC_CFGR)

    此寄存器的主要作用就是对AHB、APB1、APB2等进行时钟配置,也就是上面框图讲解的最后一部分的控制内容。

    写法:RCC->CFGR
    功能:配置相应时钟总线时钟,选择系统时钟的来源
    位 1:0 SW:系统时钟切换 (System clock switch)
    00:选择HSI作为系统时钟时钟源
    01:选择HSE作为系统时钟时钟源
    10:选择PLL作为系统时钟时钟源

    位 3:2 SWS:系统时钟切换状态 (System clock switch status)
    为00:表示HIS选择成功
    为01:表示HSE选择成功
    为10:表示PLL选择成功

    具体写法:
    while(!(PLL->CFGR & (1<<3)));
    //等待系统时钟切换成HIS
    while ( RLL->CFGR & (3<<2) );
    

    位 7:4 HPRE:AHB 预分频器 (AHB prescaler)
    可以修改AHB总线的频率
    0xxx:系统时钟不分频
    1000:系统时钟 2 分频
    1001:系统时钟 4 分频
    1010:系统时钟 8 分频
    1011:系统时钟 16 分频
    1100:系统时钟 64 分频
    1101:系统时钟 128 分频
    1110:系统时钟 256 分频
    1111:系统时钟 512 分频

    位 15:10 PPRE1:APBx分频器
    0xx:AHB 时钟不分频
    100:AHB 时钟 2 分频
    101:AHB 时钟 4 分频
    110:AHB 时钟 8 分频
    111:AHB 时钟 16 分频

    4.RCC 时钟中断寄存器 (RCC_CIR)

    功能:在上面各个配置过程中,各类时钟准备就绪就会进入中断执行操作,默认不操作,在这里一般不需要执行中断。所以这个寄存器基本上不操作的。

    修改系统时钟配置为内部时钟

    在搞清楚了框图以及对应的寄存器后,就可以尝试配置一个时钟了,这里实现这样一个需求,使用HSI作为基础时钟源配置系统时钟200Mhz,APB2为50MHZ;APB1为25MHZ。
    这类需求一般不常用,但是一旦拿到的板子没有外接晶振或者说自己画的板子外接晶振挂了,不工作,就可以采用类似的操作,将PLL时钟源选择为HSI,进而为整个系统提供时钟源。

    代码流程

    还是老规矩,先写一个伪代码来理清思路:

    ST的默认时钟文件------HSE
    HSI系统时钟配置的初始化函数
    {
       打开HSI的时钟振荡器
       等待HSI准备就绪
    
       先切换系统时钟到HSI//由于系统运行过程中不能没有时钟,而配置PLL又需要先将PLL关闭,所以需要执行此操作,现将系统时钟暂时切换到HSI。
       等待系统时钟切换成功
    
       关闭锁相环
       选择锁相环需要的时钟源
       配置M分频
       配置N倍频
       配置P分频
    
       打开PLL锁相环
       等待PLL锁相环锁定
       
       系统时钟切换回PLL锁相环
       等待系统时钟准备就绪
    }
    

    编程代码

    还是一样的需要新建文件然后导入工程,这里就不再一步一步的演示了

    //配置代码:
    #include "Hsi.h"
    
    /*******************************************
    *函数名    :HSI_Init
    *函数功能  :初始化HSI函为系统时钟
    *函数参数  :无
    *函数返回值:无
    *函数描述  :
    将系统时钟调整为200MHZ
    APB2为50MHZ
    APB1为25MHZ
    *********************************************/
    void HSI_Init(void)
    {
    		RCC->CR |=(1<<0);//打开HSI的适中振荡器
    		while(!(RCC->CR & (1<<1)));//等待HSI就位
    		RCC->CFGR &= ~(3<<0);//将系统时钟暂时切换至HSI
    		while(RCC->CFGR & (3<<2));//等待时钟切换完成
    	
    		RCC->CR &=~(1<<24);//关闭锁相环
    		RCC->PLLCFGR &=~(1<<22);//PLL选择HSI为输入时钟源
    	
    		RCC->PLLCFGR &=~(0X3F<<0);//清零
    		RCC->PLLCFGR |=(16<<0);  //M16分频
    	
    		RCC->PLLCFGR &=~(0X1FF<<6);//清零
    		RCC->PLLCFGR |=(400<<6);  //N倍频 400
    	
    		RCC->PLLCFGR &=~(3<<16);//P分频 2
    	
    		RCC->CR |=(1<<24);//打开锁相环
    		while(!(RCC->PLLCFGR & (1<<25)));//等待锁相环锁定
    		//切换回PLL作为系统时钟
    		RCC->CFGR |= (1<<1);//将系统时钟暂时切换回PLL
    		while(!(RCC->CFGR & (1<<3)));//等待时钟切换完成
    		
    		RCC->CFGR &=~(0xf<<4);//清零,将AHB时钟设置为不分频200MHZ
    		
    		RCC->CFGR &=~(0x7<<10);//清零
    		RCC->CFGR |=(0x6<<10);//将APB1的时钟设置为25MHZ8分频
    		
    		RCC->CFGR &=~(0x7<<13);//清零
    		RCC->CFGR |=(0x5<<13);//将APB2的时钟设置为50MHZ//4分频
    		
    }
    
    //.H:
    #ifndef _HSI_H
    #define _HSI_H
    #include "stm32f4xx.h"
    
    void HSI_Init(void);
    #endif
    

    验证

    效果:由于APB2的时钟变成了50MHZ所以一种检验方式如下图,修改BRR内对应的频率为50MHZ然后编译下载,串口助手可以正常打印就说明正常。

    另外一种检验方式就是添加库函数,调用对应库函数即可查看。
    未修改之前的主频是168Mhz,如下图右下的框图所示。

    修改后的主频是200MHZ,APB1的频率是25MHZ,APB2的频率是50MHZ。

    使用NDK的Debug仿真查看STM32的系统频率的操作方式:

    1.需要使用到库函数,要找一个库函数的代码,复制对应的
    “stm32f4xx_rcc.h” 和对应的"stm32f4xx_rcc.c"到自己工程目录下并添加进工程;使用库函数的同学可以直接在自己的库里面找,将其添加进工程即可。
    2.使用寄存器的小伙伴记得打开stm32f4xx_rcc.h,
    添加一个宏定义“ #define assert_param(expr) ((void)0)”,使用库函数的同学直接跳转至第3步。

    3.在main.h包含头文件“stm32f4xx_rcc.h”,然后再main.c声明一个结构体变量

    RCC_ClocksTypeDef Get_Rcc_Clock;    //声明一个结构体变量,获取系统时钟状态
    

    4.在单次运行代码的位置,添加:

    RCC_GetClocksFreq(&Get_Rcc_Clock);  //仿真的时候就可以在结构体get_rcc_clock中看见各个外设的时钟了
    

    5.编译通过后,点击Debug按钮,来到如下界面,在右下角底部找到“Call Stack + Locals”,选中;然后点击2位置的全速运行,等待3秒,然后点击叛变的停止运行,4对应的框会出现对应的频率。

    此时频率还是16进制的数,为了方便查看,随便选择一个变量选中,然后右键弹出2位置的16进制显示,把这个复选框取消即可。
    为了

    STM32F4获取并查看当前系统时钟频率——http://t.csdn.cn/E9ATa
    STM32获取当前系统时钟——http://t.csdn.cn/uPwvO
    此法是仿真的频率并不是实际采集的频率,只是理论值,实际最好还是采用上一种串口修改频率不乱码的方案来解决。

    总结

    本文主要是介绍了STM32的是时钟树,捋了一下时钟的配置流程,文中如有不足还请指出。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 嵌入式学习笔记——STM32的时钟树

    发表评论