1.什么是时钟对单片机有什么作用
时钟是由电路产生的具有周期性的脉冲信号,相当于单片机的心脏,要想使用单片机的外设必须开启相应的时钟,驱动外设的本质是操作寄存器,而寄存器是由D触发器构成,而触发器需要时钟才能改写值,所以要想操作寄存器必须开启对应外设的时钟
。对CPU来说假设CPU在一个时钟周期内执行一条指令(二进制代码),若时钟频率越高,而时钟等于1/f为频率的倒数,则时钟周期更短则在相同的时间CPU能够执行更多的指,CPU的运行速度更快,文章的后面会有STM32超频实验,让灯闪烁的更快。(后面会详细讲解实验原理)
2.为什么要有时钟树
STM32时钟系统主要的目的就是给相对独立的外设模块提供时钟,主要也是为了降低整个芯片的功耗,所有外设时钟默认都是关闭状态(disable)当我们使用某个外设就要开启这个外设的时钟(enable)
,不同外设需要的时钟频率不同,没必要所有外设都用高速时钟造成浪费,而且有些外设也接受不了这么高的频率,这也是为什么STM32有四个时钟源的原因,就是兼容不同速度的外设,STM32的四个时钟源分别为:HSE、 LSE、HSI、LSI
先粗略看一下时钟图,接下来就是分时钟源一 一详细讲解(重点),这里以原子的STM32f10RCT6mini板子为例
1.来源:
2.控制
通常的配置是SYSCLK=PLLCLK=72M,配置流程,这里也非常重要后面实验就按照这个过程写代码
监视高速外部时钟HSE的工作状态。若HSE失效,会自动切换高速内部时钟HSI作为系统时钟的输入,保证系统的正常运行。一旦CSS被激活,并且HSE时钟出现故障,CSS中断就产生,并且NMI也自动产生。NMI将被不
断执行,直到CSS中断挂起位被清除。
引脚
,由PA8复用所得。可以把时钟信号输出供外部使用,也可以用示波器检测时钟信号的参数(峰峰值,频率…)STM32单片机上电,就会执行启动文件(汇编代码编写)中的复位程序。
执行复位程序
1.调用SystemInit系统初始化函数完成系统时钟的配置(配置成72MHZ)
2.调用_main函数初始化堆栈指针,然后再调用C库函数main函数,去到C语言的世界
所以跳转到C语言的main函数时,已经完成了系统时钟(SYSCLK)的配置。
以芯片型号: STM32F10X_HD为例
这里将系统时钟配置成官方推荐的72MHZ。
步骤:
由外部晶振(HSE)提供的8MHz通过PLLXTPRE分频器不分频,输入到PLLSRC,然后通过PLLMUL锁相环进行9倍频后输出PLLCLK时钟(72MHZ),然后通过系统时钟切换SW 将PLLCLK作为系统时钟(72MHZ)。然后通过AHB预分频器进行1分频得到AHB总线时钟(HCLK),然后再分别经过APB1,APB2预分频器分别2分频,1分频,给APB1(PCLK1)APB2(PCLK2)总线提供时钟,进而给分别挂载在APB1,APB2总线的外设提供时钟
配合图来理解:
还有对条件编译不熟的一定要看->预处理指令不然怎么调用的函数都不知道
重点来了
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/
/* Enable HSE :使能 HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
//等待HSE就绪并做超时处理
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
//启动成功执行下一步的代码
if (HSEStatus == (uint32_t)0x01)
{
/*使能预取指 cpu在FLASH取代码*/
FLASH->ACR |= FLASH_ACR_PRFTBE;
/* Flash 2 wait state :设置成两个等待周期 */
FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
/* HCLK = SYSCLK =72M */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK2 = HCLK =72M */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
/* PCLK1 = HCLK =36M */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; //二分频
/* 锁相环配置: PLLCLK = HSE * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL));
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
/* Enable PLL :使能PLL*/
RCC->CR |= RCC_CR_PLLON;
/* Wait till PLL is ready :等待PLL稳定*/
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
/* Select PLL as system clock source :选择PLL作为系统时钟*/
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* Wait till PLL is used as system clock source :等待PLLCLK切换为系统时钟*/
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong clock
configuration. User can add here some code to deal with this error */
//如果HSE 启动失败,用户可以在这里添加处理错误的代码
}
}
老样子把一些if语句折叠看看函数的整体框架:
如果HSE启动成功就执行折叠的if语句的内容
这里由于空间有限实在是不好一个一个去分析寄存器操作,我自己已经一个一个宏跳转然后对应着寄存器看,建议可以一条一条代码对着手册看,看看是不是真的是操作的寄存器的相对应的位
到这里就把系统时钟配置完了,还有总线AHB,APB1,APB2的时钟也配置完成,这些总线上挂载的外设的时钟,要使用哪个外设就开启哪个外设的时钟
这些寄存器的相应位就是对应挂载在总线上的外设时钟使能位。
#include "rccclkconfig.h"
void HSE_System_Config( uint32_t RCC_PLLMul_x)
{
ErrorStatus HSEStatus;
/* 把RCC寄存器配置成复位值 */
RCC_DeInit();
/* 使能SHE */
RCC_HSEConfig(RCC_HSE_ON);
HSEStatus= RCC_WaitForHSEStartUp();
/* 判断SHE启动是否成功 */
if ( HSEStatus ==SUCCESS )
{
/*使能预取指 cpu在FLASH取代码*/
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
/* 设置成两个等待周期 */
FLASH_SetLatency(FLASH_Latency_2);
/* HCLK = SYSCLK =72M */
RCC_HCLKConfig(RCC_SYSCLK_Div1);
/* PCLK2 = HCLK =72M */
RCC_PCLK1Config(RCC_HCLK_Div2);
/* PCLK1 = HCLK =36M */
RCC_PCLK2Config(RCC_HCLK_Div1);
/* 锁相环配置: PLLCLK = HSE * x(2-16) = 8*x MHz */
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_x);
/* 使能PLL*/
RCC_PLLCmd(ENABLE);
/* 等待PLL稳定 */
while( RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
/* 选择pLL作为系统时钟 */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
/* 等待PLLCLK切换为系统时钟 */
while( RCC_GetSYSCLKSource() != 0x08) ;
}
else
{
/* If HSE fails to start-up, the application will have wrong clock
configuration. User can add here some code to deal with this error */
}
}
代码已有详细注释是用库函数编写的,不过基本与前面寄存器版本的一致,只不过把他们封装成了函数,这里要注意的一点是配置前一定要调用一个 RCC_DeInit();函数把RCC寄存器配置成复位值 。
原理:对CPU来说假设CPU在一个时钟周期内执行一条指令(二进制代码),若时钟频率越高,而时钟等于1/f为频率的倒数,则时钟周期更短则在相同的时间CPU能够执行更多的指,CPU的执行代码速度更快,延时的时间就会变短,灯就会闪烁的更快。
#define SOFT_DELAY Delay(0x0FFFFF);
void Delay(__IO u32 nCount);
int main(void)
{
HSE_System_Config( RCC_PLLMul_2);
/* LED 端口初始化 */
LED_GPIO_Config();
while(1)
{
LED_G(NO);
LED_R(OFF);
Delay(0x0FFFFF);
LED_G(OFF);
LED_R(NO);
Delay(0x0FFFFF);
}
}
void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
这里为了有更明显的实验效果,先让PLLMUL锁相环进行2倍频后输出PLLCLK时钟(16MHZ),然后通过系统时钟切换SW将PLLCLK作为系统时钟(16MHZ)。
然后让PLLMUL锁相环进行16倍频后输出PLLCLK时钟(128MHZ),然后通过系统时钟切换SW将PLLCLK作为系统时钟(128MHZ)。
PLLMUL锁相环二倍频 SYSCLK(16MHZ)实验效果:
PLLMUL锁相环二倍频 SYSCLK(128MHZ)实验效果:
是不是直接起飞,很糊凑合看叭我不会上传视频唉
总而言之时钟树非常重要一定要掌握!!!最好可以自己写一个库函数版的系统时钟初始化函数,好啦本文到这就结束啦,如果本文对你有帮助就赶快收藏点赞叭!!!。