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) ;
    }
}

相关推荐
东临碣石82几秒前
【AI论文】潜在区域划分网络:生成建模、表征学习与分类的统一原理
学习
环球经济报5 分钟前
“常小豚苏超限定款”公益联名:以热爱之名守护生态赛场
笔记
xinfei080317 分钟前
第五天——为什么要学习
学习·每天一篇小感悟
Suger99936 分钟前
centos网络打流测试
linux·网络·centos
小何好运暴富开心幸福1 小时前
操作系统之初识Linux
linux·运维·服务器·bash
半导体守望者2 小时前
TR帝尔编码器GSD文件 PROFIBUS XML PROFINET EtherCAT 文件 ADH CDH CMV等
xml·经验分享·笔记·机器人·自动化·制造
こ进制掌控者2 小时前
Ubuntu server 24.04.3 设置静态IP
linux·tcp/ip·ubuntu
路弥行至2 小时前
C语言入门教程 | 第一讲:C语言零基础入门教程:第一个程序到变量运算详解
c语言·开发语言·经验分享·笔记·单片机·其他·课程设计
泡沫冰@2 小时前
shell编程:sed - 流编辑器(5)
linux
myw0712053 小时前
Leetcode94.二叉数的中序遍历练习
c语言·数据结构·笔记·算法