ESP32 IDF开发 驱动篇⑤GPIO及外部中断讲解


别迷路-导航栏
快速导航找到你想要的(文章目录)

此篇文章如果对你有用,请点赞收藏,您的支持就是博主坚持的动力。

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、以下是调试的结果:

在这里插入图片描述
所有文章源代码:https://download.csdn.net/download/lu330274924/88518092

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

物联网程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值