ESP32-S3学习笔记<2>:GPIO的应用

ESP32-S3学习笔记<2>:GPIO的应用

  • [1. 头文件包含](#1. 头文件包含)
  • [2. GPIO的配置](#2. GPIO的配置)
    • [2.1 pin_bit_mask](#2.1 pin_bit_mask)
    • [2.2 mode](#2.2 mode)
    • [2.3 pull_up_en和pull_down_en](#2.3 pull_up_en和pull_down_en)
    • [2.4 intr_type](#2.4 intr_type)
  • [3. 设置GPIO输出/获取GPIO输入](#3. 设置GPIO输出/获取GPIO输入)
  • [4. 中断的使用](#4. 中断的使用)
    • [4.1 gpio_install_isr_service](#4.1 gpio_install_isr_service)
    • [4.2 gpio_isr_handler_add](#4.2 gpio_isr_handler_add)
    • [4.3 gpio_intr_enable](#4.3 gpio_intr_enable)
    • [4.4 中断服务函数](#4.4 中断服务函数)
  • [5. 示例](#5. 示例)

1. 头文件包含

需要包含以下头文件:

c 复制代码
#include "esp_attr.h"
#include "driver/gpio.h"
#include "hal/gpio_types.h"

2. GPIO的配置

使用如下函数配置GPIO:

c 复制代码
esp_err_t gpio_config(const gpio_config_t *pGPIOConfig);

配置信息结构体 gpio_config_t 的定义为:

c 复制代码
typedef struct {
    uint64_t pin_bit_mask;          /*!< GPIO pin: set with bit mask, each bit maps to a GPIO */
    gpio_mode_t mode;               /*!< GPIO mode: set input/output mode                     */
    gpio_pullup_t pull_up_en;       /*!< GPIO pull-up                                         */
    gpio_pulldown_t pull_down_en;   /*!< GPIO pull-down                                       */
    gpio_int_type_t intr_type;      /*!< GPIO interrupt type                                  */
#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER
    gpio_hys_ctrl_mode_t hys_ctrl_mode;       /*!< GPIO hysteresis: hysteresis filter on slope input    */
#endif
} gpio_config_t;

其中:

2.1 pin_bit_mask

指定要配置的GPIO。需要按位指定GPIO。由于 ESP32-S3 引脚数超过32位,所以用一个64位数表达。正确写法是:

c 复制代码
pin_bit_mask = 1ull << GPIO_NUM_1 ;

特别需要注意写法 1ull ,指定是 unsigned long long 类型。否则位宽不够,左移32位后就溢出了。

2.2 mode

mode为一枚举类型,定义为:

c 复制代码
typedef enum {
    GPIO_MODE_DISABLE = GPIO_MODE_DEF_DISABLE,  
    GPIO_MODE_INPUT = GPIO_MODE_DEF_INPUT,     
    GPIO_MODE_OUTPUT = GPIO_MODE_DEF_OUTPUT,   
    GPIO_MODE_OUTPUT_OD = ((GPIO_MODE_DEF_OUTPUT) | (GPIO_MODE_DEF_OD)), 
    GPIO_MODE_INPUT_OUTPUT_OD = ((GPIO_MODE_DEF_INPUT) | (GPIO_MODE_DEF_OUTPUT) | (GPIO_MODE_DEF_OD)),
    GPIO_MODE_INPUT_OUTPUT = ((GPIO_MODE_DEF_INPUT) | (GPIO_MODE_DEF_OUTPUT)),   
} gpio_mode_t;

主要选项有:仅输入、仅输出、仅开漏输出、输入输出、带有开漏的输入输出。

需要注意的是,如果配置为仅输出,是没有办法通过后续的 gpio_get_level 函数来获取到GPIO当前的输出值的。某些应用可能需要读取当前输出值(例如翻转一个GPIO,如LED亮/灭切换等),这时就要配置GPIO为输入输出。

2.3 pull_up_en和pull_down_en

指定GPIO的上拉和下拉。可用的选项有:

c 复制代码
typedef enum {
    GPIO_PULLUP_DISABLE = 0x0,     /*!< Disable GPIO pull-up resistor */
    GPIO_PULLUP_ENABLE = 0x1,      /*!< Enable GPIO pull-up resistor */
} gpio_pullup_t;

typedef enum {
    GPIO_PULLDOWN_DISABLE = 0x0,   /*!< Disable GPIO pull-down resistor */
    GPIO_PULLDOWN_ENABLE = 0x1,    /*!< Enable GPIO pull-down resistor  */
} gpio_pulldown_t;

2.4 intr_type

中断指定。仅代码控制输入输出的话,可以指定禁用中断。选项有:

c 复制代码
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;

可用的中断选项有:禁用中断、上升沿中断、下降沿中断、任意边沿中断(上升沿或下降沿)、低电平中断、高电平中断。

3. 设置GPIO输出/获取GPIO输入

以下2个函数,用于设置GPIO输出,或者读取GPIO的输入。

c 复制代码
/* 设置输出 */
esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level);

/* 获取输入 */
int gpio_get_level(gpio_num_t gpio_num)

这里 gpio_num 不需要像 gpio_config 那样按位设置,直接填写GPIO即可,例如 GPIO_NUM_4level 可以是0或1,表示设置低电平或高电平输出。

4. 中断的使用

使用GPIO的中断,除了在GPIO配置中设置中断类型(上升沿、下降沿、高低电平中断)外,还需要几个步骤:

  1. gpio_install_isr_service ,安装isr服务。这个函数是逐引脚触发中断用的,即后续需要为特定的引脚绑定中断服务函数。此外还有一个函数 gpio_isr_register ,用来为所有的GPIO绑定中断服务函数。也就是说,使用后者时,任意GPIO触发了中断,都会使用同一个通断服务函数。感觉还是前者更常用一些。
  2. gpio_isr_handler_add , 为指定的GPIO添加一个中断服务函数。同时还可以设置一些属性。
  3. gpio_intr_enable ,使能中断。这个函数实测不是必需的。前两个函数应用之后,中断默认使能了(在 gpio_isr_handler_add 函数中)。

4.1 gpio_install_isr_service

此函数安装isr服务。函数本身不指定GPIO。所以即使有多个GPIO需要配置中断,此函数也只调用一次。如果多次调用,运行会报错。

c 复制代码
esp_err_t gpio_install_isr_service(int intr_alloc_flags) ;

参数 intr_alloc_flags 有如下这些选项:

c 复制代码
#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

#define ESP_INTR_FLAG_LOWMED    (ESP_INTR_FLAG_LEVEL1|ESP_INTR_FLAG_LEVEL2|ESP_INTR_FLAG_LEVEL3) ///< Low and medium prio interrupts. These can be handled in C.
#define ESP_INTR_FLAG_HIGH      (ESP_INTR_FLAG_LEVEL4|ESP_INTR_FLAG_LEVEL5|ESP_INTR_FLAG_LEVEL6|ESP_INTR_FLAG_NMI) ///< High level interrupts. Need to be handled in assembly.

主要设置中断优先级、边沿中断、是否放在内部指令RAM中等。

需要注意,按照网站上的说法,如果 intr_alloc_flags 中设置了标记 ESP_INTR_FLAG_IRAM ,则中断服务函数需要设置属性 IRAM_ATTR 。该属性将中断服务函数放在内部指令RAM中,以提高性能。建议加上。

4.2 gpio_isr_handler_add

此函数为特定的GPIO绑定中断服务函数。

c 复制代码
esp_err_t gpio_isr_handler_add(gpio_num_t gpio_num, gpio_isr_t isr_handler, void *args);

第一个参数用来指定GPIO。

第二个参数指定中断服务函数。

第三个参数指定传递给中断服务函数的参数。可以为 NULL

4.3 gpio_intr_enable

此函数使能中断。

c 复制代码
esp_err_t gpio_intr_enable(gpio_num_t gpio_num);

实际上,gpio_isr_handler_add 函数中已经有使能代码。实测这个函数也不是必需的。

4.4 中断服务函数

中断服务函数形如:

c 复制代码
void IRAM_ATTR __TEST_GPIO_InterruptHandler(void *pvArg)
{
    __TEST_GPIO_ToggleLED() ;

    return ;
}

属性 IRAM_ATTR 用于将服务函数放在内部IRAM中,提高性能。

5. 示例

以下例子,配置2个输入GPIO(不带中断的A和带中断的B)和一个输出GPIO。输出GPIO驱动一个LED。输入GPIO,一个配置为单输入,一个配置为中断输入,中断由下降沿触发。

中断触发时,翻转LED的显示状态。

主循环轮询A的输入,如果是低电平,每隔一段时间翻转一次LED。

文件 test_gpio.h

c 复制代码
#define TEST_GPIO_LED_GPIO             (GPIO_NUM_1)   /* led control pin                      */
#define TEST_GPIO_INPUT_WITHOUT_INT    (GPIO_NUM_4)   /* input pin with no interrupt          */
#define TEST_GPIO_INPUT_WITH_INT       (GPIO_NUM_0)   /* input pin with interrupt             */

void TEST_GPIO_Init(void) ;
void TEST_GPIO_ToggleLED(void) ;
int TEST_GPIO_GetInputGPIOWithoutInt(void) ;

文件 test_gpio.c

c 复制代码
#include "esp_attr.h"
#include "driver/gpio.h"
#include "hal/gpio_types.h"

#include "test_gpio.h"

void __TEST_GPIO_INIT_InitLEDGPIO(void)
{
    esp_err_t           iRetVal ;
    const gpio_config_t stGPIOConfig = 
        {
            .pin_bit_mask = 1ull << TEST_GPIO_LED_GPIO ,
            .mode         = GPIO_MODE_INPUT_OUTPUT ,
            .pull_up_en   = GPIO_PULLUP_DISABLE ,
            .pull_down_en = GPIO_PULLDOWN_DISABLE ,
            .intr_type    = GPIO_INTR_DISABLE 
        } ;

    iRetVal = gpio_config(&stGPIOConfig) ;
    
    if(ESP_OK != iRetVal)
    {
        printf("Config LED GPIO error! Return value is %d\n", iRetVal) ;
    }
    
    return ;
}

void __TEST_GPIO_ToggleLED(void)
{
    int iGPIOInputVal ;
    
    iGPIOInputVal = gpio_get_level(TEST_GPIO_LED_GPIO) ;
    
    if(0 == iGPIOInputVal)
    {
        gpio_set_level(TEST_GPIO_LED_GPIO, 1) ;
    }
    else 
    {
        gpio_set_level(TEST_GPIO_LED_GPIO, 0) ;
    }
    
    return ;
}

void __TEST_GPIO_INIT_InitInputWithNoInt(void)
{
    esp_err_t           iRetVal ;
    const gpio_config_t stGPIOConfig = 
        {
            .pin_bit_mask = 1ull << TEST_GPIO_INPUT_WITHOUT_INT ,
            .mode         = GPIO_MODE_INPUT ,
            .pull_up_en   = GPIO_PULLUP_ENABLE ,
            .pull_down_en = GPIO_PULLDOWN_DISABLE ,
            .intr_type    = GPIO_INTR_DISABLE 
        } ;

    iRetVal = gpio_config(&stGPIOConfig) ;
    
    if(ESP_OK != iRetVal)
    {
        printf("Config input GPIO without interrupt error! Return value is %d\n", iRetVal) ;
    }
    
    return ;
}

void IRAM_ATTR __TEST_GPIO_InterruptHandler(void *pvArg)
{
    __TEST_GPIO_ToggleLED() ;

    return ;
}

void __TEST_GPIO_INIT_InitInputWithInt(void)
{
    esp_err_t           iRetVal ;
    const gpio_config_t stGPIOConfig = 
        {
            .pin_bit_mask = 1ull << TEST_GPIO_INPUT_WITH_INT ,
            .mode         = GPIO_MODE_INPUT ,
            .pull_up_en   = GPIO_PULLUP_ENABLE ,
            .pull_down_en = GPIO_PULLDOWN_DISABLE ,
            .intr_type    = GPIO_INTR_NEGEDGE 
        } ;

    iRetVal = gpio_config(&stGPIOConfig) ;
    
    if(ESP_OK != iRetVal)
    {
        printf("Config input GPIO with interrupt error! Return value is %d\n", iRetVal) ;
    }
    
    /* GPIO interrupt*/
    iRetVal = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_EDGE) ;
    
    if(ESP_OK != iRetVal)
    {
        printf("Install GPIO isr service error! Return value is %d\n", iRetVal) ;
    }
    
    /* add interrupt handler */
    gpio_isr_handler_add(TEST_GPIO_INPUT_WITH_INT, __TEST_GPIO_InterruptHandler, NULL);
    
    /* enable interrupt */
    //gpio_intr_enable(TEST_GPIO_INPUT_WITH_INT) ;
    
    return ;
}

void TEST_GPIO_Init(void)
{
    /* config all GPIOs */
    __TEST_GPIO_INIT_InitLEDGPIO() ;
    __TEST_GPIO_INIT_InitInputWithNoInt() ;
    __TEST_GPIO_INIT_InitInputWithInt() ;
}

void TEST_GPIO_ToggleLED(void)
{
    __TEST_GPIO_ToggleLED() ;
}

int TEST_GPIO_GetInputGPIOWithoutInt(void)
{
    return gpio_get_level(TEST_GPIO_INPUT_WITHOUT_INT) ;
}

文件 main.c

c 复制代码
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"

#include "test_gpio.h"

void app_main(void)
{
    int iGPIOInputVal ;
    
    TEST_GPIO_Init() ;
    
    while (true)
    {
        iGPIOInputVal = TEST_GPIO_GetInputGPIOWithoutInt() ;
        
        if(0 == iGPIOInputVal)
        {
            TEST_GPIO_ToggleLED() ;
        }
        
        vTaskDelay(500) ;
    }
}

相关推荐
c76919 分钟前
【文献笔记】ARS: Automatic Routing Solver with Large Language Models
人工智能·笔记·语言模型·自然语言处理·llm·论文笔记·cvrp
cherishSpring1 小时前
Redis学习笔记
redis·笔记·学习
★YUI★1 小时前
学习游戏制作记录(战斗系统简述以及击中效果)7.22
学习·游戏
悲伤小伞2 小时前
Linux_Ext系列文件系统基本认识(一)
linux·运维·服务器·c语言·编辑器
喜欢你,还有大家3 小时前
Linux笔记2——常用命令-1
linux·服务器·笔记
Ro Jace3 小时前
图像分析学习笔记(2):图像处理基础
图像处理·笔记·学习
Gappsong8743 小时前
Rufus:Ubuntu U盘启动盘制作工具详解
linux·c++·web安全·网络安全
慕y2743 小时前
Java学习第六十三部分——K8s
java·开发语言·学习
dessler4 小时前
RabbitMQ-交换机(Exchange)
linux·分布式·zookeeper·云原生·kafka·rabbitmq
程序员编程指南5 小时前
Qt开发环境搭建全攻略(Windows+Linux+macOS)
linux·c语言·c++·windows·qt