ESP32-S3 实战指南:BOOT-KEY 按键驱动开发全解析

一、基础知识

本篇我们使用 BOOT 按键来学习一下 GPIO 功能,首先补充一下相关术语介绍。

1、GPIO(General Purpose Input/Output)

  • GPIO 是微控制器上的通用引脚,既可以作为输入(读取外部信号),也可以作为输出(控制外部设备)。
  • 在 ESP32 中,GPIO 引脚可以灵活配置,用于多种用途,例如读取传感器数据、控制 LED、驱动电机等。

2、输入(Input)

  • 当 GPIO 配置为输入时,它可以读取外部信号的状态(高电平或低电平)。
  • 例如,读取按键是否被按下(按键按下时,引脚电平会发生变化)。

3、输出(Output)

  • 当 GPIO 配置为输出时,它可以控制外部设备的状态(例如点亮 LED 或驱动继电器)。
  • 例如,将 GPIO 设置为高电平(3.3V)来点亮 LED,或设置为低电平(0V)来关闭 LED。

4、内部上拉(Pull-up)和下拉(Pull-down)

  • 上拉电阻:将 GPIO 引脚通过内部电阻连接到电源(3.3V),使引脚在未连接外部信号时保持高电平。
  • 下拉电阻:将 GPIO 引脚通过内部电阻连接到地(GND),使引脚在未连接外部信号时保持低电平。
  • 这些电阻可以确保 GPIO 引脚在没有外部信号时处于确定的状态,避免悬空(floating)导致的不稳定。

5、中断引脚(Interrupt Pin)

  • 中断是一种硬件机制,当 GPIO 引脚的状态发生变化时(例如从高电平变为低电平),会触发一个中断信号。
  • 中断可以让微控制器立即响应外部事件,而不需要不断轮询(polling)引脚状态。
  • 例如,当按键按下时,GPIO 引脚的电平变化会触发中断,微控制器可以立即执行相应的处理程序。

6、BOOT 按键和 IO0 引脚

  • BOOT 按键:ESP32 开发板上通常有一个 BOOT 按键,用于进入下载模式或复位。
  • IO0 引脚:这是 ESP32 的一个 GPIO 引脚,编号为 GPIO0。它通常与 BOOT 按键连接。
  • 当 BOOT 按键按下时,IO0 引脚的电平会发生变化(例如从高电平变为低电平)。

7、设置为 GPIO 中断,接收按键请求

  • 将 IO0 引脚配置为中断引脚,用于检测 BOOT 按键的按下事件。
  • 当按键按下时,IO0 引脚的电平变化会触发中断,微控制器可以立即执行相应的代码(例如处理按键请求)。

二、设计图

更多详细资料请查询官网 立创·实战派ESP32-S3开发板 - 立创开源硬件平台

三、实战操作

1、Copy 示例工程 sample_project

示例工程路径:$HOME/esp/esp-idf/tools/templates/sample_project

直接在 esp-idf 目录下搜索 sample_project 也可以找到,接着复制一份工程代码到自己的编码路径,然后修改文件夹名字为 boot_key。

可以看到模板工程下只有一个空函数 app_main,函数体还没有代码,我们需要参考官方提供的 gpio 示例进行编码,路径在:$HOME/esp/esp-idf/examples/peripherals/gpio/generic_gpio

2、引入头文件

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"

// ...

使用 printf 函数,需要添加 stdio.h 头文件。string.h 和 stdlib.h 我们这里用不着,可以去掉。接下来是 3 个 freeRTOS 的头文件,最后一个头文件是用于 gpio 的配置。

3、gpio 配置

3.1、官方示例

首先看一下官方源码示例是怎么写的,再来看看我们需要修改哪些地方,代码注释如下:

cpp 复制代码
// 官方示例代码
void app_main(void)
{
    // Step1、定义了一个 gpio_config_t 结构体变量。
    gpio_config_t io_conf = {};
    // Step2、定义引脚中断类型,这里是 GPIO_INTR_DISABLE,表示中断关闭
    io_conf.intr_type = GPIO_INTR_DISABLE;
    // Step3、配置模式,这里是 GPIO_MODE_OUTPUT 表示输出模式
    io_conf.mode = GPIO_MODE_OUTPUT;
    // Step4、配置引脚
    io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
    // Step5、配置是否打开下拉电阻
    io_conf.pull_down_en = 0;
    // Step6、配置是否打开上拉电阻
    io_conf.pull_up_en = 0;
    // Step7、使用 gpio_config 函数进行配置
    gpio_config(&io_conf);
}

3.2、对应改动点说明

改动点1、修改引脚中断类型

cpp 复制代码
//falling edge interrupt
io_conf.intr_type = GPIO_INTR_NEGEDGE;

**++开发板上的按键没有按下的时候是高电平,按下去以后是低电平,因此定义成下降沿中断。++**这里原来是 GPIO_INTR_DISABLE,表示中断关闭,这里我们修改为 GPIO_INTR_NEGEDGE,即下降沿中断。这些宏定义在 gpio_types.h 文件中被定义,我们在 gpio_example_main.c 文件中的 GPIO_INTR_DISABLE 上单击右键,然后选择"转到定义",就可以找到这几个宏定义,如下所示:

cpp 复制代码
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;

改动点2、修改 gpio 模式

cpp 复制代码
//set as input mode
io_conf.mode = GPIO_MODE_INPUT;

修改为输入模式,就可以读取外部信号的状态,按键按下时,捕获到引脚电平变化信号。

改动点3、修改引脚

cpp 复制代码
//bit mask of the pins GPIO0
io_conf.pin_bit_mask = 1<<GPIO_NUM_0;

因为 BOOT 按键连接到了 GPIO0,所以这里我们把原来的 GPIO_OUTPUT_PIN_SEL 修改为 1<<GPIO_NUM_0,实际上 GPIO_NUM_0 是一个宏定义,值为 0。

改动点4、修改打开上下拉电阻

cpp 复制代码
//disable pull-down mode
io_conf.pull_down_en = 0;
//enable pull-up mode
io_conf.pull_up_en = 1;

打开上拉电阻,将 GPIO 引脚通过内部电阻连接到电源,使引脚在未连接外部信号时保持高电平,从而避免引脚悬空导致的不稳定问题。

补充:为什么不用下拉电阻?

下拉电阻也可以用于避免引脚悬空,但它会将引脚在没有外部信号时拉低到地(0V)。在按键检测中,通常使用上拉电阻,因为按键按下时会将引脚拉低到地,这种设计更符合直觉和常见的硬件电路设计。

3.3、改动代码整合

改动后的代码可以简化成这样子,

cpp 复制代码
// ...

void app_main(void)
{
    //zero-initialize the config structure.
    gpio_config_t io_conf = {
        .intr_type = GPIO_INTR_NEGEDGE,
        .mode = GPIO_MODE_DEF_INPUT,
        .pin_bit_mask = 1<<GPIO_NUM_0,
        .pull_down_en = 0,
        .pull_up_en = 1
    };
    
    //configure GPIO with the given settings
    gpio_config(&io_conf);

}

4、中断服务函数

4.1、官方示例

同样的,首先看一下官方源码示例是怎么写的,再来看看我们需要修改哪些地方,代码注释如下:

cpp 复制代码
// 官方示例代码

#define ESP_INTR_FLAG_DEFAULT 0

static QueueHandle_t gpio_evt_queue = NULL;

static void IRAM_ATTR gpio_isr_handler(void* arg)
{
    uint32_t gpio_num = (uint32_t) arg;
    xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}

static void gpio_task_example(void* arg)
{
    uint32_t io_num;
    for (;;) {
        if (xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
            printf("GPIO[%"PRIu32"] intr, val: %d\n", io_num, gpio_get_level(io_num));
        }
    }
}

void app_main(void)
{
    // ...

    // Step1、创建了一个队列,队列消息数量为 10,gpio_evt_queue 是队列句柄
    gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
    // Step2、创建了一个任务,任务名称为 gpio_task_example
    xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);

    // Step3、启动 GPIO 中断服务,其中 ESP_INTR_FLAG_DEFAULT 的值是 0
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
    // Step4、添加某个 GPIO 的中断,第 1 个参数指定哪个 GPIO 产生中断,第 2 个参数是中断服务函数的名称,第 3 个参数是中断服务函数的参数
    gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);

    // ...
}

4.2、对应改动点说明

cpp 复制代码
gpio_isr_handler_add(GPIO_NUM_0, gpio_isr_handler, (void*) GPIO_NUM_0);

这里我们只需要添加 GPIO0 的中断,因此修改了参数1、参数3。

4.3、改动代码整合

cpp 复制代码
// ...

void app_main(void)
{
    // ...

    //create a queue to handle gpio event from isr
    gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
    //start gpio task
    xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);

    //install gpio isr service
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
    //hook isr handler for specific gpio pin
    gpio_isr_handler_add(GPIO_NUM_0, gpio_isr_handler, (void*) GPIO_NUM_0);
}

5、完整代码

cpp 复制代码
#include <stdio.h>
// #include <string.h>
// #include <stdlib.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"

#define ESP_INTR_FLAG_DEFAULT 0

static QueueHandle_t gpio_evt_queue = NULL;

static void IRAM_ATTR gpio_isr_handler(void* arg)
{
    uint32_t gpio_num = (uint32_t) arg;
    xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}

static void gpio_task_example(void* arg)
{
    uint32_t io_num;
    for (;;) {
        if (xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
            printf("GPIO[%"PRIu32"] intr, val: %d\n", io_num, gpio_get_level(io_num));
        }
    }
}

void app_main(void)
{
    //zero-initialize the config structure.
    gpio_config_t io_conf = {
        .intr_type = GPIO_INTR_NEGEDGE,
        .mode = GPIO_MODE_DEF_INPUT,
        .pin_bit_mask = 1<<GPIO_NUM_0,
        .pull_down_en = 0,
        .pull_up_en = 1
    };
    
    //configure GPIO with the given settings
    gpio_config(&io_conf);

    //create a queue to handle gpio event from isr
    gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
    //start gpio task
    xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);

    //install gpio isr service
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
    //hook isr handler for specific gpio pin
    gpio_isr_handler_add(GPIO_NUM_0, gpio_isr_handler, (void*) GPIO_NUM_0);
}

四、编译下载

配置 menu config,设置 flash size 为 16 MB,

一键烧录,

boot 按键效果,

可以看到,当我们按下 boot 键的时候,控制台输出 GPIO0 intr, val: 0 符合预期。

五、生成默认配置

编译下载后,结果没有问题的话,使用 idf.py save-defconfig 命令生成 sdkconfig.defaults 文件。这个命令要打开"命令终端"执行,看结果的"串口终端"不行。

bash 复制代码
idf.py save-defconfig 

回车执行命令后,会看到工程中多了一个 sdkconfig.defaults 文件,点击打开 sdkconfig.defaults 文件,会看到里面的内容。这个文件里面包含了我们对 menuconfig 的修改。

这时候,我们可以把工程中配置和编译生成的文件夹全部去掉,最后的文件如下所示:

使用 VSCode 重新打开工程,在选择目标芯片后,sdkconfig.defaults 文件里面的配置就配置到 menuconfig 里面了,省去了手动配置 menucofig。如果 menuconfig 里面配置的内容很多,这个文件就显得很有必要了。

相关推荐
雨田言炎1 小时前
STM32之RCC系统时钟控制(芯片的心脏控制器)
stm32·单片机·嵌入式硬件
李永奉1 小时前
日常使用的锂电池上,新、旧锂电池的差异分析
单片机·嵌入式硬件·物联网·智能手机·电脑
额额额对了2 小时前
IMX6ULL 杂项设备 LED 驱动
stm32·单片机·嵌入式硬件
长春的KAKA3 小时前
Proxmark3 Iceman分支CLI命令总表
嵌入式硬件
半杯咖啡半行码3 小时前
STM32开发之IIC通信详解:从协议基础到FM24CL04实战
stm32·单片机·嵌入式硬件
硅基手札4 小时前
【I2C 技术系列 05】I2C 多主仲裁与时钟同步
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
szxinmai主板定制专家4 小时前
面向虚拟电厂协调控制:RK3588+FPGA+AI边缘控制器设计思路与算力评估
人工智能·嵌入式硬件·fpga开发·机器人·jetson·zynq·振动采集
硅基手札5 小时前
【I2C 技术系列 01】I2C 电气特性与物理层
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
comyccccc5 小时前
TB6612电机驱动板原理图与PCB设计学习笔记
单片机·嵌入式硬件
gscsded6 小时前
MCUboot裸机移植实战
单片机·嵌入式硬件·arm