ESP32 IDF开发 驱动篇⑤GPIO及外部中断讲解
- 1、前言
- 2、原理
- 3、相关函数
- 4、软件设计
- 5、实例分析
- 6、以下是调试的结果:
别迷路-导航栏
快速导航找到你想要的(文章目录)
此篇文章如果对你有用,请点赞收藏,您的支持就是博主坚持的动力。
1、前言
本章节将介绍esp32硬件相关操作,及简单介绍FreeRTOS API在IDF中的简单应用,FreeRTOS的详细讲解请参考【安富莱】FreeRTOS操作系统教程:http://www.armbbs.cn/forum.php?mod=viewthread&tid=17658
本章学习目的:
①学习 ESP32 GPIO输入/输出的配置
②掌握ESP32 GPIO 中断API的使用方法
③学习FreeRTOS API的任务创建及消息队列
2、原理
ESP32 的 GPIO 接口介绍:
ESP32芯片具有40个物理GPIO引脚。 某些GPIO引脚无法使用或芯片封装上没有相应的引脚(请参阅技术参考手册)。 每个引脚都可以用作通用I / O或可以连接到内部外围信号。请注意,GPIO6-11通常用于SPI闪存(不可使用)。
GPIO34-39只能设置为输入模式,没有软件上拉或下拉功能。提供了单独的“ RTC GPIO”支持,当GPIO路由到“ RTC”低功耗和模拟子系统时,该功能便起作用。 当处于深度睡眠状态,运行超低功耗协处理器或使用ADC / DAC /等模拟功能时,可以使用这些引脚功能。
3、相关函数
有关GPIO相关函数请参考driver/include/driver/gpio.h
①、gpio_config GPIO通用配置。
/***********************************************************************
* 函数: gpio_config
* 描述: 配置GPIO的模式,上拉,下拉,IntrType
* 参数:pGPIOConfig:指向GPIO配置结构的指针
* 返回: ESP_OK成功,ESP_ERR_INVALID_ARG参数错误
* 备注:
************************************************************************/
esp_err_t gpio_config(const gpio_config_t *pGPIOConfig)
②、gpio_reset_pin 将gpio重置为默认状态
/***********************************************************************
* 函数: gpio_reset_pin
* 描述: 将gpio重置为默认状态(选择gpio功能,启用上拉并禁用输入和输出)。
* 参数: gpio_num:GPIO号。
* 返回: 始终返回ESP_OK。
* 备注:此功能还将此引脚的IOMUX配置为GPIO功能,并断开通过GPIO矩阵配置的任何其他外设输出。
************************************************************************/
esp_err_t gpio_reset_pin(gpio_num_t gpio_num)
③、gpio_set_intr_type 设置中断触发类型
/***********************************************************************
* 函数: gpio_set_intr_type
* 描述: 设置中断触发类型
* 参数: gpio_num:GPIO号 ,intr_type中断类型,从gpio_int_type_t中选择
* 返回: ESP_OK成功 ,ESP_ERR_INVALID_ARG参数错误
* 备注:
************************************************************************/
esp_err_t gpio_set_intr_type(gpio_num_t gpio_num, gpio_int_type_t intr_type);
④、esp_err_t gpio_intr_enable(gpio_num_t gpio_num); 启用GPIO模块中断信号
⑤、esp_err_t gpio_intr_disable(gpio_num_t gpio_num); 禁用GPIO模块中断信号
⑥、gpio_set_level(gpio_num_t gpio_num, uint32_t level); GPIO设置输出电平
/***********************************************************************
* 函数:
* 描述: GPIO设置输出电平
* 参数: gpio_num:GPIO号 ,level 0:低; 1:高
* 返回: ESP_OK成功 ,ESP_ERR_INVALID_ARG参数错误
* 备注:
************************************************************************/
esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level);
⑦、gpio_get_level(gpio_num_t gpio_num); GPIO获取输入电平
/***********************************************************************
* 函数:
* 描述: GPIO获取输入电平
* 参数: gpio_num:GPIO号
* 返回: 0 GPIO输入级别为0 ,1 GPIO输入级别为1
* 备注:
************************************************************************/
int gpio_get_level(gpio_num_t gpio_num);
⑧、esp_err_t gpio_set_direction(gpio_num_t gpio_num, gpio_mode_t mode); 配置GPIO方向,例如output_only,input_only,output_and_input
⑨、esp_err_t gpio_set_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull); 配置GPIO上拉/下拉电阻
⑩、gpio_install_isr_service安装驱动程序的GPIO ISR处理程序服务
/***********************************************************************
* 函数:
* 描述: 安装驱动程序的GPIO ISR处理程序服务,该服务允许每针GPIO中断处理程序。
* 参数: intr_alloc_flags:用于分配中断的标志
* 返回: *-ESP_OK成功
*-ESP_ERR_NO_MEM没有内存可安装此服务
*-已安装ESP_ERR_INVALID_STATE ISR服务。
*-ESP_ERR_NOT_FOUND找不到带有指定标志的空闲中断
*-ESP_ERR_INVALID_ARG GPIO错误
* 备注:此函数与gpio_isr_register()不兼容-如果使用该函数,则为所有GPIO中断注册一个全局ISR。
* 如果使用此功能,则ISR服务将提供全局GPIO ISR,并通过gpio_isr_handler_add()
* 函数注册各个引脚处理程序。
************************************************************************/
esp_err_t gpio_install_isr_service(int intr_alloc_flags);
⑪、void gpio_uninstall_isr_service(void); 卸载驱动程序的GPIO ISR服务,以释放相关资源。
⑫、 gpio_isr_register寄存器GPIO中断处理程序
/***********************************************************************
* 函数:
* 描述: 寄存器GPIO中断处理程序
* 参数: fn中断处理函数。
* intr_alloc_flags用于分配中断的标志*
* arg处理程序函数的参数
* handle指向返回句柄的指针。如果为非NULL,则将在此处返回中断的句柄。
* 返回: *-ESP_OK成功;
*-ESP_ERR_INVALID_ARG GPIO错误
*-ESP_ERR_NOT_FOUND找不到带有指定标志的空闲中断
* 备注:
************************************************************************/
esp_err_t gpio_isr_register(void (*fn)(void *), void *arg, int intr_alloc_flags, gpio_isr_handle_t *handle);
⑬、 gpio_isr_handler_add为相应的GPIO引脚添加ISR处理程序
/***********************************************************************
* 函数:
* 描述: 为相应的GPIO引脚添加ISR处理程序。
* 参数: * @param gpio_num GPIO编号
* @param isr_handler ISR处理函数,用于对应的GPIO号。
* ISR处理程序的@param args参数。
* 返回: *-ESP_OK成功
*-ESP_ERR_INVALID_STATE状态错误,ISR服务尚未初始化。
*-ESP_ERR_INVALID_ARG参数错误
* 备注:
************************************************************************/
esp_err_t gpio_isr_handler_add(gpio_num_t gpio_num, gpio_isr_t isr_handler, void *args);
⑭、esp_err_t gpio_isr_handler_remove(gpio_num_t gpio_num); 删除相应GPIO引脚的ISR处理程序。
⑮、FreeRTOS 任务创建
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, /* 任务函数 */
const char * const pcName, /* 任务名 */
unsigned short usStackDepth, /* 任务栈大小,单位 word,也就是 4 字节 */
void *pvParameters, /* 任务参数 */
UBaseType_t uxPriority, /* 任务优先级 */
TaskHandle_t *pvCreatedTask /* 任务句柄 */
);
⑯、函数 xQueueCreate 用于创建消息队列
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, /* 消息个数 */
UBaseType_t uxItemSize ); /* 每个消息大小,单位字节 */
⑰、函数 xQueueReceive 用于接收消息队列中的数据
BaseType_t xQueueReceive(
QueueHandle_t xQueue, /* 消息队列句柄 */
void *pvBuffer, /* 接收消息队列数据的缓冲地址 */
TickType_t xTicksToWait /* 等待消息队列有数据的最大等待时间 */
);
⑱、函数 xQueueSendFromISR 用于中断服务程序中消息发送
BaseType_t xQueueSendFromISR(
QueueHandle_t xQueue, /* 消息队列句柄 */
const void *pvItemToQueue, /* 要传递数据地址 */
BaseType_t *pxHigherPriorityTaskWoken /* 高优先级任务是否被唤醒的状态保存 */
);
函数 xQueueSend 用于任务中消息发送。
BaseType_t xQueueSend(
QueueHandle_t xQueue, /* 消息队列句柄 */
const void * pvItemToQueue, /* 要传递数据地址 */
TickType_t xTicksToWait /* 等待消息队列有空间的最大等待时间 */
);
4、软件设计
1)、初始化gpio_config_t结构体
gpio_config_t结构体参数:
typedef struct {
uint64_t pin_bit_mask; /*!< GPIO引脚:设置位掩码,每个位映射到一个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中断类型 */
} gpio_config_t;
gpio_int_type_t枚举类型
typedef enum {
GPIO_INTR_DISABLE = 0, /*!< Disable GPIO interrupt */
GPIO_INTR_POSEDGE = 1, /*!< GPIO interrupt type : rising edge */
GPIO_INTR_NEGEDGE = 2, /*!< GPIO interrupt type : falling edge */
GPIO_INTR_ANYEDGE = 3, /*!< GPIO interrupt type : both rising and falling edge */
GPIO_INTR_LOW_LEVEL = 4, /*!< GPIO interrupt type : input low level trigger */
GPIO_INTR_HIGH_LEVEL = 5, /*!< GPIO interrupt type : input high level trigger */
GPIO_INTR_MAX,
} gpio_int_type_t;
通过初始化结构体初始化 GPIO 的常用格式是:
gpio_config_t gpio_conf = {
.intr_type = GPIO_PIN_INTR_DISABLE, //禁用中断
.mode = GPIO_MODE_OUTPUT, //设置为输出模式
.pin_bit_mask = GPIO_OUTPUT_PIN_SEL, //输出指定引脚
.pull_down_en = 0, //禁止下拉
.pull_up_en = 0, //禁止上拉
};
设置io中断配置:
//设置IO中断
gpio_conf.intr_type = GPIO_PIN_INTR_POSEDGE;//设置上升沿中断
gpio_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;//输入指定引脚
gpio_conf.mode = GPIO_MODE_INPUT; //设置为输入模式
gpio_conf.pull_up_en = 1; //使能上拉
gpio_config(&gpio_conf);
改变引脚的中断触发方式为上升沿和下降沿:
gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_ANYEDGE);
安装gpio 中断服务,接收所有的中断等级
gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1);
接收中断等级
#define ESP_INTR_FLAG_LEVEL1 (1<<1) ///< Accept a Level 1 interrupt vector (lowest priority)
#define ESP_INTR_FLAG_LEVEL2 (1<<2) ///< Accept a Level 2 interrupt vector
#define ESP_INTR_FLAG_LEVEL3 (1<<3) ///< Accept a Level 3 interrupt vector
#define ESP_INTR_FLAG_LEVEL4 (1<<4) ///< Accept a Level 4 interrupt vector
#define ESP_INTR_FLAG_LEVEL5 (1<<5) ///< Accept a Level 5 interrupt vector
#define ESP_INTR_FLAG_LEVEL6 (1<<6) ///< Accept a Level 6 interrupt vector
#define ESP_INTR_FLAG_NMI (1<<7) ///< Accept a Level 7 interrupt vector (highest priority)
#define ESP_INTR_FLAG_SHARED (1<<8) ///< Interrupt can be shared between ISRs
#define ESP_INTR_FLAG_EDGE (1<<9) ///< Edge-triggered interrupt
#define ESP_INTR_FLAG_IRAM (1<<10) ///< ISR can be called if cache is disabled
#define ESP_INTR_FLAG_INTRDISABLED (1<<11) ///< Return with this interrupt disabled
最后添加中断服务程序:
gpio_isr_handler_add(GPIO_INPUT_IO_1, gpio_isr_handler, (void*) GPIO_INPUT_IO_1);
5、实例分析
复制上一个工程改名字为gpio_example即可文件名字改为gpio_example.C makefile文件也改成gpio_example即可,然后复制一下代码测试。
/**********************************************************************
* 文件名: gpio_example.c
* 创建人:
* 创建日期:
* 修改人:
* 修改日期:
* 版本号: V1.1
* 备注:
* 公司:
********************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
/*************************************************************************
*此测试代码显示了如何配置gpio以及如何使用gpio中断。
*
* GPIO状态:
* GPIO18:输出
* GPIO19:输出
* GPIO4:输入,上拉,上升沿和下降沿中断
* GPIO5:输入,上拉,上升沿中断。
*测试:
*将GPIO18与GPIO4连接
*将GPIO19与GPIO5连接
*在GPIO18 / 19上生成脉冲,从而触发GPIO4 / 5上的中断
************************************************************************/
#define GPIO_OUTPUT_IO_0 18
#define GPIO_OUTPUT_IO_1 19
#define GPIO_OUTPUT_PIN_SEL ((1ULL<<GPIO_OUTPUT_IO_0) | (1ULL<<GPIO_OUTPUT_IO_1))
#define GPIO_INPUT_IO_0 4
#define GPIO_INPUT_IO_1 5
#define GPIO_INPUT_PIN_SEL ((1ULL<<GPIO_INPUT_IO_0) | (1ULL<<GPIO_INPUT_IO_1))
//#define ESP_INTR_FLAG_DEFAULT 0
static xQueueHandle gpio_evt_queue = NULL;
/***********************************************************************
* 函数:
* 描述: 中断服务函数
* 参数:
* 返回: 无
* 备注:ISR handlers 中断处理函数必须放到 IRAM 里面,通过加 IRAM_ATTR 属性就能把代码或者变
量放到 IRAM 里面
************************************************************************/
static void IRAM_ATTR gpio_isr_handler(void* arg)
{
uint32_t gpio_num = (uint32_t) arg;
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}
/***********************************************************************
* 函数:
* 描述: gpio任务函数
* 参数:
* 返回: 无
* 备注:
************************************************************************/
static void vTaskGpio(void* arg)
{
uint32_t io_num;
for(;;) {
//接收到中断程序发送来的消息
if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY))
{
printf("GPIO[%d] intr, val: %d\n", io_num, gpio_get_level(io_num));
}
}
}
/***********************************************************************
* 函数:
* 描述: 主函数
* 参数:
* 返回: 无
* 备注:
************************************************************************/
void app_main(void)
{
//设置IO输出
gpio_config_t gpio_conf = {
.intr_type = GPIO_PIN_INTR_DISABLE, //禁用中断
.mode = GPIO_MODE_OUTPUT, //设置为输出模式
.pin_bit_mask = GPIO_OUTPUT_PIN_SEL, //输出指定引脚
.pull_down_en = 0, //禁止下拉
.pull_up_en = 0, //禁止上拉
};
gpio_config(&gpio_conf);
//设置IO中断
gpio_conf.intr_type = GPIO_PIN_INTR_POSEDGE;//设置上升沿中断
gpio_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;//输入指定引脚
gpio_conf.mode = GPIO_MODE_INPUT; //设置为输入模式
gpio_conf.pull_up_en = 1; //使能上拉
gpio_config(&gpio_conf);
gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_ANYEDGE);//改变引脚的中断触发方式为上升沿和下降沿
//创建队列
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
//创建任务
xTaskCreate(vTaskGpio, // 任务函数
"vTaskGpio", // 任务名
2048, // 任务栈大小,单位 word,也就是 4 字节
NULL, // 任务参数
2, // 任务优先级
NULL); // 任务句柄
//安装gpio 中断服务,接收所有的中断等级
gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1);
//指定引脚为ISR中断引脚,并且添加中断服务程序
gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
gpio_isr_handler_add(GPIO_INPUT_IO_1, gpio_isr_handler, (void*) GPIO_INPUT_IO_1);
//删除中断引脚
gpio_isr_handler_remove(GPIO_INPUT_IO_0);
//再次添加中断服务程序
gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
int gpio_io = 0;
while(1) {
gpio_io = !gpio_io;
printf("GPIO_OUTPUT_IO_0_1: %d\n", gpio_io);
gpio_set_level(GPIO_OUTPUT_IO_0, gpio_io);
gpio_set_level(GPIO_OUTPUT_IO_1, gpio_io);
vTaskDelay(1000 / portTICK_RATE_MS);
}
}
6、以下是调试的结果: