tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(基础篇)V1.0.2 — 第7章:按键与中断系统 + 配套代码05~06" author: "尚硅谷研究院" date: 2026-07-15
用生活理解:中断就像你正在写代码,突然电话响了——你暂停写代码、接电话、处理完、挂电话、继续写代码。按键中断就是"按键按下 → 暂停主程序 → 处理按键 → 恢复主程序"。NVIC 就像一个智能电话秘书,知道哪个电话更重要、该先接哪个。
如果没有中断,CPU 只能轮询检测按键状态——就像一直盯着电话看它响不响,浪费 CPU 时间。中断机制允许外设在需要时主动通知 CPU,CPU 不用一直等。
STM32F103 的中断体系由两部分组成:
| 部件 | 功能 | 参考手册章节 |
|---|---|---|
| NVIC (嵌套向量中断控制器) | 中断优先级管理、使能/屏蔽 | §8 NVIC |
| EXTI (外部中断/事件控制器) | 管理 GPIO 引脚的边沿检测与中断请求 | §9 EXTI |
中断响应过程:
GPIO引脚电平变化 → EXTI检测边沿 → NVIC判断优先级 → CPU暂停当前任务
→ 跳转到中断向量表 → 执行中断服务函数(ISR) → 返回主程序
NVIC 支持 16 级可编程中断优先级(0 最高,15 最低)。
优先级分组:4 位优先级可拆分为"抢占优先级 + 子优先级"
| 分组 | 分配 | 抢占优先级位数 | 子优先级位数 |
|---|---|---|---|
| NVIC_PriorityGroup_0 | 0位抢占 + 4位子优先 | 0 | 4 |
| NVIC_PriorityGroup_1 | 1位抢占 + 3位子优先 | 2级 | 8级 |
| NVIC_PriorityGroup_2 | 2位抢占 + 2位子优先 | 4级 | 4级 |
| NVIC_PriorityGroup_3 | 3位抢占 + 1位子优先 | 8级 | 2级 |
| NVIC_PriorityGroup_4 | 4位抢占 + 0位子优先 | 16级 | 0 |
规则:抢占优先级高的可以打断低的;相同抢占优先级下,子优先级高的先执行但不打断。
STM32F103 有 20 个 EXTI 通道(EXTI0~EXTI19),每个 GPIO 引脚都可以配置为 EXTI 输入。
EXTI配置流程:
1. GPIO配置(输入模式) → 2. AFIO选择中断线 → 3. EXTI配置(触发边沿+使能) → 4. NVIC配置(优先级+使能)
EXTI 关键寄存器:
| 寄存器 | 功能 | 位说明 |
|---|---|---|
| IMR | 中断屏蔽寄存器 | 1=允许中断请求,0=禁止 |
| EMR | 事件屏蔽寄存器 | 1=允许事件请求,0=禁止 |
| RTSR | 上升沿触发选择 | 1=允许上升沿触发 |
| FTSR | 下降沿触发选择 | 1=允许下降沿触发 |
| SWIER | 软件中断事件寄存器 | 写1=软件触发中断 |
| PR | 挂起寄存器 | 1=有中断请求,写1清除 |
参考:参考手册 §9(EXTI 寄存器描述)+ §8(NVIC 寄存器描述)
开发板上有 4 个按键:
| 按键 | 对应引脚 | 连接方式 | 按下时电平 |
|---|---|---|---|
| KEY_UP (SW4) | PF10 | 上拉输入 | 低变高 |
| KEY_1 (SW2) | ? | 外部电路 | 取决于设计 |
| KEY_2 | ? | 外部电路 | 取决于设计 |
| KEY_3 (SW5) | PF10? | 上拉输入 | 低变高 |
说明:PF10 对应的 KEY 为上拉输入模式——按键未按下时 PF10=低(下拉到GND),按下时 PF10=高(上拉到 VCC)。
消抖:机械按键在按下/释放瞬间会产生约 5~10ms 的抖动(电平快速跳变),需要软件消抖:
理想波形: __┃┃________
实际波形: __┃_┃_┃┃_┃_┃____ ← 抖动
软件消抖: 检测到边沿 → 等待15ms → 再次确认电平
按下 KEY_1(PF10),翻转 LED1 的开关状态。使用外部中断 EXTI15_10 实现。
项目路径:stm32/05_key_led_register
文件:stm32/05_key_led_register/Hardware/KEY/key.h
#ifndef __KEY_H
#define __KEY_H
#include "stm32f10x.h"
void Key_Init(void);
#endif
文件:stm32/05_key_led_register/Hardware/KEY/key.c
#include "key.h"
#include "delay.h"
#include "led.h"
// KEY初始化:配置PF10为上拉输入 + EXTI中断
void Key_Init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPFEN;
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
GPIOF->CRH &= ~GPIO_CRH_MODE10;
GPIOF->CRH |= GPIO_CRH_CNF10_1;
GPIOF->CRH &= ~GPIO_CRH_CNF10_0;
GPIOF->ODR &= ~GPIO_ODR_ODR10;
AFIO->EXTICR[2] |= AFIO_EXTICR3_EXTI10_PF;
EXTI->RTSR |= EXTI_RTSR_TR10;
EXTI->IMR |= EXTI_IMR_MR10;
NVIC_SetPriorityGrouping(3);
NVIC_SetPriority(EXTI15_10_IRQn, 3);
NVIC_EnableIRQ(EXTI15_10_IRQn);
}
// EXTI15_10中断服务函数(10~15共用一个中断入口)
void EXTI15_10_IRQHandler(void)
{
/* 1. 清除中断标志位 */
// PR寄存器的对应位写1清除挂起标志
// 如果不清除,退出中断后立即再次进入
EXTI->PR |= EXTI_PR_PR10; // |=: PR10写1清除
/* 2. 软件消抖 */
Delay_ms(10); // 等待10ms避开抖动期
/* 3. 再次确认按键确实按下 */
// 读取IDR判断PF10是否仍为高电平(屏蔽状态)
if ((GPIOF->IDR & GPIO_IDR_IDR10) != 0) // &测试: IDR10位是否为1
{
// 翻转LED1状态
LED_Toggle(LED1);
}
}
文件:stm32/05_key_led_register/User/main.c
#include "key.h"
#include "led.h"
int main(void)
{
LED_Init(); // LED初始化
Key_Init(); // KEY初始化(含中断配置)
while (1)
{
// 主循环什么都不用做,等待中断发生
// 按键按下 → EXTI中断 → ISR翻转LED → 返回继续等
}
}
项目路径:stm32/06_key_led_hal
CubeMX 配置要点:
GPIO_EXTI10 + GPIO_PULLUP(上拉输入)EXTI15_10_IRQnstm32f1xx_it.c 的 EXTI15_10_IRQHandler() 中添加用户代码HAL 库中断处理流程:
外部中断 → HAL库的 HAL_GPIO_EXTI_IRQHandler() 调用
→ HAL_GPIO_EXTI_Callback() → 用户重写Callback函数
文件:stm32/06_key_led_hal/Core/Src/main.c(用户代码部分)
/* USER CODE BEGIN 0 */
// HAL库的中断回调函数(由HAL_GPIO_EXTI_IRQHandler调用)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == KEY3_Pin) // PF10 对应 KEY3_Pin
{
HAL_Delay(10); // 消抖
if (HAL_GPIO_ReadPin(GPIOF, KEY3_Pin) == GPIO_PIN_SET)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0); // 翻转 LED1
}
}
}
/* USER CODE END 0 */
对比寄存器版:HAL 库将中断的底层操作(PR清除、中断线判断)封装在
HAL_GPIO_EXTI_IRQHandler中,用户只需要重写 Callback 函数。
| 函数/操作 | 代码 | 说明 |
|---|---|---|
| KEY初始化 | Key_Init() |
时钟+GPIO+AFIO+EXTI+NVIC |
| 清除中断标志 | `EXTI->PR | = EXTI_PR_PR10` |
| 读取引脚 | GPIOF->IDR & GPIO_IDR_IDR10 |
&测试:测量PF10电平 |
| 软件消抖 | Delay_ms(15) |
避开机械抖动期 |
| HAL函数 | 说明 |
|---|---|
HAL_GPIO_ReadPin(GPIOx, pin) |
读取引脚电平 |
HAL_GPIO_TogglePin(GPIOx, pin) |
翻转引脚电平 |
HAL_GPIO_EXTI_IRQHandler(pin) |
EXTI中断入口(清标志+调Callback) |
HAL_GPIO_EXTI_Callback(pin) |
用户重写的回调函数 |
| 函数 | 说明 |
|---|---|
NVIC_EnableIRQ(IRQn) |
使能中断通道 |
NVIC_DisableIRQ(IRQn) |
禁止中断通道 |
NVIC_SetPriority(IRQn, priority) |
设置中断优先级 |
NVIC_SetPriorityGrouping(group) |
设置优先级分组 |