为什么时钟配置是第一道坎

STM32 上电默认跑内部 8MHz HSI。不配时钟,定时器不准、串口乱码、外设不工作。理解时钟树是 STM32 开发的基础。

一、时钟源

STM32F103 有 5 个时钟源:

时钟源 频率 精度 用途
HSI 8MHz 差(±1%,温漂大) 默认启动,调试
HSE 4~16MHz 高(晶振 ppm 级) 正式产品必用
PLL 最高 72MHz 取决于输入源 系统时钟 SYSCLK
LSI ~40KHz IWDG 独立看门狗
LSE 32.768KHz RTC 实时时钟

二、时钟树(F103 完整路径)

关键规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. SYSCLK = HSE / PLLXTPRE × PLLMUL
例:8MHz / 1 × 9 = 72MHz

2. HCLK = SYSCLK / AHB_Prescaler
例:72MHz / 1 = 72MHz

3. PCLK1 = HCLK / APB1_Prescaler ≤ 36MHz(F103 限制)
例:72MHz / 2 = 36MHz

4. PCLK2 = HCLK / APB2_Prescaler ≤ 72MHz
例:72MHz / 1 = 72MHz

5. 定时器时钟 = {
if APB prescaler == 1 → PCLK
else → PCLK × 2
}
例:APB1=36M(/2) → TIM2~7 = 36M×2 = 72MHz

三、HAL 库时钟配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

/* 1. 配置时钟源 */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 开启外部晶振
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL 源 = HSE
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // ×9 = 72MHz
HAL_RCC_OscConfig(&RCC_OscInitStruct);

/* 2. 配置总线分频 */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK |
RCC_CLOCKTYPE_SYSCLK |
RCC_CLOCKTYPE_PCLK1 |
RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // PLL 输出
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // HCLK = 72MHz
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // PCLK1 = 36MHz
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // PCLK2 = 72MHz
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
// FLASH_LATENCY_2: SYSCLK>48MHz 需要 2 个等待周期
}

CubeMX 图形化配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Pinout → RCC:
HSE: Crystal/Ceramic Resonator
LSE: Disable

Clock Configuration:
HSE: 8 MHz
PLL Source: HSE
PLL Mul: ×9
SYSCLK: 72 MHz ← 最终系统时钟
HCLK: 72 MHz
APB1: 36 MHz (max for F103)
APB2: 72 MHz

右下角自动计算各外设时钟,确保不超限(红色=超限)

四、时钟输出验证(MCO 引脚)

1
2
3
4
5
6
7
8
9
10
11
12
/* PA8 输出 SYSCLK,用示波器直接测频率 */
void MCO_Config(void) {
GPIO_InitTypeDef gpio = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
gpio.Pin = GPIO_PIN_8;
gpio.Mode = GPIO_MODE_AF_PP;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &gpio);

HAL_RCC_MCOConfig(RCC_MCO, RCC_MCO1SOURCE_SYSCLK, RCC_MCODIV_1);
// PA8 输出 72MHz 方波 → 示波器验证
}

五、各外设时钟速查

外设 挂在哪个总线 时钟来源 典型频率
SYSTICK Cortex HCLK/8 或 HCLK 9MHz 或 72MHz
USART1 APB2 PCLK2 72MHz
USART2/3 APB1 PCLK1 36MHz
SPI1 APB2 PCLK2 72MHz
SPI2 APB1 PCLK1 36MHz
I2C1/2 APB1 PCLK1 36MHz
TIM1/8 APB2 PCLK2×2 = 72M×2 144MHz
TIM2~7 APB1 PCLK1×2 = 36M×2 72MHz
ADC APB2 PCLK2 (÷2~8) 最大 14MHz
USB 专用 48MHz 必须 48MHz

六、常见配置错误

错误 1:波特率不准

1
2
3
4
5
6
7
8
USART1: 波特率 = PCLK2 / (16 × USARTDIV)
= 72MHz / (16 × 39.0625) = 115200

如果误配 PCLK2 为 36MHz:
= 36MHz / (16 × 19.53125) = 115200... 不对!
→ 实际输出 115384,误差 0.16% → 可能乱码

规则:波特率误差 > 2% 就会丢字节。

错误 2:定时器周期不准

1
2
3
4
5
TIM2: 定时频率 = (PCLK1 × 2) / (PSC+1) / (ARR+1)
= 72MHz / 7200 / 10000 = 1Hz ✓

如果忘记 APB1 的 ×2 规则:
= 36MHz / 7200 / 10000 = 0.5Hz ✗ 差了一倍!

错误 3:忘记配 FLASH_LATENCY

1
2
SYSCLK > 48MHz 必须 FLASH_LATENCY = 2
否则:程序随机 HardFault,时好时坏。

错误 4:USB 时钟不准

1
2
3
4
USB 需要精确 48MHz。
HSE=8MHz ×9=72MHz ×(2/3)=48MHz → OK
HSE=12MHz ×6=72MHz → USB 只能 72/1.5=48 → OK
HSE=8MHz ×8=64MHz → USB 只能 64/1.5≠48 → 不行!

七、外设时钟开关(省电)

1
2
3
4
5
6
7
8
9
/* 每个外设使用前必须开时钟,不用时关闭省电 */
__HAL_RCC_GPIOA_CLK_ENABLE(); // GPIOA
__HAL_RCC_USART1_CLK_ENABLE(); // 串口1
__HAL_RCC_I2C1_CLK_ENABLE(); // I2C1
__HAL_RCC_TIM2_CLK_ENABLE(); // 定时器2

/* 关闭不用的外设时钟降低功耗 */
__HAL_RCC_ADC1_CLK_DISABLE();
__HAL_RCC_SPI2_CLK_DISABLE();

八、调试时钟问题的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 用示波器测 MCO (PA8) 输出确认 SYSCLK
→ 72MHz 应该是稳定方波

2. 用定时器 + 输出比较翻转引脚测频率
→ TIM2_CH1(PA0) 输出 1KHz PWM → 示波器测

3. 串口输出 SystemCoreClock 变量
→ printf("SYSCLK=%lu\r\n", SystemCoreClock);

4. CubeMX Clock Configuration 红色警告
→ 任何红色数字 = 超限,必须调整分频

5. 逻辑分析仪捕获 I2C/SPI 时钟线频率
→ 频率与实际配置不符 = 时钟树计算有误