参考资料:
野火STM32F407_霸天虎V2开发板原理图2025.pdf
1-STM32F4xx中文参考手册1.pdf
什么是RCC?
RCC即Reset Clock Control,也叫复位和时钟控制器。
时钟树
要想弄明白系统时钟是怎么回事,时钟树是逃避不了的一个话题。如下图:

接下来的内容我会尽量用最朴实的语言描述好时钟树,助于咱们理解它。
系统时钟
黄色区域:这段内容是重中之重,重点讲解。也就是我们常说的系统时钟。
①高速的外部时钟信号--HSE,可以由有源晶振和无源晶振提供,频率从4~26MHz不等。当使用有源晶振时,时钟从OSC_IN引脚进入,OSC_OUT引脚悬空;当用无源晶振时,时钟从OSC_IN和OSC_OUT进入,并且要配谐振电容。本开发板HSE使用25MHz的无源晶振,如下图

②主要内容--PLL锁相环。它的主要作用就是对时钟进行倍频,然后将它输出到各功能部件。PLL的时钟输入来自HSE(为主)或者HSI经过PLL时钟输入分频因子M(2~63)的分频,也就是VCO的时钟输入,VCO的时钟必须在1M~2M之间,当我们选择HSE=25M为PLL的时钟输入,M应设置为25,得到VCO的时钟就是1M;VCO的时钟经过倍频因子N倍频之后,即为VCO的时钟输出,VCO时钟输出必须在192~432之间,这里N配置为336,也就是VCO的时钟输出是336M,经后面的PLLCLK分频因子P(P可以取2,4,6,8,这里取最小值为2)后系统时钟为336/2=168M(当然VCO时钟输出取最大值432M时N为432,即为超频,此时系统时钟为216M,不建议);还有两个分频因子Q和R,其中经分频因子Q提供USB_OTGFS要求的48M,则Q=336/48=7。(详细内容可查看参考手册中6.3.2 RCC_PLLCFGR寄存器描述)
简言之:
VCOCLK_IN=PLLCLK_IN/M=HSE/25=25M/25=1M
VCOCLK_OUT=VCOCLK_IN*N=1M*336=336M
PLLCLK_OUT=VCOCLK_OUT/P=336M/2=168M
USBCLK=VCOCLK_OUT/Q=336M/7=48M
③SYSCLK就是系统时钟,它的来源有3种即HSI、HSE和PLLCLK。由RCC_CFGR
的SW位配置。我们选择PLLCLK作为SYSCLK的时钟源即SYSCLK=PLLCLK=168M。(如果HSE出现故障,SYSCLK会自动切换成HSI=16M,直到HSE恢复正常)
④这部分是SYSCLK经AHB预分频器(AHBPRESC)得到的AHB总线时钟,也就是HCLK。分频因子可以是1,2,4,8,16,64,128,256,512,由RCC_CFGR的HPRE位设置。片上的外设大部分都是经HCLK分频得到,具体多少具体设置。这里我们设置1分频,得到HCLK=SYSCLK=168M。
⑤⑥接下来就是APBx的总线时钟(x可以是1或2)。
其中APB2的总线时钟PCLK2由HCLK经高速APB2预分频器 得到,分频因子可以是1,2,4,8,16,由RCC_CFGR的PPRE2位设置。APB2属于高速时钟总线,片上的高速外设 挂载在它这里,如USART1、SPI1等,具体多少具体设置。这里我们设置2分频,得到PCLK2=HCLK/2=84M。
其中APB2的总线时钟PCLK1由HCLK经低速APB1预分频器 得到,分频因子可以是1,2,4,8,16,由RCC_CFGR的PPRE1位设置。APB1属于低速时钟总线,片上的低速外设 挂载在它这里,如USART2/3/4/5、SPI2/3、I2C1等,具体多少具体设置。这里我们设置4分频,得到PCLK1=HCLK/4=42M。
RTC时钟电路
A区域:RTC时钟电路,由时钟树可知,RTC时钟源可选3路中任意一路,分别是LSI、LSE和HSE。由RCC备份域控制器(RCC_BDCR)的RTCSEL1:0选择RTC时钟源(选择 RTC 时钟源后,除非备份域复位,否则其不可再更改。可使用 BDRST 位对其进行复位),其中HSE的时钟由RCC_CFGR的RTCPRE位设置HSE 分频系数得到1MHz。通常由LSE为RTC提供时钟,大小是32.768KHz,LSE由外接的晶体谐振器产生,谐振电容精度要求高,否则不容易起振。

独立看门狗时钟电路
B区域:独立看门狗时钟电路,由内部低速时钟LSI提供,大小为32KHz。
I2S时钟电路
C区域:I2S时钟电路,可选两路时钟源任意一路,即外部时钟引脚I2S_CKIN和专用PLLI2SCLK提供。由RCC_CFGR的I2SSRC位配置。
PHY以太网时钟电路
D区域:PHY以太网时钟电路,本开发板选择的PHY芯片是LAN8720A,使用RMII接口,此时PHY芯片只需提供一路时钟给MCU即可。
USB2.0 PHY时钟电路
E区域:USB2.0 PHY时钟电路,F407的USB没有集成PHY,要想实现USB高速传输,必须外置USB PHY芯片,常用芯片是USB3300,当外接USB PHY芯片时,PHY芯片需要给MCU提供一个时钟。
微控制器时钟输出引脚(MCO)
F区域:微控制器时钟输出引脚(MCO),即对外提供时钟,相当于一个有源晶振。F407有2个MCO,由PA8/9复用得到。MCO1的时钟源由RCC_CFGR的MCO1位选择时钟源(LSE、HSE、HSI、PLLCLK),由RCC_CFGR的MCO1PRE位选择几分频(无,2,3,4,5);MCO2的时钟源由RCC_CFGR的MCO2位选择时钟源(SYSCLK、PLLI2SCLK、HSE、PLLCLK),由RCC_CFGR的MCO2PRE位选择几分频(无,2,3,4,5);MCO1和MCO2的最大输出速率是100M。如图:

库函数是怎么设置系统时钟的?
我们找到库函数是怎么设置系统时钟的,找它的方法:在启动文件里找到SystemInit,F12定位位置,转到system_stm32f4xx.c的SystemInit函数,函数体内找到SetSysClock(),F12定位位置,该函数就是我们要找的配置系统时钟的函数。
以下内容是配置系统时钟的函数经过裁剪后的程序(将不是F407的程序已经删掉了,这是精简F407专用版):
cpp
static void SetSysClock(void)
{
/******************************************************************************/
/* 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;
}
//HSE启动成功
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;
/* PCLK2 = SYSCLK / 2*/
RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;
/* PCLK1 = SYSCLK / 4*/
RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;
/* 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)
{
}
/* 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;
/* 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 */
//HSE启动出错处理
}
}
总结下使用HSE设置系统时钟的步骤:
1.开启HSE并等待HSE稳定;
2.设置AHB(HCLK)、APB2、APB1的预分频因子;
3.设置PLL的时钟来源以及VCO输入时钟分频因子M、VCO输出时钟倍频因子N、PLLCLK时钟分频因子P、48MHz时钟(USB_OTGFS,SDIO,RNG)分频因子Q;
4.开启主PLL并等待其稳定;
5.将主PLLCLK设置为系统时钟SYSCLK并通过读取时钟切换状态位确保PLLCLK被选为系统时钟。
实验--实现HSE/HSI作为系统时钟并输出时钟
手搓一个使用HSE或HSI作为系统时钟的实验,并且使用MCO对外提供时钟输出
有一个小知识点:
当HSE直接或者间接(经过PLL倍频)作为系统时钟的时候,如果HSE发生故障,不仅HSE会关闭,PLL也会关闭,此时系统会将HSI设置为系统时钟,即SYSCLK=HSI=16M,如果没有开启CSS和CSS中断的话,整个系统只会在低速率下运行,相当于瘫痪。如果开启了CSS功能,此时在CSS中断里会执行补救措施,会将HSI重新设置系统时钟为168M,但只是权宜之计,因为HSI较于HSE精度低。
下面我们将手搓这个实验【程序源码放到附件了】