

在用 ESP32 写项目的时候,很多人一开始都只会用 gpio_set_level 去控制 LED,或者用 gpio_get_level 轮询输入电平。但说实话,这种方式在真正项目里几乎是不够用的。你不可能一直在死循环里等一个按钮按下吧?不仅浪费资源,响应还慢。那怎么破?答案就是——GPIO 中断。
这篇文章就围绕官方例程,讲讲怎么用 ESP-IDF 来搞定 GPIO 的中断配置,以及怎么通过任务和队列去处理这些中断事件。
在做项目的过程中,我们经常会遇到这些问题:
这些问题的本质其实就是对“中断机制”掌握不够。所以今天就来通过一段实际代码,带你完整过一遍中断的配置 + 触发 + 响应 + 处理流程。
这段代码,其实模拟的就是一个“硬件触发 + 软件响应”的完整流程。
你可以想象成:
是不是很像一个物联网设备在处理各种外设数据的过程?比如家里的智能门锁:按下开锁键 -> 中断触发 -> MCU 判断身份 -> 开锁并反馈。
使用 while(1) 一直轮询一个按键,如下面代码:
void app_main(void)
{
//zero-initialize the config structure.
gpio_config_t io_conf = {};
//disable interrupt
io_conf.intr_type = GPIO_INTR_DISABLE;
//set as output mode
io_conf.mode = GPIO_MODE_OUTPUT;
//bit mask of the pins that you want to set,e.g.GPIO18/19
io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
//disable pull-down mode
io_conf.pull_down_en = 0;
//disable pull-up mode
io_conf.pull_up_en = 0;
//configure GPIO with the given settings
gpio_config(&io_conf);
//interrupt of rising edge
io_conf.intr_type = GPIO_INTR_POSEDGE;
//bit mask of the pins, use GPIO4/5 here
io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;
//set as input mode
io_conf.mode = GPIO_MODE_INPUT;
//enable pull-up mode
io_conf.pull_up_en = 1;
gpio_config(&io_conf);
//change gpio interrupt type for one pin
gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_ANYEDGE);
//create a queue to handle gpio event from isr
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
//start gpio task
xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);
//install gpio isr service
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
//hook isr handler for specific gpio pin
gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
//hook isr handler for specific gpio pin
gpio_isr_handler_add(GPIO_INPUT_IO_1, gpio_isr_handler, (void*) GPIO_INPUT_IO_1);
//remove isr handler for gpio number.
gpio_isr_handler_remove(GPIO_INPUT_IO_0);
//hook isr handler for specific gpio pin again
gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
printf("Minimum free heap size: %"PRIu32" bytes\n", esp_get_minimum_free_heap_size());
int cnt = 0;
while (1) {
printf("cnt: %d\n", cnt++);
vTaskDelay(1000 / portTICK_PERIOD_MS);
gpio_set_level(GPIO_OUTPUT_IO_0, cnt % 2);
gpio_set_level(GPIO_OUTPUT_IO_1, cnt % 2);
}
}终端执行指令
idf.py -p PORT flash monitor按照注释连好线(GPIO18 → GPIO4、GPIO19 → GPIO5),运行这段代码:
执行结果如下:

我们可以试试下面这个例程。让硬件响应更“聪明”,让你的代码更“省力”。
#define GPIO_OUTPUT_IO_0 CONFIG_GPIO_OUTPUT_0
#define GPIO_OUTPUT_IO_1 CONFIG_GPIO_OUTPUT_1
#define GPIO_INPUT_IO_0 CONFIG_GPIO_INPUT_0
#define GPIO_INPUT_IO_1 CONFIG_GPIO_INPUT_1这些是从配置里取出定义好的 GPIO 编号(你也可以在 menuconfig 里改)。
然后用位运算凑出 “输出引脚掩码” 和 “输入引脚掩码”,方便一口气配置多个引脚。
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
gpio_config(&io_conf);先配置输出引脚,不启用中断,然后再配置输入引脚,并开启上拉电阻 + 设置中断类型。
注意:这里
GPIO_INPUT_IO_0是双边沿触发(即升沿、降沿都响应),而GPIO_INPUT_IO_1是只在上升沿触发。
static void IRAM_ATTR gpio_isr_handler(void* arg) {
uint32_t gpio_num = (uint32_t) arg;
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}这个函数非常关键。它会在中断触发时执行(注意是 ISR 环境,要快),然后把中断的引脚号送进一个队列,留给主任务慢慢处理。
static void gpio_task_example(void* arg) {
uint32_t io_num;
for (;;) {
if (xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
printf("GPIO[%d] intr, val: %d\n", io_num, gpio_get_level(io_num));
}
}
}这就是一个后台任务,负责从队列里拿到中断事件,然后做处理。实际项目里你可以替换成业务逻辑,比如控制其他外设、发 MQTT 消息、存日志等等。
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
gpio_isr_handler_add(GPIO_INPUT_IO_1, gpio_isr_handler, (void*) GPIO_INPUT_IO_1);这部分代码就是把中断服务、队列、任务全都注册起来,形成一个完整的响应链路。
+-----------+ 触发 +----------------+ 事件送队列 +-------------------+
| 外部信号 | ----------------> | ISR 中断处理函数 | ------------------> | 后台任务逻辑处理 |
+-----------+ +----------------+ +-------------------+通过这段代码,我们可以搞清楚以下几个关键知识点:
这些技巧不仅适用于按钮处理、传感器感知、低功耗唤醒等场景,也是你做嵌入式开发的必备能力。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。