目录
- 前言
- [一、RCC 的核心概念与时钟树配置原理](#一、RCC 的核心概念与时钟树配置原理)
-
- [1.1 RCC 的定义与核心作用](#1.1 RCC 的定义与核心作用)
- [1.2 RCC 的功能划分与 CPU 访问机制](#1.2 RCC 的功能划分与 CPU 访问机制)
- [1.3 时钟使能的硬件本质](#1.3 时钟使能的硬件本质)
-
- [1.3.1 与门的工作原理](#1.3.1 与门的工作原理)
- [1.4 RCC 的寻址与访问机制](#1.4 RCC 的寻址与访问机制)
-
- [1.4.1 统一编址](#1.4.1 统一编址)
- [1.4.2 寄存器封装](#1.4.2 寄存器封装)
- [二、关键 RCC 寄存器详解](#二、关键 RCC 寄存器详解)
-
- [2.1 时钟控制寄存器 (RCC_CR)](#2.1 时钟控制寄存器 (RCC_CR))
-
- [2.1.1 内部高速时钟使能 (HSION)](#2.1.1 内部高速时钟使能 (HSION))
- [2.1.2 外部高速时钟使能 (HSEON)](#2.1.2 外部高速时钟使能 (HSEON))
- [2.1.3 其他关键位](#2.1.3 其他关键位)
- [2.2 时钟配置寄存器 (RCC_CFGR)](#2.2 时钟配置寄存器 (RCC_CFGR))
-
- [2.2.1 PLL 倍频系数 (PLLMUL)](#2.2.1 PLL 倍频系数 (PLLMUL))
- [2.2.2 高速 APB 预分频器 (PPRE2)](#2.2.2 高速 APB 预分频器 (PPRE2))
- [2.2.3 低速 APB 预分频器 (PPRE1)](#2.2.3 低速 APB 预分频器 (PPRE1))
- [2.3 外设时钟使能寄存器](#2.3 外设时钟使能寄存器)
-
- [2.3.1 APB2 外设时钟使能寄存器 (RCC_APB2ENR)](#2.3.1 APB2 外设时钟使能寄存器 (RCC_APB2ENR))
- [2.3.2 APB1 外设时钟使能寄存器 (RCC_APB1ENR)](#2.3.2 APB1 外设时钟使能寄存器 (RCC_APB1ENR))
- [三、RCC 时钟系统实验设计与原理](#三、RCC 时钟系统实验设计与原理)
-
- [3.1 RCC 的核心定位与实验思路](#3.1 RCC 的核心定位与实验思路)
- [3.2 硬件连接与控制逻辑解析](#3.2 硬件连接与控制逻辑解析)
- 四、项目配置与代码实现细节
-
- [4.1 CubeMX 关键配置项](#4.1 CubeMX 关键配置项)
- [4.2 自定义延时函数的陷阱与修正](#4.2 自定义延时函数的陷阱与修正)
-
- 编译器优化的"坑"
- [volatile 关键字的救赎](#volatile 关键字的救赎)
- [4.3 时钟树配置策略](#4.3 时钟树配置策略)
- 结语


🎬 云泽Q :个人主页
🔥 专栏传送入口 : 《C语言》《数据结构》《C++》《Linux》《蓝桥杯系列》《笔试算法》《AI赋能》《STM32》《Python》
⛺️遇见安然遇见你,不负代码不负卿~
前言
大家好啊,我是云泽Q,欢迎阅读我的文章,一名热爱计算机技术的在校大学生,喜欢在课余时间做一些计算机技术的总结性文章,希望我的文章能为你解答困惑~
一、RCC 的核心概念与时钟树配置原理
1.1 RCC 的定义与核心作用
RCC 的全称是 Reset and Clock Control,即复位和时钟控制。在本篇文章中,我们主要聚焦于其 Clock Control(时钟控制)的功能。RCC 在 MCU 内部扮演着至关重要的角色,它相当于整个时钟系统的"总管家"。
我们需要明确 RCC 与时钟树的关系:RCC 是支持我们进行配置和管理时钟树的硬件模块。时钟信号在 MCU 内部通过总线进行布线,连接着 CPU、内存以及各类片上外设。无论是系统核心的运行,还是外设的驱动,都依赖于这套精密的时钟网络。
然而,时钟树本身是一个复杂的硬件结构,其中的关键节点------包括时钟源的选择、倍频、分频以及各个外设的使能开关------都需要被精确配置。我们在 STM32CubeMX 等工具中点击设置倍频、分频参数,本质上就是在生成这些配置信息。而要真正理解这些配置是如何生效的,我们就必须找到一个硬件层面的突破口,这个突破口就是"寄存器"。所谓的配置,归根结底就是对 RCC 内部寄存器的读写操作。

1.2 RCC 的功能划分与 CPU 访问机制
RCC 作为 MCU 内部的一个片上外设,其内部并非只有一个功能模块。从整体架构来看,RCC 内部主要划分为三大功能区域:

- RTC(实时时钟):用于维持系统的时间计数,本次暂不展开。
- 复位功能:负责 MCU 的系统复位管理。比如我们把板子烧完程序后按一下复位按键,程序就能重新运行,这就是复位功能在起作用。关于复位的具体机制,后续在讲解程序编译烧写流程时再详细说明。
- 配置时钟树:这是我们本篇文章的核心关注点,也是 RCC 最重要的功能。
1.3 时钟使能的硬件本质

当我们想要开启某个外设(例如 GPIO)时,我们在代码中调用的开启函数,其底层物理动作究竟是什么?
本质上,开启外设就是把该设备接入到对应的时钟线上。我们可以把时钟线想象成一条输送动力的管道,而每个外设前面都有一个"开关"。只有当这个开关闭合时,时钟信号才能流向该外设,外设才能工作。
这个"开关"在硬件电路中,实际上是一个逻辑门电路。在参考手册的电路图中,它被明确画为一个**与门(AND Gate)**结构。
1.3.1 与门的工作原理

这个与门有两个输入端:
- 时钟输入端(Input):连接着总线上的时钟信号(如 PCLK1 或 PCLK2)。
- 使能端(Enable):连接着 RCC 寄存器中的某一位(Bit)。
根据与门的逻辑特性,只有当两个输入都为高电平(1)时,输出端(Output)才会有时钟信号输出。
- 如果寄存器位为 0,无论时钟信号如何跳动,输出永远为 0,外设处于"断电"状态。
- 如果寄存器位为 1,时钟信号就能无损地通过与门,驱动外设工作。

因此,我们在软件层面写的"开启 GPIO 时钟",在硬件层面就是向 RCC 的特定寄存器写入一个 1,将与门的使能端拉高,从而闭合开关。
1.4 RCC 的寻址与访问机制
RCC 作为 MCU 内部的一个片上外设,它的访问方式与我们熟悉的 GPIO 是完全一致的。
1.4.1 统一编址
在 ARM Cortex-M 架构中,内存、片上外设(包括 RCC、GPIO、USART 等)都是统一编址的。这意味着 RCC 在内存映射中拥有一个固定的起始物理地址。CPU 可以通过系统总线直接访问这个地址。

1.4.2 寄存器封装
RCC 内部包含大量的寄存器,这些寄存器在物理上是连续排列的。在软件驱动开发中,我们会将这些寄存器封装成一个结构体(Struct)。
- 结构体的基地址对应 RCC 的起始物理地址。
- 结构体内的成员变量对应具体的寄存器,它们之间的偏移量(Offset)对应硬件上的地址偏移(如 0x00, 0x04, 0x08 等)。
通过这种方式,CPU 就可以像访问普通内存变量一样,精准地修改 RCC 内部的每一个配置位,从而完成对整个时钟树的控制。在阅读源码时,大家会看到 RCC 一定有一个定义的基地址,所有的配置操作都是基于这个基地址加上偏移量来进行的。
二、关键 RCC 寄存器详解
要掌握 RCC 的配置,就必须深入理解几个核心寄存器。这些寄存器直接对应了我们在 CubeMX 中看到的各种选项。
2.1 时钟控制寄存器 (RCC_CR)
RCC_CR (Clock Control Register) 是 RCC 中最基础的寄存器,主要用于控制时钟源的开启与状态监测。

2.1.1 内部高速时钟使能 (HSION)
- 位置:位 0 (Bit 0)
- 功能:Internal high-speed clock enable。
- 操作:由软件置 '1' 开启内部高速时钟(HSI),清零则关闭。HSI 是芯片内部自带的 RC 振荡器,通常频率为 8MHz,不需要外部元件即可起振,是系统启动时的默认时钟源。
2.1.2 外部高速时钟使能 (HSEON)
- 位置:位 16 (Bit 16)
- 功能:External high-speed clock enable。
- 操作:由软件置 '1' 开启外部高速时钟(HSE),清零则关闭。HSE 通常接外部晶振(如 8MHz),精度比 HSI 高。
2.1.3 其他关键位
除了上述两个使能位,RCC_CR 还包含 PLL 的使能位(PLLON)、时钟就绪标志位(如 HSIRDY, HSERDY, PLLRDY)等。这些就绪标志位是只读的,用于告诉 CPU 时钟源是否已经稳定起振,只有在稳定后才能进行切换操作。
2.2 时钟配置寄存器 (RCC_CFGR)
如果说 RCC_CR 负责"点火",那么 RCC_CFGR (Clock Configuration Register) 就负责"调速"。它决定了系统的最终运行频率以及各总线的分频系数。

2.2.1 PLL 倍频系数 (PLLMUL)
- 位置:位 21:18
- 功能:PLL multiplication factor。
- 详解 :锁相环(PLL)的作用是将输入的时钟频率进行倍频,以获得更高的系统主频。该字段由软件设置来确定倍频系数。
000x: 保留0010: PLL 4 倍频输出0011: PLL 5 倍频输出- ...以此类推...
0111: PLL 9 倍频输出1101: PLL 6.5 倍频输出
- 注意 :只有在 PLL 关闭的情况下才可被写入。警告:PLL 的输出频率绝对不能超过 72MHz(针对 STM32F1 系列)。
2.2.2 高速 APB 预分频器 (PPRE2)
- 位置:位 13:11
- 功能:APB high-speed prescaler (APB2)。
- 详解 :用于控制高速 APB2 时钟(PCLK2)的预分频系数。
0xx: HCLK 不分频100: HCLK 2 分频101: HCLK 4 分频110: HCLK 8 分频111: HCLK 16 分频
2.2.3 低速 APB 预分频器 (PPRE1)
- 位置:位 10:8
- 功能:APB low-speed prescaler (APB1)。
- 详解 :用于控制低速 APB1 时钟(PCLK1)的预分频系数。
0xx: HCLK 不分频100: HCLK 2 分频- ...
111: HCLK 16 分频
- 注意 :软件必须保证 APB1 时钟频率不超过 36MHz。这是由挂载在 APB1 上的外设性能决定的。
2.3 外设时钟使能寄存器
这是我们在开发中最常打交道的寄存器组。MCU 的外设分布在不同的总线上,因此使能寄存器也按总线分类。
2.3.1 APB2 外设时钟使能寄存器 (RCC_APB2ENR)

APB2 是高速总线,挂载着对速度要求较高的外设。
- 位 2 (IOPAEN):IO port A clock enable。置 1 开启 GPIOA 时钟,置 0 关闭。这是点亮 LED 或读取按键前必须做的第一步。
- 位 3 (IOPBEN):IO port B clock enable。
- 位 4 (IOPCEN):IO port C clock enable。
- 其他外设:还包括 USART1 (位 14)、SPI1 (位 12)、TIM1 (位 11)、ADC1/2 (位 9/10) 等。
2.3.2 APB1 外设时钟使能寄存器 (RCC_APB1ENR)

APB1 是低速总线,最大频率通常限制在 36MHz。
- 位 0 (TIM2EN):TIM2 定时器时钟使能。
- 位 1 (TIM3EN):TIM3 定时器时钟使能。
- 位 14 (SPI2EN):SPI2 时钟使能。
- 位 17 (USART2EN):USART2 时钟使能。
- 其他外设:还包括 I2C1/2、CAN、USB、DAC 等。
三、RCC 时钟系统实验设计与原理
3.1 RCC 的核心定位与实验思路
在 STM32 的片上外设体系中,RCC(Reset and Clock Control,复位和时钟控制)扮演着极其特殊的角色。它不像 GPIO 那样可以直接对外输出高低电平来控制硬件,也不像 UART 那样直接进行数据收发。RCC 本质上是一个"对内服务"的模块,它的核心任务是为 MCU 内部的其他所有外设提供时钟源。可以说,没有 RCC 提供的时钟信号,CPU 无法执行指令,外设也无法工作。
为了直观地验证 RCC 对系统运行速度的影响,我们设计了一个基于 LED 闪烁的实验。虽然通常控制 LED 闪烁我们会使用标准库提供的 HAL_Delay 函数,但在本次 RCC 实验中,为了更纯粹地观察时钟频率与 CPU 执行效率的关系,我们决定摒弃 HAL_Delay,转而采用一种更底层的延时方式:自定义一个基于 while 循环的 CPU_Delay 函数。
这个函数的逻辑非常简单:通过传入一个巨大的计数值(例如 654545),让 CPU 在一个 while(cnt--) 的死循环中不断执行减法操作,直到计数值减为 0 才退出。这种延时方式的本质是消耗 CPU 的运算周期。既然代码执行的指令数量是固定的,那么这段代码跑完所需的时间,就完全取决于 CPU 的运行速度(即主频 FCLK)。
因此,我们的实验逻辑链条非常清晰:
- 编写一个依赖 CPU 运算速度的软件延时函数。
- 通过 STM32CubeMX 动态调整 RCC 的时钟树配置(主要是分频系数)。
- 观察在相同代码、相同循环次数下,LED 闪烁频率随主频降低而变慢的现象。
这就相当于把 CPU 变成了一个"时钟频率显示器":主频越快,循环跑得越快,灯闪得越快;主频越慢,循环跑得越慢,灯闪得越慢。
3.2 硬件连接与控制逻辑解析
本次实验选用的硬件对象是开发板上的红色 LED4。通过分析原理图可知,LED4 对应的网络标号为 SYS_LED1,其物理引脚连接在 MCU 的 PF8 端口上。

从电路设计上看,LED4 采用了共阳极接法:
- 阳极:连接到 VDD_3.3V 电源。
- 阴极:串联了一个 680Ω 的限流电阻(R47)后,连接到 PF8 引脚。
- 型号:使用的是 NCD0805G1 封装的红色 LED。
基于这种电路结构,GPIO 的控制逻辑如下:
- 输出高电平(High) :PF8 为 3.3V,LED 两端无压差,电流不导通,LED 熄灭。
- 输出低电平(Low) :PF8 为 0V,LED 两端形成 3.3V 压差,电流导通,LED 点亮。
在 STM32CubeMX 的配置界面中,我们需要将 PF8 配置为 GPIO_Output 模式,输出类型选择 Output Push Pull(推挽输出),初始电平设为 High(默认熄灭),并且不需要上拉或下拉电阻。这样的配置确保了我们可以直接通过翻转 PF8 的电平来控制 LED 的亮灭状态。
四、项目配置与代码实现细节
4.1 CubeMX 关键配置项
在生成工程代码之前,我们需要在 STM32CubeMX 中完成以下核心配置:
- MCU 选型 :选择
STM32F103ZETx系列芯片。 - 调试接口 :务必开启
SYS->Debug->Serial Wire。这是为了防止芯片锁死导致无法再次下载程序。 - GPIO 设置:找到 PF8 引脚,将其设置为 GPIO_Output。在配置栏中确认 Mode 为 Output Push Pull,Pull-up/Pull-down 为 No pull-up and no pull-down,Maximum output speed 设为 Low(对于点灯实验,低速足以减少EMI)。
- 时钟源 :在 Clock Configuration 选项卡中,确保 HSE(外部高速晶振)被选中并设置为 Crystal/Ceramic Resonator 模式,频率输入为 8MHz。

4.2 自定义延时函数的陷阱与修正
这是本实验中最关键的代码环节。为了实现依赖主频的延时,我们编写了如下函数:
c
void CPU_Delay(uint32_t cnt)
{
while(cnt--);
}
在主循环中调用方式如下:
c
while (1)
{
HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_8); // 翻转 PF8 电平
CPU_Delay(654545); // 延时约 0.2s (72MHz下)
}
编译器优化的"坑"
初看这段代码似乎没有问题,但在实际编译运行时,你可能会发现 LED 根本不闪烁,或者闪烁极快肉眼无法察觉。这是因为现代编译器(如 GCC 或 ARMCC)非常聪明,它会进行激进的优化。
当编译器看到 while(cnt--) 时,它发现 cnt 只是一个局部变量,且在循环内部除了自减没有任何其他副作用(比如没有被外部中断修改,也没有被用来驱动外设)。为了提高运行效率,编译器可能会将这个变量直接放入 CPU 的高速寄存器中进行运算,甚至如果优化等级过高,它可能认为这个空循环毫无意义而直接将其"优化掉"(删除)。一旦变量进入寄存器或被优化,原本预期的内存访问延迟就会消失,导致延时失效。

volatile 关键字的救赎
为了解决这个问题,必须使用 volatile 关键字修饰参数。修改后的代码如下:
c
void CPU_Delay(volatile uint32_t cnt)
{
while(cnt--);
}

volatile 的字面意思是"易变的"。它告诉编译器:"这个变量随时可能被改变,不要把它缓存到寄存器里,也不要优化掉对它的读写操作,每次都必须老老实实地去内存中读取它的值。"
在 Cortex-M3 内核的架构中,这意味着:
- 禁止寄存器缓存 :每次执行
cnt--时,CPU 必须从内存(Memory)中读取cnt的值到寄存器,减 1 后再写回内存。 - 保持内存可见性:这种频繁的内存读写操作虽然降低了效率,但正是我们想要的------它强制产生了固定的时间开销,从而实现了精准的软延时。
4.3 时钟树配置策略
为了验证不同主频下的效果,我们需要在 CubeMX 的 Clock Configuration 页面调整 AHB Prescaler(AHB 预分频器)。
场景一:全速运行(基准)
- SYSCLK:72 MHz(由 HSE 8MHz 经过 PLL 9倍频得到)。
- HCLK (AHB Prescaler):/1。
- 结果 :CPU 主频为 72MHz。此时
CPU_Delay(654545)大约耗时 0.2秒左右,LED 闪烁节奏适中。
场景二:降频测试
- SYSCLK:保持 72 MHz 不变。
- HCLK (AHB Prescaler):修改为 /4。
- 结果:CPU 主频变为 72 / 4 = 18 MHz。
- 现象预期 :由于 CPU 速度变成了原来的 1/4,执行同样的 65万次循环所需的时间将变成原来的 4 倍。肉眼观察到的 LED 闪烁频率会明显变慢。

结语
