STM32的中断系统
先想象你在写作业(主程序),突然有人敲门(外部事件)。你先放下手里的笔,去开门(处理中断),然后回来继续写作业(回到主程序)。中断也如此。
什么是中断?
中断 = 程序在运行中,遇到某个特殊事件时,暂停当前任务 → 去执行一个专门的中断处理程序 → 再返回原任务。
STM32 中的中断有很多来源:
外部中断(EXTI):来自外部引脚的事件(按键、传感器信号等)。
内部外设中断:来自外设的事件(串口收到数据、定时器溢出、ADC转换完成等)。
异常(Exception):比如硬件故障(硬Fault)、系统滴答 SysTick。
1.STM32 中断的硬件基础
中断的物理来源
STM32 中断的信号来源主要有两大类:
外部硬件事件
来自 MCU 引脚的电平变化(高变低、低变高、双边沿)。
如:按键、传感器信号、外部模块的状态输出。
电气路径:引脚 → GPIO 输入缓冲器 → AFIO 选择器 → EXTI 模块 → NVIC。
内部外设事件
来自 MCU 内部外设的事件。
如:USART 接收到数据、TIM 定时器溢出、ADC 转换完成。
电气路径:外设状态寄存器(SR)设置中断标志位 → NVIC。
外部中断硬件流向
以 PA0 外部中断 为例:
PA0 引脚 ──> TTL 施密特触发器(整形去抖) ──> GPIO 输入电路 ↓AFIO(Alternate Function I/O,多功能映射)↓EXTI0(外部中断线0)↓NVIC(嵌套向量中断控制器)↓CPU 跳转到 ISR(中断服务程序)
重点:GPIO 只是信号入口,EXTI 才是中断检测单元。
中断响应流程
当事件发生时,底层流程是这样:
事件触发
例如 PA0 下降沿 → EXTI0 寄存器标志位 PR0 = 1。
EXTI 检查
EXTI 使能位 IMR0 = 1 且触发类型匹配(FTSR / RTSR)。
NVIC 检查
NVIC 使能该中断,且没有被屏蔽。
CPU 处理
保存当前执行环境(寄存器、程序计数器 PC、程序状态寄存器 xPSR)。
从中断向量表取 ISR 地址。
执行 ISR
执行用户定义的中断服务函数。
中断返回
恢复寄存器和 PC,回到被打断的地方继续执行。
2.举例红外传感器计次
开时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 开 GPIOB 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 开 AFIO 时钟(用于引脚与中断线的映射)
STM32 外设要先有时钟才能工作,GPIOB 和 AFIO 都要开。
AFIO 是 Alternate Function IO,外部中断要靠它把“引脚”映射到“EXTI 线”。
配置 GPIO 输入模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; // 选择 PB14
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO 速度(输入模式无实质作用)
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_Mode_IPU:输入模式,上拉(空闲时保持高电平)。
当外部设备拉低 PB14,就会产生一个下降沿,触发中断。
GPIO 与 EXTI 线绑定
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
STM32 有 16 条 EXTI 线(0~15),分别对应引脚号相同的 GPIO。
这里把 PB14 映射到 EXTI14。
注意:PB14、PC14、PA14 不能同时用 EXTI14,因为 EXTI14 只能绑定一个。
配置 EXTI(中断线)
EXTI_InitStructure.EXTI_Line = EXTI_Line14; // 选择 EXTI14
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能这条线
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 设置为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
EXTI_Init(&EXTI_InitStructure);
EXTI_Mode_Interrupt:触发时进入中断函数。
EXTI_Trigger_Falling:下降沿触发。
这样 PB14 从高电平变低电平时就会触发中断。
配置 NVIC(中断控制器)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置优先级分组
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; // EXTI10~15 共用一个中断号
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 子优先级 1
NVIC_Init(&NVIC_InitStructure);
EXTI15_10_IRQn:PB14 对应 EXTI14,属于 EXTI10~15 范围内的共享中断向量。
抢占优先级 + 子优先级 决定中断响应顺序。
中断执行过程(你的 EXTI15_10_IRQHandler)
void EXTI15_10_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Line14) == SET) // 判断是不是 EXTI14 触发{CountSensor_Count++; // 计数 +1}EXTI_ClearITPendingBit(EXTI_Line14); // 清除挂起标志,否则会一直进中断
}
PB14 检测到下降沿 → EXTI14 触发 → NVIC 调用中断函数。
EXTI_GetITStatus() 检查是不是 EXTI14 引起的中断(因为 EXTI10~15 共用一个函数)。
计数器 CountSensor_Count 自增。
EXTI_ClearITPendingBit() 清除中断标志位,否则下一次不会触发。
主程序如何使用中断计数
int main(void)
{OLED_Init(); // 初始化 OLEDCountSensor_Init(); // 初始化传感器中断OLED_ShowString(1,1,"Count:");while(1){OLED_ShowNum(1, 7, CountSensor_Get(), 5); // 显示中断计数值}
}
主程序并不去检测 PB14 状态,而是直接从 CountSensor_Get() 读取中断里更新的变量。
每次 PB14 有下降沿,就会进入中断加一,主循环只负责显示。
PB14 引脚变化↓
GPIO 检测到下降沿↓
EXTI14 触发中断请求↓
NVIC 根据优先级响应↓
执行 EXTI15_10_IRQHandler()↓
CountSensor_Count++
清除中断标志位
3.中断优先级的细节
STM32 优先级由两部分组成:
抢占优先级(Preemption Priority)
决定能否打断别人。
响应优先级 / 子优先级(Subpriority)
同级别抢占时,谁先执行。
STM32F1 的优先级分组(NVIC_PriorityGroupConfig()
)会把这两种优先级分配到不同位数:
分组 | 抢占优先级位数 | 子优先级位数 |
---|---|---|
Group 0 | 0 | 4 |
Group 1 | 1 | 3 |
Group 2 | 2 | 2 |
Group 3 | 3 | 1 |
Group 4 | 4 | 0 |
4.STM32 中断配置函数速查表
模块 | 函数名 | 功能说明 | 典型用途 | 备注 |
---|---|---|---|---|
EXTI(外部中断) | EXTI_DeInit() | 将 EXTI 外设寄存器恢复默认值 | 重新配置 EXTI 前清空配置 | 关闭所有中断线、清触发设置 |
EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct) | 配置中断线(模式、触发方式、使能) | 配置按键、传感器等外部信号中断 | 必须先 GPIO_EXTILineConfig() 选端口 | |
EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct) | 填充默认配置 | 避免结构体未初始化 | 一般先调用它再改字段 | |
EXTI_GenerateSWInterrupt(uint32_t EXTI_Line) | 软件触发中断 | 测试 ISR | 开发调试用 | |
EXTI_GetFlagStatus(uint32_t EXTI_Line) | 读取挂起标志位(Pending) | 轮询检测事件 | 不判断中断是否被屏蔽 | |
EXTI_ClearFlag(uint32_t EXTI_Line) | 清除挂起标志位 | 轮询处理完成后调用 | ISR 中推荐 EXTI_ClearITPendingBit() | |
EXTI_GetITStatus(uint32_t EXTI_Line) | 判断中断触发 且 使能 | ISR 判断当前中断线触发 | 比 GetFlagStatus 更精确 | |
EXTI_ClearITPendingBit(uint32_t EXTI_Line) | 清除中断挂起位(写 1 清零) | ISR 末尾调用,防止重复进中断 | 对应 EXTI_PR 寄存器 | |
NVIC(内核中断控制) | NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup) | 配置优先级分组(抢占优先级 vs 子优先级位数) | 初始化前设置优先级策略 | 影响所有中断优先级结构 |
NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct) | 配置中断通道的优先级、使能状态 | 开启 EXTI15_10、USART 等中断 | 需先设置优先级分组 | |
NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset) | 设置中断向量表基地址和偏移 | Bootloader 跳转到应用程序 | 可选 SRAM / Flash | |
NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState) | 设置低功耗模式下 NVIC 行为 | 节能应用 | 模式有 NVIC_LP_SEVONPEND 、NVIC_LP_SLEEPDEEP 等 | |
SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource) | 配置 SysTick 定时器时钟源 | 选择 HCLK 或 HCLK/8 | SysTick 常用于 OS 节拍或延时 |
使用顺序建议(外部中断场景)
GPIO 配置 → GPIO_Init() + GPIO_EXTILineConfig()
EXTI 配置 → EXTI_Init()(触发方式 + 使能)
NVIC 配置 → NVIC_PriorityGroupConfig() → NVIC_Init()
ISR 编写 → 判断 EXTI_GetITStatus() → 业务逻辑 → EXTI_ClearITPendingBit()
5.除此之外,还有内部中断
内部中断类型 | 说明 |
---|---|
定时器中断 (TIMx) | 定时器溢出、更新事件、捕获比较等产生的中断 |
USART 中断 | 数据发送完成、中断接收、错误等事件 |
ADC 中断 | 转换完成中断 |
DMA 中断 | 数据传输完成或错误 |
I2C、SPI 中断 | 通信事件、中断 |
SysTick 中断 | 系统节拍计时器周期到达中断 |
RTC 中断 | 实时时钟闹钟或秒脉冲中断 |
PVD 中断 | 电压检测器(PVD)产生的电压异常中断 |
USB 中断 | USB 事件产生的中断 |
看门狗中断(WWDG、IWDG) | 看门狗超时触发的中断 |
核心异常(Fault) | 硬件异常,如硬件故障、总线错误、使用错误等 |
内部中断和外部中断的区别
特点 | 外部中断(EXTI) | 内部中断 |
---|---|---|
触发源 | 外部引脚电平变化 | 内部外设事件或者系统模块产生 |
触发机制 | 由 GPIO 引脚电平变化触发 | 由定时器、通信接口、ADC 等产生 |
应用场景 | 按键、传感器、外部脉冲采集等 | 定时周期任务、通信数据处理、模拟采样等 |