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_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需要配置中断,此函数也只调用一次。如果多次调用,运行会报错。
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) ;
}
}