通常在项目中,您希望 ESP32 执行其正常程序,同时持续监视某种事件。一种广泛采用的解决方案是使用中断。
ESP32 为每个内核提供最多 32 个中断槽。每个中断都有一定的优先级,可以分为两种类型。
硬件中断——这些中断是为了响应外部事件而发生的。例如,GPIO 中断(按下按键时)或触摸中断(检测到触摸时)。
软件中断——这些中断是为了响应软件指令而发生的。例如,简单的定时器中断或看门狗定时器中断(当定时器超时时)。
在 ESP32 中,我们可以定义一个中断服务例程函数,当 GPIO 引脚改变其逻辑电平时将调用该函数。
ESP32 板上的所有 GPIO 引脚都可以配置为充当中断请求输入。

在 Arduino IDE 中,我们使用一个名为 的函数attachInterrupt()来逐个引脚设置中断。语法如下所示。
attachInterrupt(GPIOPin, ISR, Mode);
该函数接受三个参数:
GPIOPin – 将 GPIO 引脚设置为中断引脚,告诉 ESP32 要监控哪个引脚。
ISR – 是每次中断发生时将调用的函数的名称。
模式– 定义何时应触发中断。预定义了五个常量作为有效值:
| LOW | 每当引脚为低电平时触发中断 |
| HIGH | 每当引脚为高电平时触发中断 |
| CHANGE | 每当引脚改变值(从高到低或从低到高)时触发中断 |
| FALLING | 当引脚从高电平变为低电平时触发中断 |
| RISING | 当引脚从低电平变为高电平时触发中断 |
当您希望 ESP32 不再监控该引脚时,可以调用该detachInterrupt()函数。语法如下所示。
detachInterrupt(GPIOPin);
中断服务例程 (ISR) 是每次 GPIO 引脚上发生中断时都会调用的函数。
它的语法如下所示。
void IRAM_ATTR ISR() {
Statements;
}
ESP32 中的 ISR 是特殊类型的函数,它们具有大多数其他函数所没有的一些独特规则。
什么是 IRAM_ATTR?
当我们使用该属性标记一段代码时IRAM_ATTR,编译后的代码将放置在 ESP32 的内部 RAM (IRAM) 中。否则,代码将保存在 Flash 中。而且 ESP32 上的 Flash 比内部 RAM 慢得多。
如果我们要运行的代码是中断服务例程(ISR),我们通常希望尽快执行它。如果我们必须“等待”ISR 从闪存加载,那么事情可能会出现严重错误。
理论已经够多了!让我们看一个实际的例子。
让我们将一个按钮连接到 ESP32 上的 GPIO#18 (D18)。您不需要对该引脚进行任何上拉,因为我们将在内部将该引脚上拉。

将按钮连接到 ESP32 以实现 GPIO 中断
下面的草图演示了中断的使用以及编写中断服务程序的正确方法。
该程序监视 GPIO#18 (D18) 的下降沿。换句话说,它会寻找按下按钮时发生的从逻辑高电平到逻辑低电平的电压变化。当这种情况发生时,该函数isr被调用。此函数中的代码计算按钮被按下的次数。
struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};
Button button1 = {18, 0, false};
void IRAM_ATTR isr() {
button1.numberKeyPresses++;
button1.pressed = true;
}
void setup() {
Serial.begin(115200);
pinMode(button1.PIN, INPUT_PULLUP);
attachInterrupt(button1.PIN, isr, FALLING);
}
void loop() {
if (button1.pressed) {
Serial.printf("Button has been pressed %u times\n", button1.numberKeyPresses);
button1.pressed = false;
}
}
上传草图后,按 ESP32 上的 EN 按钮并以波特率 115200 打开串行监视器。按下按钮后,您将获得以下输出。

在草图的开头,我们创建一个名为 的结构Button。该结构具有三个成员——引脚编号、按键次数和按下状态。仅供参考,结构是单个名称下不同类型(但逻辑上彼此相关)的变量的集合。
struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
}
然后,我们创建 Button 结构的一个实例,并将引脚编号初始化为18,按键次数初始化为0,默认按下状态初始化为false。
Button button1 = {18, 0, false};
下面的代码是一个中断服务程序。前面提到,ESP32 中的 ISR 必须具有该IRAM_ATTR属性。
在 ISR 中,我们只需将 KeyPresses 计数器增加 1 并将按钮按下状态设置为 True。
void IRAM_ATTR isr() {
button1.numberKeyPresses += 1;
button1.pressed = true;
}
在代码的设置部分,我们首先初始化与 PC 的串行通信,然后启用 D18 GPIO 引脚的内部上拉。
isr接下来,我们告诉 ESP32 监视 D18 引脚,并在引脚从高电平变为低电平(即下降沿)时调用中断服务程序。
Serial.begin(115200);
pinMode(button1.PIN, INPUT_PULLUP);
attachInterrupt(button1.PIN, isr, FALLING);
在代码的循环部分,我们简单地检查按钮是否被按下,然后打印到目前为止按键被按下的次数,并将按钮按下状态设置为 false,以便我们可以继续接收中断。
if (button1.pressed) {
Serial.printf("Button 1 has been pressed %u times\n", button1.numberKeyPresses);
button1.pressed = false;
}
中断的一个常见问题是,同一事件经常会多次触发中断。如果您查看上面示例的串行输出,您会注意到即使您只按一次按钮,计数器也会增加几次。

要找出发生这种情况的原因,您必须查看信号。如果您在按下按钮时监控信号分析仪上引脚的电压,您将得到如下信号:

您可能感觉立即发生了接触,但实际上按钮内的机械部件在进入特定状态之前会接触多次。这会导致触发多个中断。
这纯粹是一种被称为“开关弹跳”的机械现象,就像丢球一样——它会弹跳几次,然后最终落地。每一次弹跳都会引起一次中断,从而调动执行一次中断程序。所以就出现了按动一次触发了多次打印的情况。
如何消除开关弹跳的过程称为“去弹跳”。有两种方法可以实现这一目标。
这里重写了上面的草图,以演示如何以编程方式消除中断反跳。在此草图中,我们允许 ISR 在每次按下按钮时仅执行一次,而不是多次执行。
对草图的更改突出显示在绿色的。
struct Button {
const uint8_t PIN;
uint32_t numberKeyPresses;
bool pressed;
};
Button button1 = {18, 0, false};
//建立一个变量用来保存上次调用中断处理程序的时间,如果这个时间小于250毫秒则不再确认按下按钮。
unsigned long button_time = 0;
unsigned long last_button_time = 0;
void IRAM_ATTR isr() {
button_time = millis();
if (button_time - last_button_time > 250)
{
button1.numberKeyPresses++;
button1.pressed = true;
last_button_time = button_time;
}
}
void setup() {
Serial.begin(115200);
pinMode(button1.PIN, INPUT_PULLUP);
attachInterrupt(button1.PIN, isr, FALLING);
}
void loop() {
if (button1.pressed) {
Serial.printf("Button has been pressed %u times\n", button1.numberKeyPresses);
button1.pressed = false;
}
}
让我们再次查看按下按钮时的串行输出。请注意,每次按下按钮只会调用一次 ISR。

此修复之所以有效,是因为每次执行 ISR 时,都会将函数返回的当前时间millis()与上次调用 ISR 的时间进行比较。
如果在 250ms 之内,ESP32 会忽略中断并立即返回到正在执行的操作。如果没有,它会执行语句中的代码if,增加计数器并更新last_button_time变量,以便该函数有一个新值可以在将来触发时进行比较。
这个问题也可以在远程序的基础上,在loop模块中打印以前延迟10毫秒再做一次判断,看是否还是按下的状态,如果是则打印不是则不打印。
串行接口是一种可以将接收来自CPU的并行数据字符转换为连续的串行数据流发送出去,同时可将接收的串行数据流转换为并行的数据字符供给CPU的器件。一般完成这种功能的电路,我们称为串行接口电路。
MimiClaw 是一款基于 ESP32-S3 芯片的超轻量级AI助手,适合嵌入式AI与物联网开发者快速部署本地化AI代理。本系列教程基于MimiClaw的Arduino移植版本进行讲解,小节主要讲解部署和测试。
就像我们用手机打开WiFi功能后可以浏览附近的可用WiFi。要将手机连接到热点,通常需要打开Wi-Fi设置应用程序,列出可用的网络,然后选择所需的热点。然后输入密码(或不输入密码),可以使用ESP32进行相同的操作。
本文本介绍配置飞书机器人为MimiClaw的一个输入/输出端,和添加一个控制WS2812与LED的控制技能。
一块 30 块钱的开发板 + 一个大模型 API,就能做出可以听懂人话的智能硬件。 本文记录完整安装过程和踩坑经验,确保你跟着做就能跑通。
本文将从手绘架构图入手,逐层拆解 MimiClaw 的分层设计、核心模块、数据流转与底层实现,带你解剖这只“智能虾”的技术骨架,看懂在 C 语言加持下,AI 智能体如何以可穿戴设备的形态,在你身边稳稳运行、离线服务、主动响应。
本文介绍如何在不脱离 ArduinoIDE 可视化开发的前提下,通过一个名为 platform.local.txt 的小文件,实现对 ESP32 编译流程的精准控制。
本文将系统分析程序体积增长的五大根源,并提供经过验证的优化方案,帮助减小固件大小。
本文所DIY的语音助手设备端使用的是MicroPython、服务端是Python,对于很多开发者来说MicroPython入门没难度。
本小节使用音频开发框架实现一个音频录制到文件的示例。
I2S协议通过BCLK、LRCLK和DATA三线精准传输音频数据,但时序边沿、帧格式、时钟源等细节常引发噪声或断连。本文详解ESP32的I2S实现,从协议原理到ESP-IDF v5.x代码配置,助你避开常见陷阱,确保音频稳定传输。