GPIO(General Purpose Input/Output),即通用输入输出端口,是计算机系统中常见的一种外设接口。它允许CPU或其他主要芯片与外部设备进行双向通信,以实现控制和数据传输。
在嵌入式系统中,GPIO通常是控制外围器件(如LED、开关、电机等)的最常见方式。它们使用数字信号(0或1)进行控制,并且可以以不同模式配置,例如输入、输出、上升沿中断、下降沿中断等。
ESP32-S3 芯片具有 56 Pin(引脚),其中可以分化出 45 路GPIO(GPIO0 ~ GPIO48,其中缺失了GPIO22 ~ GPIO25);
ESP32-S3-WROOM-1 模组中共有 41 个 Pin(管脚),其中只有36个可以作为 GPIO 使用;
ESP32-S3-WROOM-2 模组中共有 41 个 Pin(管脚),其中只有33个可以作为 GPIO 使用。
在使用GPIO之前,通常需要进行针脚的初始化,以设定特定的模式和方向。对于输出GPIO引脚,需要配置其输出高或低电平来控制连接到该引脚的设备。对于输入GPIO引脚,则需要在针脚上设置内部上拉或下拉电阻,并将其配置为中断模式,以便在检测到特定电平变化时触发中断处理程序。
ESP32-S3 芯片具有 45 个物理 GPIO 焊盘。部分GPIO焊盘不能使用或芯片封装上没有对应管脚。每个焊盘均可用作通用 I/O 或可连接到内部外围信号。
- Strapping 引脚:GPIO0、GPIO3、GPIO45 和 GPIO46 是 Strapping 引脚。
- SPI0/1:GPIO26-32 通常用于 SPI flash 和 PSRAM,不推荐用于其他用途。当使用 Octal Flash 或 Octal PSRAM 或两者时,GPIO33~37 连接到 SPIIO4 ~ SPIIO7 和 SPIDQS。因此在 ESP32-S3R8 / ESP32-S3R8V 板上 GPIO33~37 也不推荐用于其他用途。
- USB-JTAG:默认情况下,USB-JTAG 使用 GPIO 19 和 20。为了将它们用作 GPIO,驱动程序将禁用 USB-JTAG。
还有单独的“RTC GPIO”支持,当 GPIO 被路由到“RTC”低功耗和模拟子系统时,它会起作用。这些引脚功能可在以下情况下使用:
- 仅有部分输入信号可以直接通过 IO MUX 直连外设,这些输入信号在表 6-2 “信号可经由 IO MUX 直接输入”一栏中被标为“yes”。剩余其它信号只能通过 GPIO 交换矩阵连接至外设;
- ESP32-S3 共有 45 个 GPIO 管脚,因此从 GPIO SYNC 进入到 GPIO 交换矩阵的输入共有 45 个;
- 位于 VDD3P3_CPU 电源域和 VDD3P3_RTC 电源域的管脚由 IE、OE、WPU 和 WPD 信号控制;
- 仅有部分输出信号可通过 IO MUX 直连管脚,这些输出信号在表 6-2“信号可经由 IO MUX 直接输出”一栏中被标为“yes”。剩余其它信号只能通过 GPIO 交换矩阵连接至外设;
- 从 GPIO 交换矩阵到 IO MUX 的输出共有 45 个,对应 GPIO X:0 ~ 21、26 ~ 48。
图中展示了芯片焊盘 (PAD) 的内部结构,即芯片逻辑与 GPIO 管脚之间的电气接口。45 个 GPIO 管脚均采用这一结构,且由 IE、OE、WPU 和 WPD 信号控制。
ESP32 的 IO MUX(Multiplexing)是一种硬件资源分配方案,用于控制芯片上的 GPIO 引脚,以适应不同的应用场景和扩展需求。它可以将多个不同的功能映射到同一个物理引脚上,从而实现 GPIO 引脚的复用和扩展。
在 ESP32 中,每个 GPIO 引脚都有多个功能可供选择,例如普通输入输出、中断、PWM、SPI、I2C、UART 等。通过使用 IO MUX,我们可以自由地将这些功能映射到不同的物理引脚上,以满足不同的应用需求。
ESP32 芯片中的 IO MUX 由两部分组成:GPIO Matrix 和 Peripheral Input Output Matrix(PIOM)。这两个矩阵分别用于控制 GPIO 引脚和外设接口的复用和映射。
GPIO Matrix:用于控制 GPIO 引脚的复用和映射。它包含多个 GPIO 输入/输出通道,每个通道可以映射到多个物理引脚上,并通过寄存器进行控制和配置。
PIOM:用于控制外设接口的复用和映射。例如,当我们要使用 SPI 接口时,可以将某个 GPIO 引脚的功能设置为 SPI 输入或输出,然后将其与其他 SPI 引脚连接起来,以实现 SPI 数据传输。
需要注意的是,在使用 IO MUX 时需要考虑多个因素,例如引脚的电气参数、信号完整性、时序要求等。为了确保系统的可靠性和稳定性,开发者需要仔细阅读相关文档和参考资料,进行正确的 IO MUX 配置和调试。
总之,IO MUX 是 ESP32 芯片中一种重要的硬件资源分配方案,可以实现 GPIO 引脚和外设接口的复用和映射。开发者可以根据具体的应用需求和系统设计进行 IO MUX 设置和调整。
说明:
- IE:输入使能
- OE:输出使能
- WPU:内部弱上拉
- WPD:内部弱下拉
- Bonding pad:接合焊盘,芯片逻辑的结点,实现芯片封装内晶片与 GPIO 管脚之间的物理连接。
以下具有颜色标注的 GPIO 尽量不用使用
在ESP32-IDF中,GPIO的初始化主要包括以下几个步骤:
ESP32-IDF编程中,通过 gpio_config 初始化 GPIO,该函数需要传入一个 gpio_config_t 类型的结构体,该结构体在 driver/gpio.h 中定义,原型如下:
typedef struct {
uint64_t pin_bit_mask; /* GPIO引脚的位掩码 */
gpio_mode_t mode; /* GPIO引脚的工作模式 */
gpio_pull_mode_t pull_up_en; /* GPIO引脚的上拉电阻是否使能 */
gpio_pull_mode_t pull_down_en; /* pull_down_en */
gpio_int_type_t intr_type; /* GPIO引脚的中断类型 */
} gpio_config_t;
成员 | 描述 |
---|---|
pin_bit_mask | GPIO引脚的位掩码。通过将GPIO引脚与位掩码进行按位与运算,可以选择需要配置的GPIO引脚。例如,若要配置GPIO0和GPIO2,则将位掩码设置为0x00000005,即二进制0101。 |
mode | GPIO引脚的工作模式。可选项有GPIO_MODE_INPUT、GPIO_MODE_OUTPUT、GPIO_MODE_OUTPUT_OD、GPIO_MODE_INPUT_OUTPUT、GPIO_MODE_INPUT_OUTPUT_OD等。其中,“_OD”表示开漏输出。 |
pull_up_en | GPIO引脚的上拉电阻是否使能。可选项有GPIO_PULLUP_DISABLE、GPIO_PULLUP_ENABLE。 |
pull_down_en | GPIO引脚的下拉电阻是否使能。可选项有GPIO_PULLDOWN_DISABLE、GPIO_PULLDOWN_ENABLE。 |
intr_type | GPIO引脚的中断类型。可选项有GPIO_INTR_DISABLE、GPIO_INTR_POSEDGE、GPIO_INTR_NEGEDGE、GPIO_INTR_ANYEDGE、GPIO_INTR_LOW_LEVEL、GPIO_INTR_HIGH_LEVEL,分别表示禁用中断、上升沿触发、下降沿触发、双边沿触发、低电平触发和高电平触发。 |
开漏输出(Open drain)是一种输出电路类型,特指输出端口只能将其电平输出为低电平或高阻态。开漏输出的优势在于可以将多个开漏输出端口连接到一条输出总线上,实现共用的目的。在开漏输出模式下,输出引脚只能拉低,而不能主动拉高,需要借助外部上拉电阻来拉高。当输出引脚处于高阻态时,相当于与输出端口断开连接,此时输出总线上的电平仅受其他的输出端口控制。开漏输出通常用于实现多个设备共享同一个总线,并且这个总线是被动拉高的(比如I2C),由其他设备负责将总线拉高,而开漏输出设备只是负责将总线拉低。它也可以用于输出低电平时的电流放大,保护负载等应用场合。 |
在使用GPIO前,建议先进行初始化操作。此外,在使用GPIO时,还需要根据实际需求配置相应的中断类型、下拉/上拉电阻等参数。
该函数用于操作 GPIO 的电平变化,函数原型如下:
void gpio_set_level(gpio_num_t gpio_num, uint32_t level);
成员 | 描述 |
---|---|
gpio_num | 引脚编号,其定义在“esp_idf/components/driver/include/driver/gpio.h”文件中。 |
level | 是需要设置的输出电平值,可以是0或1,其中0表示低电平,1表示高电平。 |
在使用该函数时,需要先通过 gpio_set_direction() 函数将指定GPIO设为输出模式。
该函数用于读取 GPIO 当前的电平状态,函数原型如下:
int gpio_get_level(gpio_num_t gpio_num);
成员 | 描述 |
---|---|
gpio_num | 引脚编号,其定义在“esp_idf/components/driver/include/driver/gpio.h”文件中。 |
返回值 | 返回值为当前GPIO输入电平值,可以是0或1,其中0表示低电平,1表示高电平,如果读取失败则返回-1。 |
在使用该函数时,需要先通过 gpio_set_direction() 函数将指定GPIO设为输入模式。
由于 gpio_get_level() 函数会返回当前GPIO的输入电平值,因此需要确保GPIO已经处于稳定状态。在读取之前,建议使用 vTaskDelay() 函数等待一段时间,确保GPIO电平已经稳定。
// 配置GPIO4为输入模式
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_INPUT);
// 获取GPIO4的输入电平值
int level = gpio_get_level(GPIO_NUM_4);
if (level == -1) {
ESP_LOGE(TAG, "Failed to get level of GPIO4");
} else if (level == 0) {
ESP_LOGI(TAG, "GPIO4 is in low level");
} else if (level == 1) {
ESP_LOGI(TAG, "GPIO4 is in high level");
}
该函数设置指定GPIO引脚的工作模式的函数,包括输入模式(GPIO_MODE_INPUT)和输出模式(GPIO_MODE_OUTPUT),函数原型如下:
esp_err_t gpio_set_direction(gpio_num_t gpio_num, gpio_mode_t mode);
成员 | 描述 |
---|---|
gpio_num | 引脚编号,其定义在“esp_idf/components/driver/include/driver/gpio.h”文件中。 |
gpio_mode_t | 是需要设置的工作模式,包括输入模式(GPIO_MODE_INPUT)和输出模式(GPIO_MODE_OUTPUT)。 |
返回值 | 返回值是函数执行结果,如果设置成功则返回ESP_OK,否则返回ESP_FAIL。 |
在进行 gpio_set_direction() 操作之前应该先确保GPIO引脚已经被正确配置了拉电阻或者上下拉电阻。可以通过 gpio_pullup_en() 和 gpio_pulldown_en() 函数或者引脚初始化结构体中的 pull_up_en 和 pull_down_en 来配置上拉电阻和下拉电阻。如果要将一个GPIO从输入模式切换到输出模式或者从输出模式切换到输入模式,则应该先将该GPIO的输出电平值设为默认初始值,以避免在切换时出现异常情况
该函数用于配置指定GPIO引脚的上下拉电阻模式,函数原型如下:
esp_err_t gpio_set_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull);
成员 | 描述 |
---|---|
gpio_num | 引脚编号,其定义在“esp_idf/components/driver/include/driver/gpio.h”文件中。 |
pull | 是需要设置的上下拉电阻模式,包括上拉电阻(GPIO_PULLUP_ONLY)、下拉电阻(GPIO_PULLDOWN_ONLY)或禁止上下拉电阻(GPIO_FLOATING) |
返回值 | 返回值是函数执行结果,如果设置成功则返回ESP_OK,否则返回ESP_FAIL。 |
在使用 gpio_set_pull_mode() 函数之前,必须先将GPIO设置为输入模式(即使用 gpio_set_direction() 函数将其设为GPIO_MODE_INPUT)。 |
注意:在配置上下拉电阻模式时应谨慎选择,错误的配置可能会造成GPIO电平不稳定或者IO电路损坏。特别是在使用上拉电阻时,应该特别注意IO引脚的工作电压和上拉电阻的效应,以避免上拉电阻过大或过小,导致GPIO输入电平出现异常。一般来说,推荐使用ESP-IDF提供的 gpio_pullup_en() 和 gpio_pulldown_en() 函数来配置上下拉电阻,这些函数在配置电阻时,会对电阻值进行一定程度的优化,以达到最佳的电路性能和电气特性。
该函数是 GPIO 的引脚重制函数,其主要作用是将指定的GPIO引脚设置为默认值以及关闭与该引脚相关的任何外设功能,函数原型如下:
void gpio_reset_pin(gpio_num_t gpio_num);
成员 | 描述 |
---|---|
gpio_num | GPIO 引脚号 |
当调用该函数时,系统会将指定的GPIO引脚重置为默认状态,包括以下操作:
使用 gpio_reset_pin() 函数时,必须先用 gpio_pad_select_gpio() 函数将该引脚选择为GPIO模式。
该函数用于检查 GPIO 引脚是否有效,函数原型如下:
bool gpio_num_is_valid(int gpio_num);
成员 | 描述 |
---|---|
gpio_num | GPIO 引脚号 |
返回值 | 该函数返回一个布尔值(true 或 false),表示指定的 GPIO 引脚号是否有效。如果指定的引脚号是有效的,则返回 true,否则返回 false。 |
该函数的作用是将指定的引脚号转换为 GPIO 端口号,以便后续进行 GPIO 配置,函数原型如下:
void gpio_pad_select_gpio(gpio_num_t gpio_num);
成员 | 描述 |
---|---|
gpio_num | 表示要转换的引脚号。在使用该函数时,我们需要将需要操作的引脚号传入函数中,函数会自动将其转换为对应的 GPIO 端口号。 |
在 ESP32 开发中,每个可编程的 GPIO 引脚都有一个对应的引脚编号(即 gpio_num_t 类型)。但是,并非所有引脚都是标准的 GPIO 引脚,一些引脚可能有其他功能或者限制,在使用这些引脚时需要特别注意。因此,在进行 GPIO 操作时,我们需要将需要操作的引脚转换为对应的 GPIO 端口号,以确保跟硬件进行对应。 |
gpio_pad_select_gpio() 函数的主要作用就是将给定的引脚号转换为对应的 GPIO 端口号。它接收一个参数 gpio_num,表示需要转换的引脚号,然后将该引脚号转换为对应的 GPIO 端口号。转换后的结果会将该引脚所在的寄存器地址与 GPIO 模块内部的地址对应起来,从而方便后续使用这个 GPIO 端口进行操作。
具体而言,当调用 gpio_pad_select_gpio() 后,它会根据传入的 gpio_num 参数计算出该引脚所在的寄存器地址,并将该地址与 GPIO 模块内部的地址对应起来。这个操作可以让系统知道该引脚对应的 GPIO 端口是什么,以便后续进行 GPIO 配置和操作。在这个过程中,如果给定的引脚号不是标准的 GPIO 引脚,或者不存在,那么该函数将返回一个错误码。
中断用于处理在程序正常执行期间不发生但在特定触发发生时发生的事件。例如,如果我们编写一个使 LED 闪烁的程序,微控制器将一个一个地执行每个命令。但是如果我们想监控一个开关来开始或停止闪烁,只有在所有其他任务完成之后才能完成检查,即它不会是实时的。这就是中断发挥作用的地方。有了中断,我们就不需要不断地检查数字输入引脚的状态。当发生中断时,控制器停止执行主程序,并调用称为 ISR 或中断服务程序的函数。然后控制器执行 ISR 内部的任务,然后在 ISR 执行完成后返回主程序。
ESP32的每个内核共有 32 个中断。每个中断都有一定的优先级,大多数(但不是全部)中断都连接到中断多路复用器。因为中断源比中断多,所以有些中断是与多个中断源共享的。
ESP32 中中断的主要分类是基于中断源。它们是硬件中断和软件中断。
外部或硬件中断
硬件中断响应外部硬件事件而发生。例如,当检测到触摸时会发生触摸中断,而当 GPIO 引脚的状态发生变化时会发生 GPIO 中断。GPIO 中断和触摸中断属于这一类。
软件中断
当触发软件事件(例如定时器溢出)时,会发生这种类型的中断。定时器中断是软件中断的一个例子。我们将在即将发布的 ESP32 定时器专用教程中讨论有关定时器的更多信息。
若要想在 ESP-IDF 中使用 GPIO 中断,则在配置 GPIO 引脚的时候,需要设置 gpio_config_t 中的 gpio_int_type_t 成员,或者通过 gpio_intr_enable() 函数启用中断,并通过 gpio_set_intr_type() 设置中断的触发类型。
该函数用于是否在某个引脚上启用中断的设定,函数原型如下:
esp_err_t gpio_intr_enable(gpio_num_t gpio_num);
成员 | 描述 |
---|---|
gpio_num | 引脚编号,其定义在“esp_idf/components/driver/include/driver/gpio.h”文件中。 |
返回值 | 返回值是函数执行结果,如果设置成功则返回ESP_OK,否则返回ESP_FAIL。 |
在使用 gpio_intr_enable() 函数之前,必须先将GPIO设置为输入模式(即使用 gpio_set_direction() 函数将其设为GPIO_MODE_INPUT),并配置好相应的上下拉电阻模式。
在启用中断功能之后,当GPIO引脚电平发生变化(例如从低电平变为高电平,或从高电平变为低电平)时,系统会自动触发GPIO中断,并执行相应的中断处理函数。需要注意的是,如果在处理中断时需要执行耗时操作,应该尽可能地将这些操作移到中断处理函数之外,以避免中断响应时间过长导致系统性能下降。
该函数用于是否在某个引脚上禁用中断的设定,函数原型如下:
esp_err_t gpio_intr_disable(gpio_num_t gpio_num);
成员 | 描述 |
---|---|
gpio_num | 引脚编号,其定义在“esp_idf/components/driver/include/driver/gpio.h”文件中。 |
返回值 | 返回值是函数执行结果,如果禁用成功则返回ESP_OK,否则返回ESP_FAIL。 |
在使用 gpio_intr_disable() 函数之前,必须先将GPIO设置为输入模式(即使用 gpio_set_direction() 函数将其设为GPIO_MODE_INPUT),并配置好相应的上下拉电阻模式和中断类型。
在禁用中断功能之后,当GPIO引脚电平发生变化时,系统将不再自动触发GPIO中断,并不会执行相应的中断处理函数。如果需要重新启用中断功能,只需再次调用 gpio_intr_enable() 函数即可。
该函数用于即中断添加到 GPIO 中断处理程序中,函数原型如下:
esp_err_t gpio_isr_handler_add(gpio_num_t gpio_num, gpio_isr_t isr_handler, void* args);
成员 | 描述 |
---|---|
gpio_num | 引脚编号,其定义在“esp_idf/components/driver/include/driver/gpio.h”文件中。 |
isr_handler | 中断处理函数的指针,即在 GPIO 中断发生时自动执行的函数。该函数必须符合 gpio_isr_t 类型的函数原型,即返回 void 类型并带有一个 void* 类型的参数(见下文) |
args | 传递给中断处理函数的参数,可以是任意类型的指针。 |
中断服务函数原型: |
void IRAM_ATTR my_isr_handler(void* args)
{
// 处理中断事件
...
}
该函数的作用是将指定的中断处理函数添加到 GPIO 系统中断处理程序中,并在指定的 GPIO 引脚上启用中断功能。当 GPIO 引脚的电平变化满足所设置的中断触发条件时,中断处理函数将被自动调用。
由于中断处理函数属于系统中断服务例程,因此在编写中断处理函数时,需要遵循一些特定的规则和限制,比如使用 IRAM_ATTR 修饰符以确保函数位于 IRAM 中,以及使用一些特殊的宏(例如 portENTER_CRITICAL() / portEXIT_CRITICAL())来管理中断状态等。此外,由于中断处理函数的执行时间应该尽量短,因此应该避免在中断处理函数中执行复杂和耗时的操作。
该函数用于从 GPIO 中断处理程序中删除中断处理函数,函数原型如下:
esp_err_t gpio_isr_handler_remove(gpio_num_t gpio_num);
成员 | 描述 |
---|---|
gpio_num | 需要删除中断处理函数的 GPIO 引脚号 |
该函数的作用是将指定 GPIO 引脚上的中断处理函数从 GPIO 系统中断处理程序中移除,并禁用相应的 GPIO 中断功能。这样,在该 GPIO 引脚产生中断信号时,就不会再调用与之相关联的中断处理函数了。
当使用 gpio_isr_handler_add() 函数添加中断处理函数后,如果不再需要该中断处理函数,就应该使用 gpio_isr_handler_remove() 函数将其删除,以释放系统资源并保证系统稳定性。此外,当在多个任务中使用相同的 GPIO 引脚和中断处理函数时,应确保每个任务都在适当的时候调用 gpio_isr_handler_remove() 函数来删除中断处理函数,从而避免因中断重复注册而导致的不必要的问题。
该函数用于为 GPIO 安装中断服务,并制定中断服务的类型,函数原型如下:
esp_err_t gpio_install_isr_service(int intr_alloc_flags);
成员 | 描述 |
---|---|
intr_alloc_flags | 中断分配标志,用于指定中断分配方式和优先级等。该参数的取值可以是 0 或者一组或多组以下标志位的按位或操作结果 ,如果该参数为 0,则默认采用边沿触发模式、中等优先级。 |
中断类型:
该函数的作用是初始化和启动 GPIO 中断服务,并为所有 GPIO 引脚提供中断处理功能。在调用该函数后,可以使用 gpio_isr_handler_add() 函数向系统中断服务例程中添加中断处理函数,并使用 gpio_isr_handler_remove() 函数从中删除中断处理函数。
在使用 GPIO 中断功能时,必须要先调用 gpio_install_isr_service() 函数来注册和启动中断服务,否则中断处理函数将不能正确地被调用。此外,当使用多个任务处理 GPIO 中断时,每个任务都应该独立的调用 gpio_install_isr_service() 函数来注册中断服务,以保证中断服务的正常运行和任务的安全。
该函数用于卸载 GPIO 上的中断服务,函数原型如下:
void gpio_uninstall_isr_service(void);
该函数的作用是卸载和停止 GPIO 中断服务,并释放相关资源,包括系统中断处理程序、中断向量表、中断服务例程和中断资源等。在调用该函数后,就不能再使用 gpio_isr_handler_add() 和 gpio_isr_handler_remove() 函数来添加和删除中断处理函数了。
在调用 gpio_uninstall_isr_service() 函数前,必须要保证所有任务和线程都不再使用 GPIO 中断服务,并且已经逐步删除所有添加到 GPIO 中断服务中的中断处理函数。否则,可能会导致系统崩溃或其他不良后果。
该函数用于在 GPIO 中断服务系统中添加中断处理函数并将其中断事件放入队列,函数原型如下:
void gpio_isr_handler_add_with_queue(gpio_num_t gpio_num, gpio_isr_t isr_handler, void* args, QueueHandle_t queue);
成员 | 描述 |
---|---|
gpio_num | GPIO 引脚号 |
isr_handler | 中断处理函数的指针。 |
args | 中断处理函数的参数。 |
queue | 指向中断事件队列的指针。 |
该函数的作用是在 GPIO 中断服务系统中添加一个中断处理函数,并将其中断事件放入指定的队列中。当相应的 GPIO 引脚发生中断事件时,中断处理函数会被调用,执行相关操作,并将中断事件的信息打包成一个 gpio_event_t 结构体,然后将其压入中断事件队列中。
使用 gpio_isr_handler_add_with_queue() 函数时,必须先使用 gpio_install_isr_service() 函数来注册和启动 GPIO 中断服务。此外,如果在多个任务中使用相同的 GPIO 引脚和中断处理函数时,应该为每个任务创建不同的队列,并分别传递给 gpio_isr_handler_add_with_queue() 函数,以避免因队列操作竞争而导致的问题。
该函数用于从 GPIO 中断服务系统中删除中断处理函数并将其中断事件从队列中移除,函数原型如下:
void gpio_isr_handler_remove_from_queue(gpio_num_t gpio_num, QueueHandle_t queue);
成员 | 描述 |
---|---|
gpio_num | GPIO 引脚号 |
queue | 指向中断事件队列的指针。 |
该函数的作用是从 GPIO 中断服务系统中删除一个中断处理函数,并将其中断事件从指定的队列中移除。当相应的 GPIO 引脚不再需要进行中断处理时,可以使用该函数将其对应的中断处理函数从中断服务系统和队列中删除,以释放系统资源并避免不必要的中断事件处理。同时,该函数也可以用于在运行时动态添加和删除中断事件队列。
使用 gpio_isr_handler_remove_from_queue() 函数前,必须要保证当前没有中断事件正在被处理,并且已经逐步删除所有添加到 GPIO 中断服务中的中断处理函数,否则可能会导致系统崩溃或其他不良后果。
该函数用于申请一个 GPIO 中断的函数,函数原型如下:
int gpio_intr_alloc(gpio_num_t gpio_num, int intr_flags, int priority, void (*fn)(void*), void* arg, gpio_isr_handle_t* handle);
成员 | 描述 |
---|---|
gpio_num | GPIO 引脚号 |
intr_flags | 中断类型,可取边沿触发或电平触发(GPIO_INTR_LOW_LEVEL、GPIO_INTR_HIGH_LEVEL、GPIO_INTR_NEGEDGE 或 GPIO_INTR_POSEDGE)。 |
priority | 中断优先级,取值范围为 0~3。 |
fn | 中断处理函数的指针。 |
arg | 中断处理函数的参数。 |
handle | 返回的 GPIO 中断句柄,用于后续的中断释放和管理。 |
该函数的作用是申请一个指定 GPIO 引脚的中断,并返回表示该中断的句柄。通过调用该函数,可以将指定的 GPIO 引脚设置为输入模式,并在该引脚上检测到对应类型的中断事件时调用指定的中断处理函数。中断处理函数在执行时会传入 arg 参数。
使用 gpio_intr_alloc() 函数前,必须先使用 gpio_install_isr_service() 函数来注册和启动 GPIO 中断服务。此外,在使用完中断后,需要使用 gpio_intr_free() 函数将其释放,以便回收中断资源并避免未知问题。
该函数用于绑定 GPIO 的中断模式,函数原型如下:
esp_err_t gpio_set_intr_type(int gpio_num, gpio_int_type_t intr_type);
成员 | 描述 |
---|---|
gpio_num | 要设置中断类型的 GPIO 引脚号。 |
intr_type | GPIO 引脚的中断类型,可选的类型包括 GPIO_INTR_DISABLE(禁用中断)、GPIO_INTR_POSEDGE(上升沿中断)、GPIO_INTR_NEGEDGE(下降沿中断)、GPIO_INTR_ANYEDGE(任何边缘中断)和 GPIO_INTR_LOW_LEVEL(低电平中断)。 |
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#define GPIO_LED 5
#define GPIO_KEY 4
#define ESP_INTR_FLAG_DEFAULT 0
TaskHandle_t xTask_LED = NULL; // LED 控制线程
esp_timer_handle_t xTimer_Key = NULL; // 按键去抖定时器
static void IRAM_ATTR gpio_isr_handler(void* arg)
{
esp_timer_start_once(xTimer_Key, 20000); // 20ms后启动定时(这里的单位是us)
}
// 去抖动函数
void timer_key_cb(void *args){
if(gpio_get_level(GPIO_KEY) == 1){
xTaskNotify(xTask_LED, 1, eSetBits);
}
}
void app_main(void)
{
// 配置 LED 引脚的gpio_config
gpio_config_t cnf = {
.mode = GPIO_MODE_INPUT_OUTPUT, // 这里必须要设置为输入输出混合模式
.pin_bit_mask = 1ULL << GPIO_LED // 设置LED引脚掩码
};
gpio_config(&cnf); // 启用配置
// 配置案件引脚的gpio_config
cnf.mode = GPIO_MODE_INPUT; // 设置为输出模式
cnf.pin_bit_mask = 1ULL << GPIO_KEY; // 设置按键开关掩码位
cnf.pull_down_en = GPIO_PULLDOWN_ENABLE; // 使能下拉电阻
cnf.intr_type = GPIO_INTR_POSEDGE; // 设置为下降沿触发中断
gpio_config(&cnf);
// 设置按键中断
gpio_set_intr_type(GPIO_KEY, GPIO_INTR_POSEDGE); // 这一行和在结构体中设置效果相同
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT); // 启用中断
gpio_isr_handler_add(GPIO_KEY, gpio_isr_handler, NULL); // 设置中断服务函数
// 获得 mainTask 句柄
xTask_LED = xTaskGetCurrentTaskHandle();
// 使用ESP创建高精度定时器
esp_timer_create_args_t timer_args = {
.callback = &timer_key_cb,
.name = "Key-Timer"
};
esp_timer_create(&timer_args, &xTimer_Key); // 创定时器
// 等待按键触发
uint32_t events = 0;
while(1){
if(xTaskNotifyWait(UINT32_MAX, UINT32_MAX, &events, portMAX_DELAY)==pdTRUE){
gpio_set_level(GPIO_LED, !gpio_get_level(GPIO_LED));
}
}
}
这里有一点需要注意,与Arduino中操作 pinMode 不同,在ESP32中,如果对引脚即想做写入操作,又想做读出操作,则必须将引脚的模式设置为 GPIO_MODE_INPUT_OUTPUT 模式,否则引脚读出会出错。