一、RCC_CFGR 寄存器(Reset and Clock Control - Clock Configuration Register)
1、概念
- RCC (Reset and Clock Control):这是一个专门负责控制 复位 和 时钟 的外设模块。
- CFGR (Configuration Register): 这是一个配置寄存器。
作用解释:RCC_CFGRRCC\_CFGRRCC_CFGR 寄存器就是用来配置和选择 STM32 中各个总线和外设的时钟源和分频系数的。它是时钟系统配置中最重要的寄存器之一。
- 时钟源选择: 决定了系统时钟(SYSCLK)从哪里来(例如,是内部高速时钟 HSI、外部高速时钟 HSE 还是 PLL 锁相环)。
- 分频设置: 决定了系统时钟如何分配给不同的总线(AHB、APB1、APB2)和内核(Cortex System Timer 等),以控制它们运行的速度。
2、 RCC_CFGRRCC\_CFGRRCC_CFGR 寄存器中的两个相关字段:
- SWSWSW(System Clock Switch): 这是一个控制位,您写入这个字段来选择您希望使用哪个时钟源作为 SYSCLKSYSCLKSYSCLK。
- SWSSWSSWS(System Clock Switch Status): 这是一个状态位,您读取这个字段来确认系统是否已经成功切换到 SWSWSW 所选择的时钟源。
SWSSWSSWS 用于指示系统时钟 (SYSCLKSYSCLKSYSCLK) 当前使用的是哪个时钟源。
| SWS 值 | 含义(当前系统时钟源) |
|---|---|
| 00 | HSI (High-speed Internal Clock) - 内部高速时钟 |
| 01 | HSE (High-speed External Clock) - 外部高速时钟 |
| 10 | PLL (Phase-Locked Loop) - 锁相环输出时钟 |
| 11 | 预留/其他(依具体型号而定) |
简单来说:
- SWSWSW →\rightarrow→ 你告诉 MCU 用哪个时钟 (Write);
- SWSSWSSWS →\rightarrow→ MCU 告诉你它正在用哪个时钟 (Read)。
在代码中进行时钟初始化时,通常会配置 SWSWSW 字段 ,然后等待(或检查) SWSSWSSWS 字段,直到它变为期望的值,这代表时钟切换成功。
3、RCC_ClocksTypeDef结构体
RCC_ClocksTypeDef结构体中定义:SYSCLK_Frequency、HCLK_Frequency、PCLK1_Frequency、PCLK2_Frequency。这四个成员变量,是 STM32 时钟树(Clock Tree)和速度分配的关键。它们定义了芯片内部几个最重要的时钟频率。
| 变量名 | 对应时钟 | 全称 | 作用/含义 |
|---|---|---|---|
| SYSCLK_Frequency | SYSCLK (System Clock) | 系统时钟 | MCU 的主时钟。 它驱动 CPU 内核(Cortex-M)和 AHB 总线。它的频率决定了 MCU 的基本运行速度。SYSCLK 的来源可以是 HSI、HSE 或 PLLCLK。 |
| HCLK_Frequency | HCLK (AHB Clock) | AHB 总线时钟 | 高速外设和存储器的时钟。 它驱动 AHB (Advanced High-performance Bus) 总线、SRAM 存储器、Flash 存储器和所有连接到 AHB 总线上的高速外设(例如 DMA、GPIO 等)。HCLK 是由 SYSCLK 经过 AHB 预分频器 (AHB Prescaler) 分频后得到的。 |
| PCLK1_Frequency | PCLK1 (APB1 Clock) | APB1 低速外设时钟 | 低速外设的时钟。 它驱动连接到 APB1 (Advanced Peripheral Bus 1) 总线上的外设。通常用于低速且对频率要求不高的外设(例如 Timer 2/3/4/5/6/7,USART 2/3/4/5,I2C 1/2/3 等)。PCLK1 是由 HCLK 经过 APB1 预分频器 分频后得到的。 |
| PCLK2_Frequency | PCLK2 (APB2 Clock) | APB2 高速外设时钟 | 高速外设的时钟。 它驱动连接到 APB2 (Advanced Peripheral Bus 2) 总线上的外设。通常用于高速或对时序有严格要求的外设(例如 Timer 1/8,USART 1/6,SPI 1/4,ADC 等)。PCLK2 是由 HCLK 经过 APB2 预分频器 分频后得到的。 |
二、NvicConfig:外设中断配置函数
主要包含4类操作:
① 设置中断优先级分组(Priority Grouping)
说明:
决定抢占/子优先级结构:
先占优先级(preemption priority)
响应 / 子优先级(sub priority)
例如(STM32):
javascript
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
分组决定优先级的划分方式。
NVIC_PriorityGroup决定 优先级字段里多少位用于抢占优先级,多少位用于子优先级。
在 Cortex-M 内核中,NVIC 的中断优先级一般是 4 位(0~15)。
这些 4 位会被拆分成两部分:
| Priority Group | 抢占位数 | 子优先级位数 |
|---|---|---|
| Group 0 | 0 位 | 4 位 |
| Group 1 | 1 位 | 3 位 |
| Group 2 | 2 位 | 2 位 |
| Group 3 | 3 位 | 1 位 |
| Group 4 | 4 位 | 0 位 |
📌 Group 数字越大 → 抢占优先级越多 → 子优先级越少
为什么很多工程默认用 NVIC_PriorityGroup_4?
因为简单!只需要关心一个优先级(抢占优先级),中断只按"谁优先级更高"来打断,没有子优先级这种细节,适合大部分 STM32 项目。
javascript
NVIC_PriorityGroup_4
表示:
抢占优先级占 4 位
子优先级占 0 位(没有子优先级概念)
所以:
✔ 你可以设置抢占优先级 0 ~ 15
✔ 所有中断没有平级排序,全靠抢占优先级决定先后
深入理解:
假设优先级分组是 NVIC_PriorityGroup_2(2 位抢占,2 位子优先级):
| 中断 | 抢占优先级 | 子优先级 |
|---|---|---|
| EXTI0 | 1 | 0 |
| USART1 | 2 | 0 |
| TIM3 | 1 | 1 |
结论:
javascript
EXTI0(1,0) 和 TIM3(1,1)
→ 抢占一样
→ 子优先级 0 < 1
→ EXTI0 先执行
USART1(2,x) 抢占级低
→ 不能打断 EXTI0 或 TIM3
→ 永远排最后
② 为某个外设选择中断优先级
典型步骤:
javascript
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
设置内容:
中断线号(IRQ)
抢占优先级
响应/子优先级
打开 NVIC 中断通道
③ 使能(Enable)NVIC 中断通道
例如:
javascript
NVIC_EnableIRQ(USART1_IRQn);
或者在初始化结构里 Cmd = ENABLE。
④ 可选:清除挂起中断(Pending Flag)
说明:防止进入旧中断
确保启动前中断状态是干净的:
javascript
NVIC_ClearPendingIRQ(EXTI0_IRQn);
三、EXTI 和 NVIC
1、概念
EXTI 负责"外部事件/引脚 → 产生中断请求"
NVIC 负责"多个中断请求 → 排队、抢占、执行"
2、NVIC(Nested Vectored Interrupt Controller)
NVIC 是 Cortex-M 内核的一部分,作用是:管理所有中断(外设 / 内部 / 外部)
决定:
- 谁先执行
- 谁能打断谁
- 谁在等
3、EXTI(External Interrupt / Event Controller)
EXTI 是 芯片外设模块,负责:把 GPIO 引脚或内部事件,转换成中断请求
常见用途:
- 按键中断
- 外部传感器触发
- 外部同步信号
EXTI 本身不直接连 GPIO,
中间有个 EXTI Line(中断线):
javascript
GPIO 引脚
↓
EXTI Line (0~15)
↓
NVIC (EXTI0_IRQn, EXTI9_5_IRQn...)
3.1 EXTI 配置代码
① GPIO 配置为输入
javascript
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
② GPIO → EXTI Line 映射
javascript
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
③ EXTI 配置
javascript
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
④ NVIC 配置
javascript
NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
⑤ 中断服务函数(ISR)
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// 用户处理代码
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
⚠️ 必须清 pending 位,否则会一直进中断
3.2 NVIC 和 EXTI 的关系(重点)
| 模块 | 作用 |
|---|---|
| GPIO | 物理引脚 |
| EXTI | 把引脚变化变成中断请求 |
| NVIC | 决定是否执行、何时执行 |
| ISR | 真正执行代码的地方 |