STM32 - 时钟系统详解
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 个等待周期。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!