单片机中断原理

中断原理是掌握 STM32 外设编程的关键知识点。

中断的核心概念(通俗理解)

中断的本质是:外设 / 外部事件主动打断 CPU 的正常执行流程,请求 CPU 优先处理自己的紧急事务,处理完成后 CPU 恢复原流程。

中断优先级

STM32 的中断优先级由两部分组成,共 4 位(可配置为 0-15 级,数值越小优先级越高):

  • 抢占优先级(Preemption Priority) :高抢占优先级的中断可以打断正在执行的低抢占优先级中断("抢占" 特性)。
  • 响应优先级(Sub Priority/Secondary Priority) :仅当两个中断抢占优先级相同 时,响应优先级决定谁先执行;响应优先级高的先执行,但无法打断对方。

中断优先级分组可以使用NVIC_PriorityGroupConfig函数,

复制代码
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);

函数参数说明

参数 NVIC_PriorityGroup 是枚举类型(实际为宏定义),对应 5 种分组方式,取值如下

|----------------------|---------|---------|
| 分组编号 | 抢占优先级位数 | 响应优先级位数 |
| NVIC_PriorityGroup_0 | 0 | 4 |
| NVIC_PriorityGroup_1 | 1 | 3 |
| NVIC_PriorityGroup_2 | 2 | 2 |
| NVIC_PriorityGroup_3 | 3 | 1 |
| NVIC_PriorityGroup_4 | 4 | 0 |

简单代码示例(外部中断)

cs 复制代码
#include "stm32f10x.h"

// 初始化PA0为外部中断输入
void EXTI0_Init(void)
{
// 定义3个"配置结构体":就像3张表格,分别填GPIO、EXTI、NVIC的设置
    GPIO_InitTypeDef GPIO_InitStruct;
    EXTI_InitTypeDef EXTI_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;

    // 1. 使能时钟(GPIOA、AFIO:复用功能时钟,外部中断需要)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

    // 2. 配置PA0为上拉输入
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; 
    // 上拉输入,把设置写入ST按键按下,PA0 和 GND 连通,变成低电平 
    GPIO_Init(GPIOA, &GPIO_InitStruct); // 把设置写入STM32的寄存器,生效

    // 3. 将PA0映射到EXTI0(外部中断线0)
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);

    // 4. 配置EXTI0中断参数
    EXTI_InitStruct.EXTI_Line = EXTI_Line0; // 中断线0
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式(而非事件模式)
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发(按键按下)
    EXTI_InitStruct.EXTI_LineCmd = ENABLE; // 使能中断线0
    EXTI_Init(&EXTI_InitStruct);

    // 5. 配置NVIC(中断优先级+使能)
    NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn; // 外部中断0的IRQ通道
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级1
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; // 响应优先级0
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // 使能该通道
    NVIC_Init(&NVIC_InitStruct);
}

// 6. 中断服务函数(必须用固定名称)
void EXTI0_IRQHandler(void)
{
    // 检查中断标志位,避免误触发
    if(EXTI_GetITStatus(EXTI_Line0) != RESET)
    {
        // 这里写中断处理逻辑(比如翻转LED)
        GPIO_WriteBit(GPIOC, GPIO_Pin_13, !GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13));
        
        // 清除中断标志位(关键!否则会一直触发中断)
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}

int main(void)
{
    // 初始化LED(假设PC13接LED)
    GPIO_InitTypeDef GPIO_InitStruct;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStruct);

    EXTI0_Init(); // 初始化外部中断

    while(1)
    {
        // 主程序常规工作(比如循环打印)
        // 中断会打断这个循环,处理完自动返回
    }
}

这段代码是让 STM32 的 PA0 引脚(接按键)按下时,触发中断,自动翻转 PC13 引脚(接 LED)的电平(亮 / 灭),主程序不用一直盯着按键,效率更高。

整个代码分 3 部分:

  1. 初始化函数 EXTI0_Init():给硬件 "做设置",告诉 STM32:PA0 是中断引脚、触发条件是按键按下、这个中断的优先级是多少;
  2. 中断服务函数 EXTI0_IRQHandler():中断发生时(按键按下),STM32 自动执行的代码(核心是翻转 LED);
  3. 主函数 main():程序入口,初始化 LED 和中断后,主程序进入空循环(模拟常规工作)。

STM32 有 16 条外部中断线(EXTI0~EXTI15),但引脚有上百个,需要手动把 "PA0 引脚" 和 "EXTI0 中断线" 绑定,这样 PA0 的电平变化才能触发中断。

cs 复制代码
    // 4. 配置EXTI0中断规则
    EXTI_InitStruct.EXTI_Line = EXTI_Line0;               // 选中中断线0
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;      // 模式:中断
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;  // 触发条件:下降沿(高→低,对应按键按下)
    EXTI_InitStruct.EXTI_LineCmd = ENABLE;                // 使能这条中断线
    EXTI_Init(&EXTI_InitStruct);                          // 写入寄存器,生效

触发条件解释:Falling(下降沿)= 按键按下(PA0 从高电平变低电平);如果是Rising(上升沿)= 按键松开;Rising_Falling= 按下 / 松开都触发。

cs 复制代码
    // 5. 配置NVIC(中断控制器):给中断设优先级+开启
    NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;        // 选中"外部中断0"这个通道
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级1(数字越小优先级越高)
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;      // 响应优先级0
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;         // 开启这个中断通道
    NVIC_Init(&NVIC_InitStruct);                         // 写入寄存器,生效
}

NVIC 是 "中断管家":告诉管家 "外部中断 0 可以用,优先级是 1(比较高)",这样管家收到 PA0 的中断请求后,会通知 CPU 处理。

cs 复制代码
void EXTI0_IRQHandler(void)  // 函数名固定!不能改,改了CPU找不到
{
    // 先检查:是不是EXTI0中断触发的(避免误触发)
    if(EXTI_GetITStatus(EXTI_Line0) != RESET)
    {
        // 中断处理逻辑:翻转PC13的电平(LED亮/灭切换)
        GPIO_WriteBit(GPIOC, GPIO_Pin_13, !GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13));
        
        // 关键中的关键:清除中断标志位!
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}
  • EXTI_GetITStatus:查 "中断标志位",如果是RESET(0),说明不是 EXTI0 触发的,直接跳过;
  • GPIO_WriteBit(..., !GPIO_ReadOutputDataBit(...)):先读 PC13 当前电平(0 或 1),!是 "取反"(0 变 1,1 变 0),再写回 PC13------ 实现 LED 翻转;
  • EXTI_ClearITPendingBit:清除中断标志位!如果不清除,NVIC 会认为 "中断还没处理完",会反复触发这个函数,导致 LED 疯狂闪、程序卡死。
cs 复制代码
int main(void)
{
    // 先初始化LED(PC13):配置为推挽输出
    GPIO_InitTypeDef GPIO_InitStruct;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // 给GPIOC开时钟
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;               // 选中PC13
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;         // 推挽输出(能驱动LED)
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;        // 输出速度(新手随便设,不影响)
    GPIO_Init(GPIOC, &GPIO_InitStruct);                   // 生效

    EXTI0_Init(); // 调用上面的函数,初始化外部中断

    while(1)  // 主程序循环(模拟常规工作)
    {
        // 这里可以写主程序要做的事,比如:
        // 读取传感器、控制电机、打印数据等
        // 即使在循环里,按键按下也会触发中断,打断循环去翻转LED
    }
}

while(1)是死循环:主程序会一直在这里执行常规任务,中断是 "插队" 的 ------ 按键按下时,CPU 暂停循环,去执行EXTI0_IRQHandler,执行完再回到循环继续。

执行流程

  1. 程序启动,先执行main():初始化 LED→初始化中断→进入while(1)空循环;
  2. 你没按按键:CPU 一直在while(1)里 "空转",PA0 是高电平,无中断;
  3. 你按下按键:PA0 从高→低(下降沿),触发 EXTI0 中断请求;
  4. NVIC(中断管家)检查:这个中断已开启、优先级够高→通知 CPU;
  5. CPU 暂停while(1),自动跳转到EXTI0_IRQHandler执行:
    • 检查中断标志位→翻转 LED→清除标志位;
  6. 服务函数执行完,CPU 回到while(1),继续空转;
  7. 你松开按键:PA0 从低→高(上升沿),但我们只设了下降沿触发,所以无反应。
  • 中断代码的核心是3 个配置 + 1 个服务函数:GPIO 引脚配置→EXTI 中断线配置→NVIC 优先级配置→中断服务函数(处理逻辑 + 清标志位);
  • 中断服务函数名必须固定 (如EXTI0_IRQHandler),且一定要清除中断标志位,否则会卡死;
  • 中断的本质是 "插队":主程序正常运行,按键按下时 CPU 临时去处理 LED 翻转,处理完回来继续。
代码关键原理对应:
  • GPIO_EXTILineConfig:把外设(PA0)和中断线(EXTI0)关联,触发源和中断请求绑定;
  • NVIC_Init:配置中断优先级并使能 NVIC 通道,满足 "响应条件";
  • EXTI0_IRQHandler:中断服务函数,是 CPU 处理中断的核心逻辑;
  • EXTI_ClearITPendingBit:清除中断挂起标志,否则 NVIC 会认为中断未处理,反复触发。
相关推荐
上海合宙LuatOS2 小时前
LuatOS核心库API——【hmeta 】硬件元数据
单片机·嵌入式硬件·物联网·算法·音视频·硬件工程·哈希算法
白太岁5 小时前
操作系统开发:(7) 优先级反转与继承、TLS 及核亲和性
c语言·单片机·系统架构
嵌入式×边缘AI:打怪升级日志6 小时前
环境监测传感器从设备程序设计(ADC采集与输出控制)
单片机·嵌入式硬件·fpga开发
张槊哲7 小时前
IIC时序图详解
单片机
Hello_Embed7 小时前
Modbus 传感器开发:STM32F030 串口编程
笔记·stm32·单片机·嵌入式·freertos·modbus
cameron_tt7 小时前
stm32智能垃圾桶
stm32·单片机·嵌入式硬件
视觉AI8 小时前
USB转网口+Windows共享网络异常:ax650无法上网排查与完美解决
网络·windows·stm32
myron66888 小时前
基于STM32LXXX的模数转换芯片ADC(ADS1110A0IDBVR)驱动C程序设计
c语言·stm32·嵌入式硬件
風清掦9 小时前
【江科大STM32学习笔记-06】TIM 定时器 - 6.1 定时器的基本定时功能
笔记·stm32·学习