基于铁头山羊(38, 39)、HAL库教程(16, 17)、江科大(13-2)三教程综合整理
时钟(Clock)是数字电路的心跳。芯片内部的每个寄存器、计数器、通信接口等所有数字逻辑都在时钟沿的驱动下工作。RCC(Reset and Clock Control)模块负责:
上图:STM32 系统架构(包含总线矩阵与各外设挂载关系)(来源:STM32入门教程 PPT 第9页)
上图:STM32F10x 时钟树结构示意图(来源:STM32入门教程 PPT 第62页)
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 时钟 |
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。
┌──────────────┐
│ 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等 │
└───────────┘ └────────┘
| 总线 | 最高频率 | 挂载的典型外设 |
|---|---|---|
| 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配置步骤:
为什么需要FLASH等待周期?
FLASH->ACR = 0x32;AHB/APB预分频的作用:
启动文件(startup_stm32f10x_xx.s)在跳转到 main 之前会调用 SystemInit()。
SystemInit() 的功能:
SetSysClock() 设置系统时钟SystemCoreClock 全局变量(记录当前 SYSCLK 频率)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 的配置步骤(典型):
以下代码来自 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 起振失败 — 可在此添加错误处理代码 */
}
}
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) { }
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
// system_stm32f10x.c 中定义
uint32_t SystemCoreClock = 72000000;
// 用户代码可直接读取
OLED_ShowNum(1, 8, SystemCoreClock, 8); // 显示当前系统主频
如果程序运行中修改了时钟配置(比如降低主频进入低功耗),需要调用
SystemCoreClockUpdate()更新此变量。
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 |
通过修改 system_stm32f10x.c 中的宏定义来改变 SYSCLK 频率,观察主循环运行快慢和 SystemCoreClock 的值。
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)。注意修改主频后:
system_stm32f10x.c 中的 SetSysClock() 会自动处理)症状:某个外设(GPIO、定时器、USART 等)寄存器写不进去,功能异常。
排查:检查是否调用了对应的 RCC_xxxPeriphClockCmd() 开启了时钟。
原因:STM32 的外设默认时钟是关闭的(省电设计),开启前无法访问其寄存器。
运行 SystemCoreClock 不正确可能的原因:
system_stm32f10x.c 中的宏没有正确配置| 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 |
// 标准外设库函数封装的是寄存器操作,等效于:
// 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->CR: HSION, HSIRDY, HSEON, HSERDY, PLLON, PLLRDYRCC->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/8RCC->APB1ENR: TIM2~7, USART2~5, I2C1/2, SPI2/3, PWR, BKP, CANRCC->AHBENR: DMA1/2, CRC, FSMC
```