第十二章 EXIT实验
本章将介绍如何将GPIO引脚作为外部中断输入来使用。通过本章的学习,开发者将学习到GPIO作为外部中断输入的使用。
本章分为如下几个小节:
12.1 外部中断简介
12.2 硬件设计
12.3 程序设计
12.4 下载验证
12.1 中断简介
在上一章节中,我们虽然实现了GPIO口输入功能的读取,但代码始终在检测IO输入口的变化,导致在代码量增加时,按键检测部分的轮询效率降低。尤其在某些特定场合,如按键可能一天才被按下一次,实时检测将造成大量时间浪费。为优化此问题,我们引入了外部中断的概念。外部中断即在按键被按下(触发中断)时执行相关功能,从而显著节省CPU资源,因此在实际项目中应用广泛。
1,什么是外部中断
外部中断属于硬件中断,由微控制器外部事件触发。微控制器的特定引脚被设计为对特定事件(如按钮按压、传感器信号变化等)作出响应,这些引脚通常称为“外部中断引脚”。一旦外部中断事件发生,当前程序执行将立即暂停,并跳转到相应的中断服务程序(ISR)进行处理。处理完毕后,程序会恢复执行,从被中断的地方继续。下图是CPU中断处理过程。
图12.1.1 CPU中断处理过程
对于嵌入式和实时系统而言,外部中断的使用至关重要,它能使系统对外部事件作出即时响应,极大提升系统效率和实时性。
2,ESP32-S3外部中断
ESP32 S3的外部中断具备两种触摸类型:
(1)电平触发:高、低电平触发,要求保持中断的电平状态直到CPU响应。
(2)边沿触发:上升沿和下降沿触发,这类型的中断一旦触发,CPU即可响应。
ESP32S3的外部中断功能能够以非常精确的方式捕捉外部事件的触发。开发者可以通过配置中断触发方式(如上升沿、下降沿、任意电平、低电平保持、高电平保持等)来适应不同的外部事件,并在事件发生时立即中断当前程序的执行,转而执行中断服务函数。
ESP32-S3支持六级中断,同时支持中断嵌套,也就是优先级中断可以被高优先级中断打断。如下表12.1.2中的优先级一栏,数字越大表明该中断的优先级越高。其中,NMI中断拥有最高优先级,此类中断已经触发,CPU必须处理。
| 类别 | 种类 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 内部中断 | 定时器0 | |
| 内部中断 | 软件 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 边沿触发 | |
| 内部中断 | 解析 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | NMI | |
| 内部中断 | 定时器1 | |
| 内部中断 | 定时器2 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 边沿触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 电平触发 | |
| 外部中断 | 边沿 | |
| 内部中断 | 软件 | |
| 外部中断 | 边沿 | |
| 外部中断 | 电平触发 | |
表12.1.1 ESP32S3中断
在ESP32S3中,中断系统被用于响应各种内部和外部事件。这些中断按照其触发方式和优先级进行分类。上表详细列出了ESP32S3的中断号、类别、种类以及相应的优先级。通过配置这些中断,开发者可以实现对各种事件的及时响应和处理,提高系统的效率和实时性。
(1)中断号:每个中断的唯一标识符,用于在程序中引用和配置特定的中断。
(2)类别:中断的来源类型,分为外部中断和内部中断。外部中断由外部设备或信号触发,如按键、传感器等;内部中断则由微控制器内部的硬件事件触发,如定时器溢出、软件中断等。
(3)种类:中断的触发方式,包括电平触发和边沿触发。电平触发是在输入信号达到特定电平(如高电平或低电平)时触发中断;边沿触发则是在输入信号从一种电平状态变化到另一种状态时触发中断。
(4)优先级:中断的响应优先级。当多个中断同时发生时,微控制器会根据中断的优先级来决定先处理哪个中断。较高的优先级意味着中断将优先得到处理。
在开发过程中,开发者可以根据实际需求配置中断的触发方式、优先级等参数,以实现高效、可靠的事件处理机制。
12.2 硬件设计
12.2.1 例程功能
实验现象:按下BOOT按键可控制LED状态翻转。
12.2.2 硬件资源
1. LED
LED-IO1
2. 按键
BOOT-IO0
12.2.3 原理图
BOOT原理图已在11.2.3章节中详细阐述,为避免重复,此处不再赘述。
12.3 程序设计
12.3.1 程序流程图
程序流程图能帮助我们更好的理解一个工程的功能和实现的过程,对学习和设计工程有很好的主导作用。下面看看本实验的程序流程图:
图12.3.1.1 EXIT实验程序流程图
12.3.2 EXIT函数解析
接下来,作者将介绍一些常用的GPIO EXIT函数,这些函数的描述及其作用如下:
1,注册中断函数
该函数用来注册中断服务,该函数原型如下所示:
void gpio_install_isr_service(esp_intr_alloc_flag_t flags);
该函数的形参描述如下表所示:
参数 | 描述 |
| 中断标志位 |
| 使用 Level 1 中断级别。在中断服务程序执行期间禁用同级别的中断 |
| 使用 Level 2 中断级别。在中断服务程序执行期间禁用同级别和 Level 1 的中断 |
| 使用边沿触发方式。使能 GPIO 边沿触发中断 |
| 使用中低水平触发方式。使能 GPIO 中低电平触发中断。 |
| 使用高电平触发方式。使能 GPIO 高电平触发中断 |
表12.3.2.1 gpio_install_isr_service函数形参描述
返回值:无。
2,分配中断函数
该函数设置某个管脚的中断服务函数,该函数原型如下所示:
esp_err_t gpio_isr_handler_add(gpio_num_t gpio_num,
gpio_isr_t isr_handler, void* args);
该函数的形参描述如下表所示:
参数 | 描述 |
| GPIO 引脚号,指定要分配中断处理程序的 GPIO 引脚 |
| 指向中断处理函数的函数指针。中断处理函数是一个用户定义的回调函数,将在中断发生时执行 |
| 传递给中断处理程序的参数。这是一个指向用户特定数据的指针,可以在中断处理程序中使用 |
表12.3.2.2 gpio_isr_handler_add函数形参描述
返回值:ESP_OK表示设置成功,ESP_FAIL表示设置失败。
实现一个中断服务程序的回调函数,在函数中处理中断响应。(其中函数名可以随意起名,但是要符合C语言标准)中断处理函数需要声明为 IRAM_ATTR,以确保其运行在内存中的可执行区域。下面是中断函数的模板。
void IRAM_ATTR gpio_isr_handler(void* arg) {
/* 处理中断响应 */
}
3,开启外部中断函数
该函数用来配置某个管脚开启外部中断,该函数原型如下所示:
void gpio_intr_enable(gpio_num_t gpio_num)
该函数的形参描述如下表所示:
参数 | 描述 |
| GPIO 引脚号,指定要分配中断处理程序的 GPIO 引脚 |
表12.3.2.3 gpio_intr_enable函数形参描述
返回值:无。
注意:在使用gpio_intr_enable()函数之前,开发者需要先通过gpio_install_isr_service()函数和gpio_isr_handler_add()函数来安装和注册中断处理程序。
12.3.3 EXIT驱动解析
在IDF版的03_exit例程中,作者在03_exit \components\BSP路径下新增了一个EXIT文件夹,用于存放exit.c和exit.h这两个文件。其中,exit.h文件负责声明EXIT相关的函数和变量,而exit.c文件则实现了EXIT的驱动代码。下面,我们将详细解析这两个文件的实现内容。
1,exit.h文件
/* 引脚定义 */
#define BOOT_INT_GPIO_PIN GPIO_NUM_0
/*IO操作*/
#define BOOT gpio_get_level(BOOT_INT_GPIO_PIN)
/* 函数声明 */
void exit_init(void); /* 外部中断初始化程序 */
2,exit.c文件
/**
* @NOTE IRAM_ATTR: 这里的IRAM_ATTR属性用于将中断处理函数存储在内部RAM中, 目的在于减少延迟
* @retval 无
*/
static void IRAM_ATTR exit_gpio_isr_handler(void *arg) {
uint32_t gpio_num = (uint32_t) arg;
if (gpio_num == BOOT_INT_GPIO_PIN)
{
LED_TOGGLE();
}
}
/**
* @brief 外部中断初始化程序
* @param 无
* @retval 无
*/
void exit_init(void)
{
gpio_config_t gpio_init_struct;
/* 配置BOOT引脚和外部中断 */
gpio_init_struct.mode = GPIO_MODE_INPUT; /* 选择为输入模式 */
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; /* 上拉使能 */
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; /* 下拉失能 */
gpio_init_struct.intr_type = GPIO_INTR_NEGEDGE; /* 下降沿触发 */
gpio_init_struct.pin_bit_mask = 1ull << BOOT_INT_GPIO_PIN;
gpio_config(&gpio_init_struct); /* 配置使能 */
/* 注册中断服务 */
gpio_install_isr_service(ESP_INTR_FLAG_EDGE);
/* 设置GPIO的中断回调函数 */
gpio_isr_handler_add(BOOT_INT_GPIO_PIN, exit_gpio_isr_handler,
(void*) BOOT_INT_GPIO_PIN);
/* 使能GPIO模块中断信号 */
gpio_intr_enable(BOOT_INT_GPIO_PIN);
}
开启管脚的外部中断操作相对简便。首先,需要将管脚配置为下降沿触发(GPIO_INTR_NEGEDGE)和输入模式(GPIO_MODE_INPUT)。完成配置后,需要调用gpio_install_isr_service函数来注册中断服务,并调用gpio_isr_handler_add函数来注册外部中断的回调函数。最后,调用gpio_intr_enable函数启用外部中断功能。其中,exit_gpio_isr_handler回调函数负责实现LED灯状态的切换。
12.3.4 CMakeLists.txt文件
打开本实验BSP下的CMakeLists.txt文件,其内容如下所示:
set(src_dirs
EXIT
LED)
set(include_dirs
EXIT
LED)
set(requires
driver)
idf_component_register(SRC_DIRS ${src_dirs}
INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
上述的红色EXIT驱动需要由开发者自行添加,以确保EXIT驱动能够顺利集成到构建系统中。这一步骤是必不可少的,它确保了EXIT驱动的正确性和可用性,为后续的开发工作提供了坚实的基础。
12.3.5 实验应用代码
打开main/main.c文件,该文件定义了工程入口函数,名为app_main。该函数代码如下。
/**
* @brief 程序入口
* @param 无
* @retval 无
*/
void app_main(void)
{
esp_err_t ret;
ret = nvs_flash_init(); /* 初始化NVS */
if (ret == ESP_ERR_NVS_NO_FREE_PAGES
|| ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
led_init(); /* 初始化LED */
exit_init(); /* 初始化按键 */
while(1)
{
vTaskDelay(10);
}
}
本实验的应用代码很简单,在初始化完LED和外部中断后,就进入一个while循环,以便通过串口助手查看单片机运行状态。另外,按键控制LED亮灭状态翻转的操作都在对应EXTI的中断服务函数中完成了。 12.4 下载验证
在完成编译和烧录操作后,可以看到板子上的LED处于亮起状态,若此时按下KEY按键,则能够看到LED处于熄灭状态。如此往复该操作,将会看到LED处在熄灭与亮起的状态之间转换,与预期的实验现象效果相符。