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

news/2025/2/23 13:44:49

一、基础知识

本篇我们使用 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、引入头文件

#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、官方示例

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

// 官方示例代码
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、修改引脚中断类型

//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 上单击右键,然后选择“转到定义”,就可以找到这几个宏定义,如下所示:

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 模式

//set as input mode
io_conf.mode = GPIO_MODE_INPUT;

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

改动点3、修改引脚

//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、修改打开上下拉电阻

//disable pull-down mode
io_conf.pull_down_en = 0;
//enable pull-up mode
io_conf.pull_up_en = 1;

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

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

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

3.3、改动代码整合 

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

// ...

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、官方示例

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

// 官方示例代码

#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、对应改动点说明

gpio_isr_handler_add(GPIO_NUM_0, gpio_isr_handler, (void*) GPIO_NUM_0);

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

4.3、改动代码整合

// ...

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、完整代码

#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 键的时候,控制台输出 GPIO[0] intr, val: 0 符合预期。

五、生成默认配置

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

idf.py save-defconfig 

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

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

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


http://www.niftyadmin.cn/n/5863446.html

相关文章

国产开源PDF解析工具MinerU

前言 PDF的数据解析是一件较困难的事情&#xff0c;几乎所有商家都把PDF转WORD功能做成付费产品。 PDF是基于PostScript子集渲染的&#xff0c;PostScript是一门图灵完备的语言。而WORD需要的渲染&#xff0c;本质上是PDF能力的子集。大模型领域&#xff0c;我们的目标文件格…

C++算法基础笔记

算法学习 C语法字符和字符串输出输出字符串拼接和扩充检查字符串是否存在大写、小写字母字符数组换行 C语法 字符和字符串输出输出 在C 中使用如下语法实现对容器中的对象进行遍历&#xff0c;类似于js或python的for in语法 for (element_declaration : container)#include …

L2-【英音】地道语音语调

文章目录 英音音标节奏节奏感体现词重音句重音音节的划分音节的突出元音单元音/ɑː//ɔ://u://i://ɜː////ʌ//ɒ//ʊ//ɪ//ə//e/双元音/eɪ//aɪ//aʊ//əʊ//ɔɪ//ɪə//ʊə//eə/目标 1.相较于之前有较大进步的语音面貌; 2.自己可以为自己纠正发音; 3.在听到一篇文章…

【Agent的革命之路——LangGraph】工作流中的 Command 模式

这篇文章我们将 LangGraph中的控制流&#xff08;边&#xff09;和状态更新&#xff08;节点&#xff09;结合起来使用。比如&#xff0c;我们希望同时执行状态更新并决定下一步要转到哪个节点&#xff0c;且这些操作在同一个节点中完成。而正好 LangGraph 提供了一种方法&…

3D Gaussian Splatting 数学原理与推导

概述 3D Gaussian Splatting源于EWA的理论基础,本文重点从EWA模型出发,详细推导3D GS中的数学原理,加深对鱼眼相机畸变场景下3D GS渲染结果的理解。 引言 神经辐射场(NeRF)的出现,在新视图合成领域引起了广泛关注,显著提高了合成结果的质量。然而,NeRF 训练和渲染速…

EasyX图形库使用教程

文章目录 EasyX 图形库基础使用教程&#xff08;快速上手&#xff09;前言&#xff1a;本文简单详细的介绍了EasyX图形库的常用函数和操作&#xff0c;帮助EasyX的快速上手1、绘制简单的图形窗口1.1头文件1.2创建图形化窗口initgraphy函数closegraph函数 1.3窗口坐标 2、设置图…

go 环境准备

配置路径&#xff1a; GOROOT&#xff1a;D:\GoGOPATH&#xff1a;go的工作目录 D:\workspacego 验证版本&#xff1a;go version 配置第三方仓库&#xff1a; GO111MODULE&#xff1a;开启mod模式GOPROXY&#xff1a;go语言三方库地址GOSUMDB&#xff1a;go语言软件包的M…

【ORB-SLAM3】鲁棒核函数的阈值设置

问题背景 阈值 δ \delta δ 是 Huber 鲁棒核函数的重要参数。首先给出结论&#xff0c;在ORB-SLAM系列中&#xff0c;该阈值选取的原则为&#xff1a; 单目情况下&#xff0c;根据95%置信水平下两自由度卡方检验的临界值&#xff0c; δ \delta δ 设置为 5.991 \sqrt{5.9…