一、前言

在学习 STM32 外部中断时,经常会看到这样的配置流程:

GPIO_Init(...);
GPIO_EXTILineConfig(...);
EXTI_Init(...);
NVIC_Init(...);

很多初学者会疑惑:

明明我只是想让一个按键触发中断,为什么既要配置 EXTI,又要配置 NVIC?

其实,EXTINVIC 都和中断有关,但它们负责的层次不同。

一句话理解:

EXTI:负责检测外部中断事件
NVIC:负责管理 CPU 是否响应这个中断

也就是说:

EXTI 负责“中断从哪里来”
NVIC 负责“CPU 要不要处理它”

二、EXTI 是什么?

EXTI 的全称是:

External Interrupt/Event Controller
外部中断/事件控制器

它主要用于检测外部引脚上的电平变化,比如:

上升沿
下降沿
上升沿和下降沿

例如,一个按键连接在 PB14 上,当按键按下时,PB14 的电平发生变化,EXTI 就可以检测到这个变化,并产生中断请求。

比如配置 PB14 为下降沿触发中断:

EXTI_InitTypeDef EXTI_InitStructure;

EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;

EXTI_Init(&EXTI_InitStructure);

这段代码的意思是:

配置 EXTI14 这条外部中断线
工作模式是中断模式
触发方式是下降沿触发
使能这条 EXTI 中断线

所以,EXTI 主要解决的问题是:

哪个外部引脚触发中断?
通过哪条 EXTI 线触发?
上升沿触发还是下降沿触发?
是否允许这条 EXTI 线产生中断请求?

三、NVIC 是什么?

NVIC 的全称是:

Nested Vectored Interrupt Controller
嵌套向量中断控制器

它是 ARM Cortex-M 内核中的中断管理器。

STM32 中的中断源有很多,比如:

EXTI 外部中断
TIM 定时器中断
USART 串口中断
ADC 中断
DMA 中断
SPI 中断
I2C 中断

这些中断最终都要交给 NVIC 管理。

NVIC 主要负责:

是否允许某个中断进入 CPU
设置中断优先级
多个中断同时发生时,决定先处理谁
管理中断嵌套

例如配置 EXTI15_10 中断通道:

NVIC_InitTypeDef NVIC_InitStructure;

NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

这段代码的意思是:

使能 EXTI15_10 这个中断通道
设置抢占优先级为 1
设置响应优先级为 1
允许 CPU 响应这个中断

所以,NVIC 主要解决的问题是:

这个中断 CPU 允不允许响应?
这个中断优先级是多少?
多个中断同时来了先执行谁?
这个中断能不能打断其他中断?

四、EXTI 和 NVIC 的区别

可以用一张表来理解:

对比项 EXTI NVIC
全称 External Interrupt/Event Controller Nested Vectored Interrupt Controller
中文名 外部中断/事件控制器 嵌套向量中断控制器
所属位置 STM32 外设部分 ARM Cortex-M 内核部分
负责内容 检测外部引脚触发事件 管理 CPU 是否响应中断
主要配置 中断线、触发边沿、中断/事件模式 中断通道、优先级、使能
作用对象 GPIO 外部中断线 所有中断源
举例 EXTI_Line14 EXTI15_10_IRQn

简单来说:

EXTI 管的是外部中断的“触发条件”
NVIC 管的是 CPU 对中断的“响应规则”

五、为什么配置中断时 EXTI 和 NVIC 都要配置?

一个完整的中断过程需要两步:

第一步:外设产生中断请求
第二步:CPU 响应中断请求

EXTI 负责第一步:

检测到 PB14 发生下降沿
产生 EXTI14 中断请求

NVIC 负责第二步:

允许 EXTI15_10_IRQn 这个中断通道进入 CPU
然后 CPU 执行中断服务函数

如果只配置 EXTI,不配置 NVIC:

EXTI 能检测到外部中断事件
但是 CPU 不会进入中断服务函数

如果只配置 NVIC,不配置 EXTI:

CPU 虽然允许响应这个中断通道
但是 EXTI 没有产生中断请求
所以中断也不会发生

因此,配置外部中断时,EXTI 和 NVIC 必须同时配置。


六、以 PB14 按键中断为例

假设按键连接在 PB14 上,希望按键按下时触发外部中断。

完整的中断路径如下:

PB14 引脚电平变化
        ↓
AFIO 将 PB14 映射到 EXTI14
        ↓
EXTI14 检测下降沿
        ↓
EXTI 产生中断请求
        ↓
NVIC 接收 EXTI15_10_IRQn 中断请求
        ↓
CPU 暂停当前程序
        ↓
进入 EXTI15_10_IRQHandler()
        ↓
执行中断服务函数

这里要注意一点:

PB14 对应的是 EXTI_Line14
但是中断通道是 EXTI15_10_IRQn
中断服务函数是 EXTI15_10_IRQHandler

原因是 STM32 把 EXTI10 到 EXTI15 合并到了同一个中断通道里:

EXTI10
EXTI11
EXTI12
EXTI13
EXTI14
EXTI15
        ↓
EXTI15_10_IRQn
        ↓
EXTI15_10_IRQHandler()

所以在中断服务函数中,需要判断到底是哪一条 EXTI 线触发的中断。


七、PB14 外部中断配置示例

下面是一个基于 STM32F10x 标准外设库的 PB14 外部中断配置示例。

1. GPIO 和 AFIO 配置

void ExtiKey_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    // 1. 开启 GPIOB 和 AFIO 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);

    // 2. 配置 PB14 为上拉输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // 3. 将 PB14 映射到 EXTI14
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);

    // 4. 配置 EXTI14
    EXTI_InitStructure.EXTI_Line = EXTI_Line14;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    // 5. 配置 NVIC
    NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

2. 中断服务函数

void EXTI15_10_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line14) == SET)
    {
        // 这里写按键按下之后要执行的代码

        // 清除中断挂起标志位
        EXTI_ClearITPendingBit(EXTI_Line14);
    }
}

这里有一个非常重要的细节:

EXTI_ClearITPendingBit(EXTI_Line14);

这句代码必须写。

因为中断发生后,EXTI 的挂起标志位会被置位。如果不清除这个标志位,程序可能会一直重复进入中断服务函数。


八、GPIO、AFIO、EXTI、NVIC 的关系

配置外部中断时,通常需要配置四个部分:

GPIO:配置引脚输入模式
AFIO:把 GPIO 引脚映射到 EXTI 线
EXTI:配置中断线和触发方式
NVIC:配置中断通道和优先级

以 PB14 为例:

GPIO:把 PB14 配置成上拉输入
AFIO:把 PB14 连接到 EXTI14
EXTI:配置 EXTI14 为下降沿触发
NVIC:打开 EXTI15_10_IRQn 中断通道

完整关系如下:

PB14
 ↓
GPIO 输入模式
 ↓
AFIO 映射到 EXTI14
 ↓
EXTI14 检测下降沿
 ↓
NVIC 管理 EXTI15_10_IRQn
 ↓
CPU 执行 EXTI15_10_IRQHandler()

九、为什么 PB14 对应 EXTI14?

STM32 的 EXTI 线和 GPIO 引脚编号是一一对应的。

例如:

PA0 / PB0 / PC0 / PD0  对应 EXTI0
PA1 / PB1 / PC1 / PD1  对应 EXTI1
PA2 / PB2 / PC2 / PD2  对应 EXTI2
...
PA14 / PB14 / PC14 / PD14 对应 EXTI14

所以:

PB14 对应 EXTI_Line14
PC13 对应 EXTI_Line13
PA0  对应 EXTI_Line0

但是需要注意:

同一条 EXTI 线同一时间只能连接一个 GPIO 端口

也就是说,EXTI14 可以选择连接 PA14,也可以选择连接 PB14,也可以选择连接 PC14,但不能同时连接多个。

例如:

GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);

这句代码的意思是:

把 GPIOB 的 Pin14,也就是 PB14,连接到 EXTI14

十、总结

EXTI 和 NVIC 的核心区别是:

EXTI:负责检测外部中断触发条件
NVIC:负责管理 CPU 是否响应中断

配置外部中断时,两个都要配置:

EXTI 不配置:没有中断请求
NVIC 不配置:CPU 不响应中断

外部中断的完整流程可以总结为:

GPIO 输入配置
    ↓
AFIO 外部中断线映射
    ↓
EXTI 配置触发方式
    ↓
NVIC 配置中断优先级
    ↓
编写中断服务函数

用生活中的例子理解:

EXTI 像门铃检测器,负责发现有人按门铃;
NVIC 像大脑的调度器,负责决定是否去处理这个门铃事件。

所以,EXTI 和 NVIC 不是重复配置,而是分别负责中断系统的不同环节。只有 EXTI 产生中断请求,并且 NVIC 允许 CPU 响应,中断服务函数才会真正执行。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐