08-时钟系统(RCC).md 35 KB

STM32 时钟系统:RCC

基于铁头山羊(38, 39)、HAL库教程(16, 17)、江科大(13-2)三教程综合整理


1. 时钟系统的作用

时钟(Clock)是数字电路的心跳。芯片内部的每个寄存器、计数器、通信接口等所有数字逻辑都在时钟沿的驱动下工作。RCC(Reset and Clock Control)模块负责:

  • 管理 STM32 的所有时钟源
  • 分配时钟到各个外设总线
  • 配置系统主频(SYSCLK)
  • 控制外设时钟的使能与关闭(省电)

STM32 系统架构图 上图:STM32 系统架构(包含总线矩阵与各外设挂载关系)(来源:STM32入门教程 PPT 第9页)

STM32 时钟树结构 上图:STM32F10x 时钟树结构示意图(来源:STM32入门教程 PPT 第62页)

2. 时钟源

STM32F10x 共有 5 个时钟源

时钟源 名称 频率 精度 用途
HSI 内部高速振荡器 8 MHz 较低(出厂校准) 上电默认时钟源,可作为 PLL 输入
HSE 外部高速振荡器 4~16 MHz(常用 8 MHz) 高(晶振决定) 可作为 PLL 输入,系统时钟
PLL 锁相环 最高 72 MHz 取决于输入源 倍频输出作为 SYSCLK
LSI 内部低速振荡器 40 kHz 较低 独立看门狗(IWDG)、RTC
LSE 外部低速振荡器 32.768 kHz 高(专门音叉晶振) RTC 时钟

2.1 PLL 时钟来源

PLL 的输入可以选择:

HSI(8 MHz)
   ↓ 二分频 → 4 MHz → PLL ×2~×16 → 最高 72 MHz
   
HSE(8 MHz 外部晶振)
   ↓ 直通或二分频 → 8 MHz 或 4 MHz → PLL ×2~×16 → 最高 72 MHz

默认配置: HSE = 8 MHz → 不分频 → PLL ×9 = 72 MHz。

2.2 系统时钟树

                      ┌──────────────┐
                      │   HSI 8 MHz  │
                      └──────┬───────┘
                             │
                      ┌──────▼───────┐
                      │   HSE 8 MHz  │
                      └──────┬───────┘
                             │
                      ┌──────▼───────┐
                      │  PLL ×9      │  → 72 MHz
                      └──────┬───────┘
                             │
                   ┌─────────▼──────────┐
                   │  SYSCLK 选择器      │  ← 可选择 HSI / HSE / PLL
                   └─────────┬──────────┘
                             │
                      ┌──────▼───────┐
                      │  AHB 分频器   │  → HCLK(内核、存储器、DMA)
                      └──────┬───────┘
                             │
              ┌──────────────┼────────────────┐
              │              │                │
       ┌──────▼──────┐ ┌────▼─────┐   ┌──────▼──────┐
       │ APB1 分频器  │ │ APB2 分频器│   │ 其他外设    │
       │ ≤36 MHz     │ │ ≤72 MHz   │   │             │
       └──────┬──────┘ └────┬─────┘   └─────────────┘
              │             │
        ┌─────▼─────┐  ┌───▼────┐
        │ TIM 等     │  │GPIO、ADC│
        │ APB1 外设  │  │USART1等 │
        └───────────┘  └────────┘

2.3 各总线最高频率

总线 最高频率 挂载的典型外设
AHB 72 MHz 内核、存储器、DMA
APB1 36 MHz TIM2~TIM7、USART2~5、SPI2/3、I2C1/2
APB2 72 MHz GPIO、USART1、SPI1、ADC1/2、TIM1/TIM8

注意:APB1 上的定时器(TIM2~TIM7)时钟源实际等于 APB1 ×2(当 APB1 分频系数 ≠1 时),所以在 APB1=36MHz 时 TIM 仍可获得 72MHz 的计数时钟。

时钟配置流程详解(尚硅谷风格)

STM32上电后默认使用HSI(8MHz),要切换为HSE+PLL=72MHz需要按照固定流程:

PLL配置步骤:

  1. 打开HSE并等待稳定 → 2. 配置FLASH等待周期(72MHz需2个等待周期)
  2. 配置PLL倍频系数 → 4. 配置AHB/APB预分频器
  3. 开启PLL并等待稳定 → 6. 切换系统时钟为PLL输出

为什么需要FLASH等待周期?

  • FLASH读取速度跟不上CPU速度时,需要插入等待周期
  • 72MHz时至少需要2个等待周期,否则读FLASH会出错导致程序跑飞
  • 设置寄存器:FLASH->ACR = 0x32;

AHB/APB预分频的作用:

  • AHB最高72MHz(通常=SYSCLK)
  • APB2最高72MHz(连接GPIO、USART1、SPI1、ADC等高速外设)
  • APB1最高36MHz(连接USART2~5、I2C、SPI2/3、TIM2~7等低速外设)
  • 当APB1预分频≠1时,定时器时钟自动翻倍(2×APB1时钟)

3. SystemInit 与默认时钟配置

3.1 上电启动流程

启动文件(startup_stm32f10x_xx.s)在跳转到 main 之前会调用 SystemInit()

SystemInit() 的功能:

  1. 复位 RCC 寄存器
  2. 配置外部存储器接口(FSMC 等)
  3. 调用 SetSysClock() 设置系统时钟
  4. 更新 SystemCoreClock 全局变量(记录当前 SYSCLK 频率)

3.2 SetSysClock 配置流程

SetSysClock()system_stm32f10x.c 中实现,根据宏定义选择目标频率:

// 在 system_stm32f10x.c 中可选择以下宏之一
// #define SYSCLK_FREQ_HSE    HSE_VALUE    // 直接使用 HSE
// #define SYSCLK_FREQ_24MHz  24000000
// #define SYSCLK_FREQ_36MHz  36000000
// #define SYSCLK_FREQ_48MHz  48000000
// #define SYSCLK_FREQ_56MHz  56000000
#define SYSCLK_FREQ_72MHz  72000000         // 默认启用
// #define SYSCLK_FREQ_24MHz  24000000

SYSCLK = 72 MHz 的配置步骤(典型):

  1. 等待 HSI 就绪(上电默认 HSI)
  2. 使能 HSE,等待 HSE 就绪
  3. 配置 Flash 等待周期(72MHz 需要 2 个等待周期)
  4. 配置 AHB 分频 = 1(AHB = SYSCLK = 72 MHz)
  5. 配置 APB1 分频 = 2(APB1 = 36 MHz)
  6. 配置 APB2 分频 = 1(APB2 = 72 MHz)
  7. 配置 PLL:HSE 不分频 → PLL ×9 → 72 MHz
  8. 使能 PLL,等待 PLL 就绪
  9. 切换 SYSCLK 到 PLL 输出
  10. 等待 SYSCLK 切换完成

3.3 SetSysClockTo72 完整代码(标准库)

以下代码来自 system_stm32f10x.c,当定义 SYSCLK_FREQ_72MHz 宏时被 SetSysClock() 调用:

static void SetSysClockTo72(void)
{
    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

    /* 使能 HSE */
    // CR寄存器bit16:HSEON, |=按位或:保持其他位不变,只将bit16置1,启动外部晶振
    RCC->CR |= ((uint32_t)RCC_CR_HSEON);

    /* 等待 HSE 就绪,超时则退出 */
    do
    {
        // & 按位与:读取CR寄存器bit17(HSERDY),仅保留该位,其余位清零
        // HSERDY=1表示HSE已稳定振荡,=0表示尚未就绪
        HSEStatus = RCC->CR & RCC_CR_HSERDY;
        StartUpCounter++;
    } while ((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

    // & 按位与:再次读取HSERDY位判断HSE是否起振成功
    if ((RCC->CR & RCC_CR_HSERDY) != RESET)
        HSEStatus = (uint32_t)0x01;
    else
        HSEStatus = (uint32_t)0x00;

    if (HSEStatus == (uint32_t)0x01)
    {
        /* 使能预取缓冲 */
        // ACR寄存器bit2:PRFTBE, |=按位或:保持其他位不变,将bit2置1启用预取缓冲
        // 预取缓冲可以加速CPU从Flash读取指令,减少等待周期的影响
        FLASH->ACR |= FLASH_ACR_PRFTBE;

        /* Flash 2 个等待周期(72MHz 需要) */
        // ACR寄存器[2:0]:LATENCY位域控制等待周期数
        // &= ~按位与+取反:先将LATENCY位域[2:0]全部清零(3位同时清零)
        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
        // |=按位或:将LATENCY位域设为010(二进制)=2个等待周期,与上一步配合
        // 72MHz必须≥2个等待周期,否则Flash读取跟不上CPU速度导致取指错误
        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;

        /* HCLK = SYSCLK(AHB 不分频) */
        // CFGR寄存器[7:4]:HPRE位域,0011=AHB不分频(SYSCLK=HCLK)
        // |=按位或:保持其他位不变,将HPRE位域写入分频编码
        RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;

        /* PCLK2 = HCLK(APB2 不分频) */
        // CFGR寄存器[13:11]:PPRE2位域,000=APB2不分频(PCLK2=HCLK=72MHz)
        // |=按位或:写入PPRE2编码,保持其他CFGR位不变
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;

        /* PCLK1 = HCLK / 2(APB1 二分频) */
        // CFGR寄存器[10:8]:PPRE1位域,100=APB1二分频(PCLK1=HCLK/2=36MHz)
        // |=按位或:写入PPRE1编码,保持其他位不变
        // APB1最高36MHz,72MHz必须二分频,否则APB1外设可能工作异常
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

        /* PLL 配置:PLLCLK = HSE × 9 = 72 MHz */
        // CFGR寄存器bit16:PLLSRC(选择PLL输入源),bit17:PLLXTPRE(HSE预分频)
        // [21:18]:PLLMUL(倍频系数)    // &= ~按位与+取反:将这3个位域一并清零,为重新写入做准备
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC |
                                            RCC_CFGR_PLLXTPRE |
                                            RCC_CFGR_PLLMULL));
        // |=按位或: PLLSRC=1选择HSE,PLLMUL=0111(×9),PLLXTPRE=0(不分频)
        // 输出=8MHz×9=72MHz,保持其他CFGR位不变
        RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);

        /* 使能 PLL */
        // CR寄存器bit24:PLLON, |=按位或:保持其他位不变,将bit24置1启动PLL
        RCC->CR |= RCC_CR_PLLON;
        // &按位与:读取CR寄存器bit25(PLLRDY),循环等待PLL锁相完成
        // PLLRDY=1表示输出已稳定,=0表示还在锁定过程中
        while ((RCC->CR & RCC_CR_PLLRDY) == 0) { }

        /* 选择 PLL 作为系统时钟 */
        // CFGR寄存器[1:0]:SW位域,选择系统时钟源
        // &= ~按位与+取反:将SW两位同时清零(清除之前的选择)
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        // |=按位或: SW=10(二进制),选择PLL输出作为系统时钟SYSCLK
        RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;

        /* 等待 PLL 切换完成 */
        // & 按位与:读取CFGR寄存器[3:2]:SWS位域,只保留这2位
        // 0x08=0000_1000(SWS=10),表示系统时钟已切换为PLL
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { }
    }
    else
    {
        /* HSE 起振失败 — 可在此添加错误处理代码 */
    }
}

3.4 SetSysClockTo72 HAL库等价代码

static void SetSysClockTo72(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    /* HSE 振荡器配置:使能 HSE,作为 PLL 输入 */
    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;
    RCC_OscInitStruct.PLL.PLLMUL     = RCC_PLL_MUL9;       /* HSE × 9 = 72 MHz */
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { }

    /* 时钟树配置:SYSCLK = PLL72MHz,总线分频 */
    RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_HCLK  |
                                       RCC_CLOCKTYPE_SYSCLK |
                                       RCC_CLOCKTYPE_PCLK1  |
                                       RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;    /* HCLK = 72 MHz */
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;      /* PCLK2 = 72 MHz */
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;      /* PCLK1 = 36 MHz */

    /* Flash 等待周期:72MHz 需要 2 个等待周期 */
    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { }
}

3.6 SetSysClockToHSE 完整代码(标准库 — HSE 直通)

static void SetSysClockToHSE(void)
{
    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

    // CR[16]: HSEON=1, |=按位或: 启动外部高速晶振,保持其他CR位不变
    RCC->CR |= ((uint32_t)RCC_CR_HSEON);

    do
    {
        // &按位与: 读取CR[17]HSERDY位,只保留该位,等待HSE稳定
        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;

    if (HSEStatus == (uint32_t)0x01)
    {
        // ACR[2]: PRFTBE=1, |=按位或: 使能预取缓冲,加速Flash读取
        FLASH->ACR |= FLASH_ACR_PRFTBE;
        // ACR[2:0]: LATENCY位域, &= ~按位与+取反: 全部清零
        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
        // |=按位或: LATENCY=000=0个等待周期(HSE直通,8MHz无需等待)
        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_0;

        // CFGR[7:4]: HPRE=0011, |=按位或: AHB不分频,HCLK=8MHz
        RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
        // CFGR[13:11]: PPRE2=000, |=按位或: APB2不分频,PCLK2=8MHz
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
        // CFGR[10:8]: PPRE1=000, |=按位或: APB1不分频,PCLK1=8MHz
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV1;

        // CFGR[1:0]: SW系统时钟选择位域
        // &= ~按位与+取反: 先清零SW两位
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        // |=按位或: SW=01(二进制), 选择HSE作为SYSCLK源
        RCC->CFGR |= (uint32_t)RCC_CFGR_SW_HSE;

        // &按位与: 读取CFGR[3:2]SWS状态位, 0x04=SWS=01,等待切换完成
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x04) { }
    }
}

/* --- HAL库等价 --- */
static void SetSysClockToHSE(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_NONE;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);

    RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_HCLK  |
                                       RCC_CLOCKTYPE_SYSCLK |
                                       RCC_CLOCKTYPE_PCLK1  |
                                       RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_HSE;
    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
}

3.7 SetSysClockTo24 完整代码(标准库 — 24 MHz)

static void SetSysClockTo24(void)
{
    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

    // CR[16]: HSEON=1, |=按位或: 使能HSE晶振
    RCC->CR |= ((uint32_t)RCC_CR_HSEON);

    do
    {
        // & 按位与: 读取CR[17]HSERDY位,仅保留该位,等待HSE稳定
        HSEStatus = RCC->CR & RCC_CR_HSERDY;
        StartUpCounter++;
    } while ((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

    // & 按位与: 再次检验HSERDY位是否=1,确认HSE已稳定
    if ((RCC->CR & RCC_CR_HSERDY) != RESET)
        HSEStatus = (uint32_t)0x01;
    else
        HSEStatus = (uint32_t)0x00;

    if (HSEStatus == (uint32_t)0x01)
    {
        // ACR[2] PRFTBE, |= 按位或: 保持其他位不变,使能预取缓冲
        FLASH->ACR |= FLASH_ACR_PRFTBE;
        // ACR[2:0] LATENCY, &= ~ 按位与+取反: 清零等待周期位域
        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
        // |= 按位或: LATENCY=000=0等待周期; 24MHz<36MHz无需等待
        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_0;

        // CFGR[7:4] HPRE, |= 按位或: HPRE=0011, AHB不分频, HCLK=SYSCLK
        RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
        // CFGR[13:11] PPRE2, |= 按位或: PPRE2=000, APB2不分频, PCLK2=AHB
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
        // CFGR[10:8] PPRE1, |= 按位或: PPRE1=000, APB1不分频(24MHz<36MHz上限)
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV1;

        // &=~: 清零PLLSRC,PLLXTPRE,PLLMULL三个位域,准备重新配置
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
        // |=: PLLSRC=1选择HSE, PLLXTPRE=1(HSE二分频→4MHz), PLLMUL=0101(×6)
        // 输出=4MHz×6=24MHz
        RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLXTPRE_HSE_Div2 | RCC_CFGR_PLLMULL6);

        // CR[24] PLLON, |= 按位或: 保持其他位不变,启动PLL
        RCC->CR |= RCC_CR_PLLON;
        // & 按位与: 读取CR[25]PLLRDY位,循环等待PLL锁定完成(PLLRDY=1)
        while ((RCC->CR & RCC_CR_PLLRDY) == 0) { }

        // CFGR[1:0] SW, &= ~ 按位与+取反: 清零SW两位,清除之前的时钟源选择
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        // |= 按位或: SW=10(二进制),选择PLL输出作为系统时钟SYSCLK
        RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
        // & 按位与: 读取CFGR[3:2]SWS状态位, 0x08=SWS=10,等待切换完成
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { }
    }
}

/* --- HAL库等价 --- */
static void SetSysClockTo24(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource  = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL     = RCC_PLL_MUL6;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);

    RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_ALL;
    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
}

3.8 SetSysClockTo36 完整代码(标准库 — 36 MHz)

static void SetSysClockTo36(void)
{
    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

    // CR[16] HSEON, |= 按位或: 保持其他位不变,启动HSE外部晶振
    RCC->CR |= ((uint32_t)RCC_CR_HSEON);

    do
    {
        // & 按位与: 读取CR[17]HSERDY位,仅保留该位,等待HSE稳定
        HSEStatus = RCC->CR & RCC_CR_HSERDY;
        StartUpCounter++;
    } while ((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

    // & 按位与: 读取CR[17]HSERDY位判断HSE是否起振成功
    if ((RCC->CR & RCC_CR_HSERDY) != RESET)
        HSEStatus = (uint32_t)0x01;
    else
        HSEStatus = (uint32_t)0x00;

    if (HSEStatus == (uint32_t)0x01)
    {
        // ACR[2] PRFTBE, |= 按位或: 保持其他位不变,使能预取缓冲
        FLASH->ACR |= FLASH_ACR_PRFTBE;
        // ACR[2:0] LATENCY, &= ~ 按位与+取反: 清零等待周期位域
        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
        // |= 按位或: LATENCY=001=1等待周期, 36MHz需要1个等待周期
        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_1;

        // CFGR[7:4] HPRE, |= 按位或: HPRE=0011, AHB不分频
        RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
        // CFGR[13:11] PPRE2, |= 按位或: PPRE2=000, APB2不分频
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
        // CFGR[10:8] PPRE1, |= 按位或: PPRE1=000, APB1不分频(36MHz上限)
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV1;

        // CFGR寄存器bit16:PLLSRC,bit17:PLLXTPRE,[21:18]:PLLMUL
        // &= ~ 按位与+取反: 将这三个位域一并清零,为重新配置做准备
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
        // |= 按位或: PLLSRC=1选HSE, PLLXTPRE=1(HSE二分频→4MHz), PLLMUL=0111(×9)
        // 输出=4MHz×9=36MHz
        RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLXTPRE_HSE_Div2 | RCC_CFGR_PLLMULL9);

        // CR[24] PLLON, |= 按位或: 保持其他位不变,启动PLL
        RCC->CR |= RCC_CR_PLLON;
        // & 按位与: 读取CR[25]PLLRDY位,循环等待PLL锁定完成
        while ((RCC->CR & RCC_CR_PLLRDY) == 0) { }

        // CFGR[1:0] SW, &= ~ 按位与+取反: 清零SW两位
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        // |= 按位或: SW=10,选择PLL为SYSCLK
        RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
        // & 按位与: 读取CFGR[3:2]SWS状态位, 0x08=SWS=10,等待切换完成
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { }
    }
}

/* HAL库等价版 */
static void SetSysClockTo36(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource  = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL     = RCC_PLL_MUL9;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);

    RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_ALL;
    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1);
}

3.9 SetSysClockTo48 完整代码(标准库 — 48 MHz)

static void SetSysClockTo48(void)
{
    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

    // CR[16] HSEON, |= 按位或: 保持其他位不变,启动HSE晶振
    RCC->CR |= ((uint32_t)RCC_CR_HSEON);

    do
    {
        // & 按位与: 读取CR[17]HSERDY位,仅保留该位,等待HSE稳定
        HSEStatus = RCC->CR & RCC_CR_HSERDY;
        StartUpCounter++;
    } while ((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

    // & 按位与: 再次检验HSERDY位确认HSE已稳定
    if ((RCC->CR & RCC_CR_HSERDY) != RESET)
        HSEStatus = (uint32_t)0x01;
    else
        HSEStatus = (uint32_t)0x00;

    if (HSEStatus == (uint32_t)0x01)
    {
        // ACR[2] PRFTBE, |= 按位或: 保持其他位不变,使能预取缓冲
        FLASH->ACR |= FLASH_ACR_PRFTBE;
        // ACR[2:0] LATENCY, &= ~ 按位与+取反: 清零等待周期位域
        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
        // |= 按位或: LATENCY=001=1等待周期, 48MHz需要1个等待周期
        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_1;

        // CFGR[7:4] HPRE, |= 按位或: HPRE=0011, AHB不分频
        RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
        // CFGR[13:11] PPRE2, |= 按位或: PPRE2=000, APB2不分频
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
        // CFGR[10:8] PPRE1, |= 按位或: PPRE1=100, APB1二分频(48/2=24MHz<36MHz)
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

        // CFGR: PLLSRC[16], PLLXTPRE[17], PLLMUL[21:18]
        // &= ~ 按位与+取反: 将三个位域清零,准备写入新配置
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
        // |= 按位或: PLLSRC=1选HSE, PLLMUL=0101(×6), PLLXTPRE=0不分频
        // 输出=8MHz×6=48MHz
        RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL6);

        // CR[24] PLLON, |= 按位或: 保持其他位不变,启动PLL
        RCC->CR |= RCC_CR_PLLON;
        // & 按位与: 读取CR[25]PLLRDY位,循环等待PLL锁定完成
        while ((RCC->CR & RCC_CR_PLLRDY) == 0) { }

        // CFGR[1:0] SW, &= ~ 按位与+取反: 清零SW两位
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        // |= 按位或: SW=10,选择PLL为SYSCLK
        RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
        // & 按位与: 读取CFGR[3:2]SWS状态位, 0x08=SWS=10,等待切换完成
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { }
    }
}

/* HAL库等价版 */
static void SetSysClockTo48(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource  = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL     = RCC_PLL_MUL6;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);

    RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_ALL;
    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1);
}

3.10 SetSysClockTo56 完整代码(标准库 — 56 MHz)

static void SetSysClockTo56(void)
{
    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

    // CR[16] HSEON, |= 按位或: 保持其他位不变,启动HSE晶振
    RCC->CR |= ((uint32_t)RCC_CR_HSEON);

    do
    {
        // & 按位与: 读取CR[17]HSERDY位,仅保留该位,等待HSE稳定
        HSEStatus = RCC->CR & RCC_CR_HSERDY;
        StartUpCounter++;
    } while ((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

    // & 按位与: 再次检验HSERDY位确认HSE已稳定
    if ((RCC->CR & RCC_CR_HSERDY) != RESET)
        HSEStatus = (uint32_t)0x01;
    else
        HSEStatus = (uint32_t)0x00;

    if (HSEStatus == (uint32_t)0x01)
    {
        // ACR[2] PRFTBE, |= 按位或: 保持其他位不变,使能预取缓冲
        FLASH->ACR |= FLASH_ACR_PRFTBE;
        // ACR[2:0] LATENCY, &= ~ 按位与+取反: 清零等待周期位域
        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
        // |= 按位或: LATENCY=010=2等待周期, 56MHz需2个等待周期
        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;

        // CFGR[7:4] HPRE, |= 按位或: HPRE=0011, AHB不分频(56MHz)
        RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
        // CFGR[13:11] PPRE2, |= 按位或: PPRE2=000, APB2不分频(56MHz)
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
        // CFGR[10:8] PPRE1, |= 按位或: PPRE1=100, APB1二分频(56/2=28MHz<36MHz)
        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

        // CFGR: PLLSRC[16], PLLXTPRE[17], PLLMUL[21:18]
        // &= ~ 按位与+取反: 将三个位域清零,准备写入新配置
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
        // |= 按位或: PLLSRC=1选HSE, PLLMUL=0110(×7), PLLXTPRE=0不分频
        // 输出=8MHz×7=56MHz
        RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL7);

        // CR[24] PLLON, |= 按位或: 保持其他位不变,启动PLL
        RCC->CR |= RCC_CR_PLLON;
        // & 按位与: 读取CR[25]PLLRDY位,循环等待PLL锁定完成
        while ((RCC->CR & RCC_CR_PLLRDY) == 0) { }

        // CFGR[1:0] SW, &= ~ 按位与+取反: 清零SW两位
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        // |= 按位或: SW=10,选择PLL为SYSCLK
        RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
        // & 按位与: 读取CFGR[3:2]SWS状态位, 0x08=SWS=10,等待切换完成
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { }
    }
}

/* HAL库等价版 */
static void SetSysClockTo56(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource  = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL     = RCC_PLL_MUL7;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);

    RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_ALL;
    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}

3.11 MCO 时钟输出配置

MCO(Microcontroller Clock Output)可将内部时钟从 PA8 引脚输出,供外部测量或作为其它芯片的时钟源。

/* 标准库版:PA8 输出 PLL/2 = 36 MHz */
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

RCC_MCOConfig(RCC_MCO_PLLCLK_DIV2);   /* MCO = PLL / 2 */

/* 其他可选时钟源 */
// RCC_MCOConfig(RCC_MCO_HSI);        /* MCO = HSI 8 MHz       */
// RCC_MCOConfig(RCC_MCO_HSE);        /* MCO = HSE 8 MHz       */
// RCC_MCOConfig(RCC_MCO_SYSCLK);     /* MCO = SYSCLK           */
// RCC_MCOConfig(RCC_MCO_PLLCLK_DIV2);/* MCO = PLLCLK / 2      */
/* HAL库等价:PA8 输出 PLL/2 = 36 MHz */
GPIO_InitTypeDef GPIO_InitStruct = {0};

__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin       = GPIO_PIN_8;
GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

HAL_RCC_MCOConfig(RCC_MCO1SOURCE_PLLCLK, RCC_MCODIV_2);

3.12 SystemCoreClock 变量

// system_stm32f10x.c 中定义
uint32_t SystemCoreClock = 72000000;

// 用户代码可直接读取
OLED_ShowNum(1, 8, SystemCoreClock, 8);  // 显示当前系统主频

如果程序运行中修改了时钟配置(比如降低主频进入低功耗),需要调用 SystemCoreClockUpdate() 更新此变量。


4. 外设时钟使能

RCC 通过三个寄存器组控制外设时钟的开关。使用时只需调用以下标准库函数:

总线 标准库函数 HAL 宏
APB2 RCC_APB2PeriphClockCmd() __HAL_RCC_GPIOA_CLK_ENABLE()
APB1 RCC_APB1PeriphClockCmd() __HAL_RCC_TIM2_CLK_ENABLE()
AHB RCC_AHBPeriphClockCmd() __HAL_RCC_DMA1_CLK_ENABLE()

标准库示例:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

HAL库示例:

__HAL_RCC_GPIOB_CLK_ENABLE();     /* APB2 — GPIOB 时钟使能 */
__HAL_RCC_TIM2_CLK_ENABLE();      /* APB1 — TIM2 时钟使能 */
__HAL_RCC_DMA1_CLK_ENABLE();      /* AHB  — DMA1 时钟使能 */

__HAL_RCC_USART1_CLK_ENABLE();    /* APB2 — USART1 时钟使能 */
__HAL_RCC_ADC1_CLK_ENABLE();      /* APB2 — ADC1 时钟使能 */
__HAL_RCC_SPI1_CLK_ENABLE();      /* APB2 — SPI1 时钟使能 */

不能在初始化函数中关闭时钟后再开启,否则外设寄存器会丢失配置。

常见外设的时钟总线归属

APB1(36 MHz 最高) APB2(72 MHz 最高)
TIM2, TIM3, TIM4, TIM5, TIM6, TIM7 TIM1, TIM8
USART2, USART3, USART4, USART5 USART1
SPI2, SPI3 SPI1
I2C1, I2C2 GPIOA~GPIOC(部分)
PWR, BKP AFIO, EXTI

5. 修改主频实验

5.1 实验思路

通过修改 system_stm32f10x.c 中的宏定义来改变 SYSCLK 频率,观察主循环运行快慢和 SystemCoreClock 的值。

5.2 代码

int main(void)
{
    OLED_Init();
    
    OLED_ShowString(1, 1, "SYSCLK:");
    OLED_ShowNum(1, 8, SystemCoreClock, 8);
    
    while (1)
    {
        OLED_ShowString(2, 1, "Running");
        Delay_ms(500);
        OLED_ShowString(2, 1, "       ");
        Delay_ms(500);
    }
}

OLED 上会显示当前的 SYSCLK 值(如 72000000)。注意修改主频后:

  • Flash 等待周期需要同步调整(system_stm32f10x.c 中的 SetSysClock() 会自动处理)
  • APB1 外设不能超过 36 MHz
  • 使用 HSI 作为 PLL 源时,最大只能到 64 MHz(HSI/2 × 16 = 64 MHz)

6. RCC 相关常见问题

6.1 外设不工作

症状:某个外设(GPIO、定时器、USART 等)寄存器写不进去,功能异常。

排查:检查是否调用了对应的 RCC_xxxPeriphClockCmd() 开启了时钟。

原因:STM32 的外设默认时钟是关闭的(省电设计),开启前无法访问其寄存器。

6.2 主频与预期不符

运行 SystemCoreClock 不正确可能的原因:

  • HSE 晶振故障(起振失败),自动回退到 HSI 8 MHz
  • system_stm32f10x.c 中的宏没有正确配置
  • PLL 倍频系数超出芯片支持的频率范围

6.3 修改 APB 分频对外设的影响

APB1 分频 APB1 时钟 TIM 时钟源
1(不分频) 72 MHz 72 MHz
2(分频) 36 MHz 72 MHz(×2 自动补偿)
4 18 MHz 36 MHz(×2)
8 9 MHz 18 MHz(×2)

APB1 定时器(TIM2~TIM7)时钟 = APB1 ×2 仅当 APB1 分频系数 ≠ 1 时成立。调试定时器频率时要注意这一点。


尚硅谷:时钟使能宏定义速查

总线 寄存器 外设 宏定义
APB2 RCC->APB2ENR ADC1 RCC_APB2ENR_ADC1EN
APB2 RCC->APB2ENR USART1 RCC_APB2ENR_USART1EN
APB2 RCC->APB2ENR GPIOA~GPIOC RCC_APB2ENR_IOPAEN ~ RCC_APB2ENR_IOPCEN
APB2 RCC->APB2ENR AFIO RCC_APB2ENR_AFIOEN
APB2 RCC->APB2ENR TIM1 RCC_APB2ENR_TIM1EN
APB2 RCC->APB2ENR SPI1 RCC_APB2ENR_SPI1EN
APB1 RCC->APB1ENR TIM2~TIM4 RCC_APB1ENR_TIM2EN ~ RCC_APB1ENR_TIM4EN
APB1 RCC->APB1ENR USART2 RCC_APB1ENR_USART2EN
APB1 RCC->APB1ENR I2C1/I2C2 RCC_APB1ENR_I2C1EN / RCC_APB1ENR_I2C2EN
AHB RCC->AHBENR DMA1 RCC_AHBENR_DMA1EN

寄存器:RCC外设时钟使能(尚硅谷风格)

// 标准外设库函数封装的是寄存器操作,等效于:
// APB2ENR[2] IOPAEN位, |=按位或: 保持其他位不变, GPIOA外设时钟使能
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// APB2ENR[9] ADC1EN位, |=按位或: ADC1外设时钟使能
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
// APB2ENR[14] USART1EN位, |=按位或: USART1外设时钟使能
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
// APB1ENR[0] TIM2EN位, |=按位或: TIM2外设时钟使能
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
// AHBENR[0] DMA1EN位, |=按位或: DMA1外设时钟使能
RCC->AHBENR  |= RCC_AHBENR_DMA1EN;

RCC核心寄存器速查

时钟配置:

  • RCC->CR: HSION, HSIRDY, HSEON, HSERDY, PLLON, PLLRDY
  • RCC->CFGR: SW[1:0]系统时钟源, HPRE[3:0]AHB分频, PPRE1/2[2:0]APB分频
  • RCC->CFGR: ADCPRE[1:0]ADC分频, PLLSRC, PLLXTPRE, PLLMUL[3:0]

外设时钟使能:

  • RCC->APB2ENR: IOPA~G, USART1, SPI1, ADC1/2/3, TIM1/8
  • RCC->APB1ENR: TIM2~7, USART2~5, I2C1/2, SPI2/3, PWR, BKP, CAN
  • RCC->AHBENR: DMA1/2, CRC, FSMC ```