引言
在上一节中,我们深入了解了STM32的时钟树,知道了时钟源、PLL、系统时钟和总线时钟之间的关系。今天,我们进入实战环节,重点讲解STM32标准库中一个非常重要的函数------系统时钟配置函数SetSysClockTo72()。这个函数负责将系统时钟配置为72MHz,是STM32高性能运行的基础。理解这个函数如何工作,会让你对STM32的时钟配置有更扎实的掌握。
一、SetSysClockTo72()函数是什么?
SetSysClockTo72() 是STM32标准库中的一个函数,它的作用就是按照ST官方推荐的配置,将系统时钟(SYSCLK)设置为72MHz。在STM32启动时,默认使用的是8MHz的HSI(内部时钟),但通过调用这个函数,我们可以切换到更高速的72MHz时钟,从而提升整个系统的性能。这个函数通常定义在STM32标准库的 system_stm32f10x.c文件中。当你使用库开发时,在 SystemInit()函数中会调用它。理解它的实现,能帮助我们日后自定义时钟配置。
1.1 SetSysClockTo72() 函数的位置
从图片可以看出,SetSysClockTo72()函数位于代码文件 system_stm32f10x.c中。具体行号大约在第987行(图片中显示行号范围为974-1002,第987行被红色方框标注,并有"官方默认72M"的提示)。这是该函数的定义位置。

1.2 配合讲解:从启动到 SetSysClockTo72() 的调用流程
- 启动文件调用 SystemInit :第1张图片显示启动文件
startup_stm32f10x_hd.s中的复位中断服务程序(Reset_Handler)。代码中有LDR R0, =SystemInit和BLX R0指令,这表明在芯片复位后,会跳转到SystemInit函数执行。

- SystemInit 函数调用 SetSysClock :图片显示在
system_stm32f10x.c文件的SystemInit函数中(约第262行),有SetSysClock()函数调用。通过右键菜单的"Go To Definition Of 'SetSysClock'"选项,可以跳转到其定义。

- SetSysClock 函数根据宏定义选择时钟配置 :图片显示
SetSysClock函数的定义(约第419-437行)。它是一个静态函数,内部通过条件编译(如#ifdef SYSCLK_FREQ_72MHz)根据宏定义调用不同的时钟设置函数。

默认情况下,我们会定义 SYSCLK_FREQ_72MHz这个宏(在这个c文件的前面定义)。如果我们定义成24兆、36兆、48兆、56兆等,那么就会调用不同的函数把系统时钟配置成相应的频率。
宏 SYSCLK_FREQ_72MHz被定义(如图片所示,第115行有 #define SYSCLK_FREQ_72MHz 72000000),因此会调用 SetSysClockTo72()函数将系统时钟配置为72MHz。

- 最终定位到 SetSysClockTo72() :如图片所示,
SetSysClockTo72()函数是实际实现72MHz时钟配置的具体函数。官方推荐使用72MHz作为最高稳定运行频率,因此默认定义了这个宏。

二、函数工作流程概览



2.1 函数概述与芯片系列区分
SetSysClockTo72()函数的核心任务是将STM32F103的系统时钟配置为72MHz ,并同步设置HCLK、PCLK2和PCLK1的分频系数。该函数通过条件编译宏STM32F10X_CL来区分芯片系列:基础型(小容量、中容量、大容量)和互联型(STM32F105/107)。为简化分析,我们删除互联型相关的代码,专注于基础型芯片的配置流程。
2.2 使能HSE(外部高速时钟)
static void SetSysClockTo72(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* 使能HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
首先,代码执行 RCC->CR |= ((uint32_t)RCC_CR_HSEON);来使能HSE。它控制的是RCC_CR寄存器的HSEON位(第16位)来启动外部8MHz晶振,将该位置1以使能HSE。因为开发板上用的是无源晶振,起振时需要配合起振电容,需要一段时间。启动完毕后,RCC_CR寄存器的HSERDY位(硬件)会置1,我们通过判断这个位是否置1来判断HSE是否启动成功。
比如指南者开发板,这个8MHz的无源晶振就是HSE。旁边还有一个晶振是LSE,是给RTC实时时钟提供的,频率是32.768kHz。霸道开发板也是类似的,8MHz的晶振就是HSE。
2.3 等待HSE就绪与超时处理
/* 等待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;
}
接着是等待HSE就绪(Wait till HSE is ready),如果出现故障(比如晶振没焊好),需要有超时处理机制(if Time out is reached exit)。代码通过一个do-while循环来实现:读取CR寄存器的HSERDY位(第17位),如果HSE就绪了,该位由硬件在HSE稳定后置1,循环条件不满足就跳出。
2.4 配置Flash预取指和等待周期
之后还会再判断一次,如果HSE启动成功,程序就继续往下执行。当然,有成功就有失败,对应着后面的else分支。HSE启动成功后,配置Flash以确保CPU能稳定读取指令。STM32程序代码是存储在Flash里面的。内核(比如Cortex-M3)从Flash读取指令执行时,需要一定的等待时间。预取指是指,在执行当前指令时,提前将下一条可能要执行的指令从Flash读到缓冲区,提高效率。同时,因为Flash的读取速度跟不上很高的系统时钟频率,所以需要插入等待周期。这个等待周期的数量需要根据我们最终配置的系统时钟频率来设置。这两三句代码操作的寄存器属于Flash模块,不在RCC部分,具体可以参考STM32的《闪存编程手册》中关于ACR寄存器的说明。
if (HSEStatus == (uint32_t)0x01)
{
/* 使能预取指缓冲区 */
FLASH->ACR |= FLASH_ACR_PRFTBE;
/* Flash 2个等待状态 */
FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
......
else
{
/*如果HSE启动失败,应用程序将会有错误的时钟配置,用户可以在这里添加代码来处理这个错误*/
}
-
预取指功能:CPU(内核,比如Cortex-M3)------>Flash------>STM32程序代码。
-
等待状态 :当系统时钟超过一定频率(如≥48MHz),必须配置Flash等待周期。
FLASH_Latency_2表示插入2个等待状态,适用于48MHz到72MHz的系统时钟。
2.5 配置总线分频因子
/* 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;
通过配置RCC_CFGR寄存器的不同位段,设置各总线时钟:
-
HPRE(AHB预分频) :设置为1分频(
HPRE_DIV1),使HCLK = SYSCLK = 72MHz。 -
PPRE2(APB2预分频) :设置为1分频(
PPRE2_DIV1),使PCLK2 = HCLK = 72MHz(APB2总线最高支持72MHz)。 -
PPRE1(APB1预分频) :设置为2分频(
PPRE1_DIV2),使PCLK1 = HCLK/2 = 36MHz(APB1总线最高支持36MHz)。
2.6 配置并使能PLL(锁相环)
/* PLL配置: 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);
这几条语句配置PLL的时钟源和倍频系数:
-
PLLXTPRE:控制HSE是否2分频后进入PLL。此处默认HSE不分频。
-
PLLSRC:选择HSE作为PLL的输入时钟源(而非HSI/2)。
-
PLLMUL :设置倍频因子为9。基于8MHz的HSE,
PLLCLK = 8MHz * 9 = 72MHz。
配置完成后,使能PLL并等待其稳定:
/* 使能PLL */
RCC->CR |= RCC_CR_PLLON;
/* 等待PLL就绪 */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
通过循环检测PLLRDY位,等待PLL输出稳定。
2.7 选择系统时钟并等待切换完成
/* 选择PLLCLK作为系统时钟源 */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
/* 等待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 */
}
}
-
通过配置CFGR寄存器的SW位,将系统时钟SYSCLK的来源切换为PLL输出。
-
切换过程需要时间,通过循环检测SWS状态位,确认PLL已成功成为系统时钟源(SWS值为0x08)。
2.8 HSE启动失败处理
若HSE启动失败(例如晶振故障),程序会执行else分支的代码。此处默认仅有注释,开发者可在此添加错误处理机制,如切换至HSI或触发安全响应。
通过以上步骤,SetSysClockTo72()函数完成了将STM32F103系统时钟配置为72MHz的整个过程,为后续外设提供了稳定的时钟基础。
三、详细步骤解析(选看)
结合STM32中文参考手册和零死角文档,我们来分析 SetSysClockTo72()的关键代码(以STM32F103系列为例)。注意:以下代码是简化版,重点在原理说明。
步骤1: 启用HSE(外部高速时钟)
HSE是外部晶振提供的时钟,精度高,是达到72MHz的基础。函数首先会启用HSE并等待其稳定。
// 使能HSE:设置RCC_CR寄存器的HSEON位为1
RCC->CR |= RCC_CR_HSEON;
// 等待HSE就绪:循环检查HSERDY位是否为1
while (!(RCC->CR & RCC_CR_HSERDY));
原理说明:
-
HSEON位是RCC_CR寄存器的第16位,置1后启动外部晶振。
-
HSERDY是状态位,晶振起振需要时间,等待它变为1表示HSE已稳定。如果不等待直接后续操作,可能导致时钟配置失败。
-
**为什么用HSE?**HSE比HSI精度更高,能保证PLL输出72MHz的稳定性。如果项目对时序要求高(如通信外设),必须使用HSE。
步骤2: 配置Flash延迟
当系统时钟切换到高速时,CPU访问Flash存储器需要更长的等待时间,否则可能读取出错。因此,函数会配置Flash的等待周期。
// 设置Flash延迟:2个等待周期(因为72MHz > 48MHz)
FLASH->ACR |= FLASH_ACR_LATENCY_2;
// 启用Flash预取缓冲区(提升读取效率)
FLASH->ACR |= FLASH_ACR_PRFTBE;
原理说明:
-
参考手册规定:当SYSCLK ≤ 24MHz时,Flash无需等待;24MHz < SYSCLK ≤ 48MHz时,需1个等待周期;SYSCLK > 48MHz时,需2个等待周期。72MHz属于高速,所以设2。
-
预取缓冲区是Flash的缓存机制,能提前加载指令,提高CPU效率。不开启的话,系统可能运行不稳定。
-
新手注意:这是容易忽略的一步!如果忘记配置,系统在高速下可能频繁复位或运行异常。
步骤3: 配置PLL(锁相环)
PLL是"倍频器",它将HSE的8MHz信号倍频到72MHz。函数会设置PLL的输入源和倍频系数。
// 配置PLL:选择HSE作为PLL输入源(不分频),9倍频
RCC->CFGR |= RCC_CFGR_PLLSRC_HSE; // PLL源 = HSE
RCC->CFGR |= RCC_CFGR_PLLMUL_9; // 倍频因子 = 9
// 使能PLL
RCC->CR |= RCC_CR_PLLON;
// 等待PLL锁定
while (!(RCC->CR & RCC_CR_PLLRDY));
原理说明:
-
PLLSRC位选择PLL的输入时钟:HSE或HSI/2。这里选HSE(8MHz),保证输入精度。 -
PLLMUL位设置倍频系数,9倍频后:8MHz × 9 = 72MHz。 -
PLL需要时间锁定频率,等待
PLLRDY位为1后,表示PLL输出稳定。如果跳过等待,切换系统时钟可能失败。 -
**为什么是9倍频?**ST官方推荐72MHz为稳定最大值。如果使用HSI/2(4MHz)作为输入,即使16倍频也只能到64MHz,达不到72MHz。
步骤4: 配置总线分频器
系统时钟SYSCLK变为72MHz后,需要分频给AHB、APB等总线,确保外设时钟不超限。
// 配置预分频器:
// AHB分频 = 1(HCLK = SYSCLK = 72MHz)
RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
// APB1分频 = 2(PCLK1 = HCLK/2 = 36MHz,不超过36MHz限制)
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;
// APB2分频 = 1(PCLK2 = HCLK = 72MHz)
RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;
原理说明:
-
AHB总线连接内核和高速外设,1分频让HCLK跑在72MHz,发挥最大性能。
-
APB1总线用于低速外设(如USART2、I2C),最高36MHz,所以2分频得到36MHz。
-
APB2总线用于高速外设(如GPIO、ADC),支持72MHz,1分频即可。
-
分频的重要性:如果APB1设成1分频(72MHz),会超频导致外设工作异常!务必参考手册的限值。
步骤5: 切换系统时钟到PLL
最后,函数将系统时钟源从默认的HSI切换到PLL输出。
// 切换系统时钟源为PLL
RCC->CFGR |= RCC_CFGR_SW_PLL;
// 等待切换完成(检查SWS状态位)
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
原理说明:
-
SW位用于选择SYSCLK源(HSI、HSE或PLL)。这里设为PLL。 -
SWS是状态位,硬件会自动更新它。等待SWS变为PLL,确认切换成功。如果切换中途被打断,系统可能卡死。 -
切换后的变化:此时SYSCLK = PLLCLK = 72MHz,整个系统"加速"了!
四、完整流程总结
整个SetSysClockTo72()函数的流程可以概括为以下流程图(基于代码逻辑):
开始
↓
启用HSE → 等待HSERDY
↓
配置Flash延迟(2等待周期)
↓
配置PLL(源=HSE,倍频=9) → 使能PLL → 等待PLLRDY
↓
配置总线分频(AHB=1, APB1=2, APB2=1)
↓
切换时钟源到PLL → 等待SWS确认
↓
结束,SYSCLK=72MHz
关键原理:
-
顺序性:步骤不能颠倒!例如,必须先启用HSE并等待就绪,才能配置PLL;否则PLL可能用错误输入源。
-
稳定性检查:每个步骤都有"等待就绪"循环,确保硬件状态稳定后再继续。这是避免故障的核心。
-
安全限值:总线分频严格遵循手册限值,防止外设超频。