系列文章目录
持续更新...
文章目录
- 系列文章目录
- 前言
- 一、中断矩阵概述
- 二、中断功能描述
-
- 1.外部中断源
- [2.CPU 中断](#2.CPU 中断)
- [3.关闭 CPUx 的 NMI 类型中断](#3.关闭 CPUx 的 NMI 类型中断)
- 4.查询外部中断源当前的中断状态
- 三、中断类型定义及相关API
- 四、中断示例程序
- 总结
前言
ESP32 是一款功能强大的 32 位微控制器,广泛应用于物联网(IoT)和嵌入式系统中。它提供了丰富的外设接口和灵活的中断管理机制,使得开发者可以高效地处理各种实时任务和响应外部事件。在 ESP32 中,系统中断矩阵是核心组成部分之一,它将不同外设的中断信号分配到合适的处理程序,确保系统能够快速响应硬件事件。
无论是在外设交互、数据采集还是实时控制中,中断机制都扮演着至关重要的角色。本篇博文将以ESP32-S3为例介绍系统中断矩阵机制,帮助开发者理解中断系统的工作原理,并通过具体的 API 示例,展示如何配置和使用中断。
参考文档:ESP32-S3技术参考手册;ESP32-S3编程指南
一、中断矩阵概述
在 ESP32中,中断矩阵是一个关键的硬件模块,负责将各个外设的中断信号路由到处理器的中断系统,并分配给相应的中断处理程序。ESP32 提供了一个灵活且强大的中断管理系统,支持多个外设中断信号同时处理,并允许开发者精确控制中断的优先级和触发条件。
与传统的单一中断系统不同,ESP32 的中断矩阵支持更多的外设并行工作,包括但不限于 GPIO、定时器、串口(UART)、SPI、I2C 等。中断信号可以根据需要进行路由和优先级设置,从而实现高效的中断响应机制。
中断矩阵包括以下几个核心功能:
1.外设中断路由:将不同外设产生的中断信号路由到中断处理器。
2.中断优先级:为不同中断分配优先级,确保高优先级的中断可以先被处理。
3.中断触发模式:支持不同的触发类型,如边沿触发、水平触发等。
中断矩阵结构图
二、中断功能描述
在 ESP32-S3 中,中断管理系统非常灵活,并且能够处理多种不同的中断源。它包括外部中断源、中断优先级管理以及对 CPU 内部中断的处理。中断系统不仅可以响应外部硬件事件(如 GPIO 按键输入、外设信号),还支持 CPU 内部的异常和错误处理。接下来,我们将详细描述不同类型的中断以及如何配置它们。
1.外部中断源
外部中断源指的是来自硬件外设或输入引脚的中断信号。ESP32-S3 支持来自多个外部源的中断,例如 GPIO、定时器、外设模块(如 UART、SPI 等)生成的信号。当某个外部事件发生时,相关的中断信号会被传递到中断矩阵并触发中断服务程序(ISR)。开发者可以通过配置 GPIO 或其他外设的中断源来响应这些事件。
通常,外部中断源配置步骤包括:
1.选择触发条件:设置触发方式(例如,上升沿、下降沿或电平触发)。
2.配置中断优先级:为外部中断配置优先级,确保关键任务能够及时响应。
2.CPU 中断
除了外部硬件中断外,ESP32-S3 还支持 CPU 内部中断 ,主要用于处理 CPU 异常、错误或特定任务(例如,硬件定时器、软件中断等)。CPU 中断分为不同的级别,并且每个中断都由相应的中断处理程序(ISR)处理。
ESP32-S3 提供了对 CPU 中断的完全控制,允许开发者配置中断触发条件、优先级,并将中断源路由到指定的 CPU 核心(CPU0 或 CPU1)。这使得多核处理器能够同时处理不同的中断任务,优化处理速度。
ESP32-S3 支持六级中断,同时支持中断嵌套,即低优先级中断可以被高优先级中断打断。与 ARM Cortex-M 的系统正好相反。在 ARM Cortex-M 系列中,中断优先级数值较小时优先级较高。++而在 ESP32-S3(使用 Xtensa 架构的芯片)中,数字越大表示优先级越高,NMI 中断(非屏蔽中断)则是最高优先级,它的优先级数值通常是 0。++
3.关闭 CPUx 的 NMI 类型中断
在 ESP32-S3 中,CPU 还支持非屏蔽中断(NMI),这是一种特殊类型的中断,通常用于处理严重错误或需要立即响应的紧急任务。由于 NMI 中断不能被屏蔽,因此在处理这些中断时,需要小心处理。
如果开发者希望关闭某个 CPU 核心的 NMI 类型中断,可以通过相应的 API 来禁用该中断。这将停止该核心响应 NMI 类型的中断请求,直到重新启用。
开发者可以使用以下步骤来关闭 NMI 中断:
1.调用相关 API:通过 ESP32-S3 提供的函数禁用指定 CPU 的 NMI 中断。
2.确定关闭的中断源:确保不再需要该 NMI 中断源或希望延迟处理中断。
关闭 NMI 中断可以避免中断影响系统的其他关键任务,尤其在调试或测试阶段中比较常见。
4.查询外部中断源当前的中断状态
在 ESP32-S3 中,开发者可以查询外部中断源的当前状态,以便了解中断是否已经被触发。这是通过访问中断矩阵和相关的硬件寄存器来实现的。查询中断状态对于中断调试和实时监控非常有用。
开发者可以使用以下步骤来查询外部中断源的状态:
1.读取中断状态寄存器:通过硬件寄存器获取当前中断状态。
2.检查中断标志:查看中断是否已经触发,并决定是否需要处理。
通过查询中断状态,开发者能够及时了解系统中断的触发情况,确保高效响应事件。
三、中断类型定义及相关API
中断的大多数功能与 GPIO 有关,GPIO 的头文件(如 gpio_types.h、gpio.h)中包含大量中断相关的定义和函数。gpio_int_type_t 是 ESP32 中 GPIO 中断触发类型 的枚举定义,用于配置 GPIO 引脚的中断触发条件
c
typedef enum {
GPIO_INTR_DISABLE = 0, /* 禁用 GPIO 中断 */
GPIO_INTR_POSEDGE = 1, /* GPIO 中断类型:上升沿触发 */
GPIO_INTR_NEGEDGE = 2, /* GPIO 中断类型:下降沿触发 */
GPIO_INTR_ANYEDGE = 3, /* GPIO 中断类型:上升沿 + 下降沿触发 */
GPIO_INTR_LOW_LEVEL = 4, /* GPIO 中断类型:低电平持续触发 */
GPIO_INTR_HIGH_LEVEL = 5, /* GPIO 中断类型:高电平持续触发 */
GPIO_INTR_MAX, /* 枚举边界(无实际意义) */
} gpio_int_type_t;
中断使用相关常用API
c
#include "esp_err.h"
#include "driver/gpio.h"
//=============================================================================
// 中断触发类型配置
//=============================================================================
// 为指定 GPIO 引脚设置中断触发类型(上升沿、下降沿、任何边沿、低电平、高电平)
// 参数:gpio_num --- GPIO 编号
// intr_type --- 中断类型,见 gpio_int_type_t
// 返回:ESP_OK 或 ESP_ERR_INVALID_ARG
esp_err_t gpio_set_intr_type(gpio_num_t gpio_num, gpio_int_type_t intr_type);
//=============================================================================
// 中断使能 / 禁用
//=============================================================================
// 使能指定 GPIO 引脚的中断功能
// 参数:gpio_num --- GPIO 编号
// 返回:ESP_OK 或 ESP_ERR_INVALID_ARG
esp_err_t gpio_intr_enable(gpio_num_t gpio_num);
// 禁用指定 GPIO 引脚的中断功能
// 参数:gpio_num --- GPIO 编号
// 返回:ESP_OK 或 ESP_ERR_INVALID_ARG
esp_err_t gpio_intr_disable(gpio_num_t gpio_num);
//=============================================================================
// 中断服务(ISR)管理
//=============================================================================
//分配和初始化一个中断源,它是更底层的 API,它允许你手动指定中断的源、优先级、触发类型等
//intr_source:指定中断源(例如 GPIO、SPI)。
//flags:中断的分配标志(例如优先级、触发方式)。
//handler:ISR 回调函数。
//arg:透传给回调函数的参数。
//handle:返回的中断句柄。
esp_err_t esp_intr_alloc(int intr_source, int flags, intr_handler_t handler, void* arg, esp_intr_handle_t* handle);
// 安装单一的 GPIO 中断服务,必须在调用 gpio_isr_register 或 gpio_isr_handler_add 之前执行
// 参数:intr_alloc_flags --- 中断分配标志,决定中断运行在何种上下文
// 返回:ESP_OK、ESP_ERR_NO_MEM、ESP_ERR_INVALID_STATE、ESP_ERR_NOT_FOUND 或 ESP_ERR_INVALID_ARG
esp_err_t gpio_install_isr_service(int intr_alloc_flags);
// 卸载已安装的 GPIO 中断服务,移除所有中断处理器并释放资源
void gpio_uninstall_isr_service(void);
// 全局注册一个 ISR 入口(所有 GPIO 中断都会回调此函数)
// 参数:fn --- ISR 回调函数指针
// arg --- 透传给回调的用户参数
// intr_alloc_flags --- 分配标志
// handle --- 返回的中断服务句柄
// 返回:ESP_OK、ESP_ERR_INVALID_ARG、ESP_ERR_NOT_FOUND
esp_err_t gpio_isr_register(void (*fn)(void *), void *arg, int intr_alloc_flags, gpio_isr_handle_t *handle);
//=============================================================================
// 引脚级中断处理程序添加 / 移除
//=============================================================================
// 为指定 GPIO 引脚添加单独的中断处理函数(ISR)
// 参数:gpio_num --- GPIO 编号
// isr_handler --- 针对该引脚的回调函数
// args --- 透传给回调的用户参数
// 返回:ESP_OK、ESP_ERR_INVALID_ARG、ESP_ERR_INVALID_STATE
esp_err_t gpio_isr_handler_add(gpio_num_t gpio_num, gpio_isr_t isr_handler, void *args);
// 移除为指定 GPIO 引脚注册的中断处理函数
// 参数:gpio_num --- GPIO 编号
// 返回:ESP_OK、ESP_ERR_INVALID_ARG、ESP_ERR_INVALID_STATE
esp_err_t gpio_isr_handler_remove(gpio_num_t gpio_num);
//=============================================================================
// 中断状态查询
//=============================================================================
// 查询指定 GPIO 引脚的中断状态
// 参数:gpio_num --- GPIO 编号
// 返回:ESP_OK 或 ESP_ERR_INVALID_ARG
esp_err_t gpio_intr_status(gpio_num_t gpio_num);
// 查询当前的所有中断状态
// 返回:中断状态掩码,若某个 GPIO 引脚发生中断,则相应位置为 1
uint64_t gpio_intr_status_all(void);
//=============================================================================
// 系统中断管理
//=============================================================================
// 清除指定 GPIO 引脚的中断标志
// 参数:gpio_num --- GPIO 编号
// 返回:ESP_OK 或 ESP_ERR_INVALID_ARG
esp_err_t gpio_clear_intr_status(gpio_num_t gpio_num);
// 清除所有 GPIO 的中断标志
// 返回:ESP_OK 或 ESP_ERR_INVALID_ARG
esp_err_t gpio_clear_intr_status_all(void);
//=============================================================================
// 中断优先级管理
//=============================================================================
// 设置指定中断的优先级
// 参数:gpio_num --- GPIO 编号
// priority --- 中断优先级,0 为最高优先级
// 返回:ESP_OK 或 ESP_ERR_INVALID_ARG
esp_err_t gpio_set_intr_priority(gpio_num_t gpio_num, int priority);
//=============================================================================
// 中断使能 / 禁用
//=============================================================================
// 禁用所有 GPIO 中断(将所有中断标志清除)
void gpio_intr_disable_all(void);
// 使能所有 GPIO 中断(恢复所有中断的使能状态)
void gpio_intr_enable_all(void);
1.这两个 API 函数 gpio_isr_register 和 gpio_isr_handler_add都用于注册中断服务程序(ISR),但它们有一些关键的区别:
gpio_isr_register:用于注册一个全局的中断服务程序,所有 GPIO 中断都回调同一个函数。需要在 ISR 中通过传递的参数区分是哪一个引脚触发了中断。适用于当多个 GPIO 中断可以共享同一个处理函数时。
c
void IRAM_ATTR my_isr(void *arg) {
// 这里可以通过读取 GPIO 状态或参数 arg 判断是哪个引脚
printf("GPIO 中断触发,参数:%d\n", (int)arg);
}
void app_main() {
gpio_install_isr_service(0);
gpio_isr_register(my_isr, (void*)123, 0, NULL); // 所有 GPIO 中断都回调 my_isr
}
gpio_isr_handler_add:为每个 GPIO 引脚单独注册中断处理程序。每个 GPIO 引脚触发的中断会调用它们各自的 ISR。适用于需要为每个引脚单独处理的场景。
c
void IRAM_ATTR gpio0_isr(void *arg) {
printf("GPIO0 中断触发\n");
}
void IRAM_ATTR gpio2_isr(void *arg) {
printf("GPIO2 中断触发\n");
}
void app_main() {
gpio_install_isr_service(0);
gpio_isr_handler_add(GPIO_NUM_0, gpio0_isr, NULL); // GPIO0 单独处理
gpio_isr_handler_add(GPIO_NUM_2, gpio2_isr, NULL); // GPIO2 单独处理
}
选择使用哪个函数取决于你是否希望将多个引脚的中断处理集中在一个回调函数中,还是希望每个引脚拥有独立的回调函数。
2.esp_intr_alloc 和 gpio_install_isr_service 都与中断服务程序(ISR)的安装与配置有关,但它们的作用和使用场景有所不同:
esp_intr_alloc 是一个更底层的中断管理函数,可以处理任何硬件中断。
c
esp_err_t esp_intr_alloc(int intr_source, int intr_flags, intr_handler_t handler, void* arg, intr_handle_t* out_handle);
// 以 GPIO 为例,intr_source 用 ETS_GPIO_INTR_SOURCE
// intr_flags 用 ESP_INTR_FLAG_IRAM 表示中断服务放在 IRAM
esp_err_t ret = esp_intr_alloc(
ETS_GPIO_INTR_SOURCE, // 中断源
ESP_INTR_FLAG_IRAM, // 标志位(如放在 IRAM)
my_gpio_isr_handler, // 中断处理函数
NULL, // 传递给处理函数的参数
&handle // 返回的中断句柄
);
gpio_install_isr_service 只是其中针对 GPIO 中断的封装,适用于更简单的 GPIO 中断配置和处理。
c
//ESP_INTR_FLAG_LEVELx:指定中断优先级(x 为 1~5)。
//ESP_INTR_FLAG_IRAM:中断服务例程放在 IRAM,适合对速度要求高的场景。
//ESP_INTR_FLAG_SHARED:允许中断共享。
//ESP_INTR_FLAG_EDGE:边沿触发。
//举例:如果你想让中断服务例程放在 IRAM 并设置优先级为 3:
gpio_install_isr_service(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL3);
//0 表示采用默认方式安装 GPIO 中断服务,不做特殊内存或优先级分配
gpio_install_isr_service(0);
如果你只需要处理 GPIO 中断,使用 gpio_install_isr_service 会更简便;如果你需要处理其他外设的中断,或需要更复杂的中断管理,则应使用 esp_intr_alloc。
四、中断示例程序
本示例代码演示了如何在 ESP32-S3 上使用外部中断来处理按键输入(GPIO0)
代码中使用了一个边沿中断触发机制,当按键被按下时,产生一个中断,触发相应的中断服务程序(ISR)。事件组 (EventGroup) 用于在线程中同步按键事件。
非 FreeRTOS 版本:适合不需要多任务的场景
c
#include <stdio.h>
#include "driver/gpio.h"
#define KEY_GPIO GPIO_NUM_0 // 假设使用 GPIO 0 作为按键输入
// GPIO 中断服务程序
void IRAM_ATTR gpio_isr_handler(void *arg)
{
// 在中断服务程序中,可以执行一些需要的操作
printf("Key pressed!\n");
}
void app_main(void)
{
// 配置 GPIO 引脚
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << KEY_GPIO), // 设置 GPIO 0
.mode = GPIO_MODE_INPUT, // 设置为输入模式
.pull_up_en = GPIO_PULLUP_ENABLE, // 启用上拉电阻
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用下拉电阻
.intr_type = GPIO_INTR_NEGEDGE, // 设置为负边沿中断(按键按下时触发)
};
gpio_config(&io_conf); // 配置 GPIO
// 安装中断服务
gpio_install_isr_service(0); // 安装默认中断服务
gpio_isr_handler_add(KEY_GPIO, gpio_isr_handler, NULL); // 添加中断处理函数
// 进入主循环
while (1)
{
// 在死循环中,可以继续做其他任务,或者做一些其他操作
vTaskDelay(1000 / portTICK_PERIOD_MS); // 1秒延时
}
}
FreeRTOS 版本(带任务管理)
c
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "freertos/event_groups.h"
#define KEY_GPIO GPIO_NUM_0
EventGroupHandle_t keyev = NULL; // 事件组句柄
void IRAM_ATTR gpio_isr_handler(void *arg)// GPIO 中断服务程序
{
xEventGroupSetBitsFromISR(keyev, BIT0, NULL); // 设置事件组的位
}
void task_key(void *pvParameters)
{
EventBits_t uxBits;
for (;;)
{
uxBits = xEventGroupWaitBits(keyev, BIT0, pdTRUE, pdFALSE, portMAX_DELAY); // 等待事件组的位被设置
if (uxBits & BIT0) // 检查是否设置了按键事件
{
printf("Key pressed!\n");
}
}
}
void app_main(void)
{
keyev = xEventGroupCreate(); // 创建事件组
gpio_config_t io_conf =
{
.pin_bit_mask = (1ULL << KEY_GPIO), // 设置 GPIO 0
.mode = GPIO_MODE_INPUT, // 设置为输入模式
.pull_up_en = GPIO_PULLUP_ENABLE, // 启用上拉
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用下拉
.intr_type = GPIO_INTR_NEGEDGE, // 启用负边缘中断
};
gpio_config(&io_conf);
xTaskCreatePinnedToCore(task_key, "Key Task", 2048, NULL, 5, NULL, 1); // 创建按键任务
gpio_install_isr_service(0); // 安装 GPIO 中断服务
gpio_isr_handler_add(KEY_GPIO, gpio_isr_handler, (void *)KEY_GPIO); // 添加中断处理函数
}
总结
ESP32 提供了灵活且强大的中断管理系统,其中断矩阵是其核心组成部分之一。通过中断矩阵,开发者可以灵活地将各种外设中断信号路由到 CPU 的中断处理程序,从而确保系统能够高效地响应外部事件。
本篇博文详细介绍了 ESP32 的中断矩阵机制,涵盖了外部中断源、CPU 中断、以及如何管理和查询中断状态。通过举例和代码示范,读者不仅能掌握如何在 ESP32 中配置和使用中断,还能理解中断优先级和嵌套的工作原理。
希望本篇博文能够帮助大家更好地理解ESP32的中断系统,欢迎大家交流指正。