0. 前言
0.1 什么是时钟
时钟是由电路产生的具有周期性的脉冲信号,相当于单片机的心脏,给单片机提桶一个统一的信号
要想使用单片机的外设必须开启相应的时钟,
0.2 时钟对单片机作用
驱动外设的本质是操作寄存器,而寄存器是由D触发器构成,而D触发器需要时钟才能改写值,所以要想操作寄存器必须开启对应外设的时钟。
对CPU来说,假设CPU在一个时钟周期 (时钟等于 1/f (频率的倒数) 内执行一条指令(二进制代码)
若时钟频率越高,则时钟周期更短,则在相同的时间CPU能够执行更多的指令,CPU的运行速度更快
0.3 为什么要有时钟树
STM32时钟系统主要的目的就是给相对独立的外设模块提供时钟
为了降低整个芯片的功耗,所有外设时钟默认都是关闭状态(disable);当我们使用某个外设就要开启这个外设的时钟(enable)
不同外设需要的时钟频率不同,没必要所有外设都用高速时钟造成浪费,而且有些外设也接受不了这么高的频率,这也是为什么STM32有四个时钟源(HSE、 LSE、HSI、LSI)的原因,就是为了兼容不同速度的外设
1. 时钟树
以下是 STM32F40x 的时钟系统框图
在STM32中,有5个时钟源:
① LSI(Low Speed External Clock, 低速内部时钟):RC振荡器,频率为32KHz。独立看门狗的时钟源只能是LSI,同时LSI还可以做RTC的时钟源。
② LSE(Low Speed Internal Clock, 低速外部时钟):接频率为32.768KHz的石英晶体,LSE主要是RTC的时钟源。
③ HSI(High Speed Internal Clock, 高速内部时钟):RC振荡器,频率为16MHz,精度不高
④ HSE(High Speed External Clock, 高速外部时钟):可接石英/陶瓷谐振器,或外接时钟源,频率范围是2MHz~16MHz。
⑤ PLL(Phase Locked Loop, 锁相环倍频输出):理论上不能算是时钟源,只是接收时钟源后对其进行分/倍频,分/倍频 倍数可软件调节
Notice: 以上频率仅针对STM32F40x,其它的以数据手册为准
1)系统时钟 (SYSCLK) 选择
在系统复位后,默认系统时钟为 HSI。在直接使用 HSI 或者通过 PLL 使用时钟源来作为系统时钟时,该时钟源无法停止。
只有在目标时钟源已就绪时(时钟在启动延迟或 PLL 锁相后稳定时),才可从一个时钟源切换到另一个。如果选择尚未就绪的时钟源,则切换在该时钟源就绪时才会进行。RCC 时钟
控制寄存器 (RCC_CR) 中的状态位指示哪个(些)时钟已就绪,以及当前哪个时钟正充当系统时钟。
2)时钟输出功能
共有两个微控制器时钟输出 (MCO) 引脚:
● MCO1
用户可通过可配置的预分配器(从 1 到 5)向 MCO1 引脚 (PA8) 输出四个不同的时钟源:
— HSI 时钟
— LSE 时钟
— HSE 时钟
— PLL 时钟
所需的时钟源通过 RCC 时钟配置寄存器 (RCC_CFGR) 中的 MCO1PRE[2:0] 和 MCO1[1:0]位选择。
● MCO2
用户可通过可配置的预分配器(从 1 到 5)向 MCO2 引脚 (PC9) 输出四个不同的时钟源:
— HSE 时钟
— PLL 时钟
— 系统时钟 (SYSCLK)
— PLLI2S 时钟
所需的时钟源通过 RCC 时钟配置寄存器 (RCC_CFGR) 中的 MCO2PRE[2:0] 和 MCO2 位选择。
对于不同的 MCO 引脚,必须将相应的 GPIO 端口在复用功能模式下进行设置。
MCO 输出时钟不得超过 100 MHz(最大 I/O 速度)。
2. 时钟配置
2.1 配置流程
①. 在STM32单片机复位之后,首先进入 startup 程序:
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
可以看出,在进入main主程序之前,先触发了 SystemInit() 函数(打开相应的时钟晶振,分频选择),这样就可以保证不需要每次都把时钟配置程序写入main.c文件了;
同样,当你想要执行自定义时钟配置程序时也可以改动这个部分。
②. SystemInit()
该函数位于 system_stm32f4xx.c 文件中
/**
* @brief Setup the microcontroller system
* Initialize the Embedded Flash Interface, the PLL and update the
* SystemFrequency variable.
* @param None
* @retval None
*/
void SystemInit(void)
{
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
/* Reset the RCC clock configuration to the default reset state ------------*/
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset CFGR register */
RCC->CFGR = 0x00000000;
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset PLLCFGR register */
RCC->PLLCFGR = 0x24003010;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Disable all interrupts */
RCC->CIR = 0x00000000;
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* Configure the System clock source, PLL Multiplier and Divider factors,
AHB/APBx prescalers and Flash settings ----------------------------------*/
SetSysClock();
...
}
可以看到,SystemInit()函数的作用就是使RCC_CR寄存器Bit0 置1,即开启 16MHz HSI 振荡器,复位其他的时钟,然后转到SetSysClock()函数中。
对于系统时钟,默认情况下就是SystemInit 函数的 SetSysClock() 函数中间判断的
③. SetSysClock()
点击查看代码
static void SetSysClock(void)
{
#if defined (STM32F40_41xxx) || defined (STM32F427_437xx) || defined (STM32F429_439xx) || defined (STM32F401xx)
/******************************************************************************/
/* PLL (clocked by HSE) used as System clock source */
/******************************************************************************/
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* Enable HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
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)
{
/* Select regulator voltage output Scale 1 mode */
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_VOS;
/* HCLK = SYSCLK / 1*/
RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
#if defined (STM32F40_41xxx) || defined (STM32F427_437xx) || defined (STM32F429_439xx)
/* PCLK2 = HCLK / 2*/
RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;
/* PCLK1 = HCLK / 4*/
RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;
#endif /* STM32F40_41xxx || STM32F427_437x || STM32F429_439xx */
#if defined (STM32F401xx)
/* PCLK2 = HCLK / 2*/
RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;
/* PCLK1 = HCLK / 4*/
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;
#endif /* STM32F401xx */
/* Configure the main PLL */
RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
(RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
/* Enable the main PLL */
RCC->CR |= RCC_CR_PLLON;
/* Wait till the main PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
#if defined (STM32F427_437xx) || defined (STM32F429_439xx)
/* Enable the Over-drive to extend the clock frequency to 180 Mhz */
PWR->CR |= PWR_CR_ODEN;
while((PWR->CSR & PWR_CSR_ODRDY) == 0)
{
}
PWR->CR |= PWR_CR_ODSWEN;
while((PWR->CSR & PWR_CSR_ODSWRDY) == 0)
{
}
/* Configure Flash prefetch, Instruction cache, Data cache and wait state */
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
#endif /* STM32F427_437x || STM32F429_439xx */
#if defined (STM32F40_41xxx)
/* Configure Flash prefetch, Instruction cache, Data cache and wait state */
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;
#endif /* STM32F40_41xxx */
#if defined (STM32F401xx)
/* Configure Flash prefetch, Instruction cache, Data cache and wait state */
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_2WS;
#endif /* STM32F401xx */
/* Select the main PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= RCC_CFGR_SW_PLL;
/* Wait till the main PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
{
}
}
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 */
}
#endif
不要被上面的长度吓到了,其实无非就是对RCC几个寄存器的赋值,里面注释很详细,对着寄存器功能表,可以很容易理解其意思!
主要有耐心去看,很容易就搞明白怎么回事了!
2.3 配置寄存器
STM32中,对时钟的配置,即是对 RCC(Reset Clocl Control,复位和时钟寄存器)的配置,时钟寄存器包含如下几个:
2.3.1 时钟控制寄存器(RCC_CR)
偏移地址:0x00
复位值:0x0000 XX83,其中 X 未定义。
访问:无等待周期,按字、半字和字节访问
2.3.2 RCC 时钟配置寄存器 (RCC_CFGR)
偏移地址:0x08
复位值:0x0000 0000
访问:0 等待周期 2,按字、半字和字节访问
只有在时钟源切换期间进行访问时才会插入 1 或 2 个等待周期。