STM32 外部中断 EXTI 实战记录(一)—— 从 GPIO 到计数
一、我想实现什么功能
用 STM32 检测一次遮挡,每触发一次,计数器加 1,并在 OLED 上显示结果。
这是一个非常经典的 EXTI 入门实验,但我实际做的时候,踩了不少坑。
二、硬件与环境
|
项目 |
说明 |
|---|---|
|
MCU |
STM32F103C8T6 |
|
开发环境 |
Keil uVision5 |
|
编译器 |
ARMCC V5 |
|
外设 |
GPIO、EXTI、NVIC、OLED |
|
输入信号 |
对射式红外 |
接线比较简单:
|
STM32 |
红外对射模块 |
|---|---|
|
PB14 |
DO |
|
3.3V |
VCC |
|
GND |
GND |
|
OLED |
STM32 |
|---|---|
|
GND |
PB6 |
|
VCC |
PB7 |
|
SCL |
PB8 |
| SDA | PB9 |
PB6,PB7分别接面包板的正负极。

三、EXTI 到底是什么?
EXTI(External Interrupt)就是让 GPIO 引脚在电平变化时打断 CPU,立刻执行一段代码。
整体流程大致是:
GPIO → AFIO → EXTI → NVIC → CPU
-
GPIO:检测电平变化
-
AFIO:选择哪个引脚连到哪根中断线
-
EXTI:管理中断线和触发方式
-
NVIC:决定中断能不能进 CPU、谁先谁后
四、我的 EXTI 初始化代码
1️⃣ 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
注意:AFIO 的时钟一定要开,不然 EXTI 根本不工作。
2️⃣ GPIO 配置
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
我用的是上拉输入,这样未遮挡时是高电平,遮挡后是低电平。
3️⃣ EXTI 配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
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);
这里我选择了下降沿触发。
4️⃣ NVIC 配置
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
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);
一个小提醒:
NVIC 中断分组整个工程只需要设置一次,一般放在 main 里。
五、我踩的第一个坑:Keil 编译不过
一开始我写成了这样:
RCC_APB2PeriphClockCmd(...);
GPIO_InitTypeDef GPIO_InitStructure; // ❌
Keil 直接报错:
error #268: declaration may not appear after executable statement
原因是:
👉 Keil ARMCC V5 只支持 C89,变量必须在函数最前面声明。
✅ 正确写法:
void CountSensor_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 后面才是执行语句
}
六、我踩的第二个坑:一次变多次
功能跑起来了,但问题来了:
明明只遮挡一次,计数器却加了 3、4、5……
后来我才明白,这是抖动:
高 → 低 → 高 → 低 → 高
每次下降沿都会触发一次中断。
✅ 我的解决方法(软件消抖)
void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line14) == SET)
{
Delay_ms(10); // 等待抖动结束
CountSensor_Count++;
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
📌 中断里一定要清中断标志位,否则会一直进中断,程序直接卡死。
初期为了快速验证功能,我在中断中使用了 Delay_ms(10)做软件消抖,确实解决了抖动导致的多次计数问题。
但这种方式属于阻塞式消抖,会占用 CPU 并影响系统中断响应,不适合复杂工程。
在实际项目中,应使用非阻塞方式(如定时器消抖或状态机)来替代中断内延时。
七、最终效果
✅ 遮挡一次,计数 +1
✅ OLED 实时显示
✅ 不再误触发
对射式红外传感器计数
八、总结
这是我第一次完整跑通 STM32 外部中断,收获很大:
-
EXTI ≠ NVIC,两者分工不同
-
Keil 使用的是 C89,变量声明位置要注意
-
抖动是物理现象,不是程序写错了
-
中断函数名不能乱改,否则进不了中断
-
中断里不适合写复杂逻辑
九、给同样在入门的朋友一点建议
如果你也在学 STM32:
-
先写 GPIO,再写 EXTI
-
不要一上来就用 CubeMX + HAL
-
多踩坑,多写博客,比只看教程有用得多
这篇是我 EXTI 系列的第一篇,后面我会尝试用 定时器消抖 ,欢迎一起交流 😊
更多推荐


所有评论(0)