ESP32S3-IDF GPIO

GPIO简介


ESP32S3提供了多达45个物理GPIO管脚,这些管脚不仅可以作为通用的输入输出接口,还可以连接到内部外设信号。通过GPIO交换矩阵、IO MUX和RTC IO MUX,可以灵活地配置外设模块的输入信号来源于任何GPIO管脚,同时外设模块的输出信号也可以连接到任意GPIO管脚。

esp32 主从一对多 esp32 io数量_esp32 主从一对多

GPIO配置

结构体方法:

#include "driver/gpio.h"

// GPIO配置结构体
gpio_config_t io_conf = {
    .pin_bit_mask = (1ULL<<GPIO_NUM_4), // 选择GPIO4
    .mode = GPIO_MODE_OUTPUT,           // 设置为输出模式
    .pull_up_en = GPIO_PULLUP_DISABLE,  // 禁用上拉
    .pull_down_en = GPIO_PULLDOWN_ENABLE, // 启用下拉
    .intr_type = GPIO_INTR_DISABLE      // 禁用中断
};

// 配置GPIO
esp_err_t ret = gpio_config(&io_conf);
if (ret != ESP_OK) {
    printf("GPIO配置失败\n");
}

此外,ESP-IDF还提供了一种更简单的方法来配置GPIO。

#include "driver/gpio.h"

// 设置GPIO22为输出
gpio_set_direction(GPIO_NUM_22, GPIO_MODE_OUTPUT);
// 设置GPIO22输出低电平
gpio_set_level(GPIO_NUM_22, 0);

这里我们使用gpio_set_directiongpio_set_level函数来配置它的方向和输出电平。

LED灯闪烁实验

这里我参考正点原子的教学案例,将LED的代码部分单独放到.c .h文件中

main文件
/**
 * @file main.c
 * @author 宁子希 
 * @brief LED点灯实验
 * @version 0.1
 * @date 2024-03-10
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "LED.h"

void app_main(void)
{
    led_init();             /* 初始化LED */

    while(1){

        LED_A_TOGGLE();
        LED_B_TOGGLE();
        vTaskDelay(500/portTICK_PERIOD_MS);   /* 延时500ms */
    }
    

}
LED.h文件
/**
 * @file LED.h
 * @author 宁子希
 * @brief   LED驱动代码
 * @version 0.1
 * @date 2024-03-10
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#ifndef __LED_H_
#define __LED_H_
#include "driver/gpio.h"

//引脚定义
#define LED_A_GPIO_PIN    GPIO_NUM_10
#define LED_B_GPIO_PIN    GPIO_NUM_11


//引脚的输出的电平状态
enum GPIO_OUTPUT_state{
    GPIO_OUTPUT_HIGH,
    GPIO_OUTPUT_LOW
};

//LED端口定义 翻转LED
#define LEDA(x)    do{   x ?                                            \
                        gpio_set_level(LED_A_GPIO_PIN, GPIO_OUTPUT_HIGH): \
                        gpio_set_level(LED_A_GPIO_PIN, GPIO_OUTPUT_LOW);  \
                    }while(0)

#define LEDB(x)    do{   x ?                                            \
                        gpio_set_level(LED_B_GPIO_PIN, GPIO_OUTPUT_HIGH): \
                        gpio_set_level(LED_B_GPIO_PIN, GPIO_OUTPUT_LOW);  \
                    }while(0)
//LED取反定义
#define LED_A_TOGGLE()    do{gpio_set_level(LED_A_GPIO_PIN,!gpio_get_level(LED_A_GPIO_PIN));}while(0)
#define LED_B_TOGGLE()    do{gpio_set_level(LED_B_GPIO_PIN,!gpio_get_level(LED_B_GPIO_PIN));}while(0)   

//函数声明
void led_init(void);   //初始化LED

#endif
LED.c文件
/**
 * @file LED.c
 * @author 宁子希
 * @brief LED驱动代码
 * @version 0.1
 * @date 2024-03-10
 * 
 * @copyright Copyright (c) 2024
 * 
 */

#include "LED.h"
/**
 * @brief   初始化LED
 */
void led_init(void){
    //初始化GPIO
    gpio_config_t gpio_init_struct = {0};

    gpio_init_struct.intr_type = GPIO_INTR_DISABLE;                                     /* 失能引脚中断 */
    gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;                                     /* 输入输出模式 */
    gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;                                   /* 使能上拉 */
    gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;                              /* 失能下拉 */
    gpio_init_struct.pin_bit_mask = (1ull << LED_A_GPIO_PIN)|(1ull<<LED_B_GPIO_PIN);    /* 设置的引脚的位掩码 */
    gpio_config(&gpio_init_struct);                         /* 配置GPIO */

    LEDA(0);                                                 /* 关闭LED */
    LEDB(0);                                                 /* 关闭LED */
}

esp32 主从一对多 esp32 io数量_esp32 主从一对多_02

GPIO输入

配置GPIO输入

要配置GPIO管脚为输入模式,我们可以使用以下步骤:

  1. 使用gpio_set_direction(gpio_num, GPIO_MODE_INPUT)函数设置GPIO方向为输入。
  2. 使用gpio_get_level(gpio_num)函数读取GPIO的电平。

或者也可以使用结构体句柄的方法配置GPIO4为输入并读取其电平:

#include "driver/gpio.h"

void app_main() {
    gpio_config_t io_config;
    io_config.pin_bit_mask = (1ULL << GPIO_NUM_4);
    io_config.mode = GPIO_MODE_INPUT;
    gpio_config(&io_config);

    int level = gpio_get_level(GPIO_NUM_4);
    printf("GPIO4的电平:%d\n", level);
}

其他API方法,可以参考ESP-IDF编程指南³。

¹: ESP32-S3技术参考手册 ³: ESP-IDF编程指南

按键翻转LED实验

这次我们使用c++面向对象的方法来写按键的驱动

KEY.h
/**
 * @file KEY.h
 * @author 宁子希 
 * @brief   按键驱动
 * @version 0.1
 * @date 2024-03-11
 * 
 * @copyright Copyright (c) 2024
 * 
 */

#ifndef KEY_H
#define KEY_H

#ifdef __cplusplus
extern "C" {
#endif


#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"


/**
 * @class Key
 * @brief 按键驱动类
 */

class Key {
private:
    gpio_num_t pin;     // GPIO引脚号
    
public:
    // 构造函数
    Key(gpio_num_t pin);

    // 获取按键的电平
     int get_BOOT_value(void);

    // 初始化按键
     void init();
    // 按键扫描函数
     uint8_t scan(uint8_t mode);
};


#ifdef __cplusplus
}
#endif

#endif

注意头文件里的定义要用下面的方式包裹

#ifdef __cplusplus
extern "C" {
#endif
//实现------
#ifdef __cplusplus
}
#endif
KEY.cpp

注意源文件对类成员函数的实现不要使用内联,要不然.h和.cpp会链接不上

/**
 * @file KEY.c
 * @author 宁子希
 * @brief   按键驱动
 * @version 0.1
 * @date 2024-03-11
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#include "KEY.h"

// 构造函数
Key::Key(gpio_num_t pin): pin(pin) {}

// 获取按键的电平
 int Key::get_BOOT_value(void){
    return gpio_get_level(this->pin);
}

// 初始化按键
void Key::init(){

    gpio_config_t gpio_init_struct;

    gpio_init_struct.intr_type = GPIO_INTR_DISABLE;         /* 失能引脚中断 */
    gpio_init_struct.mode = GPIO_MODE_INPUT;                /* 输入模式 */
    gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;       /* 使能上拉 */
    gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;  /* 失能下拉 */
    gpio_init_struct.pin_bit_mask = 1ull << this->pin;      /* BOOT按键引脚 */
    gpio_config(&gpio_init_struct);                         /* 配置使能 */
}

/**
 * @brief       按键扫描函数
 * @param       mode:0 / 1, 具体含义如下:
 *              0,  不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,
 *                  必须松开以后, 再次按下才会返回其他键值)
 *              1,  支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)          
 */
uint8_t Key::scan(uint8_t mode){
    uint8_t keyValue = 0;
    static uint8_t key_boot = 1;    //按键松开标志
    if(mode){
        key_boot = 1;
    }
    if(key_boot&&(this->get_BOOT_value()==0)){
        vTaskDelay(10); //去抖动
        key_boot = 0;

        if(this->get_BOOT_value()==0){
            keyValue = 1;
        }
    }else if(get_BOOT_value()==1){
        key_boot = 1;
    }
    return keyValue;    //返回键值
}
main.cpp

void app_main(void)前要加extern "C"前缀

/**
 * @file main.cpp
 * @author 宁子希
 * @brief   按键输入实验
 * @version 0.1
 * @date 2024-03-11
 * 
 * @copyright Copyright (c) 2024
 * 
 */


#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "LED.h"
#include "KEY.h"
extern "C" void app_main(void)
{
    uint8_t keyValue;   //按键键值

    esp_err_t ret;
    ret = nvs_flash_init();     /* 初始化NVS */

    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }

    led_init(); //初始LED
    Key key(GPIO_NUM_0);    //实例化按键对象

    while(true){
        keyValue=key.scan(0);    //获取按键值

        switch(keyValue){
            case 1: //按键被按下
                LED_A_TOGGLE();
                LED_B_TOGGLE();
                break;
            default:
                break;
        }
    }
    vTaskDelay(10);
}

EXTI(GPIO外部中断)

当我们在嵌入式系统开发中需要对外部事件进行响应时,外部中断(External Interrupt,简称EXTI)是一个重要的功能。在ESP32-S3芯片上,我们可以使用GPIO管脚来实现外部中断。。

ESP32-S3的GPIO外部中断

  • ESP32-S3芯片的GPIO管脚可以配置为外部中断输入。
  • 当外部事件(例如按键按下、传感器触发等)发生时,我们可以通过配置GPIO管脚的外部中断来捕获这些事件并执行相应的操作。

配置GPIO外部中断

  1. 使用gpio_set_direction(gpio_num, GPIO_MODE_INPUT)函数将GPIO管脚设置为输入模式。
  2. 使用gpio_set_intr_type(gpio_num, GPIO_INTR_POSEDGE)函数设置外部中断类型(上升沿触发、下降沿触发等)。
  3. 使用gpio_install_isr_service(0)函数初始化外部中断服务。
  4. 使用gpio_isr_handler_add(gpio_num, isr_handler, (void*)arg)函数添加中断处理程序。
  • gpio_num:要添加中断处理程序的 GPIO 引脚号。
  • isr_handler:指向中断处理程序的函数指针。
  • args:传递给中断处理程序的上下文数据(可选)
  1. 在中断处理程序定义时可以加上IRAM_ATTRIRAM_ATTR 是一个用于声明中断服务例程(ISR)的特殊属性,用于告知编译器将编译后的代码放置在 ESP32 的内部 RAM(IRAM)中,而不是 Flash 存储器中 ,使用 IRAM_ATTR 属性可以确保它能够尽快执行,而不必等待从 Flash 加载。

配置GPIO4为上升沿触发的外部中断:

#include "driver/gpio.h"

void IRAM_ATTR isr_handler(void* arg) {
    // 处理外部中断事件
    printf("外部中断触发!\n");
}

void app_main() {
    gpio_config_t io_config;
    io_config.pin_bit_mask = (1ULL << GPIO_NUM_4);
    io_config.mode = GPIO_MODE_INPUT;
    gpio_config(&io_config);

    gpio_set_intr_type(GPIO_NUM_4, GPIO_INTR_POSEDGE);

    gpio_install_isr_service(0);
    gpio_isr_handler_add(GPIO_NUM_4, isr_handler, NULL);

    while (1) {
        // 主循环中执行其他任务
    }
}

详细的功能可以参考ESP-IDF编程指南¹。

¹: ESP-IDF编程指南

实验按键控制LED翻转(GPIO外部中断模式)

这次还是参考正点原子的教学案例,将EXTI的代码部分单独放到.c .h文件中

EXTI.h
/**
 * @file EXTI.h
 * @author 宁子希 
 * @brief   外部中断驱动
 * @version 0.1
 * @date 2024-03-11
 * 
 * @copyright Copyright (c) 2024
 * 
 */

#ifndef __EXTI_H__
#define __EXTI_H__
#include "esp_err.h"
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "esp_system.h" 
#include "esp_log.h"
#include "sdkconfig.h"
#include "LED.h"

//定义引脚
#define BOOT_INT_GPIO_PIN   GPIO_NUM_0

//IO操作
#define BOOT                gpio_get_level(BOOT_INT_GPIO_PIN)

//初始化外部中断
 void exti_init(void);  
#endif
EXTI.c
/**
 * @file EXTI.c
 * @author 宁子希
 * @brief   外部中断驱动
 * @version 0.1
 * @date 2024-03-11
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#include "EXTI.h"
static void IRAM_ATTR BOOT_exti_isr(void* data){
    uint32_t gouiNum = (uint32_t) data;
    if(gouiNum==BOOT_INT_GPIO_PIN){
        LED_A_TOGGLE();
        LED_B_TOGGLE();
    }
}


void exti_init(void){
    gpio_config_t gpio_init_config;
    gpio_init_config.mode=GPIO_MODE_INPUT;
    gpio_init_config.pull_up_en=GPIO_PULLUP_ENABLE;
    gpio_init_config.pull_down_en=GPIO_PULLDOWN_DISABLE;
    gpio_init_config.intr_type=GPIO_INTR_NEGEDGE;
    gpio_init_config.pin_bit_mask=1ull<<BOOT_INT_GPIO_PIN;

    gpio_config(&gpio_init_config);

    //初始化中断
    gpio_install_isr_service(0);

    //设置中断回调函数
    gpio_isr_handler_add(BOOT_INT_GPIO_PIN, BOOT_exti_isr, (void*)BOOT_INT_GPIO_PIN);


}
main.c
/**
 * @file main.c
 * @author 宁子希
 * @brief   外部中断实验
 * @version 0.1
 * @date 2024-03-11
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "LED.h"
#include "EXTI.h"
void app_main(void){

    esp_err_t ret;
    
    ret = nvs_flash_init(); //初始化NVS

    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND){

        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }

    led_init();             //初始化LED
    exti_init();            //初始化按键
    
    for(;;){
        vTaskDelay(10);
    }
}

程序效果

esp32 主从一对多 esp32 io数量_外部中断_03