目录
[LP GPIO](#LP GPIO)
[2.1 迟滞过滤器](#2.1 迟滞过滤器)
[2.2 毛刺滤波器](#2.2 毛刺滤波器)
[2.2.1 管脚毛刺滤波器](#2.2.1 管脚毛刺滤波器)
[2.2.2 灵活毛刺滤波器](#2.2.2 灵活毛刺滤波器)
[3.1 gpio_u.c](#3.1 gpio_u.c)
[3.2 gpio_u.h](#3.2 gpio_u.h)
[3.3 mian.c](#3.3 mian.c)
前言
本文基于上一章创建好的工程,来写一个GPIO中断加点灯的程序。对GPIO比较难理解的概念进行了梳理,开发板芯片是ESP32-P4、ESP-IDF版本是5.5.3。
一、ESP32-P4的GPIO
ESP32-P4共有104个管脚,主要分为四类:
- IO 管脚,也就是普通引脚。
- 专用接口管脚,只可用于特定外设,如 flash、MIPI DSI、MIPI CSI 等
- 模拟管脚,专用于模拟功能
- 电源管脚,为芯片组件和非电源管脚供电
GPIO中断加点灯的话使用IO 管脚即可,IO 管脚为GPIO0~GPIO54,共55个。这55个管脚除了普通的输出和输入电平功能,某些管脚还具有一些特殊功能。当然也可以不使用这些特殊功能,只充当普通管脚使用
LP GPIO
这是能在芯片深度休眠(Deep Sleep)时继续工作的特殊 GPIO 引脚,被称为低功耗管脚,普通 GPIO 在休眠时会断电、失效,只有 LP GPIO 能在休眠时保持功能。它是 ESP32 低功耗设计的关键。
功能:
- **休眠唤醒(最常用):**按键唤醒、传感器触发唤醒、外部电平变化唤醒
- 休眠时保持输出电平: 休眠时,LP GPIO 可以保持高 / 低电平不变
- 触摸传感器: ESP32 的触摸引脚全部都是 LP GPIO休眠时也能触摸唤醒。
- **ADC 模拟采集:**休眠时也能用 ADC 采集电压。
GPIO0~GPIO15都是LP GPIO。其中GPIO14、15可用于连接LP UART ,进行串口通信。

LP UART可由 HP 或 LP 外设使用,剩下的39个管脚称为高性能管脚(HP GPIO),只能由 HP外设使用。
模拟功能
这55个管脚都具有模拟功能,不过各自的模拟功能不同。下图是管脚全部的模拟功能。

ESP32-P4的GPIO0和GPIO1就一般使用XTAL功能,用来接入外部晶振,其他模拟功能的具体引脚映射去见芯片手册。
二、滤波器
2.1 迟滞过滤器
ESP32-P4 支持输入引脚的硬件迟滞,这可以减少由于输入电压在逻辑 0、1 临界值附近时采样不稳定造成的 GPIO 中断误触,尤其是当输入逻辑电平转换较慢,电平建立时间较长时。
每个引脚可以独立启用迟滞功能。默认情况下,它处于关闭状态。可以通过配置 gpio_config_t :: hys_ctrl_mode来选择启用与否。
2.2 毛刺滤波器
ESP32-P4 内置的毛刺过滤器可以过滤掉 GPIO 输入端口上的毛刺信号,在一定程度上避免错误触发中断或者是错把噪声当成有效的外设信号。
每个 GPIO 都可以使用独立的毛刺过滤器,但毛刺滤波功能仅在 HP GPIO 交换矩阵中可用。毛刺滤波器硬件支持八个通道,每个通道可从 GPIO 滤波器硬件输出的 55 个信号 (0 ~ 54) 中选择一个信号,进行二次滤波(第一次是迟滞过滤器 )。该毛刺滤波器硬件可用于对慢速信号进行滤波。
第一张图是HP GPIO交换矩阵局部图、第二张图是LP GPIO交换矩阵局部图。
可以发现,迟滞过滤器(GPIO Filter) 在两种矩阵中都具备,而**毛刺滤波器(Glitch Filter)**只在HP GPIO交换矩阵才具备。
毛刺滤波器分为管脚毛刺滤波器 和灵活毛刺滤波器
2.2.1 管脚毛刺滤波器
每个 GPIO 都可以使用独立的毛刺过滤器,该过滤器可以将那些脉冲宽度窄于 2 个采样时钟的信号剔除掉,该宽度无法配置。GPIO 对输入信号的采样时钟通常是 IO_MUX 的时钟源。
从时序图中可以看到,当信号产生跳变时,只有持续超过2个时钟周期,过滤器才会输出该信号的跳变。
代码实现流程如下:
cpp
gpio_pin_glitch_filter_config_t filter_config = {}; // 创建一个 gpio_pin_glitch_filter_config_t 结构体实例,并初始化为零
gpio_glitch_filter_handle_t filter_handle; // 定义一个 gpio_glitch_filter_handle_t 变量,用于存储创建的引脚毛刺滤波器的句柄
filter_config.gpio_num = GPIO_NUM_35; // 设置要过滤的 GPIO
filter_config.clk_src = GLITCH_FILTER_CLK_SRC_XTAL; // 设置毛刺滤波器的时钟源为 XTAL
gpio_new_pin_glitch_filter(&filter_config, &filter_handle); // 创建 GPIO35 的引脚毛刺滤波器,并将其句柄存储在 filter_handle 变量中
gpio_glitch_filter_enable(filter_handle); // 启用 GPIO35 的引脚毛刺滤波器
2.2.2 灵活毛刺滤波器
ESP32-P4 提供了 8 个灵活的毛刺过滤器,被过滤信号的脉冲宽度可以由软件进行配置。此类过滤器则称为 。每个过滤器都可以应用于任意 GPIO 输入,然而,将多个过滤器应用于同一 GPIO 上效果并不会叠加。
配置灵活毛刺过滤器的采样窗口宽度为value1,采样窗口阈值为value2。则在 value1+ 1 个周期内,如果存在 value2+ 1 个输入信号与当前输出信号值不匹配,毛刺滤波器硬件将反转输出信号。还可以将窗口宽度和窗口阈值配置为相同的值 value3,那么只有宽度大于 value3+ 1 个时钟周期的信号才会被采样
该灵活毛刺过滤器时序图的采样窗口宽度为3个时钟周期,采样窗口阈值为2个时钟周期,所以识别到T1前的4个时钟周期有3个周期输出高电平,与当前输出的低电平不匹配,输出反转;T2前的4个时钟周期有3个周期输出低电平,与当前输出的高电平不匹配,输出反转。
代码实现流程如下:
cpp
gpio_flex_glitch_filter_config_t flex_filter_config = {}; // 创建一个 gpio_flex_glitch_filter_config_t 结构体实例,并初始化为零
gpio_glitch_filter_handle_t flex_filter_handle; // 定义一个 gpio_glitch_filter_handle_t 变量,用于存储创建的灵活毛刺滤波器的句柄
flex_filter_config.gpio_num = GPIO_NUM_35; // 设置要过滤的 GPIO
flex_filter_config.clk_src = GLITCH_FILTER_CLK_SRC_XTAL; // 设置毛刺滤波器的时钟源为 XTAL
flex_filter_config.window_width_ns = 25* 4; // 设置采样窗口宽
flex_filter_config.window_thres_ns = 25* 3; // 设置采样窗口阈值
gpio_new_flex_glitch_filter(&flex_filter_config, &flex_filter_handle); // 创建 GPIO35 的灵活毛刺滤波器,并将其句柄存储在 flex_filter_handle 变量中
gpio_glitch_filter_enable(flex_filter_handle); // 启用 GPIO35 的灵活毛刺滤波器
注意:
在配置灵活毛刺过滤器的结构体中采样窗口宽度和采样窗口阈值的单位为纳秒,这时必须确定灵活毛刺过滤器使用的时钟周期值,窗口宽度和采样窗口阈值值不能低于这个周期值。比如说ESP-P4的XTAL时钟都是40MHz,那么一个周期就是25ns,如果设置的值低于25 ,代码就会报错。建议使用**(时钟周期*周期数)**的方式来赋值
三、代码编写
我创建了一个gpio_u的组件,工程结构如图:

3.1 gpio_u.c
cpp
#include <stdio.h>
#include "driver/gpio.h"
#include "driver/gpio_filter.h"
#include "gpio_u.h"
uint8_t gpio_isr_flag = 1;
void gpio35_isr_handler(void *arg);
void gpio_init(void)
{
esp_err_t gpio_flag;
gpio_config_t io_config = {}; // 创建一个 gpio_config_t 结构体实例,并初始化为零
io_config.pin_bit_mask = GPIO_BOOT; // 配置 GPIO35
io_config.mode = GPIO_MODE_INPUT; // 设置 GPIO 为输入模式
io_config.pull_up_en = GPIO_PULLUP_ENABLE; // 使能上拉电阻
io_config.pull_down_en = GPIO_PULLDOWN_DISABLE; // 禁用下拉电阻
io_config.intr_type = GPIO_INTR_POSEDGE; // 设置 GPIO35 的中断类型为上升沿触发
io_config.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; // 使能迟滞滤波器
gpio_config(&io_config); // 使用指定的设置配置 GPIO
io_config.pin_bit_mask = GPIO_LED; // 配置 GPIO7
io_config.mode = GPIO_MODE_OUTPUT; // 设置 GPIO7 为输出模式
io_config.intr_type = GPIO_INTR_DISABLE; // 禁用 GPIO7 的中断
gpio_config(&io_config); // 使用指定的设置配置 GPIO
// gpio_pin_glitch_filter_config_t filter_config = {}; // 创建一个 gpio_pin_glitch_filter_config_t 结构体实例,并初始化为零
// gpio_glitch_filter_handle_t filter_handle; // 定义一个 gpio_glitch_filter_handle_t 变量,用于存储创建的引脚毛刺滤波器的句柄
// filter_config.gpio_num = GPIO_NUM_35; // 设置要过滤的 GPIO
// filter_config.clk_src = GLITCH_FILTER_CLK_SRC_XTAL; // 设置毛刺滤波器的时钟源为 XTAL
// gpio_new_pin_glitch_filter(&filter_config, &filter_handle); // 创建 GPIO35 的引脚毛刺滤波器,并将其句柄存储在 filter_handle 变量中
// gpio_glitch_filter_enable(filter_handle); // 启用 GPIO35 的引脚毛刺滤波器
gpio_flex_glitch_filter_config_t flex_filter_config = {}; // 创建一个 gpio_flex_glitch_filter_config_t 结构体实例,并初始化为零
gpio_glitch_filter_handle_t flex_filter_handle; // 定义一个 gpio_glitch_filter_handle_t 变量,用于存储创建的灵活毛刺滤波器的句柄
flex_filter_config.gpio_num = GPIO_NUM_35; // 设置要过滤的 GPIO
flex_filter_config.clk_src = GLITCH_FILTER_CLK_SRC_XTAL; // 设置毛刺滤波器的时钟源为 XTAL
flex_filter_config.window_width_ns = 25* 4; // 设置采样窗口宽
flex_filter_config.window_thres_ns = 25* 3; // 设置采样窗口阈值
gpio_new_flex_glitch_filter(&flex_filter_config, &flex_filter_handle); // 创建 GPIO35 的灵活毛刺滤波器,并将其句柄存储在 flex_filter_handle 变量中
gpio_glitch_filter_enable(flex_filter_handle); // 启用 GPIO35 的灵活毛刺滤波器
gpio_flag = gpio_set_intr_type(GPIO_NUM_35, GPIO_INTR_POSEDGE); // 设置 GPIO35 的中断类型为上升沿触发
if (gpio_flag != ESP_OK) {
printf("Failed to set GPIO35 interrupt type\n");
}
gpio_flag = gpio_install_isr_service(0); // 安装 GPIO 中断服务
if (gpio_flag != ESP_OK)
{
printf("Failed to install GPIO ISR service\n");
}
gpio_isr_handler_add(GPIO_NUM_35, gpio35_isr_handler, NULL); // Add GPIO35 interrupt handler
gpio_intr_enable(GPIO_NUM_35); // Enable GPIO35 interrupt
gpio_dump_io_configuration(stdout, (GPIO_BOOT | GPIO_LED)); // Dump the configuration of GPIO35 and GPIO7 to the console
}
void gpio35_isr_handler(void *arg)
{
// 在这里处理 GPIO35 的中断事件
gpio_isr_flag ^= 1; // 切换标志位,表示中断事件发生
}
3.2 gpio_u.h
cpp
#ifndef __GPIO_U_H__
#define __GPIO_U_H__
#define GPIO_BOOT 1ULL << GPIO_NUM_35
#define GPIO_LED 1ULL << GPIO_NUM_7
void gpio_init(void);
extern uint8_t gpio_isr_flag;
#endif /* __GPIO_U_H__ */
3.3 mian.c
cpp
#include <stdio.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "gpio_u.h"
void app_main(void)
{
gpio_init();
while (1)
{
if (gpio_isr_flag) {
gpio_set_level(GPIO_NUM_7, 1); // 设置引脚为高电平,点亮 LED
vTaskDelay(pdMS_TO_TICKS(300)); // 任务延时 300 毫秒
gpio_set_level(GPIO_NUM_7, 0); // 设置引脚为低电平,熄灭 LED
vTaskDelay(pdMS_TO_TICKS(300)); // 再次任务延时 300 毫秒
}
else {
vTaskDelay(pdMS_TO_TICKS(100)); // 如果没有中断事件发生,任务延时 100 毫秒,避免占用过多 CPU 资源
}
}
}
3.4 GPIO中断
ESP32有两种GPIO中断初始化函数,gpio_install_isr_service与 gpio_isr_register**。**
- gpio_install_isr_service
(0):中断服务框架初始化,开启GPIO 中断公共服务,支持多个引脚共用中断,自动分发处理。 - gpio_isr_register
():底层原生中断注册,直接给 GPIO 模块注册一个全局中断函数,所有引脚中断都进这一个函数,需要自己手动判断哪个引脚触发。
使用gpio_install_isr_service
只要调用一次,会初始化整个 GPIO 中断系统,用 gpio_isr_handler_add() 给每个引脚单独绑中断函数。框架自动帮你判断哪个引脚触发、接着调用对应处理函数,处理后自动清除中断标志。我们只需要再中断函数写逻辑就行
使用gpio_isr_register
会直接向 CPU 注册 一个唯一的 GPIO 总中断函数,所有 GPIO 引脚触发中断都会进入这同一个函数,我们必须手动读取中断状态寄存器,判断哪个引脚触发了、上升沿还是下降沿,还要手动清除中断标志。
推荐使用gpio_install_isr_service()。
代码功能:
使用GPIO35作为按键输入引脚,启用上升沿中断并开启迟滞过滤器和灵活毛刺滤波器,GPIO7作为驱动LED亮灭的引脚。工程运行后,LED灯300ms亮灭闪烁。按下GPIO35后,工作停止,再按一下工作继续。
关于代码的具体实现不做讲解,因为逻辑简单,各函数的应用可参考《GPIO & RTC GPIO》