STM32的中断系统

发布于:2025-08-11 ⋅ 阅读:(15) ⋅ 点赞:(0)

先想象你在写作业(主程序),突然有人敲门(外部事件)。你先放下手里的笔,去开门(处理中断),然后回来继续写作业(回到主程序)。中断也如此。

什么是中断?

中断 = 程序在运行中,遇到某个特殊事件时,暂停当前任务 → 去执行一个专门的中断处理程序 → 再返回原任务。

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();            // 初始化 OLED
    CountSensor_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_SEVONPENDNVIC_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 等产生
应用场景 按键、传感器、外部脉冲采集等 定时周期任务、通信数据处理、模拟采样等