ESP32S3-IDF GPIO
GPIO简介
ESP32S3提供了多达45个物理GPIO管脚,这些管脚不仅可以作为通用的输入输出接口,还可以连接到内部外设信号。通过GPIO交换矩阵、IO MUX和RTC IO MUX,可以灵活地配置外设模块的输入信号来源于任何GPIO管脚,同时外设模块的输出信号也可以连接到任意GPIO管脚。
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_direction
和gpio_set_level
函数来配置它的方向和输出电平。
LED灯闪烁实验
这里我参考正点原子的教学案例,将LED的代码部分单独放到.c .h文件中
main文件
/**
* @file main.c
* @author 宁子希 (1589326497@qq.com)
* @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 宁子希 (1589326497@qq.com)
* @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 宁子希 (1589326497@qq.com)
* @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 */
}
GPIO输入
配置GPIO输入
要配置GPIO管脚为输入模式,我们可以使用以下步骤:
- 使用
gpio_set_direction(gpio_num, GPIO_MODE_INPUT)
函数设置GPIO方向为输入。 - 使用
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 宁子希 (1589326497@qq.com)
* @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 宁子希 (1589326497@qq.com)
* @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 宁子希 (1589326497@qq.com)
* @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外部中断
- 使用
gpio_set_direction(gpio_num, GPIO_MODE_INPUT)
函数将GPIO管脚设置为输入模式。 - 使用
gpio_set_intr_type(gpio_num, GPIO_INTR_POSEDGE)
函数设置外部中断类型(上升沿触发、下降沿触发等)。 - 使用
gpio_install_isr_service(0)
函数初始化外部中断服务。 - 使用
gpio_isr_handler_add(gpio_num, isr_handler, (void*)arg)
函数添加中断处理程序。gpio_num
:要添加中断处理程序的 GPIO 引脚号。isr_handler
:指向中断处理程序的函数指针。args
:传递给中断处理程序的上下文数据(可选)
- 在中断处理程序定义时可以加上IRAM_ATTR,IRAM_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 宁子希 (1589326497@qq.com)
* @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 宁子希 (1589326497@qq.com)
* @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 宁子希 (1589326497@qq.com)
* @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);
}
}
程序效果