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