一、 中断
1. 中断是什么
中断(Interrupt)。在 STM32 寄存器开发里,要是寄存器标志以 IE(Interrupt Enable,中断使能)结尾,大多数时候是用于对某个中断进行开启或者关闭的控制。
中断操作是 STM32 里针对特定的事件或者行为,去执行特定处理操作的机制,它需要【触发机制】和【注册行为】。
- 【触发机制】:程序在执行过程中,当中断出现时,会触发其他操作。
- 【注册行为】:注册在当前 STM32 执行过程中,所关注的中断内容是哪一个。
常用概念
- 断点:注册的中断触发位置,一般和电平跳变、寄存器标志位变化相对应。
- 中断源:当前中断注册的功能函数或者中断服务函数。
- 压栈:中断触发时,会把断点对应的函数进行压栈操作,进而转换为后台函数。
2. Cortex - M3 中断分类
Cortex - M3 内核中断分类:
- 内核中断 / 内部中断:属于芯片内部的中断,也可称之为【异常(Exception)】。它能应对程序在正常运行时,出现不同于常规的数据情况,这些情况可以是错误,也可以是标记。通常关注的是寄存器中的中断标志位。
- 外设中断:Cortex - M3 内核预留了外部中断控制线,能够通过外部中断控制线的高低电平切换,在出现上升沿和下降沿时进行中断触发。
3. 外部中断 / EXTI
3.1 外部中断 / EXTI 概述
- 外部中断线一共有 EXTI0 ~ EXTI19。
- 其中 EXTI0 ~ EXTI15 对应 GPIO 口,规则是 EXTI0 对应所有 GPIO 组中的 0 引脚,例如 PA0 ~ PGO。
- 其他 EXTI16 ~ EXTI19 暂时不使用。
3.2 外部中断判断逻辑框图
- 外部中断线通过
- 上升沿触发选择寄存器
- 下降沿触发选择寄存器
- 决定当前中断的触发方式。需要根据电路分析,找到默认的电平情况下,选择合理的触发方式。
3.3 按键功能实现外部中断控制 EXTI
3.3.1 按键原理图
3.3.2 AFIO 时钟使能
- 根据分析,当前 EXTI 外部中断线的控制需要涉及 AFIO 相关寄存器。AFIO 是 IO 引脚复用控制寄存器。
- 其中 AFIO_EXTICR1 ~ AFIO_EXTICR4 用于控制外部中断线相关内容。
- AFIO 对应的时钟是 APB2,需要进行使能操作。
3.3.3 外部中断线和按键对应关系
- 按键涉及到的引脚分别对应的外部中断线为:
- KEY_UP ==> PA0 ==> EXTI0
- KEY_2 ==> PE2 ==> EXTI2
- KEY_1 ==> PE3 ==> EXTI3
- KEY_0 ==> PE4 ==> EXTI4
以下寄存器操作赋值完成,对应当前外部中断线控制 / 监控:
3.3.4 外部中断上升沿或者下降沿触发寄存器配置
- 以下两个寄存器用于控制当前触发的中断方式
- EXTI_RTSR 控制对应外部中断控制线触发方式为上升沿触发
- EXTI_FTSR 控制对应外部中断控制线触发方式为下降沿触
3.3.5 外部中断屏蔽寄存器

3.3.6 中断挂起寄存器
4. IRQn 和 IRQHandler
4.1 IRQn 中断请求编号
4.2 IRQHandl

5. 外部中断控制实现
5.1 外部中断配置函数使能函数
void Key_EXTI_Interrupt_Enable(void)
{
// 因为 Key_Init 已经完成了 KEY 对应 GPIO 配置,此处仅配置中断相关
// 1. 使能AFIO时钟,用于外部中断引脚映射
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; // 更清晰的宏定义方式
// 2. 配置外部中断线与GPIO的映射关系
// EXTI0 -> PA0,EXTI2 -> PE2,EXTI3 -> PE3
AFIO->EXTICR[0] &= ~(0x0F << 0); // 清除EXTI0原有配置
AFIO->EXTICR[0] |= 0x00 << 0; // EXTI0映射到PA0 (0000)
AFIO->EXTICR[0] &= ~(0x0F << 8); // 清除EXTI2原有配置
AFIO->EXTICR[0] |= 0x04 << 8; // EXTI2映射到PE2 (0100)
AFIO->EXTICR[0] &= ~(0x0F << 12); // 清除EXTI3原有配置
AFIO->EXTICR[0] |= 0x04 << 12; // EXTI3映射到PE3 (0100)
// EXTI4 -> PE4
AFIO->EXTICR[1] &= ~(0x0F << 0); // 清除EXTI4原有配置
AFIO->EXTICR[1] |= 0x04 << 0; // EXTI4映射到PE4 (0100)
// 3. 配置中断触发方式
EXTI->RTSR |= 0x01; // EXTI0 上升沿触发 (KEY_UP)
EXTI->FTSR |= (1 << 2) | (1 << 3) | (1 << 4); // EXTI2/3/4 下降沿触发 (KEY2/1/0)
// 4. 使能外部中断线
EXTI->IMR |= (1 << 0) | (1 << 2) | (1 << 3) | (1 << 4); // 使能EXTI0/2/3/4中断
// 5. 配置NVIC中断优先级并使能中断
NVIC_SetPriority(EXTI0_IRQn, 1); // 设置EXTI0中断优先级
NVIC_SetPriority(EXTI2_IRQn, 1); // 设置EXTI2中断优先级
NVIC_SetPriority(EXTI3_IRQn, 1); // 设置EXTI3中断优先级
NVIC_SetPriority(EXTI4_IRQn, 1); // 设置EXTI4中断优先级
NVIC_EnableIRQ(EXTI0_IRQn); // 使能EXTI0中断
NVIC_EnableIRQ(EXTI2_IRQn); // 使能EXTI2中断
NVIC_EnableIRQ(EXTI3_IRQn); // 使能EXTI3中断
NVIC_EnableIRQ(EXTI4_IRQn); // 使能EXTI4中断
}
5.2 外部中断处理函数实现
/*
EXTI0_IRQHandler ==> 对应PA0引脚(KEY_UP按键)的中断处理
*/
void EXTI0_IRQHandler(void)
{
// 检查是否是EXTI0触发的中断
if (EXTI->PR & EXTI_PR_PR0)
{
// 清除中断标志位(写1清除)
EXTI->PR = EXTI_PR_PR0;
// 翻转LED0状态(假设LED0连接到PB5)
if (GPIOB->ODR & (1 << 5))
{
Led0_Ctrl(0); // 关闭LED0
}
else
{
Led0_Ctrl(1); // 打开LED0
}
}
}
/*
EXTI2_IRQHandler ==> 对应PE2引脚(KEY2按键)的中断处理
*/
void EXTI2_IRQHandler(void)
{
// 检查是否是EXTI2触发的中断
if (EXTI->PR & EXTI_PR_PR2)
{
// 清除中断标志位(写1清除)
EXTI->PR = EXTI_PR_PR2;
// 翻转蜂鸣器状态(假设蜂鸣器连接到PB8)
if (GPIOB->ODR & (1 << 8))
{
Beep_Ctrl(0); // 关闭蜂鸣器
}
else
{
Beep_Ctrl(1); // 打开蜂鸣器
}
}
}
6. 内部中断
6.1 内部中断概述
-
内部中断在 Cortex-M 内核中被称为异常,通常需要关注指定寄存器的中断标志位。
-
内部中断中,有三个具有最高优先级的中断(优先级数值越小,优先等级越高):
- RESET(复位)
- 优先级:-3(最高优先级)
- 特性:优先级不可修改
- NMI(不可屏蔽中断)
- 优先级:-2
- 特性:优先级不可修改
- HardFault(硬件错误中断)
- 优先级:-1
- 特性:优先级不可修改
- RESET(复位)
-
在内部中断中,SysTick(系统滴答定时器) 是较为重要的一种,常用于实现延时、任务调度等功能。
6.2 内部中断对应 IRQn 和 IRQHandler
7. 中断优先级
7.1 中断优先级概述
中断优先级由占先优先级和次级优先级两部分组成:
- 占先优先级:当多个中断同时触发时,MCU 优先依据占先优先级判断执行顺序。占先优先级高的中断会立即执行,其余中断进入等待状态,遵循 “占先优先级谁高谁执行” 原则。
- 次级优先级:若多个中断的占先优先级相同,MCU 则通过次级优先级决定执行顺序。需注意,若某中断已开始执行,次级优先级无法打断当前执行流程,即 “同步进入时谁高谁执行,中断执行中不得抢占” 。
7.2 中断优先级配置
7.3 涉及到的函数
1.
NVIC_SetPriorityGrouping
(全局配置)void NVIC_SetPriorityGrouping(uint32_t bits);
- 功能:设置系统全局的中断优先级分组方案,决定「占先优先级」和「次级优先级」的位数分配。
- 关键特性:
- 全局性:影响所有中断的优先级解释逻辑,系统中所有中断优先级都基于此分组规则解析。
- 一次性:通常在系统初始化阶段设置一次,运行中不应随意修改,否则可能导致中断优先级混乱。
- 基础性:为后续
NVIC_SetPriority
函数提供优先级分组的 “解读依据”,决定优先级数值中哪些位代表占先、哪些位代表次级。2.
NVIC_SetPriority
(个体中断配置)void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority);
- 功能:在
NVIC_SetPriorityGrouping
确定的分组规则下,为单个中断源设置具体优先级数值。- 关键特性:
- 个体性:仅作用于指定的
IRQn
(中断请求号),不影响其他中断。- 依赖性:
priority
数值的具体含义(占先、次级优先级如何拆分)完全由NVIC_SetPriorityGrouping
的分组规则决定。- 多次调用:可针对不同中断源单独调用,灵活配置每个中断的优先级。
案例分析(代码 + 逻辑拆解)
// 1. 全局分组配置:设置占先优先级、次级优先级各占 2 位 NVIC_SetPriorityGrouping(2); // 规则:优先级数值共 4 位(默认优先级位数与内核架构相关,此处按分组逻辑拆分) // 高 2 位 = 占先优先级,低 2 位 = 次级优先级 // 2. 为 EXTI0、EXTI1 配置优先级 NVIC_SetPriority(EXTI0_IRQn, 5); // 二进制:0101 → 占先[01],次级[01] NVIC_SetPriority(EXTI1_IRQn, 6); // 二进制:0110 → 占先[01],次级[10] // 场景:EXTI0_IRQn、EXTI1_IRQn 同时触发 // 优先级分析: // - 占先优先级:两者均为 01(相同) // - 次级优先级:EXTI0 是 01,EXTI1 是 10 → 01(EXTI0)更高 // 执行顺序:EXTI0_IRQn 先执行 // 3. 为 EXTI2、EXTI3 配置优先级 NVIC_SetPriority(EXTI2_IRQn, 2); // 二进制:0010 → 占先[00],次级[10] NVIC_SetPriority(EXTI3_IRQn, 10); // 二进制:1010 → 占先[10],次级[10] // 场景:EXTI3_IRQn 正在执行时,EXTI2_IRQn 触发 // 优先级分析: // - 占先优先级:EXTI2 是 00,EXTI3 是 10 → 00(EXTI2)更高 // 执行逻辑:EXTI3_IRQn 被“压栈暂停”,先执行 EXTI2_IRQn,执行完再回到 EXTI3 继续
8. USART 操作中断优化
8.1 USART 中断优化思路
借助 USART 相关的 USART_CR 控制寄存器 和 USART_SR 状态寄存器 ,来识别当前 USART 的工作状态,主要用于解决两类关键问题:
- 数据发送完成问题:判断串口数据是否已全部发送出去,避免数据发送不完整或重复发送。
- 数据接收完成问题:识别串口是否接收到完整数据,及时触发接收处理逻辑。
8.2 数据接收完成问题
(1)数据读取完成中断判断
当 USART 接收到数据后,需通过状态寄存器(如 USART_SR
的 RXNE
标志位,含义为 “接收数据寄存器非空” )判断数据接收状态。若该标志置位,说明有新数据接收完成,可触发中断进入数据读取流程,避免轮询等待,提升系统效率。
(2)CR 控制寄存器作用
USART_CR 控制寄存器 用于配置 USART 中断使能。例如,通过设置 USART_CR1
中的 RXNEIE
位(接收非空中断使能),当 USART_SR
的 RXNE
标志置位时,即可触发串口接收中断,进入中断处理函数读取数据,实现 “数据接收完成即响应” 的高效机制 。
一、外部中断与内部异常的区分
外部中断:
- 触发源来自芯片外部(如 GPIO 引脚电平变化,如按键输入),由外部中断控制器(EXTI)管理。
- 示例:
EXTI0_IRQHandler
(PA0 引脚)、EXTI2_IRQHandler
(PE2 引脚)等,需配置引脚映射、触发方式(上升沿 / 下降沿)、中断使能等。内部异常:
- 触发源来自芯片内部(如内核事件、硬件错误),由 Cortex-M 内核直接管理,属于 “异常” 范畴。
- 典型案例:
RESET
(复位,优先级 - 3)、NMI
(不可屏蔽中断,优先级 - 2)、HardFault
(硬件错误,优先级 - 1),优先级固定且不可修改。二、中断优先级与 NVIC 配置
优先级规则:
- 优先级数值越小,等级越高;分为 “占先优先级” 和 “次级优先级”,占先优先级高的中断可抢占正在执行的低占先优先级中断。
NVIC 关键函数:
NVIC_SetPriorityGrouping
:全局配置占先 / 次级优先级的位数分配(仅初始化时设置一次)。NVIC_SetPriority
:为单个中断源设置具体优先级(依赖全局分组规则)。三、USART 相关寄存器与中断
核心寄存器:
- SR(状态寄存器):反映 USART 工作状态,如
RXNE
(接收寄存器非空,数据已接收)、IDLE
(总线空闲,数据传输结束)。- CR(控制寄存器):配置 USART 功能,如
RE
(接收使能,基础开关)、RXNEIE
(RXNE 中断使能,允许数据接收后触发中断)、IDLEIE
(空闲中断使能,允许总线空闲时触发中断)。中断触发逻辑:
- RXNE 中断:
RE=1
(接收使能)+RXNE=1
(有数据)+RXNEIE=1
(中断使能)→ 触发中断,用于实时接收单个字节。- IDLE 中断:
RE=1
+IDLE=1
(总线空闲)+IDLEIE=1
→ 触发中断,用于判断一帧数据传输结束。USART 中断处理函数:
USART1_IRQHandler
:同时响应 RXNE 中断(存储单个字节到缓冲区)和 IDLE 中断(标记数据接收完成并回显),需在函数中通过 SR 寄存器标志位区分具体中断源。
8.3 完整代码实现
// 1. USART1中断使能与配置函数
void USART1_Interrupt_Enable(void)
{
// 使能两种中断:
// - IDLEIE(位4):数据总线空闲中断使能
// - RXNEIE(位5):接收数据寄存器非空中断使能
USART1->CR1 |= (0x01 << 4) | (0x01 << 5);
// 设置USART1中断优先级
// 全局优先级分组为2的情况下,优先级值0001表示:
// - 占先优先级:0(高2位)
// - 次级优先级:1(低2位)
NVIC_SetPriority(USART1_IRQn, 1);
// 使能USART1对应的中断请求
NVIC_EnableIRQ(USART1_IRQn);
}
// 2. 数据存储结构体定义
#define DATA_SIZE (256) // 缓冲区大小定义
typedef struct usart1_data
{
u8 data[DATA_SIZE]; // 接收数据缓冲区
u8 flag; // 数据处理标志位(1表示接收完成并回显)
u16 count; // 有效接收字节数
} USART1_Data;
extern USART1_Data usart1_val; // 外部声明,供其他模块使用
// 3. USART1中断处理函数(响应RXNE和IDLE中断)
void USART1_IRQHandler(void)
{
u32 val = 0; // 临时变量,用于清除IDLE标志位
// 若数据已处理完成(flag=1),清空缓冲区准备下次接收
if (usart1_val.flag)
{
memset(&usart1_val, 0, sizeof(USART1_Data));
}
// 处理RXNE中断(接收到单个字节)
if (USART1->SR & (0x01 << 5)) // 检测RXNE标志位(位5)
{
// 将接收寄存器数据存入缓冲区,计数+1
usart1_val.data[usart1_val.count++] = USART1->DR;
// 若缓冲区已满,回显数据并标记接收完成
if (DATA_SIZE == usart1_val.count)
{
USART1_SendBuffer(usart1_val.data, usart1_val.count);
usart1_val.flag = 1;
}
}
// 处理IDLE中断(总线空闲,一帧数据接收完成)
if (USART1->SR & (0x01 << 4)) // 检测IDLE标志位(位4)
{
// 标记数据接收完成
usart1_val.flag = 1;
// 按官方要求清除IDLE标志位:先读SR,再读DR
val = USART1->SR;
val = USART1->DR;
// 回显接收到的完整数据到PC端
USART1_SendBuffer(usart1_val.data, usart1_val.count);
}
}
https://github.com/0voice