4.配置系统时钟思路及方法

前言:

比起之前用过的三星的猎户座4412芯片,STM32F4的系统时钟可以说是小巫见大巫,首先我们需要清晰时钟产生的原理:**几乎大多数的芯片都是由晶振产生一个比较低频的频率,然后通过若干个PLL得到单片机能承受的频率(作为主频),再通过其他手段将PLL出来的频率降频分给其他外设使用。**一个时钟树一般先对复杂,我们先调出主频(及编程好时钟源、PLL倍频这一部分)其他的之后再说,如此编程才不会太复杂。

实际上这个一般厂家会给一个配置文件的,但是如果要自己做些超频之类的操作,就要彻底掌握时钟树的配置了,见人见智,追求技术的这个内容是逃不掉的。

编程思路:1.PLL倍频因子配置 2..PLL时钟源激活和切换(上电后单片机会选择一个默认的时钟源,可能是晶振也可能是内部RC电路产生的频率) 3.切换系统时钟

时钟资源概览:

下面先看一下我们这个F4的系统时钟资源,查看手册可知系统复位后是默认选择HSI这个内部RC电路产生的时钟作为这个单片机的系统时钟,但是我们要的是PLL产生的时钟。

下面看手册PLL配置的说明(可以把时钟树截图出来作参考,不过主要编程还是靠手册的文字描述),可知RCC_PLLCFGR 可以用来配置PLL(PLLI2S可以先不管,先搞出主频再说),那就配置它吧。寄存器就不放出来了,自己看手册,这里给出寄存器各个位的配置值及解释

RCC_PLLCFGR寄存器配置


可见:
PLL = VCO / PLLP
VCO = PLL时钟源*(PLLN / PLLM)
一共涉及P / N / M 三个因子,以及PLL时钟源。
PLL就是我们要选的系统时钟,PLL = 168M,如果PLLP选的是2,那么VCO就得是168*2 = 336 。VCO是336,我们的PLL时钟源如果选的是外部晶振(探索者这个开发板上晶振是8M),那么PLLN/PLLM就得等于 VCO / PLL时钟源 = 336 / 8 = 42 ,所以PLLN除以PLLM必须是等于42,PLLN配置为336(可以在192和432这个数值间任意取),则PLLM配置为 336 / 42 = 8 .分频因子就搞定了。
下面开始配置 RCC_PLLCFGR
bit[5:0]: 设置PLLM为8,即0x8<<0
bit[14:6]: 设置PLLN为336,即336<<6
bit[17:16]:设置PLLP为2,即0<<16
bit[22]: 设置PLL和PLLI2S时钟源,设为1<<22,选择HES晶振
bit[27:24]:设置USB OTG之类的时钟,随便设一个,0x7<<24
其他使用默认值,编程如下:

复制代码
RCC->PLLCFGR = 0x24003010 ;//复位值
RCC->PLLCFGR = 0x7<<24 | 1<<22 | 0<<16 | 336<<6 | 0x8<<0 ;

这样PLL就配置好了,PLL的时钟源我们选择的是HES,它还没有激活,所以 在PLL配置这一步之前,还需要将HSE激活

RCC_CR寄存器配置

查看手册HSE部分,可知要换时钟源要操作RCC_CR寄存器,时钟中断我们不需要。

同样,配置寄存器RCC_CR:(真真苦力活~)
bit[0]: HSI的开关,这个呢暂时还不能关闭,要让HSE生效之后才能关闭,不然单片机一个时钟都没有没法工作。
bit[1]: HSI的状态 1是ok,0是不ok
bit[16]: HSE的开关,置为1<<16,打开
bit[17]: HSE的状态,它就绪之后才能配置PLL,配置完PLL才能切换系统时钟(HSI时钟---->>PLL时钟)
bit[18]: HSE时钟旁路,这个要关掉,因为我们要用的是HSE晶振,配置为0<<18
bit[24]: PLL的开关,这个还没配置好之前要关掉
bit[25]:PLL是否稳定的标志
其他不管,RCC->CR复位时的默认值是 让HSI正常工作的,其他都是0,所以不改动它原有的,在它原有的值基础上进行幅值
1.配置使得HSE开始工作:

复制代码
RCC->CR | = 1<<16;
u16 retry=0;//这个只是提供短暂延时的变量
while(((RCC->CR&(1<<17))==0)&&(retry<0X1FFF))retry++;//跳出循环后说明HSE ok了
if(retry==0X1FFF)status=1;    //当然如果超过了一定时间也会跳出,表示HSE无法就绪

2.打开PLL并等待其稳定

复制代码
RCC->CR|=1<<24;			//打开主PLL
while((RCC->CR&(1<<25))==0);//等待PLL准备好 

使PLL倍频出很高的频率

有了上面的东西,我们就可以使PLL倍频出很高的频率了,结合上面两个寄存器:

复制代码
RCC->PLLCFGR = 0x7<<24 | 1<<22 | 0<<16 | 336<<6 | 0x8<<0 ;//配置PLL倍频因子

RCC->CR | = 1<<16;//激活HSE晶振
u16 retry=0;//这个只是提供短暂延时的变量
while(((RCC->CR&(1<<17))==0)&&(retry<0X1FFF))retry++;//跳出循环后说明HSE ok了
if(retry==0X1FFF)status=1; 

else{//激活HSE完成了

		RCC->CR|=1<<24;			//打开主PLL
		while((RCC->CR&(1<<25))==0);//PLL稳定


}

现在PLL理论上已经有了晶振倍频后的频率了,下面切换PLL作为系统时钟:

切换PLL作为系统时钟

查看手册,知道RCC_CFGR是管这个事的:再一波嘎嘎配置

bit[1:0]:0x2<<0 切换PLL作为系统时钟

bit[3:2]:这两个位可以读出是否切换完成,如果读出来是0x2就是切换成PLL成功

bit[7:4]:这四个位是配置AHB分频的,我记得是不分频的,设为0000 即0x0<<4

bit[12:10]: 这三个位是配置APB1分频的 设为4分频,即0x5<<10

bit[15:13]:这三个位是配置APB2分频的 设为2分频,即0x4<<13

bit[20:16]:这五个位是配置RTC分频的 可以先随便设一个设为HSE/2,即0x2<<16

其他不用管

复制代码
RCC_CFGR = 0;//清零

RCC_CFGR = 0x2<<16 | 0x4<<13 | 0x5<<10 | 0x0<<4 | 0x2<<0 ;//切换PLL为系统时钟并且设置其他分频

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

这样,综合上面所有的代码就是:

复制代码
    u16 retry=0;//这个只是提供短暂延时的变量
    u8 status=0;   
    
//按照上面的分析思路,编程流程就是:

//1.配置PLL倍频因子
    RCC->PLLCFGR = 0x7<<24 | 1<<22 |0<<16 |336<<6 |0x8<<0 ;

//2.激活HSE晶振
    RCC->CR |= 1<<16;
    while(((RCC->CR&(1<<17))==0)&&(retry<0X1FFF))retry++;//跳出循环后说明HSE ok了

    if(retry==0X1FFF)status=1;
    else{
    //激活HSE完成了
     
//3.打开PLL等待PLL输出稳定
     RCC->CR|=1<<24;			
     while((RCC->CR&(1<<25))==0);

//4.切换PLL输出为系统时钟
     RCC->CFGR = 0;//清零
     RCC->CFGR =0x2<<16 |0x4<<13 | 0x5<<10 | 0x0<<4 |0x2<<0;//切换PLL为系统时钟并且设置其他分频
     while((RCC->CFGR&(3<<2))!=(2<<2));//等待主PLL作为系统时钟成功.现在主频是168M了
    }

验证测试:

下面可以用串口来打印,验证是不是设置完成。

可见是ok了的~说明上面的系统时钟配置没问题。main函数

疑难杂症:

如果你的整个main函数是这样的:是配置完时钟后也是没法正常工作的

复制代码
#include "sys.h"
#include "usart.h" 
#include "delay.h" 

u16 myconut;
//systick中断服务函数,使用OS时用到
void SysTick_Handler(void)
{	
    myconut++;
    if(myconut>=1000){
        myconut=0;
        printf("hello\r\n");
    }

}

int main(void)
{ 
	u8 t=0;

    
    u16 retry=0;//这个只是提供短暂延时的变量
    u8 status=0;
    

    
//按照上面的分析思路,编程流程就是:

//1.配置PLL倍频因子
    RCC->PLLCFGR = 0x7<<24 | 1<<22 |0<<16 |336<<6 |0x8<<0 ;

//2.激活HSE晶振
    RCC->CR |= 1<<16;
    while(((RCC->CR&(1<<17))==0)&&(retry<0X1FFF))retry++;//跳出循环后说明HSE ok了

    if(retry==0X1FFF)status=1;
    else{
    //激活HSE完成了
     
//3.打开PLL等待PLL输出稳定
     RCC->CR|=1<<24;			
     while((RCC->CR&(1<<25))==0);

//4.切换PLL输出为系统时钟
     RCC->CFGR = 0;//清零
     RCC->CFGR =0x2<<16 |0x4<<13 | 0x5<<10 | 0x0<<4 |0x2<<0;//切换PLL为系统时钟并且设置其他分频
     while((RCC->CFGR&(3<<2))!=(2<<2));//等待主PLL作为系统时钟成功.现在主频是168M了
    }


	delay_init(168);		//初始化延时函数
    
    NVIC_SetPriorityGrouping(2);
    SysTick_Config(168000);//1ms中断一次
    NVIC_EnableIRQ(SysTick_IRQn);
    
	uart_init(84,115200);	//串口初始化为115200
	while(1)
	{

	}
}

原因是缺少了这样几行关于CPU的代码:将它加在时钟配置代码的上方即可正常运行了

复制代码
    	FLASH->ACR|=1<<8;		//指令预取使能.
		FLASH->ACR|=1<<9;		//指令cache使能.
		FLASH->ACR|=1<<10;		//数据cache使能.
		FLASH->ACR|=5<<0;		//5个CPU等待周期. 

正点原子是把它放在时钟配置里的,我也不知道为啥,但是我觉得它和时钟配置是没什么关系的,应该是另一部分的知识。正点原子时钟配置中还有这样两句关于电源的代码,我实测去掉也是可以的,不过应该还是加上比较好,但是时钟配置的部分手册没有提到,我也就没有在上面说,以免它出现的很突兀。同样要加的话加在时钟配置代码之前即可。

复制代码
		RCC->APB1ENR|=1<<28;	//电源接口时钟使能
		PWR->CR|=3<<14; 		//高性能模式,时钟可到168Mhz

完事了~系统时钟就是这样配置啦,这个算是简单的,像能跑linux的那种芯片,就得依靠厂家给的来写或者修改了,自己写的总有不到位的地方~

整个main.c代码如下:

复制代码
#include "sys.h"
#include "usart.h" 
#include "delay.h" 
//ALIENTEK 探索者STM32F407开发板 实验0
//新建工程实验  
//技术支持:www.openedv.com
//广州市星翼电子科技有限公司
u16 myconut;
//systick中断服务函数,使用OS时用到
void SysTick_Handler(void)
{	
    myconut++;
    if(myconut>=1000){
        myconut=0;
        printf("hello\r\n");
    }

}

int main(void)
{ 
	u8 t=0;
    //plln,pllm,pllp,pllq
	//Stm32_Clock_Init(336,8,2,7);//设置时钟,168Mhz
    
    
    u16 retry=0;//这个只是提供短暂延时的变量
    u8 status=0;
    
    //CPU相关的初始化
    FLASH->ACR|=1<<8;		//指令预取使能.
    FLASH->ACR|=1<<9;		//指令cache使能.
    FLASH->ACR|=1<<10;		//数据cache使能.
    FLASH->ACR|=5<<0;		//5个CPU等待周期. 
    //电源相关的初始化
    RCC->APB1ENR|=1<<28;	//电源接口时钟使能
    PWR->CR|=3<<14; 		//高性能模式,时钟可到168Mhz
    
//按照博客的分析思路,系统时钟配置的编程流程就是:

//1.配置PLL倍频因子
    RCC->PLLCFGR = 0x7<<24 | 1<<22 |0<<16 |336<<6 |0x8<<0 ;

//2.激活HSE晶振
    RCC->CR |= 1<<16;
    while(((RCC->CR&(1<<17))==0)&&(retry<0X1FFF))retry++;//跳出循环后说明HSE ok了

    if(retry==0X1FFF)status=1;
    else{
    //激活HSE完成了
     
//3.打开PLL等待PLL输出稳定
     RCC->CR|=1<<24;			
     while((RCC->CR&(1<<25))==0);

//4.切换PLL输出为系统时钟
     RCC->CFGR = 0;//清零
     RCC->CFGR =0x2<<16 |0x4<<13 | 0x5<<10 | 0x0<<4 |0x2<<0;//切换PLL为系统时钟并且设置其他分频
     while((RCC->CFGR&(3<<2))!=(2<<2));//等待主PLL作为系统时钟成功.现在主频是168M了
    }


	delay_init(168);		//初始化延时函数
    
    NVIC_SetPriorityGrouping(2);
    SysTick_Config(168000);//1ms中断一次
    NVIC_EnableIRQ(SysTick_IRQn);
    
	uart_init(84,115200);	//串口初始化为115200
	while(1)
	{

	}
}
相关推荐
三佛科技-134163842125 小时前
SM2850P无电感离线稳压器 5V输出 典型应用电路分析(管脚、关键设计要点)
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
dqsh066 小时前
关于STM32G474芯片有规律的自动重启的问题
stm32·单片机·嵌入式硬件·系统重启·原因解析
时空自由民.7 小时前
BLDC无刷直流电机作为发电机的波形图
单片机
JSMSEMI117 小时前
JSM63006 5A 28V三相无刷电机驱动电路
单片机·嵌入式硬件
国产芯片设计7 小时前
【LCD驱动实战】单颗YL1621脚位不足?双芯片联动驱动方案详解
stm32·单片机·mcu·51单片机·硬件工程
bubiyoushang8889 小时前
基于 Freescale S12 单片机的 Bootloader 开发
单片机·嵌入式硬件·mongodb
笨笨小乌龟1110 小时前
单片机的半主机模式与 MicroLib 机制(Keil UseMicroLIB)
stm32·单片机·嵌入式硬件
foundbug99914 小时前
STM32 + SHT20 温湿度测试 TFT 显示方案
stm32·单片机·嵌入式硬件
别了,李亚普诺夫14 小时前
MAX30102模块原理及代码实现
单片机·嵌入式
星夜夏空9914 小时前
STM32单片机学习(3)——前置知识学习
stm32·单片机·学习