STM32F103C8T6的系统时钟配置成72MHZ
1. 什么是 STM32 系统时钟
系统时钟(System Clock)是整个 MCU(微控制器)运行的“节拍信号”,所有 CPU 指令执行、外设操作、定时器计时、总线数据传输等,都依赖这个时钟频率来同步。
在 STM32F103C8T6 中,系统时钟的来源可以是:
HSI(内部高速时钟,8 MHz)
HSE(外部高速晶振,一般 8 MHz 或 12 MHz)
PLL(锁相环,可将输入时钟倍频)
系统时钟频率越高,MCU 执行指令的速度越快,但功耗也会随之增加。
2. 为什么要配置系统时钟
保证程序运行速度
不同应用对速度要求不同,如果时钟太低,运算、通讯可能会卡顿甚至超时;时钟过高又会增加功耗和发热。匹配外设波特率和定时精度
例如串口、I²C、SPI、PWM 等外设的时序计算都基于系统时钟,如果系统时钟不正确,波特率或频率就会不准确,导致通信失败。充分利用 MCU 性能
STM32F103 系列的最大主频是 72 MHz,如果只用默认的 8 MHz HSI,CPU 性能会浪费很多。降低功耗或发热(可按需降频)
有些低功耗场景,会在运行中动态调低系统时钟,比如从 72 MHz 切到 36 MHz。
3. 为什么常配成 72 MHz
硬件支持的最大值:STM32F103C8T6 的数据手册写明,最大系统主频是 72 MHz,高于这个值会不稳定甚至损坏芯片。
外设定时方便:72 MHz 这个值和常用外设分频系数匹配好,比如:
72 MHz / 36 / 18 / 9 / 8 / 4 等能整除,方便得到标准串口波特率和 PWM 频率
72 MHz / 72 = 1 MHz,定时器计数方便
性能最优:72 MHz 时,ARM Cortex-M3 的执行速度最快,适合要求高实时性或计算量大的任务。
HAL库
sys,c:
#include "sys.h"/*** @brief 配置 STM32F103C8T6 的系统时钟为 72 MHz(或按传入倍频数计算)* @param plln: PLL 倍频系数(RCC_PLL_MULx 枚举),典型使用 RCC_PLL_MUL9 对 8MHz HSE 倍频至 72MHz* @note 调用时机:必须在 HAL_Init() 之后调用(HAL_Init 会配置 SysTick 等基础时基,RCC 再切换系统时钟)* 时钟路径(本例典型):外部晶振 HSE(8MHz) -> PLL(×9) = 72MHz -> SYSCLK* AHB(HCLK) = 72MHz,APB1 = 36MHz(受限于最大 36MHz),APB2 = 72MHz* 该函数只做“时钟树切换 + 分频设置 + Flash 等待周期设置”,不做外设时钟开启。*/
void stm32_clock_init(uint32_t plln)
{HAL_StatusTypeDef ret = HAL_ERROR;RCC_OscInitTypeDef rcc_osc_init = {0};RCC_ClkInitTypeDef rcc_clk_init = {0};/* ----------- 1) 配置振荡器与 PLL 源/倍频 ----------- */rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 选择要配置的振荡器类型:这里仅配置 HSE(外部高速晶振)rcc_osc_init.HSEState = RCC_HSE_ON; // HSE 状态:开启外部晶振(常见 8MHz)rcc_osc_init.HSEPredivValue = RCC_HSE_PREDIV_DIV1; // HSE 预分频:F1 主流型号为 DIV1 或 DIV2;8MHz 常用 DIV1rcc_osc_init.PLL.PLLState = RCC_PLL_ON; // 打开 PLL 锁相环(用于倍频得到高主频)rcc_osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL 输入源:选 HSE(外部晶振)。另一个常见选项是 HSI/2rcc_osc_init.PLL.PLLMUL = plln; // PLL 倍频系数:典型 RCC_PLL_MUL9(8MHz ×9 = 72MHz)// 使能/配置上述振荡器与 PLL。HAL 会依次启动 HSE、配置 PREDIV、配置并使能 PLL,等待稳定ret = HAL_RCC_OscConfig(&rcc_osc_init);if (ret != HAL_OK){// 若失败,通常意味着 HSE 未振荡、参数非法或硬件异常;此处简化为死循环,可换成 Error_Handler()while(1);}/* ----------- 2) 配置总线分频与系统时钟源 ----------- */// 告诉 HAL:我们要配置哪些时钟域的参数(系统时钟、AHB、APB1、APB2)rcc_clk_init.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);rcc_clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 系统时钟源:切换到 PLL 输出(上面配置的 HSE×PLLMUL)rcc_clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB(HCLK) 分频:SYSCLK/1 = 72MHzrcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV2; // APB1 分频:HCLK/2 = 36MHz(因为 APB1 最大 36MHz)rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 分频:HCLK/1 = 72MHz// 设置时钟并配置 FLASH 等待周期// FLASH_LATENCY_2:当 SYSCLK 在 48–72MHz 区间,需要 2 个等待周期,保证取指正确ret = HAL_RCC_ClockConfig(&rcc_clk_init, FLASH_LATENCY_2);if (ret != HAL_OK){while(1);}
}
sys.h:
#ifndef __SYS_H__
#define __SYS_H__#include "stm32f1xx.h" // HAL 头文件,包含 RCC/FLASH/时钟树相关定义/*** @brief 初始化系统时钟:选择振荡器、配置 PLL、切换 SYSCLK、设置各总线分频与 Flash 等待周期* @param plln: RCC_PLL_MULx(如 RCC_PLL_MUL9),决定 PLL 倍频系数,进而决定 SYSCLK 频率*/
void stm32_clock_init(uint32_t plln);#endif
main.c:
#include "sys.h"int main(void)
{HAL_Init(); /* 初始化 HAL 库:配置 NVIC 分组、SysTick 基础时基、复位外设状态等 */stm32_clock_init(RCC_PLL_MUL9); /* 设置系统时钟:HSE(8MHz)→PLL×9=72MHz;同时 AHB=72MHz,APB1=36MHz,APB2=72MHz,FLASH_LATENCY=2 */while(1){// 用户主循环:此时 SystemCoreClock≈72MHz(可用 HAL_RCC_GetHCLKFreq() 验证)// 根据需要初始化并开启具体外设时钟(GPIO/USART/SPI/TIM...),并编写应用逻辑}
}
关键参数为什么这样选?还能怎么选?
下面把“每个参数的可选项、适用场景、为什么本例这么选”讲清楚:
A. RCC_OscInitTypeDef
OscillatorType
可选:
RCC_OSCILLATORTYPE_HSE
/HSI
/LSE
/LSI
以及它们的组合(按位或)场景:
HSE:外部高速晶振(常见 8MHz),主系统时钟的高精度来源
HSI:内部 8MHz RC 振荡器,精度一般,无需外部器件
LSE:外部 32.768kHz 晶振,RTC 低功耗、长期计时
LSI:内部 40kHz RC,低功耗/独立看门狗/RTC 备选
本例:只配置 HSE,目标是 72MHz 高性能主频和高精度外设时序。
HSEState
可选:
RCC_HSE_ON
/RCC_HSE_OFF
/RCC_HSE_BYPASS
场景:
ON
:使用晶振BYPASS
:外部时钟源输入(方波/有源时钟),不用晶振
本例:
RCC_HSE_ON
,使用 8MHz 晶振。
HSEPredivValue
(F1:DIV1 或 DIV2;部分“Connectivity line”器件支持更多分频)常见:
DIV1
(8MHz 直入 PLL)、DIV2
(把 8MHz 变 4MHz 再进 PLL)场景:若外部源不适合直接倍频,可先预分频到合适范围
本例:
DIV1
,8MHz 直接进 PLL。
PLL.PLLState
可选:
RCC_PLL_ON
/RCC_PLL_OFF
场景:需要高主频就开;低功耗应用可关。
本例:开。
PLL.PLLSource
可选:
RCC_PLLSOURCE_HSE
或RCC_PLLSOURCE_HSI_DIV2
(F1 的 HSI 只能 /2 后进 PLL)场景:
外部晶振精准 → 选 HSE
板上没晶振 → 选 HSI/2(精度差一点)
本例:HSE。
PLL.PLLMUL
可选:
RCC_PLL_MUL2
…RCC_PLL_MUL16
(不同子系列支持范围略有差异)输出:
PLLCLK = PLLSource × PLLMUL
(若用了 PREDIV,需先计算)约束:SYSCLK 最大 72MHz;APB1 最大 36MHz
本例:
RCC_PLL_MUL9
,8MHz ×9 = 72MHz,跑满主频。
B. RCC_ClkInitTypeDef
ClockType
选择需要配置的域:
RCC_CLOCKTYPE_SYSCLK | HCLK | PCLK1 | PCLK2
。本例:四个都配置。
SYSCLKSource
可选:
RCC_SYSCLKSOURCE_HSI
/HSE
/PLLCLK
场景:
刚上电默认 HSI
要高频率/高精度,切到 PLL 或 HSE
本例:
PLLCLK
(HSE×PLL)。
AHBCLKDivider
(HCLK)可选:
DIV1,2,4,8,...,512
约束:HCLK ≤ 72MHz
本例:
DIV1
,HCLK=72MHz。
APB1CLKDivider
(PCLK1)可选:
DIV1,2,4,8,16
约束:PCLK1 ≤ 36MHz(定死的)
本例:
DIV2
→ 72/2=36MHz,刚好满频。
APB2CLKDivider
(PCLK2)可选:
DIV1,2,4,8,16
约束:PCLK2 ≤ 72MHz
本例:
DIV1
→ 72MHz。
FLASH Latency
可选:
FLASH_LATENCY_0
(0WS)、_1
(1WS)、_2
(2WS)规则(F1 常用经验):
SYSCLK ≤ 24MHz → 0WS
24–48MHz → 1WS
48–72MHz → 2WS
本例:
FLASH_LATENCY_2
,保证 72MHz 取指安全。
常见可选组合与“什么时候该这么做”
省硬件/低成本:不用晶振
PLLSource = HSI/2
,例如 HSI(8MHz)/2=4MHz,再 ×16=64MHz(或 ×14=56MHz)优点:省晶振、不占引脚
缺点:精度差(通信波特率误差↑),频率不一定整好。单片机内部晶振不稳定
适合:对精度不敏感、对成本/尺寸苛刻的项目
USB 要 48MHz 时钟(F103 带 USB 的型号)
典型:
HSE=8MHz → PLL×6=48MHz
给 USB系统主频想 72MHz 时,需要选择既能给 USB 48MHz,又能让 SYSCLK 落在允许范围的组合(具体看子系列对 USB 分频器的支持)。你的 C8T6(不带 USB 外设)一般不需要考虑。
功耗优先/中低速运行
例如:
HSE=8MHz → PLL×6=48MHz
,并把 AHB/APB 适当再分频,或直接用 HSI=8MHz配套把
FLASH_LATENCY
降低,提高能效。
外设定时器“翻倍”行为
注意:当 APB 分频器 ≠ 1 时,该 APB 上的 定时器时钟会自动 ×2(比如 APB1=36MHz,则 TIM2/3/4/5 的时钟为 72MHz)。
本例:APB1=HCLK/2=36MHz ⇒ TIM2/3/4/5 实际计数时钟 72MHz;APB2=1 ⇒ TIM1 时钟 72MHz。
这点在算 PWM/输入捕获/定时中断时一定要记牢,否则频率会算错一倍。
验证与辅助 API
SystemCoreClockUpdate()
/HAL_RCC_GetSysClockFreq()
/HAL_RCC_GetHCLKFreq()
/HAL_RCC_GetPCLK1Freq()
/HAL_RCC_GetPCLK2Freq()
用来打印/校验你设置后的频率,辅助外设分频与波特率计算。失败处理建议:把
while(1);
换成Error_Handler()
,在里头打断点或串口提示;也可以加个“超时检测 HSE 未起振”的自检。
易错点与经验贴士
APB1 不得超过 36MHz:所以当 HCLK=72MHz 时,
APB1CLKDivider
必须 >= 2。Flash 等待周期要匹配主频:72MHz 一定要
FLASH_LATENCY_2
;否则各种诡异 HardFault。先配振荡器,再切 SYSCLK:顺序别反。
HSI 进 PLL 是 /2 后再倍频(F1 专属特点)。
外设波特率计算以 PCLK 为基:USART 用 PCLKx,SPI/I2C 用 PCLKx,TIM 则注意 APB≠1 时 ×2。
HAL_Init() 在前:很多人把 RCC 配在 HAL_Init() 之前,影响 SysTick/时基;建议就按现在的顺序。
HSE 频率与板卡一致:若你的板是 12MHz 晶振,
PLLMUL
要重算(如 ×6 得 72MHz),HSEPrediv
也可能需要调整。
标准库:
sys.h:
#ifndef __SYS_H__
#define __SYS_H__#include "stm32f10x.h" /* SPL 头文件:包含 RCC/FLASH 等外设寄存器与 API 原型 *//*** @brief 配置系统时钟树到 72 MHz(HSE 8MHz → PLL×9)* @param pllmul: PLL 倍频因子(SPL 的 RCC_PLLMul_x 枚举)* 常用值:* RCC_PLLMul_6 -> 8MHz×6 = 48MHz* RCC_PLLMul_9 -> 8MHz×9 = 72MHz(典型)* 注意:输入源在本函数中固定为 HSE/1;若需 HSE/2、HSI/2,请改 sys.c 中的 RCC_PLLConfig。*/
void stm32_clock_init(uint32_t pllmul);#endif
sys.c:
#include "sys.h"/*** @brief 将系统时钟配置为:HSE(8MHz) → PLL×(pllmul) → SYSCLK* 并设置 AHB/APB1/APB2 分频与 Flash 等待周期,使之适配 72MHz 运行* @param pllmul: RCC_PLLMul_x(SPL 枚举),典型为 RCC_PLLMul_9(8MHz×9=72MHz)* @note 调用顺序建议:SystemInit()(启动文件里自动调用)→ main() 中先执行 stm32_clock_init() → 再初始化外设* 若 HSE 未起振或配置失败,本实现进入死循环(可替换为 Error_Handler)*/
void stm32_clock_init(uint32_t pllmul)
{ErrorStatus HSEStartUpStatus;/* --- 0) 复位 RCC 到缺省状态(等价于上电复位后的时钟设置) -------------------- */RCC_DeInit(); /* 清除 RCC 配置寄存器,关闭 PLL,选择 HSI 为系统时钟等 *//* --- 1) 使能外部高速时钟 HSE,并等待其稳定 ------------------------------------ */RCC_HSEConfig(RCC_HSE_ON); /* 打开 HSE 晶振(8MHz,若是外部有源时钟用 RCC_HSE_Bypass) */HSEStartUpStatus = RCC_WaitForHSEStartUp();/* 轮询 HSERDY 标志,等待 HSE 起振稳定 */if (HSEStartUpStatus == SUCCESS){/* --- 2) Flash 预取与等待周期 ------------------------------------------------ */FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); /* 开启指令预取,提高取指效率 *//** F1 经验规则:* SYSCLK ≤ 24MHz -> FLASH_Latency_0* 24~48MHz -> FLASH_Latency_1* 48~72MHz -> FLASH_Latency_2* 我们目标 72MHz,因此设置 2 个等待周期。*/FLASH_SetLatency(FLASH_Latency_2);/* --- 3) 设置各总线分频 ------------------------------------------------------ *//** AHB(HCLK) = SYSCLK / 1 = 72MHz* 注意:AHB 最大 72MHz*/RCC_HCLKConfig(RCC_SYSCLK_Div1);/** APB2(PCLK2) = HCLK / 1 = 72MHz* APB2 最大 72MHz,常用为 72MHz,驱动 GPIO/ADC/TIM1/USART1 等*/RCC_PCLK2Config(RCC_HCLK_Div1);/** APB1(PCLK1) = HCLK / 2 = 36MHz* 非常重要:APB1 最大只能 36MHz(TIM2/3/4/5、USART2/3、I2C/SPI2 等位于 APB1)*/RCC_PCLK1Config(RCC_HCLK_Div2);/** 可选:配置 ADC 时钟(位于 APB2),比如 72MHz / 6 = 12MHz(≤14MHz)* 若暂不启用 ADC,可以省略;此处给出常见写法:*/// RCC_ADCCLKConfig(RCC_PCLK2_Div6);/* --- 4) 配置 PLL 输入源与倍频 ----------------------------------------------- *//** 本例选择:PLL 源 = HSE/1(RCC_PLLSource_HSE_Div1)* 倍频 = pllmul(RCC_PLLMul_x),常见为 x9 -> 72MHz** 若你的板子是 8MHz HSE,且希望 72MHz:RCC_PLLMul_9* 若你的板子是 12MHz HSE,要 72MHz,常用:HSE/1 ×6(RCC_PLLMul_6)* 若想用 HSI:改为 RCC_PLLSource_HSI_Div2(HSI 8MHz/2=4MHz 再倍频)*/RCC_PLLConfig(RCC_PLLSource_HSE_Div1, pllmul);/* --- 5) 使能 PLL 并等待锁定 ------------------------------------------------- */RCC_PLLCmd(ENABLE); /* 打开 PLL */while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET){/* 等待 PLL 锁定,就绪后才能切换系统时钟源到 PLL */}/* --- 6) 选择 PLL 作为系统时钟源 --------------------------------------------- */RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);/* 等待切换完成:读取并确认 SYSCLK 源为 PLL */while (RCC_GetSYSCLKSource() != 0x08) /* 0x08 == RCC_CFGR_SWS_PLL */{/* 等待系统时钟源切换到 PLL */}/* --- 7) (可选)更新 SystemCoreClock 变量 ----------------------------------- *//** 如果你的工程使用 CMSIS 的 SystemCoreClock 全局变量,并需要依赖它做延时/波特率计算,* 请在此调用 SystemCoreClockUpdate()(或手动赋值 72MHz)。*/SystemCoreClockUpdate();}else{/* HSE 起振失败:可能晶振焊接/电容不当/参数错误/外部时钟不稳等 */while (1){/* TODO: 放置错误指示(如点灯/串口打印),便于排查 */}}
}
main.c:
#include "sys.h"int main(void)
{/* 对于 SPL 工程,SystemInit() 一般在启动文件(startup_xxx.s)复位后自动调用,里面会把时钟先配置到缺省状态(HSI)。我们在这里再切换到目标时钟。 */stm32_clock_init(RCC_PLLMul_9); /* HSE=8MHz → PLL×9 = 72MHzAHB=72MHz, APB1=36MHz, APB2=72MHz, Flash Latency=2 *//* 之后再去初始化 GPIO、USART、SPI、TIM 等具体外设时钟和功能 */while (1){/* 用户主循环 */}
}
说明与可选项
HSE 源的三种形态
RCC_HSE_ON
:外部无源晶振(常见 8 MHz)RCC_HSE_Bypass
:外部有源时钟模块/方波输入(不用晶振本体)RCC_HSE_OFF
:关闭 HSE(低功耗或仅用 HSI 场景)
PLL 输入源的选择
RCC_PLLSource_HSE_Div1
:HSE 直接进 PLLRCC_PLLSource_HSE_Div2
:HSE/2 进 PLL(早期/特殊板可能需要)RCC_PLLSource_HSI_Div2
:HSI/2 进 PLL(省晶振但精度差)
常用倍频举例(基于 HSE=8 MHz)
48 MHz:
RCC_PLLMul_6
(USB 常用 48 MHz 基准,但 F103C8 本体无 USB 设备控制器)56 MHz:
RCC_PLLMul_7
72 MHz:
RCC_PLLMul_9
(满频、最常用)
APB 定时器“×2”规则
当 APBx 分频器 ≠ 1 时,该总线上的 定时器时钟 = PCLKx × 2。
本配置:APB1=HCLK/2=36 MHz ⇒ TIM2/3/4/5 实际计数时钟 72 MHz
APB2=HCLK/1=72 MHz ⇒ TIM1 计数时钟 72 MHz
做 PWM、输入捕获、定时中断频率计算务必考虑这条规则。
Flash 等待周期
≤24 MHz:Latency 0
24~48 MHz:Latency 1
48~72 MHz:Latency 2
72 MHz 必须设FLASH_Latency_2
,否则易 HardFault 或运行不稳。
若你的板是 12 MHz 晶振
把RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_6);
→ SYSCLK=72 MHz。
或 12 MHz/2 × 12(Connectivity Line 支持更细分频/倍频配置,普通 F103C8 无 CFGR2 多预分频选项)。
常见故障与排查
卡死在
RCC_WaitForHSEStartUp()
:HSE 未起振。检查晶振焊接/负载电容/是否应使用 Bypass。切到 PLL 后跑飞或 HardFault:多半是 Flash Latency 未正确设置 或 APB1 超频。
外设时序不准:确认
SystemCoreClockUpdate()
是否调用、波特率计算基于正确的 PCLKx;定时器是否“×2”。