STM32 时钟树详解
STM32F10x 时钟源、分频链路、外设时钟与标准库配置方法
面向嵌入式入门,以 STM32F103(标准外设库) 为主。
搞清楚时钟树,才能正确计算定时器周期、UART 波特率、ADC 采样时间。
一、为什么要懂时钟树
STM32 片内并不是“只有一个时钟”,而是由 RCC(Reset and Clock Control) 把多种时钟源、分频器、倍频器组织成一棵树:
时钟源 → SYSCLK → HCLK → PCLK1 / PCLK2 → 各外设时钟
↘ PLL / USB / ADC / RTC / TIM …对定时器来说,最关键的一点是:
TIM 计数时钟不一定等于 APB 总线时钟。
当 APB 预分频系数 ≠ 1 时,定时器时钟会自动 ×2。
常见 72 MHz 工程里:
| 时钟 | 频率 | 说明 |
|---|---|---|
| SYSCLK / HCLK / PCLK2 | 72 MHz | 系统、AHB、APB2 |
| PCLK1 | 36 MHz | APB1 上限 36 MHz |
| TIM2~TIM7 | 72 MHz | APB1 分频为 2 → 定时器 ×2 |
| TIM1 / TIM8 | 72 MHz | APB2 不分频 → 定时器 = PCLK2 |
所以教材里写 “TIM 时钟 72 MHz” 是对的,但 不是因为 PCLK1 也是 72 MHz。
二、时钟树总览
官方数据手册中的 RCC 时钟树(F103):
用流程图概括主干:
三、时钟源:每个时钟做什么
3.1 HSI — 高速内部时钟
| 项目 | 内容 |
|---|---|
| 全称 | High Speed Internal |
| 典型频率 | 8 MHz(片内 RC) |
| 作用 | 上电默认系统时钟;也可作 PLL 输入(经 /2) |
| 优点 | 无需外部晶振,启动快 |
| 缺点 | 精度较差(温度/电压影响),不适合精确波特率、USB |
复位后 MCU 默认跑在 HSI。很多 SystemInit() 会在启动阶段切到 HSE+PLL。
3.2 HSE — 高速外部时钟
| 项目 | 内容 |
|---|---|
| 全称 | High Speed External |
| 频率范围 | 4 ~ 16 MHz(最小系统板常见 8 MHz) |
| 作用 | 高精度主时钟源;经 PLL 倍频得到 72 MHz;USB 必须依赖 HSE |
| 接入方式 | 外部晶体 + 负载电容,或外部时钟直接喂 OSC_IN |
开启后要等 HSERDY 置位,再切换系统时钟,否则可能锁死或跑飞。
3.3 PLL — 锁相环倍频
| 项目 | 内容 |
|---|---|
| 全称 | Phase Locked Loop |
| 输入 | HSE(可选先 /2),或 HSI/2 |
| 倍频 | ×2 ~ ×16(PLLMUL) |
| 输出上限 | SYSCLK ≤ 72 MHz(F103);若用 HSI 作 PLL 输入,手册限制约 64 MHz |
| 作用 | 把 8 MHz 晶振倍到 72 MHz,供内核与多数外设 |
经典配置:
HSE 8 MHz → PLL ×9 → PLLCLK 72 MHz → SYSCLK3.4 LSI — 低速内部时钟
| 项目 | 内容 |
|---|---|
| 典型频率 | 约 40 kHz(不可精确) |
| 作用 | 独立看门狗 IWDG 时钟;也可作 RTC 时钟源 |
| 特点 | 低功耗、始终可用,但频率飘移大 |
3.5 LSE — 低速外部时钟
| 项目 | 内容 |
|---|---|
| 典型频率 | 32.768 kHz 晶振 |
| 作用 | RTC 首选时钟;也可输出校准时钟 |
| 特点 | 精度高,适合日历/闹钟;位于备份域,可用 VBAT 维持 |
3.6 其他派生时钟(常被忽略)
| 时钟 | 来源 | 用途 |
|---|---|---|
| CSS | 时钟安全系统 | HSE 故障时自动切回 HSI,并可产生 NMI |
| MCO | 主时钟输出(PA8) | 把 SYSCLK/HSI/HSE/PLL/2 引出到引脚,方便示波器测量 |
| USBCLK | PLLCLK /1 或 /1.5 | USB 需 48 MHz(72 MHz 时选 /1.5) |
| ADCCLK | PCLK2 /2,/4,/6,/8 | ADC 工作时钟,手册建议 ≤14 MHz |
| IWDGCLK | LSI | 独立看门狗 |
| RTCCLK | LSE / LSI / HSE÷128 | 实时时钟 |
| FCLK | = HCLK | Cortex-M3 自由运行时钟(调试等) |
| SysTick | HCLK 或 HCLK/8 | 系统滴答定时器 |
四、总线时钟:SYSCLK → HCLK → PCLK
4.1 SYSCLK(系统时钟)
三选一:
SW 选择 | 来源 |
|---|---|
| 00 | HSI |
| 01 | HSE |
| 10 | PLLCLK |
F103 最大值:72 MHz。
4.2 HCLK(AHB 时钟)
HCLK = SYSCLK / AHB_PrescalerAHB 预分频可选 /1, 2, 4, …, 512。
HCLK 供给:
- Cortex-M3 内核
- 存储器、DMA
- AHB 外设(如部分互联型外设)
- 再分出 APB1 / APB2
常见工程:AHB 不分频 → HCLK = 72 MHz。
4.3 PCLK1 / PCLK2(APB 时钟)
| 总线 | 预分频 | 上限(F103) | 挂载外设举例 |
|---|---|---|---|
| APB1 | /1,2,4,8,16 | 36 MHz | TIM2~7、USART2/3、I2C、SPI2/3、DAC、PWR、BKP |
| APB2 | /1,2,4,8,16 | 72 MHz | GPIO、AFIO、USART1、SPI1、ADC、TIM1/8 |
72 MHz 典型设置:
HCLK = 72 MHz
PCLK2 = HCLK / 1 = 72 MHz
PCLK1 = HCLK / 2 = 36 MHz五、定时器时钟:最容易算错的地方
5.1 规则
对挂在 APBx 上的定时器:
| APBx 预分频 | 定时器时钟 TIMxCLK |
|---|---|
| = 1(不分频) | TIMxCLK = PCLKx |
| > 1 | TIMxCLK = PCLKx × 2 |
5.2 72 MHz 工程对照
| 定时器 | 所在总线 | APB 预分频 | PCLK | TIM 时钟 |
|---|---|---|---|---|
| TIM2 / 3 / 4 / 5 / 6 / 7 | APB1 | /2 | 36 MHz | 72 MHz |
| TIM1 / TIM8 | APB2 | /1 | 72 MHz | 72 MHz |
因此 PSC/ARR 计算时,内部时钟模式统一按 72 MHz 来:
计数频率 = TIM_CLK / (PSC + 1)
溢出周期 = (ARR + 1) / 计数频率例:PSC = 7200-1,ARR = 10000-1 → 周期 1 s。
5.3 若你改了 APB1 分频
例如把 APB1 改成不分频(仅当 HCLK ≤ 36 MHz 才合法):
- PCLK1 = 36 MHz
- TIM2 时钟 = 36 MHz(不再 ×2)
- 原来按 72 MHz 写的 PSC/ARR 全部会翻倍变慢
改时钟树后,务必同步检查所有依赖频率的外设。
六、如何配置时钟(标准库)
6.1 启动默认路径:SystemInit()
上电复位后,启动文件会调用 SystemInit()(在 system_stm32f10x.c)。
库里通常用宏选择目标频率,例如:
#define SYSCLK_FREQ_72MHz 72000000对应内部会执行类似 SetSysClockTo72() 的流程:开 HSE → 等就绪 → 配 Flash 等待周期 → 配 AHB/APB 分频 → 配 PLL → 切 SYSCLK 到 PLL。
板子是 8 MHz 晶振 时,一般无需手改;若晶振是 12 MHz / 16 MHz,必须改 PLLMUL 或 PLLXTPRE,否则 SYSCLK 会超限或偏低。
6.2 标准库常用 RCC API
| 函数 | 作用 |
|---|---|
RCC_HSEConfig() | 开/关/旁路 HSE |
RCC_WaitForHSEStartUp() | 等待 HSE 稳定 |
RCC_PLLConfig() | 选 PLL 输入与倍频 |
RCC_PLLCmd() | 使能 PLL |
RCC_SYSCLKConfig() | 选择 SYSCLK 源 |
RCC_HCLKConfig() | AHB 预分频 |
RCC_PCLK1Config() / RCC_PCLK2Config() | APB1 / APB2 预分频 |
RCC_GetClocksFreq() | 读出当前各总线频率 |
RCC_ADCCLKConfig() | ADC 预分频 |
RCC_USBCLKConfig() | USB 预分频 |
RCC_LSEConfig() / RCC_LSICmd() | 低速时钟 |
RCC_RTCCLKConfig() | RTC 时钟源 |
RCC_MCOConfig() | PA8 输出时钟 |
RCC_xxxPeriphClockCmd() | 打开外设时钟门控 |
使用任何外设前,必须先 RCC_APBxPeriphClockCmd(..., ENABLE),否则写寄存器无效。
6.3 手写一套 72 MHz 配置(理解用)
下面逻辑与库内 SetSysClockTo72 等价,便于对照时钟树:
void SystemClock_Config_72MHz(void)
{
ErrorStatus HSEStartUpStatus;
/* 复位 RCC 到默认(HSI 作 SYSCLK) */
RCC_DeInit();
/* 打开 HSE,等待就绪 */
RCC_HSEConfig(RCC_HSE_ON);
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if (HSEStartUpStatus != SUCCESS) {
/* HSE 失败:可停在这里,或退回 HSI */
return;
}
/* Flash:72 MHz 需要 2 个等待周期,并开预取 */
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_2);
/* HCLK = SYSCLK;PCLK2 = HCLK;PCLK1 = HCLK/2 */
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
/* PLL = HSE * 9 = 72 MHz */
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
RCC_PLLCmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {
}
/* 切换 SYSCLK → PLL,并确认 */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while (RCC_GetSYSCLKSource() != 0x08) {
}
}配置顺序建议:
- 先稳定时钟源(HSE/HSI)
- 再配 Flash latency(频率升高前)
- 再配总线分频
- 再配并启动 PLL
- 最后切换 SW 到 PLL,并轮询
SWS
6.4 外设时钟门控示例
/* GPIO / AFIO / USART1 在 APB2 */
RCC_APB2PeriphClockCmd(
RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO | RCC_APB2Periph_USART1,
ENABLE);
/* TIM2 / USART2 在 APB1 */
RCC_APB1PeriphClockCmd(
RCC_APB1Periph_TIM2 | RCC_APB1Periph_USART2,
ENABLE);
/* ADC 额外还要配 ADCCLK 分频 */
RCC_ADCCLKConfig(RCC_PCLK2_Div6); /* 72/6 = 12 MHz */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);七、运行时查看当前时钟
调试时别猜频率,直接读:
RCC_ClocksTypeDef clocks;
RCC_GetClocksFreq(&clocks);
/* clocks.SYSCLK_Frequency
* clocks.HCLK_Frequency
* clocks.PCLK1_Frequency
* clocks.PCLK2_Frequency
* clocks.ADCCLK_Frequency
*/也可用 MCO 把时钟引到 PA8,用示波器量:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef gpio;
gpio.GPIO_Pin = GPIO_Pin_8;
gpio.GPIO_Mode = GPIO_Mode_AF_PP;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio);
RCC_MCOConfig(RCC_MCO_PLLCLK_Div2); /* 输出 PLL/2 = 36 MHz */八、和定时器、串口相关的计算清单
8.1 定时器
- 确认该 TIM 在 APB1 还是 APB2
- 看 APB 预分频是否 >1,决定是否 ×2
- 得到
TIM_CLK后再算 PSC / ARR
8.2 USART
波特率分频基于 PCLK(不是 TIM 那种 ×2 规则):
- USART1 → PCLK2
- USART2/3 → PCLK1
改 APB 分频后,波特率寄存器要重算。
8.3 SysTick
CMSIS 里常见:
SysTick_Config(SystemCoreClock / 1000); /* 1 ms */SystemCoreClock 应等于 HCLK。若你手动改过时钟,记得调用:
SystemCoreClockUpdate();九、配置检查清单
| 检查项 | 建议 |
|---|---|
| 外部晶振频率 | 与 PLL 倍频匹配,SYSCLK ≤ 72 MHz |
| Flash 等待 | 48~72 MHz 用 FLASH_Latency_2 |
| APB1 | ≤ 36 MHz |
| USB | PLL 必须开,USBCLK = 48 MHz |
| ADC | ADCCLK ≤ 14 MHz |
| HSE 失败 | 做好 CSS 或回退 HSI 的处理 |
| 改分频后 | 重算 TIM / USART / ADC / SysTick |
| 外设使用前 | 打开对应 RCC_APBxPeriphClockCmd |
十、一张表记住“谁给谁供时”
| 名称 | 典型值(72M 工程) | 主要消费者 |
|---|---|---|
| HSI | 8 MHz | 默认启动、备份时钟源 |
| HSE | 8 MHz | PLL 输入、高精度外设 |
| PLLCLK | 72 MHz | SYSCLK、USB(再分频) |
| SYSCLK | 72 MHz | 整棵树的根 |
| HCLK | 72 MHz | 内核、DMA、存储器、SysTick |
| PCLK1 | 36 MHz | APB1 外设总线 |
| PCLK2 | 72 MHz | APB2 外设总线 |
| TIMxCLK(APB1) | 72 MHz | TIM2~7(因 ×2) |
| TIMxCLK(APB2) | 72 MHz | TIM1/8 |
| ADCCLK | 12 MHz 等 | ADC |
| USBCLK | 48 MHz | USB |
| LSE | 32.768 kHz | RTC |
| LSI | ~40 kHz | IWDG、可选 RTC |
理解这张表之后,再去看定时器 PSC/ARR、串口波特率,就不会“频率对不上”了。