外部中断
之前我们学习了 ESP32 的按键控制,当时通过查询 GPIO 输入电平来判断按键状态,这种方法占用 CPU 资源,效率不高。本节课我们学习外部中断,通过外部中断实现按键控制 LED。
实验原理
在单片机中,中断是指当 CPU 在正常处理主程序时,突然发生了另一件事件 A(中断发生)需要 CPU 去处理,这时 CPU 就会暂停处理主程序(中断响应),转而去处理事件 A(中断服务)。当事件 A 处理完以后,再回到主程序原来中断的地方继续执行主程序(中断返回)。这一整个过程称为中断。
例如,当你正在洗衣时,突然手机响了(中断发生),你暂时中断洗衣的工作,转去接电话(中断响应和中断服务),待你接完后,再回来继续洗衣(中断返回),这一过程就是中断。
当中断过程 A 中,发生了另一个中断级别更高的中断事件 B,则 CPU 又会中断当前的 A 转而去处理 B,完毕后再回到 A 的断点继续处理。这称为中断的嵌套。
中断的嵌套涉及到中断的优先级问题,优先级高的中断就可以在打断优先级低的中断执行。
中断可以根据中断源分为 硬件中断
和 软件中断
:
硬件中断
:也被称为外部中断,硬件中断响应外部硬件事件而发生。例如,当检测到触摸时会发生触摸中断,而当 GPIO 引脚的状态发生变化时会发生 GPIO 中断。GPIO 中断和触摸中断属于这一类;软件中断
:当触发软件事件(例如定时器溢出)时,会发生这种类型的中断。定时器中断是软件中断的一个例子。
前面我们在做按键控制实验时,虽然能实现 IO 口输入功能,但代码是一直在检测 IO 输入口的变化,因此效率不高,特别是在一些特定的场合,比如某个按键,可能 1 天才按下一次去执行相关功能,这样我们就浪费大量时间来实时检测按键的情况。
为了解决这样的问题,我们引入外部中断概念,顾名思义,就是当按键被按下(产生中断)时,才去执行相关功能。这大大节省了 CPU 的资源,因此中断在实际项目中应用非常普遍。
ESP32 的外部中断有上升沿、下降沿、低电平、高电平触发模式。上升沿和下降沿触发如下:
若将按键对应 IO 配置为下降沿触发,当按键按下后即触发中断,然后在中断回调函数内执行对应的功能。
硬件电路设计
使用按键电路实现开关灯的效果。
物料清单(BOM 表):
材料名称 | 数量 |
---|---|
直插式 LED | 1 |
1kΩ 电阻 | 1 |
杜邦线(跳线) | 若干 |
面包板 | 1 |
将材料按照下图相连:
软件程序设计
Arduino 中的外部中断配置函数 attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)
包括 3 个参数:
pin
:GPIO 端口号;ISR
:中断服务程序,没有参数与返回值的函数;mode
:中断触发的方式,支持以下触发方式:LOW
低电平触发HIGH
高电平触发RISING
上升沿触发FALLING
下降沿触发CHANGE
电平变化触发
在 Arduino 中使用中断需要注意一下几点:
尽量保证中断程序内容少
避免在中断处理函数中使用阻塞函数(如
delay()
),使用非阻塞的延迟方法来处理需要延迟的操作(micros() 函数
),以保证中断的正常执行和系统的稳定性。这是因为delay()
函数会阻塞整个系统,包括中断的正常执行。当中断触发时,处理函数应该尽快执行完毕,以确保及时响应并避免中断积压;与主程序共享的变量要加上 volatile 关键字;
在 Arduino 中使用中断时,应尽量避免在中断处理函数中使用
Serial
对象的打印函数。当在中断处理函数中使用Serial
打印函数时,会导致以下问题:- 时间延迟:
Serial
打印函数通常是比较耗时的操作,它会阻塞中断的执行时间,导致中断响应的延迟。这可能会导致在中断期间丢失其他重要的中断事件或导致系统不稳定。 - 缓冲区溢出:
Serial
对象在内部使用一个缓冲区来存储要发送的数据。如果在中断处理函数中频繁调用Serial
打印函数,可能会导致缓冲区溢出,造成数据丢失或不可预测的行为。
- 时间延迟:
为了避免这些问题,建议在中断处理函数中尽量避免使用 Serial 打印函数。如果需要在中断处理函数中输出调试信息,可以使用其他方式,如设置标志位,在主循环中检查标志位并进行打印,代码如下:
#define BUTTON 14
// 定义可以在外部中断函数中使用的变量
volatile bool flag = false;
// 定义外部中断函数
void handle_interrupt() {
flag = true;
number += 10000;
}
void setup() {
Serial.begin(9600);
pinMode(BUTTON, INPUT_PULLDOWN);
// 配置中断引脚
attachInterrupt(digitalPinToInterrupt(BUTTON), handle_interrupt, FALLING);
}
void loop() {
if (flag) {
Serial.println("外部中断触发");
flag = false;
}
}
因此,我们使用外部中断点灯的代码可以这么写:
#define BUTTON 14
#define LED 2
volatile bool flag = false;
void ISR() {
flag = true;
}
void setup() {
pinMode(BUTTON, INPUT_PULLDOWN);
pinMode(LED, OUTPUT);
// 配置中断引脚
attachInterrupt(digitalPinToInterrupt(BUTTON), ISR, FALLING);
}
void loop() {
if (flag) {
digitalWrite(LED, HIGH);
delay(2000);
digitalWrite(LED, LOW);
// 重置中断标志位
flag = false;
}
}