STM32时钟系统以及配置及源码分析

发布者:数字狂舞最新更新时间:2019-07-12 来源: eefocus关键字:STM32  时钟系统  配置 手机看文章 扫描二维码
随时随地手机看文章

1.STM32F429时钟概述

时钟系统是 CPU 的脉搏,就像人的心跳一样。所以时钟系统的重要性就不言而喻了。 STM32有多个时钟来源的选择,采用一个系统时钟不是很简单吗?为什么 STM32 要有多个时钟源呢? 因为首先 STM32 本身非常复杂,外设非常的多,但是并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及 RTC 只需要几十 k 的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的 MCU 一般都是采取多时钟源的方法来解决这些问题。


下面是stm32f4的时钟系统图:



STM32 有5个时钟源:HSI、HSE、LSI、LSE、PLL。


 ①、HSI是高速内部时钟,RC振荡器,频率为16MHz,精度不高。可以直接作为系统时钟或者用作PLL时钟输入。  


 ②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~26MHz。(此处以25M为例)


 ③、LSI是低速内部时钟,RC振荡器,频率为32kHz,提供低功耗时钟。主要供独立看门狗和自动唤醒单元使用。  


 ④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。RTC  


 ⑤、PLL为锁相环倍频输出。


附录:


 PLL 为锁相环倍频输出。 STM32F4 有三个 PLL:

1) 主 PLL(PLL)由 HSE 或者 HSI 提供时钟信号,并具有两个不同的输出时钟。第一个输出 PLLP 用于生成高速的系统时钟(最高 180MHz)第二个输出 PLLQ 为 48M 时钟, 用于 USB OTG FS 时钟,随机数发生器的时钟和 SDIO时钟。


2) 第一个专用 PLL(PLLI2S)用于生成精确时钟, 在 I2S 和 SAI1 上实现高品质音频性能。 其中, N 是用于 PLLI2S vco 的倍频系数,其取值范围是: 192~432; R 是 I2S 时钟的分频系数,其取值范围是: 2~7; Q 是 SAI 时钟分频系数,其取值范围是: 2~15; P 没用到。


3) 第二个专用 PLL(PLLSAI)同样用于生成精确时钟,用于 SAI1 输入时钟,同时还为 LCD_TFT接口提供精确时钟。 其中, N 是用于 PLLSAI vco 的倍频系数,其取值范围是: 192~432;Q 是 SAI 时钟分频系数,其取值范围是: 2~15; R 是 LTDC 时钟的分频系数,其取值范围是: 2~7; P 没用到。



例子:主 PLL 时钟第一个高速时钟输出 PLLP 的计算方法:配置180MHz为例:


分析:主 PLL 时钟的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器出来之后还需要经过一个分频系数为 P(第一个输出 PLLP)或者 Q(第二个输出 PLLQ)的分频器分频之后,最后才生成最终的主 PLL 时钟。例如我们的外部晶振选择 25MHz。同时我们设置相应的分频器 M=25,倍频器倍频系数 N=360,分频器分频系数 P=2,那么主 PLL 生成的第一个输出高速时钟 PLLP 为:


PLL=25MHz * N/ (M*P)=25MHz* 360 /(25*2) = 180MHz


配置过程如下图所示: 



2.系统时钟的初始化寄存器源码分析

在系统进入主函数之前,首先会执行SystemInit这个函数对系统进行初始化



看一看这个程序的内容:


源代码如下:


void SystemInit(void)

{

/* FPU 设置------------------------------------------------------------*/

#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)

SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */

#endif

/* 复位 RCC 时钟配置为默认配置-----------*/

RCC->CR |= (uint32_t)0x00000001;//打开 HSION 位

RCC->CFGR = 0x00000000;//复位 CFGR 寄存器

RCC->CR &= (uint32_t)0xFEF6FFFF;//复位 HSEON, CSSON and PLLON 位

RCC->PLLCFGR = 0x24003010; //复位寄存器 PLLCFGR

RCC->CR &= (uint32_t)0xFFFBFFFF;//复位 HSEBYP 位

RCC->CIR = 0x00000000;//关闭所有中断

#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)

SystemInit_ExtMemCtl();

#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */

/* 配置中断向量表地址=基地址+偏移地址 ------------------*/

#ifdef VECT_TAB_SRAM

SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;

#else

SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;

#endif

}

可以看出这段代码的作用是:

1) FPU 设置

2) 复位 RCC 时钟配置为默认复位值(默认开始了 HIS)

3) 外部存储器配置

4) 中断向量表地址配置


做了这些工作,但是在F4的HAL库汇总SystemInit函数,并没有设置系统的主频和外设时钟的频率,所以所以需要自己去写这个函数。


首先先分析一下使用寄存器版本来写的SystemInit函数吧:


//系统时钟初始化函数

//plln:主PLL倍频系数(PLL倍频),取值范围:64~432.

//pllm:主PLL和音频PLL分频系数(PLL之前的分频),取值范围:2~63.

//pllp:系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)

//pllq:USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.

void Stm32_Clock_Init(u32 plln,u32 pllm,u32 pllp,u32 pllq)

{  

RCC->CR|=0x00000001; //设置HISON,开启内部高速RC振荡

RCC->CFGR=0x00000000; //CFGR清零 

RCC->CR&=0xFEF6FFFF; //HSEON,CSSON,PLLON清零 

RCC->PLLCFGR=0x24003010; //PLLCFGR恢复复位值 

RCC->CR&=~(1<<18); //HSEBYP清零,外部晶振不旁路

RCC->CIR=0x00000000; //禁止RCC时钟中断 

Sys_Clock_Set(plln,pllm,pllp,pllq);//设置时钟 

//配置向量表   

#ifdef  VECT_TAB_RAM

MY_NVIC_SetVectorTable(1<<29,0x0);

#else   

MY_NVIC_SetVectorTable(0,0x0);

#endif 

}   

这个函数需要的参数是4个,分别与系统原理图对应的是,如下图所示:



接下来,查看时钟控制CR寄存器的描述:


1.开启HISON,开启内部高速RC震荡



2.时钟配置寄存器CFRG清零



3.配置时钟控制CR寄存器, 使位HSEON,CSSON,PLLON清零(第16、19、24位) 



4.RCC PLL 配置寄存器 (RCC_PLLCFGR),恢复默认值:RCC->PLLCFGR=0x24003010;    //PLLCFGR恢复复位值


5.设置CR寄存器,外部晶振不旁路



6.RCC 时钟中断寄存器 (RCC_CIR),RCC->CR=0X00000000;  //禁止RCC时钟中断



7.设置时钟,使用函数Sys_Clock_Set(plln,pllm,pllp,pllq);//设置时钟,函数原型如下:


u8 Sys_Clock_Set(u32 plln,u32 pllm,u32 pllp,u32 pllq)

u16 retry=0;

u8 status=0;

RCC->CR|=1<<16; //HSE 开启 

while(((RCC->CR&(1<<17))==0)&&(retry<0X1FFF))retry++;//等待HSE RDY

if(retry==0X1FFF)status=1; //HSE无法就绪

else   

{

RCC->APB1ENR|=1<<28; //电源接口时钟使能

PWR->CR|=3<<14; //高性能模式,时钟可到180Mhz

RCC->CFGR|=(0<<4)|(5<<10)|(4<<13);//HCLK 不分频;APB1 4分频;APB2 2分频. 

RCC->CR&=~(1<<24); //关闭主PLL

RCC->PLLCFGR=pllm|(plln<<6)|(((pllp>>1)-1)<<16)|(pllq<<24)|(1<<22);//配置主PLL,PLL时钟源来自HSE

RCC->CR|=1<<24; //打开主PLL

while((RCC->CR&(1<<25))==0);//等待PLL准备好 

FLASH->ACR|=1<<8; //指令预取使能.

FLASH->ACR|=1<<9; //指令cache使能.

FLASH->ACR|=1<<10; //数据cache使能.

FLASH->ACR|=5<<0; //5个CPU等待周期. 

RCC->CFGR&=~(3<<0); //清零

RCC->CFGR|=2<<0; //选择主PLL作为系统时钟  

while((RCC->CFGR&(3<<2))!=(2<<2));//等待主PLL作为系统时钟成功. 

return status;

}  

8.RCC->CR|=1<<16;                //设置CR寄存器的第16位为1,HSE 开启



9.等待HSE时钟就绪,判断CR寄存器的第17位是否为1,返回1准备就绪,如果超时,返回标志位status=1.



10.如果准备就绪,使能电源接口时钟,RCC->APB1ENR|=1<<28;    //设置APB1ENR寄存器28位为1电源接口时钟使能



11.开始电源的高性能模式,PWR->CR|=3<<14;         //高性能模式,时钟可到180Mhz



11.配置RCC的CFGR寄存器,//HCLK 不分频;APB1 4分频;APB2 2分频



12.RCC->CR&=~(1<<24);    //关闭主PLL,配置主PLL的一些参数,配置完再重新打开


13.//配置主PLL,PLL时钟源来自HSE,RCC->PLLCFGR=pllm|(plln<<6)|(((pllp>>1)-1)<<16)|(pllq<<24)|(1<<22);


pllm,设置PLLM


(plln<<6),设置PLLN


(((pllp>>1)-1)<<16),设置系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)

为何是:(((pllp>>1)-1)<<16)呢?如下解释,当我们取值pllp为2时,可以得到(((pllp>>1)-1)<<16)=0


当pllp取值为4时,(((pllp>>1)-1)<<16)=1,同理,pllp取值为6时,(((pllp>>1)-1)<<16)=3.其实就是在调用函数的时候方便设置。可以看都这个寄存器的位,当第17:16为0时,就是这只pllp为2分频。



(pllq<<24),pllq:USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.


 


(1<<22),选择主PLL的时钟源是HSE外部震荡时钟、


所以第13步骤,完成的工作就是如下如所示的设置:



14.设置完主PLL之后,重新打开主PLL:  RCC->CR|=1<<24;            //打开主PLL



15.每次打开PLL之前,都要等待就绪:while((RCC->CR&(1<<25))==0);//等待PLL准备好 



16.设置FLASH寄存器参数:


FLASH->ACR|=1<<8;        //指令预取使能.

FLASH->ACR|=1<<9;        //指令cache使能.

FLASH->ACR|=1<<10;        //数据cache使能.

FLASH->ACR|=5<<0;        //5个CPU等待周期.


17.设置CFRG(RCC时钟配置寄存器)选择PLL作为系统时钟,RCC->CFGR&=~(3<<0);      //清零 RCC->CFGR|=2<<0;     



18.等待主PLL设置为系统主时钟成功:while((RCC->CFGR&(3<<2))!=(2<<2));


语句含义:当CFGR的3:2位不等于01时就说明主PLL还未就绪,就绪等待。



19.最后一步,配置向量表。


通过这样的设置系统时钟的整体配置就OK了。


如果调用函数:u8 Sys_Clock_Set(u32 plln,u32 pllm,u32 pllp,u32 pllq),产生180Mhz的主频


设置参数:外部晶振为25M的时候:plln=360,pllm=25,pllp=2,pllq=8.



3.系统时钟的初始化HAL库函数源码分析

3.1.HAL库主要是把寄存器进行封装,然后把一些参数合并到一个结构体,通过调用结构体的方式对寄存器进行赋值,间接的完成对STM32寄存器的配置。


主要是下面两个结构体:


1.RCC_OscInitTypeDef结构体:


解释:从结构体的名称可以看的出来,这个结构体主要是选择时钟源,然后是时钟的状态(开启还是关闭)




 


可以看到RCC_OscInitTypeDef 里面还嵌套了一个RCC_PLLInitTypeDef结构体,主要是用于主PLL配置用的。比如设置M分频,vco倍频等。


RCC_PLLInitTypeDef 结构体如下:


1.所以,第一步是定义一个RCC_OscInitTypeDef 结构体变量,并给这个结构体变量赋值,然后调用,HAL_RCC_OscConfig函数,把这个结构体变量传进去,使设置生效。


代码如下:


RCC_OscInitTypeDef RCC_OscInitStructure;  //定义一个结构体变量

 

 

RCC_OscInitStructure.OscillatorType=RCC_OSCILLATORTYPE_HSE;    //时钟源为HSE

RCC_OscInitStructure.HSEState=RCC_HSE_ON;                      //打开HSE

RCC_OscInitStructure.PLL.PLLState=RCC_PLL_ON;//打开PLL

RCC_OscInitStructure.PLL.PLLSource=RCC_PLLSOURCE_HSE;//PLL时钟源选择HSE

RCC_OscInitStructure.PLL.PLLM=pllm; //主PLL和音频PLL分频系数(PLL之前的分频),取值范围:2~63.

RCC_OscInitStructure.PLL.PLLN=plln; //主PLL倍频系数(PLL倍频),取值范围:64~432.  

RCC_OscInitStructure.PLL.PLLP=pllp; //系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)

RCC_OscInitStructure.PLL.PLLQ=pllq; //USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.

ret=HAL_RCC_OscConfig(&RCC_OscInitStructure);//初始化

最重要的还是看一下HAL库里面是如何对我们赋予这个结构体变量的值进行操作的:也就是HAL_RCC_OscConfig函数:


OscillatorType 这个成员变量是选择时钟时钟源的,可以看一下他的取值范围:取值范围小于15


其实在HAL库有对每一个时钟源进行宏定义的,如下:



在程序中,我们设置这个成员变量的值是:RCC_OSCILLATORTYPE_HSE ,就是值为:0x00000001


HSEState 是选择时钟的工作方式,此处选择:RCC_HSE_ON,值为:0x01


在HAL_RCC_OscConfig,函数中对HSE的配置如下,贴出一部分代码:


/* Check the parameters */

  assert_param(IS_RCC_OSCILLATORTYPE(RCC_OscInitStruct->OscillatorType));

  /*------------------------------- HSE Configuration ------------------------*/ 

  if(((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSE) == RCC_OSCILLATORTYPE_HSE)

  {

    /* Check the parameters */

    assert_param(IS_RCC_HSE(RCC_OscInitStruct->HSEState));

    /* When the HSE is used as system clock or clock source for PLL in these cases HSE will not disabled */

  //判断HSE是否作为系统时钟,或者作为PLL时钟的来源

  

  

// RCC_CFGR_SWS_HSE=0x00000004

//__HAL_RCC_GET_SYSCLK_SOURCE函数,获取CFGR位 3:2 SWS: 系统时钟切换状态 (System clock switch status)

  //判断3:2位是否为01,含义:01: HSE 振荡器用作系统时钟,这两个位为只读

  //也就是判断此时系统时钟或者主PLL时钟是否已经设置为HSE

  

  //RCC_CFGR_SWS_PLL=0x00000008 ,判断CFGR的第3为是否为1

    if((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_HSE)                                                                     ||

      ((__HAL_RCC_GET_SYSCLK_SOURCE() == RCC_CFGR_SWS_PLL) && ((RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) == RCC_PLLCFGR_PLLSRC_HSE)))

    {   //PLL作为系统时钟,  RCC_PLLCFGR_PLLSRC=0x00400000,第22位,是否选择:1:选择 HSE 振荡器时钟作为 PLL 和 PLLI2S 时钟输入

//如果HSE作为系统时钟来源,或者作为PLL时钟来源的话

      if((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) && (RCC_OscInitStruct->HSEState == RCC_HSE_OFF))

      {

  //此时HSE已经打开了,或HSE没有使能,这里的任何一种情况都会导致失败

        return HAL_ERROR;

      }

    }

    else  //否则的话,系统的时钟还没有进行初始化

    {

      /* Reset HSEON and HSEBYP bits before configuring the HSE --------------*/

[1] [2] [3]
关键字:STM32  时钟系统  配置 引用地址:STM32时钟系统以及配置及源码分析

上一篇:单片机入门学习八 STM32单片机学习五 时钟系统
下一篇:STM32学习---位带操作总结

推荐阅读最新更新时间:2024-11-23 13:40

STM32单片机常用库函数
1.GPIO初始化函数 用法: voidGPIO_Configuration(void) { GPIO_InitTypeDefGPIO_InitStructure;//GPIO状态恢复默认参数 GPIO_InitStructure.GPIO_Pin=GPIO_Pin_标号|GPIO_Pin_标号; //管脚位置定义,标号可以是NONE、ALL、0至15。 GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;//最高输出速度为50MHz GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//推挽输出 GPIO_Init(GPIOC,&GPIO_I
[单片机]
STM32定时计算详解
STM32之定时器 时间=次数x1/频率 ((1+TIM_Prescaler )/72M)*(1+TIM_Period )=((1+7199)/72M)*(1+9999)=1秒 一、定时器简介 1、时钟来源 2、定时器结构(以基本定时器为例) 二、基本定时器的编程方法 1、基本定时器的寄存器 2、例程 /** * @brief 定时器6的初始化,定时周期0.01s * @param 无 * @retval 无 */ void TIM6_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; /*AHB = 72MHz,R
[单片机]
<font color='red'>STM32</font>定时计算详解
stm32 GPIO口配置操作
stm32里面最基本的思路就是使用外设相应寄存器之前,必须开启控制对应寄存器的时钟,读者可到技术手册中查询相应的时钟控制的相应的寄存器。 这里首先开启stm32普通io口的时钟。 GPIO 作为通用输入输出口使用时,当有外部中断设置时才需要开启AFIO时钟,否则不需要开启AFIO 时钟。 然后就是进行gpio结构体的初始化设置 GPIO 常用设置里包括三个结构体的使用如下: 1、GPIO_InitTypeDef为GPIO的基本参数设置结构体,其中GPIO_Pin表示引脚号,GPIO_Speed表示引脚的速度,GPIO_Mode表示引脚的输入输出模式选择。通过这三个基本设置实现了
[单片机]
STM32基础知识:串口通信-DMA方式
1 DMA概述 直接存储器访问 (DMA) : 用于在外设与存储器之间以及存储器与存储器之间进行高速数据传输。DMA传输过程的初始化和启动由CPU完成,传输过程由DMA控制器来执行,无需CPU参与,从而节省CPU资源,提高利用率。 DMA数据传输的四个要素: 传输源 :DMA数据传输的来源 传输目标:DMA数据传输的目的 传输数量:DMA传输数据的数量 触发信号:启动一次DMA数据传输的动作 STM32的DMA控制器特点 每个DMA控制器有8个数据流,每个数据流可以映射到8个通道(或请求); 每一个DMA控制器用于管理一个或多个外设的存储器访问请求,并通过总线仲裁器来协调各个DMA请求的优先级; 数据流(st
[单片机]
<font color='red'>STM32</font>基础知识:串口通信-DMA方式
STM32库函数编程学习心得:assert_param的应用
在STM32的固件库和提供的例程中,到处都可以见到assert_param()的使用。如果打开任何一个例程中的stm32f10x_conf.h文件,就可以看到实际上assert_param是一个宏定义;在固件库中,它的作用就是检测传递给函数的参数是否是有效的参数。 所谓有效的参数是指满足规定范围的参数,比如某个参数的取值范围只能是小于3的正整数,如果给出的参数大于3,则这个assert_param()可以在运行的程序调用到这个函数时报告错误,使程序员可以及时发现错误,而不必等到程序运行结果的错误而大费周折。 这是一种常见的软件技术,可以在调试阶段帮助程序员快速地排除那些明显的错误。 它确实在程序的运行上牺牲了效率
[单片机]
stm32 SPI通信 操作寄存器
SPI(Serial Peripheral Interface--串行外设接口) 总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息。 SPI是Freescale(原 Motorola)公司首先在其处理器上定义的。 SPI是一种高速、主从式、全双工、同步传输的通信总线,SPI总线在物理层体现为四根传输线: MOSI (Master Output Slaver Input) – 主器件数据输出,从器件数据输入 MISO (Master Input Slaver Output) – 主器件数据输入,从器件数据输出 SCLK – 时钟信号,由主器件产生 NSS – 从器件使能信号,由主器
[单片机]
stm32关闭中断
我只试了一下 NVIC_SETFAULTMASK(); //关闭总中断 NVIC_RESETFAULTMASK();//开放总中断 可以开、关中断 但是汇编 asm( CPSID I ); //关中断 asm( CPSIE I ); //开中断 编译过不了 我使用的是KEIL 3.50 今天找答案的时候找到这里的,发现还有自己的回复,还有自己的一个问题,下面把答案给附上: 项目中包含 STM32F10x_StdPeriph_Lib_V3.1.2\Libraries\CMSIS\Core\CM3\core_cm3.h STM32F10x_StdPeriph_Lib_V3.1.2\Libraries\CMSI
[单片机]
图解如何安装配置DHCP服务器
点"激活"就OK了! 再来就是通过"开始"选择"控制面板"|"添加或删除程序"也可以安装DHCP。 在DHCP服务安装成功后,会生存5个文件(J50.log、J50.chk、Dhcp.tmp、Dhcp.mdb、J50#####.log)其中只有J50.chk可被删除。 随着时间的流逝,有些DHCP客户机记录记录过时并被删除,某些未用空间却保持下来了。要恢复未用空间,必须压缩DHCP数据库。 手工压缩之前,DHCP服务器必须脱机。 先用在DOS下,输入“cd %SystemRoot%\SYSTEM32\DHCP”进入DHCP目录; 再输入net stop dhcpserver命令停止D
[模拟电子]
图解如何安装<font color='red'>配置</font>DHCP服务器
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved