ESP-IDF教程2 GPIO - 输入、输出和中断
1、前提
1.1、基础知识
1.1.1、GPIO 分类
ESP32 系列芯片按照 GPIO 特殊的使用限制分类,可以将其分为如下几类:
- GPIO PIN
- GPI PIN
- Strapping PIN
- SPI FLASH PIN
- 特殊功能引脚
GPIO 引脚
表示通用的输入输出引脚,无使用限制,可以随意使用。
GPI 引脚
表示仅支持输入的引脚,不具备软件使能的上拉或下拉功能,谨慎使用。
Strapping 引脚
用于 ESP32 的启动配置项,ESP32 启动时需要配置如启动模式、内置 LDO 电压、U0TXD 打印使能等参数,这些参数需要芯片在上电或硬件复位时通过 Strapping 管脚采样其电平并锁存来决定,但当采样结束后 Strapping 引脚将恢复为普通 GPIO ,谨慎使用。
SPI FLASH 引脚
因为大部分 ESP32 芯片封装内部无 FLASH 存储,因此需要依赖外部 FLASH 芯片存储程序和数据,SPI FLASH 引脚就是用于 ESP32 FLASH 通信使用的,共有 4-6 个引脚,不要使用这些引脚。
特殊功能引脚
JTAG 和 UART0 这类专用于某一功能的引脚,除非不使用该特殊功能,否则不建议将其作为普通 GPIO 使用
1.1.2、FALSH SPI 模式
根据通信不同阶段使用的引脚数不同,ESP32 的 FALSH SPI 模式有如下四种:
- QIO
- QOUT
- DIO
- DOUT
四种 FLASH SPI 模式的具体区别如 SPI Flash Modes / Summary 所示,其中 QIO 和 QOUT 需要全部的 6 个 SPI FLASH 引脚,而 DIO 和 DOUT 仅需要其中的 4 个引脚,剩下未使用的引脚可做普通 GPIO 使用。
1.1.3、过滤器
ESP32 某些系列芯片支持 GPIO 过滤器,有如下两种:
- 毛刺过滤器
- 管脚毛刺过滤器(pin_glitch_filter)
- 灵活毛刺过滤器(flex_glitch_filter)
- 迟滞过滤器
管脚毛刺过滤器 pin_glitch_filter
仅能将脉冲宽度窄于 2 个采样时钟的信号剔除掉,采样时钟默认为 80MHz 的
SOC_MOD_CLK_APB宏,因此过滤宽度 = 2 × (1/80 MHz) = 25 ns 。
灵活毛刺过滤器 flex_glitch_filter
会在
window_width_ns时间窗口内,对信号采样多次,若某次“高”或“低”持续时间小于window_thres_ns,则当作毛刺丢弃,该功能仅部分型号支持,比如 ESP32-C6、ESP32-H2 和 ESP32-P4 ,笔者尚未测试。
管脚毛刺过滤器和灵活毛刺过滤器是各自独立的,支持为同一 GPIO 同时启用这两种过滤器。
GPIO 迟滞过滤器
“支持输入引脚的硬件迟滞,可以减少由于输入电压在逻辑 0、1 临界值附近时采样不稳定造成的 GPIO 中断误触”,看起来或许可以作为按键消抖使用,该功能仅部分型号支持,比如 ESP32-C5、ESP32-H2 和 ESP32-P4 ,笔者尚未测试。
1.1.4、外部中断
ESP32 的 GPIO 支持如下类型的几种中断:
typedef enum {
GPIO_INTR_DISABLE = 0, /* 不使用中断*/
GPIO_INTR_POSEDGE = 1, /* 上升沿触发 */
GPIO_INTR_NEGEDGE = 2, /* 下降沿触发 */
GPIO_INTR_ANYEDGE = 3, /* 边沿(上升沿/下降沿)触发 */
GPIO_INTR_LOW_LEVEL = 4, /* 低电平触发 */
GPIO_INTR_HIGH_LEVEL = 5, /* 高电平触发 */
GPIO_INTR_MAX,
} gpio_int_type_t;
与 STM32 逻辑开发环境的不同, ESP-IDF 从上到下已经有很多层,如下所示:
- APP
- Components
- HAL
- FreeRTOS
- FLASH BootLoader
- ROM BootLoader
- SoC
某些情况下,ESP32 的 FALSH 访问会被中断或者锁住,为确保中断服务函数能在任何时候可靠运行,要求中断服务函数满足以下两点要求:
- 纯粹运行在内部 RAM(IRAM)中
- 不访问 FLASH 中的数据或代码
使用属性宏 IRAM_ATTR 来告诉编译器,该函数必须放到 IRAM 中执行,外部中断服务函数模板如下:
static void IRAM_ATTR exti_isr_handler(void* arg) {
//...
}
printf 函数存储在 FLASH 中,因此不要在中断中使用,如果非要使用请用以下函数代替:
#include <rom/ets_sys.h>
int ets_printf(const char *fmt, ...);
此外,不能在中断中执行能引起上下文切换的函数和中断不安全的函数,建议在中断中利用 FreeRTOS 的队列、事件组等任务间通信机制来传递信息。
中断在分配时有一些标志位 intr_alloc_flags ,这些标志位控制着 ISR 的行为,主要有如下一些标志位:
#define ESP_INTR_FLAG_LEVELx (1<<x) /* 中断优先级,x 从 1 到 6 */
#define ESP_INTR_FLAG_NMI (1<<7) /* 最高中断优先级 7 */
#define ESP_INTR_FLAG_SHARED (1<<8) /* 共享中断 */
#define ESP_INTR_FLAG_EDGE (1<<9) /* 边沿触发中断 */
#define ESP_INTR_FLAG_IRAM (1<<10) /* 将中断处理函数放在 IRAM 中,避免访问 FLASH */
#define ESP_INTR_FLAG_INTRDISABLED (1<<11) /* ISR 安装时默认不使能中断 */
#define ESP_INTR_FLAG_LOWMED (ESP_INTR_FLAG_LEVEL1|ESP_INTR_FLAG_LEVEL2|ESP_INTR_FLAG_LEVEL3) /* 低中等级别优先级 */
#define ESP_INTR_FLAG_HIGH (ESP_INTR_FLAG_LEVEL4|ESP_INTR_FLAG_LEVEL5|ESP_INTR_FLAG_LEVEL6|ESP_INTR_FLAG_NMI) /* 高等级优先级 */
使用 GPIO 外部中断时一般只需以下两步即可:
gpio_install_isr_servicegpio_isr_handler_add
1.2、数据结构
1.2.1、GPIO
ESP-IDF 驱动头文件
#include "driver/gpio.h"
GPIO 配置结构体
typedef struct {
uint64_t pin_bit_mask; /* GPIO 引脚掩码 */
gpio_mode_t mode; /* GPIO 模式 */
gpio_pullup_t pull_up_en; /* GPIO 上拉使能 */
gpio_pulldown_t pull_down_en; /* GPIO 下拉使能 */
gpio_int_type_t intr_type; /* GPIO 中断类型*/
#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER
gpio_hys_ctrl_mode_t hys_ctrl_mode; /* GPIO 迟滞控制模式 */
#endif
}gpio_config_t;
1.2.2、毛刺过滤器
ESP-IDF 驱动头文件
#include "driver/gpio_filter.h"
管脚毛刺滤波器 配置结构体
typedef struct {
glitch_filter_clock_source_t clk_src; /* 时钟源 */
gpio_num_t gpio_num; /* 引脚号 */
}gpio_pin_glitch_filter_config_t;
灵活毛刺滤波器 配置结构体
typedef struct {
glitch_filter_clock_source_t clk_src; /* 时钟源 */
gpio_num_t gpio_num; /* 引脚号 */
uint32_t window_width_ns; /* 采样窗口宽度(ns) */
uint32_t window_thres_ns; /* 采样窗口阈值(ns) */
} gpio_flex_glitch_filter_config_t;
1.3、硬件原理图
笔者使用了合宙的 ESP32-C3 开发板,本文仅涉及 KEY 和 LED 两部分硬件,KEY 和 LED 部分的硬件原理图如下图所示:


其中两个 LED 灯 D4 和 D5 分别由 ESP32-C3 的 GPIO12 和 GPIO13 控制,当引脚输出高电平时对应的 LED 灯会被点亮。
ESP32-C3 的 GPIO9 被 10K 的电阻 R4 上拉至高电平,当按键 S2 按下时,GPIO9 被拉低至地,因此可以通过 GPIO9 输入获取按键的状态。
2、示例代码
2.1、GPIO 输出 - 点亮 LED 灯
功能:
- 固定每隔 1s 翻转一次 LED 灯的状态
程序流程如下:
- 使用
gpio_config配置 LED 引脚为 输出模式 - 通过
gpio_set_level固定周期设置引脚输出的高低电平
具体示例程序如下:
#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#define LED_GPIO_PIN GPIO_NUM_13
/* 以下内容无需修改 */
uint8_t led_pin_level = 0;
void led_init(void) {
gpio_config_t io_cfg = {
.pin_bit_mask = 1 << LED_GPIO_PIN,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = 0,
.pull_down_en = 0,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config( & io_cfg);
}
void app_main(void) {
led_init();
while (1) {
led_pin_level = !led_pin_level;
gpio_set_level(LED_GPIO_PIN, led_pin_level);
vTaskDelay(1000);
}
}
2.2、GPIO 输入 - 按键响应
功能:
- 按键 KEY 按下翻转 LED 状态
程序流程如下:
- 使用
gpio_config配置 LED 引脚为 输出模式 - 使用
gpio_config配置 KEY 引脚为 输入模式 - 通过
gpio_get_level获得 KEY 引脚输入的高低电平 - 当 KEY 引脚输入的电平为低电平时表示按键按下,通过
gpio_set_level翻转 LED 引脚输出电平
具体示例程序如下:
#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#define KEY_GPIO_PIN GPIO_NUM_9
#define LED_GPIO_PIN GPIO_NUM_13
/* 以下内容无需修改 */
uint8_t led_pin_level = 0;
void led_init(void) {
gpio_config_t io_cfg = {
.pin_bit_mask = 1 << LED_GPIO_PIN,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = 0,
.pull_down_en = 0,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config( & io_cfg);
}
void key_init(void) {
gpio_config_t io_cfg = {
.pin_bit_mask = 1 << KEY_GPIO_PIN,
.mode = GPIO_MODE_INPUT,
.pull_up_en = 1,
.pull_down_en = 0,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config( & io_cfg);
}
void app_main(void) {
led_init();
key_init();
while (1) {
if (gpio_get_level(KEY_GPIO_PIN) == 0) {
vTaskDelay(5);
if (gpio_get_level(KEY_GPIO_PIN) == 0) {
gpio_set_level(LED_GPIO_PIN, led_pin_level);
led_pin_level = !led_pin_level;
while (!gpio_get_level(KEY_GPIO_PIN)) {};
}
}
vTaskDelay(10);
}
}
2.3、GPIO 外部中断 - 按键响应
功能:
- 按键按下翻转 LED 状态
程序流程如下:
- 使用
gpio_config配置 LED 引脚为 输出模式 - 使用
gpio_config配置 KEY 引脚为 输入下降沿中断模式 - 注册并使能 KEY 引脚中断
- 按键 KEY 按下触发下降沿中断,进入中断服务子程序,发送事件到队列
- 当检测到队列非空时,通过
gpio_set_level翻转 LED 灯状态
具体示例程序如下:
#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#define KEY_GPIO_PIN GPIO_NUM_9
#define LED_GPIO_PIN GPIO_NUM_13
/* 以下内容无需修改 */
static uint8_t led_pin_level = 0;
static QueueHandle_t key_event_queue = NULL;
static void IRAM_ATTR exti_isr_handler(void * arg) {
uint32_t gpio_num = (uint32_t) arg;
BaseType_t hp = pdFALSE;
xQueueSendFromISR(key_event_queue, & gpio_num, & hp);
if (hp) portYIELD_FROM_ISR();
}
void led_init(void) {
gpio_config_t io_cfg = {
.pin_bit_mask = 1 << LED_GPIO_PIN,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = 0,
.pull_down_en = 0,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config( & io_cfg);
}
void key_init(void) {
gpio_config_t io_cfg = {
.pin_bit_mask = 1 << KEY_GPIO_PIN,
.mode = GPIO_MODE_INPUT,
.pull_up_en = 1,
.pull_down_en = 0,
/* edge */
.intr_type = GPIO_INTR_NEGEDGE,
};
gpio_config( & io_cfg);
/* Register a interrupt */
gpio_install_isr_service(ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM);
/* Add exti callback function to key gpio */
gpio_isr_handler_add(KEY_GPIO_PIN, exti_isr_handler, (void * ) KEY_GPIO_PIN);
}
static void key_task(void * arg) {
uint32_t io_num;
while (1) {
if (xQueueReceive(key_event_queue, & io_num, portMAX_DELAY)) {
// 消抖
vTaskDelay(10);
if (gpio_get_level(io_num) == 0) {
// 抬手检测
while (gpio_get_level(io_num) == 0) {
vTaskDelay(10);
}
gpio_set_level(LED_GPIO_PIN, led_pin_level);
led_pin_level = !led_pin_level;
}
}
vTaskDelay(10);
}
// Will not execute
gpio_uninstall_isr_service();
}
void app_main(void) {
/* Configure the peripheral */
led_init();
key_init();
/* Create event queue */
key_event_queue = xQueueCreate(10, sizeof(uint32_t));
xTaskCreate(key_task, "key_task", 2048, NULL, 10, NULL);
}
3、常用函数
/* GPIO 配置函数 */
esp_err_t gpio_config(const gpio_config_t *pGPIOConfig)
/* 设置 GPIO 输出电平 */
esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level)
/* 获取 GPIO 输入电平 */
int gpio_get_level(gpio_num_t gpio_num);
/* 注册 GPIO 中断服务 */
esp_err_t gpio_install_isr_service(int intr_alloc_flags)
/* 添加 GPIO 中断服务函数 */
esp_err_t gpio_isr_handler_add(gpio_num_t gpio_num, gpio_isr_t isr_handler, void *args)
/* GPIO 中断使能 */
esp_err_t gpio_intr_enable(gpio_num_t gpio_num)
4、烧录验证
“GPIO 输出 - 点亮 LED 灯” 实验效果如下:

可能由于 GIF 帧率原因 LED 看起来不是以固定周期闪烁,因此使用逻辑分析仪捕获了 LED 引脚的电平,证明了其确实是 1s 翻转一次 LED 状态,结果如下所示:

“GPIO 输入 - 按键响应” 和 “GPIO 外部中断 - 按键响应” 实验效果一样,如下所示:

ESP-IDF教程2 GPIO - 输入、输出和中断的更多相关文章
- python 教程 第十章、 输入/输出
第十章. 输入/输出 1) 文件 poem = '''Programming is fun use Python!''' f = file('poem.txt', 'w') # open for ...
- Java输入/输出教程
Java输入/输出(I/O)处理从源读取数据并将数据写入目标.通常,读取存储在文件中的数据或使用I/O将数据写入到文件中. java.io和java.nio包中包含处理输入/输出的Java类.java ...
- 第12章 GPIO输入—按键检测
第12章 GPIO输入—按键检测 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/fi ...
- 第12章 GPIO输入-按键检测—零死角玩转STM32-F429系列
第12章 GPIO输入—按键检测 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/fi ...
- Shell test 命令,Shell 输入/输出重定向
一.Shell test 命令 Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值.字符和文件三个方面的测试. 数值测试 参数 说明 -eq 等于则为真 -ne 不等于则为真 -g ...
- 十一、Shell 输入/输出重定向
Shell 输入/输出重定向 大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回到您的终端.一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端.同样,一个命令 ...
- Shell(五)Shell输入/输出重定向
Shell 输入/输出重定向 大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回到您的终端.一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端.同样,一个命令 ...
- ESP8266 SDK开发: 外设篇-GPIO输入检测
前言 官方提供了以下函数检测引脚输入状态 检测GPIO5 if( GPIO_INPUT_GET(5) == 0 ) GPIO5当前为低电平 if( GPIO_INPUT_GET(5) == 1 ) G ...
- C语言笔记 09_共用体&typedef&输入|输出
共用体 共用体允许您在相同的内存位置存储不同的数据类型.您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值.共用体提供了一种使用相同的内存位置的有效方式. 定义共用体 为了定义共用体, ...
- C ++基本输入/输出
C ++基本输入/输出 本文将学习如何使用cin对象从用户那里获取输入,并使用cout对象在示例的帮助下向用户显示输出. C ++输出 在C ++中,cout将格式化的输出发送到标准输出设备,例如屏幕 ...
随机推荐
- Assignment pg walkthrough Easy 通配符提权变种
nmap 扫描 ┌──(root㉿kali)-[~] └─# nmap -p- -A 192.168.157.224 Starting Nmap 7.94SVN ( https://nmap.org ...
- 「CF 123E」Maze
传送门 题意澄清 对于 dfs 遍历时,在某一个点进入子树的顺序并不是按输入顺序,而是假定随机选择未进入过的子树 (这纠结了我好久) . 破题思路 首先可以明确这题不能推一个 \(O(1)\) 的式子 ...
- SQL查询语句中for update使用注意事项
1.join查询语句中,适用的情况下,尽量使用of关键字对必要的表上锁,而不是锁定所有表的相关行. 上述代码是在门诊医嘱签名时,为了处方签名重复操作,在签名修改数据前对涉及医嘱行进行上锁处理,for ...
- .NET周刊【1月第3期 2025-01-19】
国内文章 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(一):从.NET IoT入门开始 https://www.cnblogs.com/GreenShade/p/1866 ...
- EMR集群信息查看-Hive
一.日志 1.hivemetastore日志 简介:查看运行情况,其它组件会通过hivemetastore获取表信息 tail -f /data/emr/hive/logs/hadoop-hiveme ...
- Hetao P1156 最大战力 题解 [ 绿 ][ 二分 ][ 最大子段和 ]
最大战力 Vjudge 原题 题解 形式化题意 给定两个数组 \(a[n]\) 和 \(b[n]\) ,需要在数组 \(b\) 中选择一个区间 \(b[l,r]\) ,替换掉区间 \(a[l,r]\) ...
- 传国玉玺易主,ai.com竟然跳转到国产AI
一.震惊!输入ai.com网址竟然见证历史 今天我在地址栏随手敲了个ai.com,结果网页"唰"地一下--居然跳到了国产AI新贵DeepSeek的官网!这感觉就像在胡同口买煎饼,结 ...
- JUC并发-4.wait和notify以及Atomic原理
大纲 1.wait()与notify()实现一个简易的内存队列 2.wait()与notify()的底层原理 3.分布式存储系统NameNode机制介绍 4.分布式存储系统的edits log机制介绍 ...
- Magnet AXIOM使用+2024獬豸杯实战
Magnet AXIOM+2024獬豸杯实战 百度网盘链接 2024獬豸杯:https://pan.baidu.com/s/1t_6Fwl6RgmEtF0UXRfVD1A?pwd=j583#list/ ...
- 盘点10个.NetCore实用的开源框架项目
连续分享.Net开源项目快3个月了,今天我们一起梳理下10个,比较受到大家欢迎的.NetCore开源框架项目. 更多开源项目,可以查看我创建的,.Net开源项目榜单! 一个专注收集.Net开源项目的榜 ...