Generic GPIO 示例
示例说明
本示例展示如何初始化和配置 GPIO 以及如何使用 GPIO 中断检测输入信号变化并进行处理。学习该示例前,建议先掌握 FreeRTOS 队列的相关内容,其基础说明及 API 使用示例可参考 队列管理 文档。
运行方法
示例完整代码见 Generic GPIO 示例。运行前的配置说明、构建与烧录流程详见示例目录下的 README.md 文件。
如需自定义配置项及查看默认值,可参考 宏定义说明 部分。
头文件说明
本示例所使用的头文件涵盖了 FreeRTOS 任务管理、常用系统工具以及 GPIO 驱动等基础模块。为任务创建、GPIO 初始化与中断处理提供支持。
各头文件按功能分类如下:
FreeRTOS 任务调度 :提供任务管理与任务及通信功能。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
系统工具 :提供输入输出、字符串处理、内存管理以及定长整数类型支持。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
GPIO 驱动 :提供 GPIO 配置、读写电平以及中断处理等功能。
#include "driver/gpio.h"
宏定义说明
本示例中涉及的宏定义主要用于定义 GPIO 输入输出引脚以及中断标志位。
宏定义按功能分类如下:
GPIO 输入/输出引脚定义
使用配置宏定义 GPIO 输入输出引脚,其具体引脚编号可在
sdkconfig
文件查看。本示例中输入引脚对应 GPIO 4 和 GPIO 5;输出引脚对应 GPIO 8 和 GPIO 9。#define GPIO_INPUT_IO_0 CONFIG_GPIO_INPUT_0 #define GPIO_INPUT_IO_1 CONFIG_GPIO_INPUT_1 #define GPIO_OUTPUT_IO_0 CONFIG_GPIO_OUTPUT_0 #define GPIO_OUTPUT_IO_1 CONFIG_GPIO_OUTPUT_1
通过位移操作生成输入输出引脚选择掩码,用于一次性配置多个输入或输出引脚。
#define GPIO_INPUT_PIN_SEL ((1ULL<<GPIO_INPUT_IO_0) | (1ULL<<GPIO_INPUT_IO_1)) #define GPIO_OUTPUT_PIN_SEL ((1ULL<<GPIO_OUTPUT_IO_0) | (1ULL<<GPIO_OUTPUT_IO_1))
备注
配置宏以
CONFIG_
开头,由 ESP-IDF 构建系统根据menuconfig
设置自动生成,保存在sdkconfig
文件中。定义 GPIO 引脚时使用配置宏而非直接写入引脚数字,可以在编译时灵活修改引脚而无需修改源代码,便于项目扩展和管理。
如果想修改使用的引脚,只需修改
menuconfig
并再次构建/编译示例后会自动同步更新,无需手动更改代码。
中断配置标志 :用于指定中断服务的配置标志。本示例中将其定义为 0 时,表示使用默认配置,即不启用任何特殊中断选项(如边沿触发方式、共享中断等),适用于大多数常规 GPIO 中断场景。
#define ESP_INTR_FLAG_DEFAULT 0
任务函数说明
本示例为了实现 GPIO 中断功能,包含两个核心函数:中断服务函数(ISR)和处理 GPIO 事件的 FreeRTOS 任务函数。
中断服务函数(ISR)
gpio_isr_handler()
为中断服务函数,在 GPIO 引脚触发中断时被调用,负责快速响应 GPIO 中断并将信息传递给任务。函数使用 IRAM_ATTR
修饰,保证函数放在内部 RAM 中,提高中断响应速度。
在本示例中该函数的执行逻辑为:
将传入的参数
arg
保存为 GPIO 引脚编号gpio_num
,需要进行类型转换以正确使用该值。将
gpio_num
发送到 FreeRTOS 消息队列。API 说明可参考 发送数据。
需要注意,在 ISR 中应尽量只执行轻量操作,以保证中断响应速度和系统稳定性。
FreeRTOS 任务函数
gpio_task_example()
为 FreeRTOS 任务函数,负责在任务上下文中处理 GPIO 事件。与 ISR 配合,实现中断触发、队列传递、任务处理的机制,避免在 ISR 中执行耗时操作。
其执行逻辑为:
循环等待消息队列中的 GPIO 引脚编号(由 ISR 发送)。API 说明可参考 接收数据。
接收到编号后,获取引脚当前电平。
打印引脚编号和电平状态。
在此任务中,消息队列的等待时间设置为 portMAX_DELAY
,即无限等待,等待过程中任务将阻塞,从而节省 CPU 资源。
备注
PRIu32
是 C 标准库中定义的格式化宏,用于在 printf()
等函数中正确打印固定宽度的无符号 32 位整数,避免手动选择 %u
、%lu
等格式符,保证跨平台打印的一致性。
主函数说明
本示例演示了 GPIO 的初始化、输入输出配置、中断响应与处理、任务与队列协作,以及简单的输出控制和系统内存监控,体现了 ESP32 上 GPIO 与 FreeRTOS 协作的完整流程。
使用多引脚掩码,同时配置 GPIO18 和 GPIO19 为输出模式,禁用上拉下拉和中断。
使用多引脚掩码,同时配置 GPIO4 和 GPIO5 为输入模式,使能上拉,并设置为上升沿触发中断。
修改
GPIO_INPUT_IO_0
的中断触发模式为任意边沿触发。
该输入引脚在初始化时被配置为上升沿触发,但示例中两个输入引脚需要分别设置为上升沿触发和任意边沿触发,以便灵活响应不同的外部事件,同时保证信号的可靠性。
因此,需要调用
gpio_set_intr_type()
单独修改其中一个引脚的中断触发模式,需要传入引脚编号和新的中断类型作为参数。进一步介绍及参数说明可参考 普通 GPIO API。
创建 FreeRTOS 队列,用于在 GPIO 中断服务程序和任务之间传递事件。有关 FreeRTOS 队列的详细介绍和 API 使用示例可参考 队列管理 文档以及 动态创建队列 API。
创建
gpio_task_example
任务,负责从队列中接收并处理 GPIO 中断事件。有关 FreeRTOS 任务创建的详细介绍和 API 使用示例可参考 任务创建和删除 文档以及 动态创建任务 API。
安装中断服务,传入已定义的中断配置标志。
将中断服务函数
gpio_isr_handler()
分别绑定到两个输入引脚,传递给 ISR 的数据为对应的 GPIO 引脚编号,用于在 ISR 内识别是哪个引脚触发了中断。移除并重新绑定
GPIO_INPUT_IO_0
的中断服务,演示如何动态管理 ISR,可用于临时移除某个引脚的中断响应或替换 ISR。
打印系统运行过程中可用最小堆内存:
用于监控 FreeRTOS 任务和应用的内存使用情况,评估程序的内存占用峰值,帮助避免因堆内存不足而导致的任务崩溃或异常。
调用
esp_get_minimum_free_heap_size()
获取自系统启动以来的最小堆内存剩余量(单位为字节)。进一步说明可参考 杂项系统 API。
进入无限循环,每次循环执行以下操作:
打印当前计数值
cnt
并自增。延时 1 秒,使任务以固定周期执行,同时释放 CPU 给其他任务。
调用
gpio_set_level()
控制两个输出引脚GPIO_OUTPUT_IO_0
和GPIO_OUTPUT_IO_1
的电平状态,使其按计数值的奇偶交替输出高低电平。进一步说明可参考 普通 GPIO API。
程序现象
代码执行结果如下:
Minimum free heap size: 388708 bytes
cnt: 0
cnt: 1
GPIO[4] intr, val: 1
GPIO[5] intr, val: 1
cnt: 2
GPIO[4] intr, val: 0
cnt: 3
GPIO[4] intr, val: 1
GPIO[5] intr, val: 1
cnt: 4
GPIO[4] intr, val: 0
cnt: 5
GPIO[4] intr, val: 1
GPIO[5] intr, val: 1
执行流程:
完成各模块初始化,包括 GPIO 配置、ISR 安装和任务创建。
gpio_task_example
任务启动后,阻塞等待消息队列中的事件信号。在主循环中改变输出 GPIO 电平,触发输入 GPIO 引脚的中断。
对应输入引脚触发中断时,ISR 被调用,将触发的 GPIO 编号发送到消息队列。
阻塞的
gpio_task_example
任务接收到队列信号后,读取引脚电平并打印事件信息。
在该示例中,输出引脚通过软件控制改变电平,其信号通过电路连接传递到输入引脚,输入引脚检测到电平变化后触发中断,并由 ISR 将事件发送到任务进行处理和打印。由于两个输入 GPIO 引脚配置了不同的中断触发模式(一个上升沿触发,一个任意边沿触发),虽然电平状态同步改变,但触发中断的条件不同,因此任务接收到的事件顺序和打印结果也有所差异。这一现象直观体现了不同中断模式对程序行为和事件响应顺序的影响。