运行效果:点击按键0,输出按键0的电平

1.关键概念
2.运行流程
2.1gpio配置
gpio配置主要分为四步
- gpio参数配置
- 初始化配置参数
- 使能中断
- 给指定GPIO端口注册对应的中断函数
2.1.1gpio参数配置(通过gpio_config_t结构体配置属性)
esp32 gpio参数配置主要配置如下五项
- 目标引脚
- gpio工作模式
- 是否启用上拉电阻
- 是否启用下拉电阻
- 中断触发模式

2.1.2初始化gpio配置参数gpio_config
参数配置结构体配置好以后,使用gpio_config初始化配置参数
2.1.3使能中断gpio_install_isr_service
使能中断以后才可以配置中断函数等
| 宏定义 | 说明 |
|---|---|
0 | 默认配置 |
ESP_INTR_FLAG_LEVEL1 ~ ESP_INTR_FLAG_LEVEL5 | 指定中断优先级 |
ESP_INTR_FLAG_IRAM | 将 ISR 放入 IRAM,加快响应速度(适合深睡唤醒) |
ESP_INTR_FLAG_SHARED | 允许中断共享 |
ESP_INTR_FLAG_EDGE | 明确声明为边沿触发 |
ESP_INTR_FLAG_LOWMED / HIGH | 控制中断分配策略 |
2.1.4配置中断函数gpio_isr_handle_add
给指定GPIO口注册对应的中断函数,并指定函数参数gpio_isr_handler_add(ESP32的中断函数是根据io编号进行绑定的)
| 参数 | 类型 | 说明 |
|---|---|---|
gpio_num | gpio_num_t | 要绑定中断的引脚编号(如 GPIO_NUM_0) |
isr_handler | gpio_isr_t | 中断回调函数指针 |
args | void* | 用户自定义参数,在回调函数中原样返回 |
注意
- 回调函数格式必须为void IRAM_ATTR gpio_isr_handler(void* arg),IRAM_ATTR 表示函数放在 IRAM(指令 RAM)中执行,避免因 Flash 访问延迟而丢失中断响应。
- 未执行上一步使能对应gpio口时会返回错误ESP_ERR_INVALID_STATE
2.2 freertos线程任务配置(和中断信息进行交互)
2.2.1 创建缓存队列xQueueCreate
该处通过缓存与中断响应函数进行数据交互
常用接口:
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
| 参数 | 说明 |
|---|---|
uxQueueLength | 队列最大消息数 |
uxItemSize | 每个消息元素的大小(字节) |
2.2.2 创建rtos线程任务xTaskCreate
xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);
| 参数 | 类型 | 说明 |
|---|---|---|
pvTaskCode | TaskFunction_t | 要执行的任务函数,必须是 void func(void *param) 格式 |
pcName | const char* | 任务名称(用于调试) |
usStackDepth | uint32_t | 栈大小,单位为 4 字节 |
pvParameters | void* | 传递参数 |
uxPriority | UBaseType_t | 任务优先级(数值越大越高) |
pxCreatedTask | TaskHandle_t* | 返回任务句柄(可为 NULL) |
2.2.3 常用其他相关接口
1.中断服务发送数据到缓存队列
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
| 参数 | 含义 |
|---|---|
xQueue | 队列句柄 |
pvItemToQueue | 指向要发送的数据的指针 |
pxHigherPriorityTaskWoken | 若唤醒更高优先级任务则置为 pdTRUE(可为 NULL) |
返回结果:
- pdPASS:数据成功放入队列
- errQUEUE_FULL:队列满,无法放入数据
2.从队列中获取数据
xQueueReceive(gpio_evt_queue, &gpio_num, portMAX_DELAY);
| 参数 | 含义 |
|---|---|
xQueue | 队列句柄 |
pvBuffer | 用于接收数据的缓冲区指针 |
xTicksToWait | 阻塞时间(单位:tick) |

返回值:
- pdPASS:成功接收到数据
- errQUEUE_EMPTY:队列为空且等待时间超时
2.3 其他相关接口
1.获取引脚电平结果
int gpio_get_level(gpio_num_t gpio_num);
3 代码示例
main.c
#include <stdio.h>
#include <inttypes.h>
#include <driver/gpio.h>
#include <hal/gpio_types.h>
#include <esp_attr.h>
#include "freertos/FreeRTOS.h" // 要使用队列queue,必须先includeFreeRTOS.h
#include <freertos/queue.h>
/*
错误点总结:
①gpio_isr_handler函数中不能直接将arg参数传递给xQueueSendFromISR,因为主函数中添加中断函数时传递的GPIO_NUM_0是一个枚举值,地址中内容为0
*/
QueueHandle_t gpio_evt_queue = NULL;
void IRAM_ATTR gpio_isr_handler(void* arg){
uint32_t gpio_num = (uint32_t)arg; // 将传入参数GPIO_NUM_0从void*转回实际数据类型uint32_t
// xQueueSendFromISR(gpio_evt_queue, arg, NULL); // arg是一个指针,其保存的地址为GPIO_NUM_0(即地址0),解析的时候会被当做空指针处理
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL); // 将端口号保存到队列中
}
void gpio_task_example(void* arg){
uint32_t gpio_num;
while(1){
xQueueReceive(gpio_evt_queue, &gpio_num, portMAX_DELAY); // 获取队列的值放到io_num变量中,portMAX_DELAY表示为空时一直等待
printf("GPIO[%lu] intr, val: %d\n", gpio_num, gpio_get_level(gpio_num)); // 无符号整数占位使用lu
}
}
void app_main(void)
{
// 1.配置gpio
gpio_config_t key_config = {
.intr_type = GPIO_INTR_NEGEDGE,
.mode = GPIO_MODE_INPUT,
.pin_bit_mask = 1 << GPIO_NUM_0,
.pull_down_en = 1,
.pull_up_en = 0,
};
// 2.初始化GPIO配置
gpio_config(&key_config);
// 3.初始化消息队列,容量为10个int32_t数据类型
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
// 4.创建线程输出队列内容
xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);
// 5.使能定时器中断
gpio_install_isr_service(0);
// 6.指定IO口,配置定时器中断函数
gpio_isr_handler_add(GPIO_NUM_0, gpio_isr_handler, (void *)GPIO_NUM_0);
}
4.问题总结
1.中断回调函数中IRAM_ATTR作用
- RAM_ATTR 是 ESP-IDF 中的一个函数属性宏,全称是 “Instruction RAM Attribute”,用于告诉编译器 将该函数放入 ESP32 的 IRAM(指令 RAM)中执行,而不是默认的 flash。
- ESP32 正常情况下大多数代码是从 Flash(外部 SPI Flash)中运行的。但当发生中断时,如果中断服务函数(ISR)所在代码段仍在 Flash 中,而此时恰好 Flash 被其他操作占用(比如写 Flash 或 WiFi 使用 SPI Flash),会导致中断函数无法及时执行,从而造成 系统崩溃或响应延迟。
2.队列声明QueueHandle_t不用指定存放数据类型吗?
freertos中队列是根据元素大小(字节进行存储的),类型安全与数据准确性由自己保证
3.为什么main函数运行完以后没有直接退出
app_main只是freertos调度任务中的一个,就算结束以后freertos还是一直进行调度(例如app_main线程中通过xTaskCreate创建的新的线程任务)
4.队列使用报错

使用queue.h前必须include FreeRTOS.h头文件

5.读写缓存队列时,怎么知道读写数据的大小
创建队列时指定了每一个元素的大小
如下每一个元素大小为uint32_t,即四个字节
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
5813

被折叠的 条评论
为什么被折叠?



