ESP32-S3学习笔记<2>:GPIO的应用
- 1. 头文件包含
- 2. GPIO的配置
- 2.1 pin_bit_mask
- 2.2 mode
- 2.3 pull_up_en和pull_down_en
- 2.4 intr_type
- 3. 设置GPIO输出/获取GPIO输入
- 4. 中断的使用
- 4.1 gpio_install_isr_service
- 4.2 gpio_isr_handler_add
- 4.3 gpio_intr_enable
- 4.4 中断服务函数
- 5. 示例
1. 头文件包含
需要包含以下头文件:
#include "esp_attr.h"
#include "driver/gpio.h"
#include "hal/gpio_types.h"
2. GPIO的配置
使用如下函数配置GPIO:
esp_err_t gpio_config(const gpio_config_t *pGPIOConfig);
配置信息结构体 gpio_config_t 的定义为:
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_FILTERgpio_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位数表达。正确写法是:
pin_bit_mask = 1ull << GPIO_NUM_1 ;
特别需要注意写法 1ull,指定是 unsigned long long 类型。否则位宽不够,左移32位后就溢出了。
2.2 mode
mode为一枚举类型,定义为:
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的上拉和下拉。可用的选项有:
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
中断指定。仅代码控制输入输出的话,可以指定禁用中断。选项有:
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的输入。
/* 设置输出 */
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_4 。level 可以是0或1,表示设置低电平或高电平输出。
4. 中断的使用
使用GPIO的中断,除了在GPIO配置中设置中断类型(上升沿、下降沿、高低电平中断)外,还需要几个步骤:
- gpio_install_isr_service ,安装isr服务。这个函数是逐引脚触发中断用的,即后续需要为特定的引脚绑定中断服务函数。此外还有一个函数 gpio_isr_register ,用来为所有的GPIO绑定中断服务函数。也就是说,使用后者时,任意GPIO触发了中断,都会使用同一个通断服务函数。感觉还是前者更常用一些。
- gpio_isr_handler_add , 为指定的GPIO添加一个中断服务函数。同时还可以设置一些属性。
- gpio_intr_enable ,使能中断。这个函数实测不是必需的。前两个函数应用之后,中断默认使能了(在 gpio_isr_handler_add 函数中)。
4.1 gpio_install_isr_service
此函数安装isr服务。函数本身不指定GPIO。所以即使有多个GPIO需要配置中断,此函数也只调用一次。如果多次调用,运行会报错。
esp_err_t gpio_install_isr_service(int intr_alloc_flags) ;
参数 intr_alloc_flags 有如下这些选项:
#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绑定中断服务函数。
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
此函数使能中断。
esp_err_t gpio_intr_enable(gpio_num_t gpio_num);
实际上,gpio_isr_handler_add 函数中已经有使能代码。实测这个函数也不是必需的。
4.4 中断服务函数
中断服务函数形如:
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 :
#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 :
#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 :
#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) ;}
}