STM32 EXTI 外部中断:函数全解析 + 5 步实战,从时钟到中断一次打通

🚀 我的STM32学习专栏:[点击直达 | 持续更新中]

按键按烂了都没进中断?编码器转得飞起计数却纹丝不动?EXTI 配置步骤多、函数杂,很多新手在 RCC、AFIO、NVIC 之间反复横跳,结果还是配不通。其实核心就 5 步,今天不废话,直接上干货,带你从时钟到中断服务函数一次性打通任督二脉。
一、AFIO 配置函数
AFIO 负责引脚重映射和EXTI 线路选择,使用 EXTI 前必须先开 AFIO 时钟。
| 函数 | 作用 | 实战重要度 |
|---|---|---|
GPIO_AFIODeInit(void) |
复位 AFIO 所有寄存器到默认值 | ⭐ 调试用 |
GPIO_PinLockConfig(GPIOx, GPIO_Pin) |
锁定引脚配置,防止后续误改 | ⭐ 防呆用 |
GPIO_EventOutputConfig(PortSource, PinSource) |
选择事件输出源引脚 | ⭐ 事件模式用 |
GPIO_EventOutputCmd(NewState) |
使能/失能事件输出 | ⭐ 事件模式用 |
GPIO_PinRemapConfig(GPIO_Remap, NewState) |
引脚重映射(如 USART1 重映射到 PB6/PB7) | ⭐⭐⭐ 重映射必用 |
GPIO_EXTILineConfig(PortSource, PinSource) |
选择 EXTI 中断线对应的引脚 | ⭐⭐⭐⭐⭐ EXTI 必用 |
核心记忆点:只要用 EXTI,
GPIO_EXTILineConfig()是 AFIO 里唯一必须调用的函数。它的作用是把PA0/PB0/PC0...中的某一个,绑定到EXTI_Line0上。
二、EXTI 配置函数
EXTI(External Interrupt/Event Controller)管理外部中断线 0~15 的触发方式和使能。
| 函数 | 作用 | 使用场景 |
|---|---|---|
EXTI_DeInit(void) |
EXTI 寄存器恢复上电默认值 | 代码初始化前清零用 |
EXTI_Init(EXTI_InitStruct) |
根据结构体初始化 EXTI | ⭐⭐⭐⭐⭐ 必用 |
EXTI_StructInit(EXTI_InitStruct) |
给结构体赋默认值 | 推荐在 EXTI_Init 前调用,避免结构体残留脏数据 |
EXTI_GenerateSWInterrupt(EXTI_Line) |
软件触发一次外部中断 | 模拟中断、自测试时用 |
EXTI_GetFlagStatus(EXTI_Line) |
读事件/状态寄存器标志 | 主程序里轮询查看 |
EXTI_ClearFlag(EXTI_Line) |
清除事件/状态寄存器标志 | 主程序里清标志 |
EXTI_GetITStatus(EXTI_Line) |
读中断挂起标志位 | ⭐⭐⭐⭐⭐ 中断函数内判断标志 |
EXTI_ClearITPendingBit(EXTI_Line) |
清除中断挂起标志位 | ⭐⭐⭐⭐⭐ 中断函数内必须清,否则卡死 |
Flag vs IT 区别:
Flag系列读写的是状态寄存器,用于主程序查询外设状态;IT系列读写的是中断挂起寄存器,用于中断服务函数。中断里不清ITPendingBit,CPU 会反复进入中断,程序直接卡死。
三、NVIC 配置函数
NVIC(Nested Vectored Interrupt Controller)是 Cortex-M3 内核的中断管家,负责分组、优先级、使能通道。
| 函数 | 作用 | 实战重要度 |
|---|---|---|
NVIC_PriorityGroupConfig(NVIC_PriorityGroup) |
设置抢占优先级和响应优先级的分组 | ⭐⭐⭐⭐⭐ 必用,且只能设一次 |
NVIC_Init(NVIC_InitStruct) |
根据结构体初始化 NVIC 通道 | ⭐⭐⭐⭐⭐ 每开一个中断就要调一次 |
NVIC_SetVectorTable(VectTab, Offset) |
设置中断向量表位置(Flash/RAM) | ⭐ 搞 Bootloader、IAP 时才用 |
NVIC_SystemLPConfig(LowPowerMode, NewState) |
系统低功耗配置 | ⭐ 低功耗场景用 |
EXTI_ClearITPendingBit(EXTI_Line) |
清 EXTI 中断挂起位 | ⭐⭐⭐⭐⭐ 中断函数末尾必加 |
注意:NVIC 是内核外设,时钟默认开启,不需要
RCC_APBxPeriphClockCmd()。EXTI 时钟也是默认开的,但 GPIO 和 AFIO 必须手动开。
四、EXTI 配置五步法(全局地图)
| 步骤 | 操作 | 核心目的 |
|---|---|---|
| 1 | RCC 开时钟 | GPIO + AFIO 的时钟必须手动打开 |
| 2 | 配置 GPIO | 设为输入模式(上拉/浮空/下拉,看硬件) |
| 3 | 配置 AFIO | 用 GPIO_EXTILineConfig() 把引脚映射到 EXTI 线 |
| 4 | 配置 EXTI | 选中断线、触发方式(上升/下降/双边沿)、使能 |
| 5 | 配置 NVIC | 分组、设优先级、使能中断通道 |
注意:EXTI 和 NVIC 时钟默认开启;NVIC 是内核外设,不需要 RCC 开时钟。RCC 只管内核外的外设。
五、实战代码:编码器 EXTI 配置
以下代码基于 STM32F103 标准库,以 GPIOB Pin0 / Pin1 接编码器 A/B 相为例,下降沿触发。
#include "stm32f10x.h"
int16_t Encoder_Count = 0; // 编码器计数值
/**
* @brief 编码器 EXTI 初始化(PB0 -> EXTI0, PB1 -> EXTI1)
* @note 每一步解析都在注释里,对照五步法食用更佳
*/
void Encoder_Init(void)
{
/* ========== 第 1 步:RCC 开时钟 ==========
* GPIOB 在 APB2 总线上,AFIO 负责引脚复用和中断映射,也必须开时钟
*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 开启 GPIOB 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 开启 AFIO 时钟
/* ========== 第 2 步:配置 GPIO 为输入模式 ==========
* 编码器一般接上拉输入(IPU),避免浮空导致电平乱跳
*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; // 同时配置 PB0、PB1
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输入模式下速度参数无实际影响,习惯性写上
GPIO_Init(GPIOB, &GPIO_InitStructure); // 写入配置
/* ========== 第 3 步:AFIO 映射中断线 ==========
* 关键函数:GPIO_EXTILineConfig()
* 作用:把 PB0 映射到 EXTI_Line0,PB1 映射到 EXTI_Line1
* 同一 EXTI_Line 同一时间只能映射到一个端口(如 PA0/PB0/PC0 三选一)
*/
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0); // PB0 -> EXTI0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1); // PB1 -> EXTI1
/* ========== 第 4 步:配置 EXTI 外设 ==========
* 选择中断线、使能线路、设中断模式、选下降沿触发
*/
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1; // 同时配置 Line0 和 Line1
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能中断线
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 设为中断模式(非事件模式)
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发(编码器常用)
EXTI_Init(&EXTI_InitStructure); // 结构体参数写入寄存器
/* ========== 第 5 步:配置 NVIC ==========
* 先分组,再配置具体通道的抢占优先级和响应优先级
*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 2位抢占 + 2位响应优先级
NVIC_InitTypeDef NVIC_InitStructure;
// --- EXTI0 通道配置(PB0)---
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; // 中断通道:EXTI0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 响应优先级 1
NVIC_Init(&NVIC_InitStructure);
// --- EXTI1 通道配置(PB1)---
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; // 中断通道:EXTI1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; // 响应优先级 2(与 EXTI0 区分)
NVIC_Init(&NVIC_InitStructure);
}
/**
* @brief 获取编码器计数值并清零
*/
int16_t Encoder_Get(void)
{
int16_t Temp = Encoder_Count;
Encoder_Count = 0;
return Temp;
}
/* ==================== 中断服务函数 ==================== */
/**
* @brief EXTI0 中断服务函数(对应 PB0)
* @note 中断函数务必简短!不要在这里写 Delay!
*/
void EXTI0_IRQHandler(void)
{
// 在中断函数里判断标志位,必须用 EXTI_GetITStatus()
if (EXTI_GetITStatus(EXTI_Line0) == SET)
{
/* 软件消抖:二次读取引脚电平,确认确实是低电平(下降沿) */
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
{
// 判断另一相电平,确定旋转方向
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
Encoder_Count--; // 反转
}
}
// 中断内清除挂起标志位,必须用 EXTI_ClearITPendingBit()
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
/**
* @brief EXTI1 中断服务函数(对应 PB1)
*/
void EXTI1_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line1) == SET)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
{
Encoder_Count++; // 正转
}
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
更多工程代码需要可在跳转链接查看:STM32_Project: 基于江科大的STM32的教程编写的程序
六、三大避坑指南
表格
| 坑点 | 原因 | 解决 |
|---|---|---|
| 进不了中断 | 忘了开 AFIO 时钟,或 GPIO_EXTILineConfig() 映射错了 |
检查 RCC 和 AFIO 映射 |
| 中断只进一次 | ISR 末尾没调 EXTI_ClearITPendingBit() |
中断内必须用 IT 系列清标志 |
| 中断里卡死 | 在 ISR 里调用了 Delay(),SysTick 优先级不够 |
中断函数只干最小的事,绝不 Delay |
七、结语
EXTI 配置的核心就是记住三张函数表 + 五步走流程。AFIO 只管映射,EXTI 只管触发方式,NVIC 只管优先级和使能,各司其职,不要搞混。
上面这套编码器代码经过实际项目验证,复制到 main.c 改改引脚就能编译通过。如果你想看 定时器编码器模式(更精准、不丢步)、DMA 双缓冲采集、ADC 多通道 DMA 等进阶玩法,可以移步我的 CSDN 专栏,源码和工程模板(STM32_Project: 基于江科大的STM32的教程编写的程序)持续更新中。
觉得有用的话,点赞收藏不迷路,评论区见! 🎯

更多推荐


所有评论(0)