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

news/2025/2/22 9:45:24/

 前言:

         比起之前用过的三星的猎户座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;			//打开主PLLwhile((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);	//串口初始化为115200while(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);//设置时钟,168Mhzu16 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);	//串口初始化为115200while(1){}
}


http://www.ppmy.cn/news/1277357.html

相关文章

scrapy的入门和使用

scrapy的入门使用 学习目标&#xff1a; 掌握 scrapy的安装应用 创建scrapy的项目应用 创建scrapy爬虫应用 运行scrapy爬虫应用 scrapy定位以及提取数据或属性值的方法掌握 response响应对象的常用属性 1 安装scrapy 命令:     sudo apt-get install scrapy 或者&#x…

数组深入学习感悟

注&#xff1a;本文学习借鉴于《代码随想录》 一.介绍数组 数组是储存在连续内存空间中的相同类型数据的集合 数组名的理解&#xff1a; 数组名就是数组⾸元素(第⼀个元素)的地址是对的&#xff0c;但是有两个例外&#xff1a; sizeof(数组名)&#xff0c;sizeof中单独放数…

基于VUE3+Layui从头搭建通用后台管理系统(前端篇)十五:基础数据模块相关功能实现

一、本章内容 本章使用已实现的公共组件实现系统管理中的基础数据中的验证码管理、消息管理等功能。 1. 详细课程地址: 待发布 2. 源码下载地址: 待发布 二、界面预览 三、开发视频 3.1 B站视频地址: 基于VUE3+Layui从头搭建通用后台管理系统合集-验证码功能实现 3.2 西瓜…

Axure之中继器的使用(交互动作reperter属性Item属性)

目录 一.中继器的基本使用 二.中继器的动作&#xff08;增删改查&#xff09; 2.1 新增 2.2 删除 2.3 更新行 2.4 效果展示 2.5 模糊查询 三.reperter属性 在Axure中&#xff0c;中继器&#xff08;Repeater&#xff09;是一种功能强大的组件&#xff0c;用于创建重复…

Python中abstractmethod的使用教程

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python中&#xff0c;抽象类和抽象方法提供了一种强制子类实现特定方法的机制。abstractmethod是abc&#xff08;Abstract Base Classes&#xff09;模块中的一部分&#xff0c;它允许定义抽象方法&#xff0c…

Spring Boot入门指南

本文为官方文档直译版本。原文链接 Spring Boot入门指南 引言Spring Boot 简介系统要求Servlet 容器GraalVM 原生镜像 安装 Spring BootJava 开发人员安装说明安装 Maven安装 Gradle 安装 Spring Boot CLI手动安装使用 SDKMAN 安装&#xff01;使用 OSX Homebrew 安装使用 MacP…

esp32-s3解决使用蓝牙ble一键配网时,蓝牙ble内存使用的内部空间,空间不足时可采用外部PSRAM

idf.py menuconfig进入到esp32配置界面&#xff0c;配置NimBLE使用外部PSRAM内存即可

从零学算法5

5.给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同&#xff0c;则该字符串称为回文字符串。 示例 1&#xff1a; 输入&#xff1a;s “babad” 输出&#xff1a;“bab” 解释&#xff1a;“aba” 同样是符合题意的答案。 示例 2&…