|
第十三章 EXIT实验
1)实验平台:正点原子DNESP32P4开发板
2)章节摘自【正点原子】ESP32-P4开发指南— V1.0
3)购买链接:https://detail.tmall.com/item.htm?id=873309579825
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子DNESP32S3开发板技术交流群:132780729
本章将详细介绍如何将GPIO引脚配置为外部中断输入,帮助开发者掌握GPIO中断的基础使用方法。这部分内容对处理按键输入、传感器信号等外部事件至关重要,通过中断方式可以有效提高系统的响应效率。
本章分为如下几个小节:
13.1 外部中断介绍
13.2 硬件设计
13.3 程序设计
13.4 下载验证
13.1 外部中断介绍
在上一章节中,我们虽然实现了GPIO口输入功能的读取,但代码始终在检测IO输入口的变化,导致在代码量增加时,按键检测部分的轮询效率降低。尤其在某些特定场合,如按键可能一天才被按下一次,实时检测将造成大量时间浪费。为优化此问题,我们引入了外部中断的概念。外部中断即在按键被按下(触发中断)时执行相关功能,从而显著节省CPU资源,因此在实际项目中应用广泛。
13.1.1 什么是中断
外部中断属于硬件中断,由微控制器外部事件触发。微控制器的特定引脚被设计为对特定事件(如按钮按压、传感器信号变化等)作出响应,这些引脚通常称为“外部中断引脚”。一旦外部中断事件发生,当前程序执行将立即暂停,并跳转到相应的中断服务程序(ISR)进行处理。处理完毕后,程序会恢复执行,从被中断的地方继续。下图是CPU中断处理过程。
图13.1.1 CPU中断处理过程
对于嵌入式和实时系统而言,外部中断的使用至关重要,它能使系统对外部事件作出即时响应,极大提升系统效率和实时性。
13.1.2 ESP32-P4外部中断介绍
ESP32-P4的HP IO MUX和HP GPIO矩阵提供了强大的中断处理能力,允许开发者配置GPIO引脚以响应外部事件。这些事件可以是按键按下、传感器信号变化等。ESP32-P4的HP IO MUX和HP GPIO矩阵可以生成以下中断信号,供中断矩阵处理。中断矩阵的相关知识请看第二章节的内容。
1)GPIO_INTR0
2)GPIO_INTR1
3)GPIO_INTR2
4)GPIO_INTR3
5)GPIO_PAD_COMP_INTR
ESP32-P4的中断矩阵为开发者提供了灵活的外部事件响应机制。ESP32-P4的55个GPIO引脚可以通过任意一个内部中断信号(GPIO_INTR0、GPIO_INTR1、GPIO_INTR2、GPIO_INTR3)来响应外部事件。这些中断信号通过中断矩阵传递和处理,确保系统可以快速响应和执行。其中,GPIO_PAD_COMP_INTR 是一种特殊的中断信号,与模拟电压比较器相关。详细信息可以参考《ESP32-P4技术参考手册》中的模拟电压比较器(Analog Voltage Comparator)章节。
根据本书籍第二章的2.4.5小节中的图2.4.5.2所示位置,可以找到GPIO_INTR0、GPIO_INTR1、GPIO_INTR2和GPIO_INTR3这些内部中断源的中断号,它们分别对应中断号74至77号,如下图所示。
图13.1.2.1 GPIO_INTR0等CPU外设中断源映射和状态寄存器
这些中断信号由ESP32-P4的GPIO引脚触发,并通过中断矩阵传递给处理器核心以响应外部事件。至于如何配置这55个GPIO的触发条件,实际上笔者在LED实验时已经讲解过,GPIO管脚支持外部中断配置。外部中断具备两种主要的触发类型:电平触发和边沿触发。电平触发指的是高电平和低电平触发,而边沿触发则包含上升沿、下降沿和任意沿的触发。
下图展示了配置这四个内部中断源(GPIO_INTR0、GPIO_INTR1、GPIO_INTR2、GPIO_INTR3)触发条件的寄存器。
表13.1.2.1 HP IO MUX和HP GPIO Matrix提供了内部中断源
上表13.1.2.1展示了ESP32-P4的HP IO MUX和HP GPIO Matrix中GPIO内部中断源的配置寄存器。针对GPIO_INTR0、GPIO_INTR1、GPIO_INTR2和GPIO_INTR3,可以通过配置不同的寄存器对GPIO的中断触发条件进行设置。
下面笔者以GPIO_INTR0为例讲解:
1)GPIO_STATUS_INTERRUPT[n] & GPIO_PINn_INT_ENA[0] (n: 0 ~ 31):用于配置GPIO0到GPIO31号引脚的启动中断。
2)GPIO_STATUS1_INTERRUPT[n] & GPIO_PINn+32_INT_ENA[0] (n: 0 ~ 22):用于配置GPIO32到GPIO54号引脚的中断触发类型。
其他内部中断(如GPIO_INTR1、GPIO_INTR2、GPIO_INTR3)的配置方式与GPIO_INTR0类似。下图为GPIO_STATUS_INTERRUPT和GPIO_PINn_INT_ENA[0] (n: 0 ~ 31)描述内容。
图13.1.2.2 GPIO_STATUS_INTERRUPT和GPIO_PINn_INT_ENA[0] (n: 0 ~ 31)描述
其他寄存器的详细描述,请参阅《ESP32-P4技术参考手册》中的7.18 节 “Interrupts”。
上述的内容是HP系统的外部中断相关内容,在低功耗(LP)系统下,ESP32-P4的LP IO MUX和GPIO矩阵可以生成LP_GPIO_INTR0中断信号(中断号为10),并通过中断矩阵发送至处理器。低功耗模式时,GPIO0~GPIO15由LP系统管理,允许将这些GPIO配置为外部中断,并由LP_GPIO_INTR0统一管理,如下图所示。
图13.1.2.3 LP IO MUX和LP GPIO矩阵内部中断源
在低功耗(LP)系统下,要开启GPIO0~GPIO15的中断,可以使用上图的LP_GPIO_STATUS_DATA[n](n: 0 ~ 15)寄存器开启中断。同时,使用LP_GPIO_PINn_INT_TYPE(n: 0 ~ 15)寄存器配置每个 GPIO 引脚的中断触发类型,包括高电平、低电平、上升沿、下降沿和任意沿触发。这些配置可以实现低功耗系统中对 GPIO 引脚的外部中断响应,有效支持低功耗操作模式下的外部事件监控需求。
13.2 硬件设计
13.2.1 例程功能
通过将BOOT按键配置为外部中断触发,可以在每次按下BOOT按键时触发中断处理程序,从而实现LED0的电平翻转效果。
13.2.2 硬件资源
1)LED灯
LED 0 - IO51
2)KEY按键
BOOT - IO35
13.2.3 原理图
KEY原理图已在12.2.3小节中详细阐述,为避免重复,此处不再赘述。
13.3 程序设计
13.3.1 GPIO的IDF驱动
接下来,笔者将介绍常用的GPIO EXIT函数,这些函数的描述及其作用如下:
1,注册中断函数gpio_install_isr_service
该函数用于注册中断服务,其函数原型如下:
- esp_err_t gpio_install_isr_service(int intr_alloc_flags);
复制代码 函数参数:
表13.3.1.1 gpio_install_isr_service函数形参描述
返回值:
ESP_OK表示操作成功。
ESP_ERR_NO_MEM表示内存不足,无法安装服务。
ESP_ERR_INVALID_STATE表示ISR服务已安装,不能重复安装。
ESP_ERR_NOT_FOUND表示未找到符合指定标志的空闲中断。
ESP_ERR_INVALID_ARG表示GPIO参数错误。
2,分配中断函数gpio_isr_handler_add
该函数用于注册某个管脚的中断服务函数,其函数原型如下:
- esp_err_t gpio_isr_handler_add( gpio_num_t gpio_num, gpio_isr_t isr_handler,
- void *args);
复制代码 函数形参:
表13.3.1.2 gpio_isr_handler_add函数形参描述
返回值:
ESP_OK表示操作成功。
ESP_ERR_INVALID_STATE表示ISR服务未初始化或状态错误。
ESP_ERR_INVALID_ARG表示参数错误。
这里需要注意ISR处理程序函数的格式,如下所示。
- void (*gpio_isr_t)(void *arg);
复制代码 在编写中断回调函数时,需要注意。
13.3.2 程序流程图
图11.3.2.1 EXIT实验程序流程图
13.3.3 程序解析
在03_exit例程中,作者在03_exit\components\BSP路径下新建EXIT文件夹,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。
1,EXIT驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。EXIT驱动源码包括两个文件:exit.c和exit.h。
下面先解析exit.h的程序。对EXIT引脚做了相关定义以及功能实现。
- <font size="3">/* 引脚定义 */</font>
- <font size="3">#define BOOT_INT_GPIO_PIN GPIO_NUM_35</font>
- <font size="3">/* IO操作 */</font>
- <font size="3">#define BOOT_INT gpio_get_level(BOOT_INT_GPIO_PIN)</font>
复制代码 上述源码,定义了BOOT按键所使用的GPIO 脚编号为 GPIO_NUM_35,即GPIO35,通过宏定义BOOT_INT,直接调用gpio_get_level(BOOT_INT_GPIO_PIN),用于读取GPIO_NUM_35引脚的电平状态。如果BOOT按键连接到GPIO35,可以直接使用BOOT_INT来判断引脚电平,从而检测 BOOT 按键的按下或释放状态。
下面我们再解析exit.c的程序,看一下初始化函数exit_init,代码如下:
- <font size="3">/**</font>
- <font size="3"> * @brief 外部中断初始化程序</font>
- <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url] 无</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">void exit_init(void)</font>
- <font size="3">{</font>
- <font size="3"> gpio_config_t gpio_init_struct;</font>
- <font size="3"> /* 配置BOOT引脚和外部中断 */</font>
- <font size="3"> gpio_init_struct.mode = GPIO_MODE_INPUT; /* 输入模式 */</font>
- <font size="3"> gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; /* 上拉使能 */</font>
- <font size="3"> gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; /* 下拉失能 */</font>
- <font size="3"> gpio_init_struct.intr_type = GPIO_INTR_NEGEDGE; /* 下降沿触发 */</font>
- <font size="3"> gpio_init_struct.pin_bit_mask = 1ull << BOOT_INT_GPIO_PIN; /* 引脚位掩码 */</font>
- <font size="3"> ESP_ERROR_CHECK(gpio_config(&gpio_init_struct)); /* 配置使能 */</font>
- <font size="3"> </font>
- <font size="3"> /* 注册中断服务 */</font>
- <font size="3"> ESP_ERROR_CHECK(gpio_install_isr_service(0));</font>
- <font size="3"> /* 设置BOOT的中断回调函数 */</font>
- <font size="3">ESP_ERROR_CHECK(gpio_isr_handler_add(BOOT_INT_GPIO_PIN,</font>
- <font size="3"> exit_gpio_isr_handler, </font>
- <font size="3">(void*)BOOT_INT_GPIO_PIN));</font>
- <font size="3">}</font>
复制代码 该函数用于配置BOOT按键GPIO引脚为下降沿外部中断模式。通过设置GPIO为输入模式并启用弱上拉,在检测到按键从高电平切换为低电平时触发中断,并调用指定的中断处理函数,实现对按键按下事件的快速响应。如下代码为GPIO35外部中断服务函数exit_gpio_isr_handler。
- <font size="3">/**</font>
- <font size="3"> * @brief 外部中断服务函数</font>
- <font size="3"> * @param arg:中断引脚号</font>
- <font size="3"> * [url=home.php?mod=space&uid=60778]@note[/url] IRAM_ATTR: 这里的IRAM_ATTR属性用于将中断处理函数存储在内部RAM中 </font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">static void IRAM_ATTR exit_gpio_isr_handler(void *arg)</font>
- <font size="3">{</font>
- <font size="3"> uint32_t gpio_num = (uint32_t) arg;</font>
- <font size="3"> </font>
- <font size="3"> if (gpio_num == BOOT_INT_GPIO_PIN)</font>
- <font size="3"> {</font>
- <font size="3"> /* 消抖 */</font>
- <font size="3"> esp_rom_delay_us(20000);</font>
- <font size="3"> if (BOOT_INT == 0)</font>
- <font size="3"> {</font>
- <font size="3"> LED0_TOGGLE();</font>
- <font size="3"> }</font>
- <font size="3"> }</font>
- <font size="3">}</font>
复制代码 exit_gpio_isr_handler函数是一个用于响应BOOT按键的外部中断处理程序。该函数利用IRAM_ATTR将中断服务函数加载到内部IRAM中,确保响应速度。通过传递的引脚号参数验证中断是否由BOOT引脚触发,若是,则执行20ms延迟以消除按键抖动。消抖后再次检测按键状态,若确认按键被按下(低电平),则切换LED的亮灭状态,达到按键控制LED的效果。
2,CMakeLists.txt文件
本例程的功能实现主要依靠EXIT驱动。要在main函数中,成功调用EXIT文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:
- <font size="3">set(src_dirs</font>
- <font size="3"> LED</font>
- <font size="3"> KEY</font>
- <font size="3"> EXIT)</font>
- <font size="3">set(include_dirs</font>
- <font size="3"> LED</font>
- <font size="3"> KEY</font>
- <font size="3"> EXIT)</font>
- <font size="3">set(requires</font>
- <font size="3"> driver)</font>
- <font size="3">idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs}</font>
- <font size="3"> REQUIRES ${requires})</font>
- <font size="3">component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)</font>
复制代码
3,main.c驱动代码
在main.c里面编写如下代码。
- <font size="3">void app_main(void)</font>
- <font size="3">{</font>
- <font size="3"> esp_err_t ret;</font>
- <font size="3"> </font>
- <font size="3"> ret = nvs_flash_init(); /* 初始化NVS */</font>
- <font size="3">if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)</font>
- <font size="3"> {</font>
- <font size="3"> ESP_ERROR_CHECK(nvs_flash_erase());</font>
- <font size="3"> ESP_ERROR_CHECK(nvs_flash_init());</font>
- <font size="3"> }</font>
- <font size="3"> led_init(); /* 初始化LED */</font>
- <font size="3"> exit_init(); /* 初始化EXIT */</font>
- <font size="3"> while(1)</font>
- <font size="3"> {</font>
- <font size="3"> vTaskDelay(pdMS_TO_TICKS(10)); /* 延时10ms */</font>
- <font size="3"> }</font>
- <font size="3">}</font>
复制代码 该函数调用了led_init和exit_init函数,分别用于初始化LED和配置BOOT按键外部中断。然后,通过一个无限循环和10ms的延时保持主任务的运行,使应用程序在后台持续待命,以便响应按键触发的中断事件。
13.4 下载验证
程序下载完之后,通过BOOT按键控制LED灯亮灭。
|