前言
什么是系统时钟,系统时钟我们可以理解为人的心脏,没有系统时钟就没有,单片机就正常无法运行。
一.系统时钟原理
1.系统时钟框图
下面来分析各个元素
1.最左边
OSC_OUT和OCS_IN是两个外部引脚,用来接晶振。根据规格书我们知道可以接4-16mhz的晶振。
mco:STM32可以选择一个时钟信号输出到MCO脚(PA8)上,可以选择为PLL 输出的2分频、HSI、HSE、或者系统时钟,可以用来验证系统时钟配置是否正确。
OSC32_OUT和OCS32_IN引脚是STM32单片机上的外部32.768 kHz低速晶体振荡器的输入和输出引脚。这些引脚用于连接外部32.768 kHz晶体振荡器,以提供低功耗时钟信号给单片机。适用于需要长时间运行、低功耗和精确时间计量的应用场景。
2.stm32的5个时钟源
1. STM32 有5个时钟源:HSI、HSE、LSI、LSE、PLL。
①、HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。
②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围4MHz~16MHz。 ③、LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。WDG
④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。RTC
⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。
系统时钟SYSCLK可来源于三个时钟源:HSI振荡器时钟、HSE振荡器时钟、PLL时钟。
3.几个重要的时钟
这些时钟都是通过时钟源、倍频器和预分频器的各种配置而得到的:
SYSCLK(系统时钟)
AHB(advanced high performance bus,高级高性能总线)总线时钟
APB1(advanced peripheral bus,高级外围设备总线)总线时钟(低速):速度最高为36MHz
APB2总线时钟(高速):速度最高为72MHz
PLL时钟
以及还有一些未提及的外设时钟(最右侧),这些外设的时钟要根据外设的需求来进行配置。
4.预分频器(为绿色)
分频器的作用是:系统时钟先经过固定的分频系数后产生相应频率的时钟,提供给单片机定时器的计时输入。
5.时钟安全系统CSS
如果外部晶振出了问题(例如被短路),系统时钟(SYSCLK)就会崩溃。因此,STM32时钟系统设置了CSS时钟安全系统,一旦HSE失效,则自动切换至SYSCLK = HSI,意味着系统用内部RC振荡器作为时钟源(对照时钟树的路线看看是怎么进行切换的!)。当然RC振荡器并不是十分精确的,当外部晶振恢复正常后,我们还是希望系统切换回外部晶振。
2.选择其中一条线
OSC_IN (8MHz) –> HSE_OSC –> 选择器 –> 选择器 –> PLL (8MHz X 9 = 72MHz) –> 作为PLLCLK –> 选择器 –> 作为SYSCLK –> AHB预分频器 –> 作为HCLK(advanced high performance bus clock,高级高性能总线时钟)
3.RCC相关定时器函数及其定义
/**
* @brief Reset and Clock Control
*/
typedef struct
{
__IO uint32_t CR; //HSI,HSE,CSS,PLL等的使能和就绪标志位
__IO uint32_t CFGR; //PLL等的时钟源选择,分频系数设定
__IO uint32_t CIR; // 清除/使能 时钟就绪中断
__IO uint32_t APB2RSTR; //APB2线上外设复位寄存器
__IO uint32_t APB1RSTR; //APB1线上外设复位寄存器
__IO uint32_t AHBENR; //DMA,SDIO等时钟使能
__IO uint32_t APB2ENR; //APB2线上外设时钟使能
__IO uint32_t APB1ENR; //APB1线上外设时钟使能
__IO uint32_t BDCR; //备份域控制寄存器
__IO uint32_t CSR; //控制状态寄存器
#ifdef STM32F10X_CL
__IO uint32_t AHBRSTR;
__IO uint32_t CFGR2;
#endif /* STM32F10X_CL */
#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)
uint32_t RESERVED0;
__IO uint32_t CFGR2;
#endif /* STM32F10X_LD_VL || STM32F10X_MD_VL || STM32F10X_HD_VL */
} RCC_TypeDef;
需要注意,STM32F10X_CL
这些都属于互联型,编写者为了兼容更多机型而采用了条件编译的办法。我们使用的是大容量(STM32F10X_HD
),因此有条件编译的语句只看STM32F10X_HD
的即可。在库函数中经常涉及这种情况。
二.系统初始化的时钟配置
不知道大家有没有发现一个问题,明明没有初始化过时钟,但是只要打开引脚相应的时钟,等和蜂鸣器就会正常工作,原因是因为系统在启动时会优先执行SystemInit,然后才会进进入main函数
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
1.首先看一下SystemInit()函数
我们可以看到在相关文件的开头定义了宏定义,那么在默认模式使用的是72MHZ。
void SystemInit (void)
{
//首先设置开启内部8MHz振荡器
RCC->CR |= (uint32_t)0x00000001;
SetSysClock();
}/**
2.SetSysClock()
由于我们把系统时钟频率通过宏定义设置为72MHz,所以这里直接进入最下面的程序即SetSysClockTo72(),
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
SetSysClockTo56();
#elif defined SYSCLK_FREQ_72MHz
SetSysClockTo72();
#endif
/* If none of the define above is enabled, the HSI is used as System clock
source (default after reset) */
}
3.SetSysClockTo72()
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* SYSCLK, HCLK, PCLK2 and PCLK1 配置---------------------------*/
/* 使能 HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);//RCC_CR寄存器第16位置1,开启HSE
/* 等待HSE就绪 超时退出 */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY; //读取RCC_CR第17位
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
if ((RCC->CR & RCC_CR_HSERDY) != RESET) //读取RCC_CR第17位
{
HSEStatus = (uint32_t)0x01; //HSE状态写1
}
else
{
HSEStatus = (uint32_t)0x00; //HSE状态写0
}
if (HSEStatus == (uint32_t)0x01) //FLASH寄存器部分可以不用管
{
/* 使能 Prefetch Buffer */
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 */
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1; //RCC_CFGR寄存器第4~7位写0,即AHB不分频,即HCLK使用72MHz时钟
/* PCLK2 = HCLK */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; //RCC_CFGR寄存器第11~13位写0,APB2不分频,即PCLK2使用72MHz时钟
/* PCLK1 = HCLK/2 */
RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; //RCC_CFGR寄存器第8~10位写100,APB1二分频,即PCLK1使用36MHz时钟
#else
/* PLL 配置: PLLCLK = HSE * 9 = 72 MHz */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL)); //RCC_CFGR寄存器的16~21位清零
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);//RCC_CFGR寄存器第16位置1,使HSE作为PLL输入时钟;17位置0不变,使HSE作为PLL输入时钟时不分频。RCC_CFGR寄存器第18~21位置0111,配置位PLL 9倍频输出给PLLCLK,即PLLCLK为72MHz
#endif /* STM32F10X_CL */
/* 使能 PLL */
RCC->CR |= RCC_CR_PLLON; //RCC->CR寄存器第24位置1,使能PLL
/* 等待PLL就绪 */
while((RCC->CR & RCC_CR_PLLRDY) == 0) //读取PLL时钟就绪标志位(第25位)
{
}
/* 选择PLL作为系统时钟源 */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); //RCC->CFGR寄存器第0~1位清零
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL; //RCC->CFGR寄存器第0~1位(SW位)置10,即PLL输出作为系统时钟
/* 等待PLL被用作系统时钟源就绪 */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) //等待RCC->CFGR寄存器第2~3位(SWS位)为10即就绪
{
}
}
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 */
}
}