在 ESP32 IDF(Espressif IoT Development Framework)中,中断是一种用于异步事件处理的重要机制。中断允许微控制器或微处理器在执行主线程代码的同时响应外部事件,而不必等待事件的发生。ESP32 IDF 使用中断来处理各种事件,包括外部硬件触发的事件、定时器事件、通信事件等。
中断源:
中断处理函数:
IRAM_ATTR
属性修饰符的函数,以确保它存储在内部 RAM 中,以减少延迟。中断控制器:
中断优先级:
中断屏蔽:
中断服务例程:
中断处理流程:
函数 gpio_set_intr_type(GPIO_PIN, gpio_int_type_t);
用来设置触发方式, gpio_int_type_t是个枚举值,有以下常量:
这些头文件包含了中断和 GPIO 相关的定义和函数。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
在代码中配置要使用的 GPIO 引脚,设置其模式为输入,并为该引脚分配一个中断号。
#define GPIO_PIN GPIO_NUM_2 // 选择要使用的 GPIO 引脚
#define GPIO_INTR GPIO_INTR_POSEDGE // 触发中断的边沿(上升沿触发)
创建一个用于处理 GPIO 中断的函数。该函数应该符合以下形式:
void IRAM_ATTR gpio_isr_handler(void* arg) {
// 处理中断事件的代码
}
这里的 IRAM_ATTR
属性用于将中断处理函数存储在内部 RAM 中,以减少延迟。
在 app_main()
函数或其他入口函数中,初始化 GPIO 引脚,将它们配置为输入模式,并设置中断处理程序。你还需要创建一个用于安装中断服务的句柄。
void app_main() {
// 配置 GPIO 引脚
gpio_pad_select_gpio(GPIO_PIN);
gpio_set_direction(GPIO_PIN, GPIO_MODE_INPUT);
gpio_set_intr_type(GPIO_PIN, GPIO_INTR);
// 创建中断句柄
gpio_install_isr_service(0);
// 注册中断处理函数
gpio_isr_handler_add(GPIO_PIN, gpio_isr_handler, NULL);
// 主任务继续执行其他操作
while (1) {
// 在这里执行其他任务
vTaskDelay(1000 / portTICK_RATE_MS);
}
}
在中断处理函数 gpio_isr_handler
中,编写处理 GPIO 中断事件的代码。你可以在函数内部执行你需要的操作,例如读取传感器数据、更新状态等。
当完成中断事件的处理后,你可以清除中断标志以准备接收下一个中断事件。
gpio_intr_enable(GPIO_PIN);
当 GPIO 引脚上的电平发生上升沿(或可以根据配置是下降沿)时,中断处理函数 gpio_isr_handler
将会执行,程序可以在其中执行特定的操作。这使得程序能够实时响应 GPIO 引脚状态的变化,例如传感器的触发、按钮的按下等事件。
中断处理程序应该尽量保持简短和高效,因为中断会在主程序中断执行时立即执行,而且不能执行阻塞操作。
如果需要更复杂的处理,可以使用中断来触发任务,以便在任务中执行耗时的操作。
在中断处理函数中直接执行业务逻辑,极容易造成程序崩溃。
可以使用队列在中断处理函数、任务之间传递信息,以避免程序可能因为竞态条件而崩溃或产生不可预测的结果。
具体操作是:
定义 QueueHandle_t 队列
中断处理函数 gpio_isr_handler
中,当 GPIO 中断被触发时,它会将触发中断的 GPIO 引脚号发送到队列 gpioEventQueue
中,以便任务能够获取该信息。
任务函数 gpio_task
中,通过调用 xQueueReceive
函数,任务会从队列 gpioEventQueue
中等待并获取触发中断的 GPIO 引脚号。一旦获取到引脚号,任务就会执行相应的操作,例如打印 GPIO 的电平状态。
这种机制确保了中断处理函数和任务之间的同步和通信。
xQueueSendFromISR
该函数作用是在中断中安全地向消息队列传值,其原型是:
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue, const void * pvItemToQueue, BaseType_t * pxHigherPriorityTaskWoken);
参数:
返回值:
下面的示例获取GPIO口的电平值,在任务处理中加了 50ms 防抖动逻辑。
#include
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "esp_log.h"
static const char* TAG = "main";
#define GPIO_PIN GPIO_NUM_2 // 选择要使用的 GPIO 引脚
// 防抖动延迟时间(以毫秒为单位)
#define DEBOUNCE_DELAY_MS 50
static volatile bool gpio_int_triggered = false;
QueueHandle_t xQueue;
static void IRAM_ATTR gpio_isr_handler(void* arg) {
gpio_int_triggered = true;
portYIELD_FROM_ISR(); // 唤醒任务以处理中断
}
static void gpio_task(void *arg) {
uint32_t ioNum;
while (1) {
if (gpio_int_triggered) {
int value = gpio_get_level(GPIO_PIN);
vTaskDelay(pdMS_TO_TICKS(DEBOUNCE_DELAY_MS)); // 防抖动延迟
if (gpio_get_level(GPIO_PIN) == value) {
ioNum = (uint32_t)GPIO_PIN;
gpio_int_triggered = false;
int gpio_value = gpio_get_level(ioNum);
ESP_LOGI(TAG, "GPIO[%lu] 触发中断,电平:%d", ioNum, gpio_value);
}
}
vTaskDelay(10 / portTICK_PERIOD_MS); // 短暂延迟以降低 CPU 负载
}
}
void app_main(void) {
// 配置 GPIO 引脚
gpio_set_direction(GPIO_PIN, GPIO_MODE_INPUT);
// 任何边沿触发,电平变化时都触发中断
gpio_set_intr_type(GPIO_PIN, GPIO_INTR_ANYEDGE);
// 创建中断句柄
gpio_install_isr_service(0);
// 注册中断处理函数
gpio_isr_handler_add(GPIO_PIN, gpio_isr_handler, (void*)GPIO_PIN);
// 创建任务来处理 GPIO 事件
xTaskCreate(gpio_task, "gpio_task", 2048, NULL, 10, NULL);
// 主任务继续执行其他操作
while (1) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}