1. GPIO中断基础:从概念到实战
各位同学,咱们今天聊聊GPIO中断。说实话,中断这个概念,我刚开始学单片机时也觉得挺玄乎的。但说白了,它就是CPU处理突发事件的一种机制——就像你正在写代码,突然有人拍你肩膀,你得先停下来看看什么事,处理完了再继续写。
1.1 中断到底是什么?
中断,就是CPU暂停当前任务,去处理一个更紧急的事件。处理完再回来接着干。这个“紧急事件”可以是按键按下、定时器溢出、串口收到数据等等。
我举个例子你就明白了。你在厨房炒菜(主程序),突然水开了(中断事件),你得关火去倒水(中断服务函数),倒完水回来继续炒菜(返回主程序)。嗯,就是这么个道理。
核心要点:中断机制让CPU能及时响应外部事件,不用一直轮询等待。这在实时系统中至关重要——你想想看,如果按键按下后要等几百毫秒才能响应,那用户体验得多糟糕。
1.2 中断优先级:谁更紧急谁先处理
多个中断同时发生时,谁先处理?这就涉及到中断优先级了。高优先级的中断可以打断低优先级的中断,这叫“中断嵌套”。
我在一个工业控制项目中就遇到过这个问题。当时用了两个外部中断,一个负责急停按钮,一个负责普通按键。急停按钮的优先级必须设得最高,否则一旦系统卡在普通按键处理中,急停就失效了——这可是安全隐患。
| 优先级级别 | 典型应用场景 | 说明 |
|---|---|---|
| 最高(0级) | 系统时钟、紧急停止 | 不可被其他中断打断 |
| 高(1级) | 高速通信(SPI、UART) | 数据不能丢 |
| 中(2级) | 定时器、ADC转换 | 周期性任务 |
| 低(3级) | GPIO按键、普通外设 | 可以稍等 |
注意:优先级不是设得越高越好。我曾经见过一个项目,把所有中断都设成最高优先级,结果高优先级中断互相嵌套,堆栈溢出直接死机。优先级要按实际需求合理分配。
1.3 中断向量表:中断的“通讯录”
中断向量表,说白了就是一张地址表。每个中断源对应一个表项,里面存着该中断服务函数(ISR)的入口地址。CPU收到中断信号后,就去查这张表,找到对应的ISR地址,跳过去执行。
以STM32为例,中断向量表默认放在Flash的起始地址0x00000000处。你可以通过修改VTOR寄存器来重定向向量表的位置——这在做Bootloader时特别有用。
// STM32中断向量表示例(部分)
__attribute__((section(".isr_vector")))
void (* const g_pfnVectors[])(void) = {
&__initial_sp, // 0: 栈顶指针
Reset_Handler, // 1: 复位
NMI_Handler, // 2: NMI
HardFault_Handler, // 3: 硬错误
MemManage_Handler, // 4: 内存管理
BusFault_Handler, // 5: 总线错误
UsageFault_Handler, // 6: 用法错误
0, 0, 0, 0, // 保留
SVC_Handler, // 11: SVC
DebugMon_Handler, // 12: 调试监视
0, // 13: 保留
PendSV_Handler, // 14: PendSV
SysTick_Handler, // 15: SysTick
// ... 外部中断从这里开始
EXTI0_IRQHandler, // 16: EXTI0
EXTI1_IRQHandler, // 17: EXTI1
// ...
};
小技巧:调试时可以用IDE查看向量表内容,确认中断是否正确映射。我习惯在启动文件里把未使用的中断都指向一个空函数,防止意外触发导致跑飞。
1.4 中断服务函数(ISR)编写规范
ISR的编写,是嵌入式开发的基本功。写不好,系统就崩。我总结了几条铁律:
- 短小精悍——ISR里只做最必要的事,比如置个标志位、读个数据。千万别在里面做延时、打印、复杂计算。
- 可重入——如果同一个中断可能嵌套触发,ISR必须是可重入的。局部变量没问题,全局变量要小心。
- 保护共享资源——ISR和主程序共享的变量,要用volatile修饰,必要时加临界区保护。
- 清除中断标志——进ISR第一件事,清除中断标志位。否则会反复触发,死循环。
- 不要调用不可重入函数——像printf、malloc这些,千万别在ISR里用。
// 规范的GPIO中断ISR示例
volatile uint8_t g_button_pressed = 0; // 共享标志,必须volatile
void EXTI0_IRQHandler(void)
{
// 1. 清除中断标志(必须)
if (EXTI->PR & EXTI_PR_PR0) {
EXTI->PR = EXTI_PR_PR0;
}
// 2. 只做最必要的事
g_button_pressed = 1; // 置标志,主循环去处理
// 3. 不要在这里做延时、打印、复杂运算
// HAL_Delay(10); // ❌ 错误!
// printf("button!\n"); // ❌ 错误!
}
我曾经踩过的坑:有一次在ISR里调用了HAL_Delay()做去抖,结果系统直接卡死。为什么?因为HAL_Delay依赖SysTick中断,而SysTick中断优先级比GPIO中断低,GPIO中断一直占着CPU,SysTick进不去——死锁了。所以记住:ISR里别用延时!
1.5 知识体系总览
下面这张图,把GPIO中断的核心知识点串起来了。你可以把它当作本章的“地图”。
1.6 本章小结
这一章我们聊了GPIO中断的四个核心知识点:
- 中断概念——CPU处理紧急事件的机制,核心是“暂停→处理→返回”
- 中断优先级——决定多个中断同时发生时的处理顺序,要合理分配
- 中断向量表——中断的“通讯录”,CPU通过它找到对应的ISR
- ISR编写规范——短小、可重入、保护共享资源、清除标志、不调不可重入函数
这些是基础中的基础。后面的章节,我们会在这个基础上,一步步深入GPIO中断的去抖动处理、响应时间优化,以及各种实战技巧。嗯,路还长,咱们一步一步来。