跳到主要内容

第4节 使用组件

重要提示:关于开发板的兼容性

本教程的核心逻辑适用于所有 ESP32 开发板,但所有操作步骤均以 微雪 ESP32-S3-Zero 迷你开发板 为例进行讲解。如果您使用其他型号的开发板,请根据实际情况修改相应设置。

本节介绍 ESP-IDF 组件系统的基本概念,包括组件的类型与结构,并通过内置组件 (GPIO) 和外部组件 (Button) 的示例,说明其在项目中的应用。

1. ESP-IDF 组件概述

ESP-IDF 组件

ESP-IDF 采用组件化设计,将系统的各项功能(如操作系统、网络协议栈、驱动、外设支持等)划分为独立的“组件”(Component)。每个组件是一个可复用、独立的代码包,专注于实现某一特定功能。在项目构建时,组件会被编译为静态库,并由主应用程序或其他组件进行链接和调用。

在此架构基础上,开发者可以灵活地添加自定义组件或集成第三方组件(如特定的云服务、协议或驱动)。通过将外部组件与 ESP-IDF 的内部核心组件相结合,可实现项目功能的扩展与定制,进而达成项目整体的模块化与高效复用。带来的优势包括:清晰的分层与依赖管理、代码复用、易于扩展与更新,从而降低项目复杂度,加快开发迭代。

2. 组件类型

在 ESP-IDF 项目中,组件主要分为以下三类:

  • 内置组件(Core/Built-in Components):这些是 ESP-IDF 框架自带的核心组件,位于 ESP-IDF 安装目录下的 components 文件夹中,提供底层驱动、网络协议栈、FreeRTOS 操作系统等基础功能。开发者可以直接在代码中包含其头文件并使用,无需额外配置。

  • 项目组件(Project Components):这些是开发者在项目根目录下的 components 文件夹中创建的组件,适合存放项目特有、可复用的功能模块,有助于保持主逻辑 main 的整洁和项目的模块化。

  • 外部组件(External/Managed Components):这些组件由社区或第三方开发者创建,并发布到 ESP-IDF 组件注册表 (ESP Component Registry)。可以通过 IDF 组件管理器 自动下载和集成到项目中,下载后会存放在项目根目录下的 managed_components 文件夹中。

3. 组件结构

一个完整的 ESP-IDF 组件通常包含以下内容:

  • 源代码
    组件实现的核心功能代码文件。
  • 头文件
    对外暴露的接口声明,供其他组件或主程序调用。
  • CMakeLists.txt
    • 定义源代码和头文件的编译方式
    • 声明组件依赖关系
    • 注册组件到构建系统
    • 配置可选特性
    • 作为 CMake 构建描述文件,指示编译器如何编译、链接和构建该组件
  • idf_component.yml
    组件管理器描述文件,列出该组件依赖的其他组件及其版本信息。ESP-IDF 组件管理器会根据此文件自动下载和集成所需依赖,确保依赖关系满足。

4. 项目中的组件

一个包含组件的项目的目录结构示例如下:

myProject/
├── CMakeLists.txt
├── sdkconfig
├── dependencies.lock
├── main/
│ ├── CMakeLists.txt
│ ├── main.c
│ ├── src1.c
│ └── idf_component.yml
├── components/
│ ├── component1/
│ │ ├── CMakeLists.txt
│ │ ├── Kconfig
│ │ └── src1.c
│ └── component2/
│ ├── CMakeLists.txt
│ ├── Kconfig
│ ├── src1.c
│ └── include/
│ └── component2.h
├── managed_components/
│ └── namespace__component-name/
│ ├── CMakelists.txt
│ ├── idf_component.yml
│ ├── src1.c
│ └── include/
│ └── src1.h
└── build/
  • main/
    项目主组件目录,包含项目的主要源代码。main 目录下通常有自己的 CMakeLists.txt 和可选的 idf_component.yml,用于声明主组件的依赖关系。应用程序必须包含一个 main 组件(名称可更改),这是保存应用程序逻辑的主要组件。

  • components/
    项目自定义组件目录。每个子目录为一个组件,包含源代码、头文件、CMakeLists.txt、Kconfig 等。可用于组织可复用代码或引入第三方组件。若有同名组件,优先使用 components/ 下的版本。

  • managed_components/
    IDF 组件管理器 自动创建,用于存放通过组件管理器下载的托管组件。每个托管组件通常包含 idf_component.yml,定义组件元数据和依赖关系。请勿手动修改该目录内容。如需修改,可将组件复制到 components/ 目录下。

  • idf_component.yml
    组件管理器描述文件,声明组件的元数据及其依赖项。可存在于 main/components/ 下的每个组件目录,以及 managed_components/ 下的托管组件目录。该文件是可选的,仅在需要声明依赖时才需要。

  • dependencies.lock
    IDF 组件管理器 自动生成,记录当前项目使用的所有托管组件及其精确版本。请勿手动修改。只有当项目中存在 idf_component.yml 文件时才会生成该文件。

5. 示例:使用内置 GPIO 组件

通过使用 ESP-IDF 内置的 esp_driver_gpio 组件 来读取按钮的电平状态。

5.1 搭建电路

需要使用的器件有:

ESP32-S3-Zero 引脚图

ESP32-S3-Zero-Pinout

接线图

5.2 包含 GPIO 库

  1. 创建一个项目。如果不清楚如何操作,请参考 从模板创建项目

  2. 在包含 ESP-IDF 内置组件前,请先查看 相应组件的文档。根据文档中的指引完成以下步骤。

    首先在 main.c 中包含头文件:

    #include "driver/gpio.h"

    然后在 main/CMakeLists.txt 中声明 esp_driver_gpio 组件:

    idf_component_register(SRCS "main.c"
    INCLUDE_DIRS "."
    REQUIRES esp_driver_gpio)

5.3 示例代码

将以下代码复制到 main/main.c 中:

#include <stdio.h>

#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define BUTTON_GPIO GPIO_NUM_7

void app_main(void)
{
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << BUTTON_GPIO),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&io_conf);

while (1) {
int level = gpio_get_level(BUTTON_GPIO);
printf("Button value: %d\n", level);
vTaskDelay(pdMS_TO_TICKS(20));
}
}

5.4 构建并烧录代码

  1. 配置烧录选项

    首先,在构建和烧录之前,请务必检查并设置正确的目标设备、串口和烧录方式。参考 第 2 节 运行示例 - 1.3 配置项目

    VS Code 工具栏

  2. 点击 VS Code 一键构建烧录监视图标 一键自动依次执行构建、烧录和监视这三个步骤。

  3. 烧录完成后,串口监视器会开始打印信息。

    • 当按钮未按下时,由于内部上拉电阻的作用,GPIO7 读取到高电平,串口监视器输出 Button value: 1
    • 当按钮被按下时,GPIO7 被连接到 GND,读取到低电平,串口监视器输出 Button value: 0

5.5 代码解析

  • 包含头文件

    #include "driver/gpio.h"
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    • driver/gpio.h: 包含了配置和操作 GPIO 所需的函数声明和类型定义。
    • freertos/FreeRTOS.hfreertos/task.h: 提供 FreeRTOS 操作系统 API,我们使用 vTaskDelay 来实现非阻塞延时。
  • 定义 GPIO 引脚

    #define BUTTON_GPIO GPIO_NUM_7

    使用宏定义将按钮连接的引脚(GPIO7)赋予一个有意义的名称,便于阅读和修改。

    备注

    不同芯片对 GPIO7 的可用性与限制不同,请检查你所用的开发板的引脚定义。

  • 配置 GPIO

    gpio_config_t io_conf = {
    .pin_bit_mask = (1ULL << BUTTON_GPIO),
    .mode = GPIO_MODE_INPUT,
    .pull_up_en = GPIO_PULLUP_ENABLE,
    .pull_down_en = GPIO_PULLDOWN_DISABLE,
    .intr_type = GPIO_INTR_DISABLE
    };
    gpio_config(&io_conf);
    • 使用 gpio_config_t 结构体来一次性配置所有参数。
    • .mode = GPIO_MODE_INPUT: 将引脚设置为输入模式,以读取外部电平。
    • .pull_up_en = GPIO_PULLUP_ENABLE: 启用内部上拉电阻。当按钮未按下时,此电阻将引脚电平拉高至 VCC,确保一个稳定的默认高电平状态,避免引脚悬空。
    • .pin_bit_mask: 指定要配置的引脚,通过位移操作 (1ULL << BUTTON_GPIO) 来选中 GPIO7。表达式 (1ULL << BUTTON_GPIO) 是一种高效的位操作。1ULL 代表一个 64 位无符号长整型 1,将其左移 BUTTON_GPIO(即 7)位,会生成一个仅在第 7 位为 1 的位掩码,从而精确地选中 GPIO7
  • 主循环

    while (1) {
    int level = gpio_get_level(BUTTON_GPIO);
    printf("Button value: %d\n", level);
    vTaskDelay(pdMS_TO_TICKS(20));
    }
    • while(1) 循环持续执行,不断检测按钮状态。
    • gpio_get_level(BUTTON_GPIO): 读取指定 GPIO 引脚的当前电平状态(0 或 1)。
    • vTaskDelay(...): 让当前任务暂停一小段时间(20 毫秒),将 CPU 时间让给其他任务。这在循环中至关重要,可以防止任务独占 CPU 资源,是 FreeRTOS 编程的基本实践。

6. 示例:使用外部 Button 组件

上面的例子只是一个简单的示例,在实际应用中还要考虑按键去抖 (debounce)、中断等复杂情况。使用现成的组件可以帮助开发者简化这一过程。

下面我们将使用社区提供的 espressif/button 组件来管理按键的去抖与事件处理。

6.1 搭建电路

需要使用的器件有:

ESP32-S3-Zero 引脚图

ESP32-S3-Zero-Pinout

接线图

6.2 包含 button 组件

对于外部库(button),我们将使用组件管理器和注册表。

  1. 前往 组件注册表(ESP Component Registry)

  2. 搜索 "button" 组件(espressif/button

  3. 复制右侧的指令:

    idf.py add-dependency "espressif/button^4.1.3"

    在组件注册表中复制添加组件指令

  4. 点击 VS Code 打开 ESP-IDF 终端按钮 打开 ESP-IDF 终端,粘贴命令。

    执行添加依赖命令

  5. 此外,还需要在代码中包含相应的头文件并调用组件文档和文件夹中提供的函数。详见代码部分。

6.3 示例代码

#include <stdio.h>

#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "iot_button.h"
#include "button_gpio.h"

#define BUTTON_GPIO GPIO_NUM_7
#define BUTTON_ACTIVE_LEVEL 0

static const char *TAG = "button_example";

// 单击回调函数
static void button_single_click_cb(void *arg, void *usr_data)
{
ESP_LOGI(TAG, "BUTTON_SINGLE_CLICK");
}

// 双击回调函数
static void button_double_click_cb(void *arg, void *usr_data)
{
ESP_LOGI(TAG, "BUTTON_DOUBLE_CLICK");
}

void app_main(void)
{
// 定义按键配置
const button_config_t btn_cfg = {0};
const button_gpio_config_t btn_gpio_cfg = {
.gpio_num = BUTTON_GPIO, // 按键连接的GPIO编号
.active_level = BUTTON_ACTIVE_LEVEL, // 有效电平(0为低电平有效,1为高电平有效)
};

// 创建按键句柄
button_handle_t gpio_btn = NULL;
esp_err_t ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &gpio_btn);
if (gpio_btn == NULL)
{
ESP_LOGE(TAG, "Button create failed");
return;
}

// 注册单击事件
iot_button_register_cb(gpio_btn, BUTTON_SINGLE_CLICK, NULL, button_single_click_cb, NULL);

// 注册双击事件
iot_button_register_cb(gpio_btn, BUTTON_DOUBLE_CLICK, NULL, button_double_click_cb, NULL);

// 主循环
while (1)
{
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

6.4 构建并烧录代码

  1. 配置烧录选项

    首先,在构建和烧录之前,请务必检查并设置正确的目标设备、串口和烧录方式。参考 第 2 节 运行示例 - 1.3 配置项目

    VS Code 工具栏

  2. 点击 VS Code 一键构建烧录监视图标 一键自动依次执行构建、烧录和监视这三个步骤。

  3. 烧录完成后,串口监视器会开始打印信息。

    • 当单击按钮时,监视器将输出:I (xxxx) button_example: BUTTON_SINGLE_CLICK
    • 当快速双击按钮时,监视器将输出:I (xxxx) button_example: BUTTON_DOUBLE_CLICK

6.5 代码解析

espressif/button 组件支持检测多种按键事件,如按下、释放、单击、双击、连续点击、长按开始、长按保持和长按释放等,并可为每种事件注册回调函数。

每个按键事件都可以注册一个回调函数。当事件发生时,组件会自动调用对应的回调函数,具有高效性和实时性,不会丢失事件。

  • 包含头文件

    #include "iot_button.h"
    #include "button_gpio.h"
    • iot_button.h 提供按键组件的核心 API 和事件定义。
    • button_gpio.h 提供 GPIO 按键的特定配置结构体和创建函数。该组件还支持 ADC 按键和矩阵按键。
  • 定义回调函数

    // 单击回调函数
    static void button_single_click_cb(void *arg, void *usr_data)
    {
    ESP_LOGI(TAG, "BUTTON_SINGLE_CLICK");
    }

    // 双击回调函数
    static void button_double_click_cb(void *arg, void *usr_data)
    {
    ESP_LOGI(TAG, "BUTTON_DOUBLE_CLICK");
    }

    针对不同的按键事件(如单击、双击)定义专门的回调函数。当组件检测到相应事件时,会自动调用这些函数。这里我们通过日志打印出检测到的事件信息。

  • 配置并创建 GPIO 按键

    const button_config_t btn_cfg = {0};
    const button_gpio_config_t btn_gpio_cfg = {
    .gpio_num = BUTTON_GPIO,
    .active_level = BUTTON_ACTIVE_LEVEL,
    };

    button_handle_t gpio_btn = NULL;
    esp_err_t ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &gpio_btn);
    if (gpio_btn == NULL) {
    ESP_LOGE(TAG, "Button create failed");
    return;
    }

    button_config_t 用于通用配置,button_gpio_config_t 用于 GPIO 相关参数。

    使用 iot_button_new_gpio_device() 创建 GPIO 按键实例。

  • 注册事件回调

    iot_button_register_cb(gpio_btn, BUTTON_SINGLE_CLICK, NULL, button_single_click_cb, NULL);
    iot_button_register_cb(gpio_btn, BUTTON_DOUBLE_CLICK, NULL, button_double_click_cb, NULL);
    • iot_button_register_cb() 函数的作用是将一个事件和一个回调函数“绑定”起来。
    • 第一个参数是按键句柄。
    • 第二个参数是事件类型,例如 BUTTON_SINGLE_CLICK(单击)或 BUTTON_DOUBLE_CLICK(双击)。更多事件请参考:按键事件
    • 第三个参数是 event_args,用于传递事件参数。对于普通事件可传入 NULL;对于需要自定义的特殊事件(如长按时长、多次点击等),则需传入相应的参数。
    • 第四个参数为回调函数。
    • 第五个参数为用户数据指针。
  • 主循环

    此组件的事件检测和回调函数调用是在其内部任务(由 FreeRTOS 软定时器驱动)中完成的,因此我们无需在主循环 while(1) 中编写任何轮询代码。保留一个空的 while(1) 循环是为了让 app_main() 不会提前返回,以保证主任务持续运行,系统稳定可靠。

7. 附录:自定义组件

有两种方式可帮助你快速创建一个组件,你也可以手动创建:

  • VS Code 命令

    使用快捷键 Ctrl + Shift + P 打开 VS Code 命令面板。然后运行 > ESP-IDF: 创建新 ESP-IDF 组件

    VS Code 创建新 ESP-IDF 组件

  • idf.py

    idf.py create-component <组件名>

8. 附录:板级支持包 (BSP)

板级支持包(BSP,Board Support Package)是在 ESP-IDF 中以组件形式提供的、针对特定开发板的硬件抽象与初始化包。它封装了板载外设(如显示屏、触摸、音频编解码器、SD 卡、LED、按钮等)的引脚配置与驱动初始化,提供统一的 API,便于快速上手、跨板复用代码并减少配置错误。

像任何 ESP-IDF 组件一样,BSP 可以通过组件管理器使用 idf_component.yml 集成到项目中。

9. 参考链接