文章目录
- 一、创建工程
-
- [ 1、从模版创建工程](# 1、从模版创建工程)
-
- [ 1.新项目向导](# 1.新项目向导)
- [ 2.选择 ESP-IDF 的版本。](# 2.选择 ESP-IDF 的版本。)
- [ 3.选择模版](# 3.选择模版)
- [ 4.项目设置](# 4.项目设置)
- [ 5.打开新项目](# 5.打开新项目)
- [ 2、编写代码](# 2、编写代码)
- [ 3、构建并烧录代码](# 3、构建并烧录代码)
-
- [ 1.配置烧录选项](# 1.配置烧录选项)
- [ 2.一键自动构建烧录](# 2.一键自动构建烧录)
- [ 3.执行结果](# 3.执行结果)
- [ 4、代码解析](# 4、代码解析)
-
- [ 1.首先包含所需的库:](# 1.首先包含所需的库:)
- [ 2.GPIO 引脚定义](# 2.GPIO 引脚定义)
- [ 3.GPIO 初始化](# 3.GPIO 初始化)
- [ 4.无限循环 while(1)](# 4.无限循环 while(1))
- 二、使用组件
-
- [ 1、ESP-IDF 组件概述](# 1、ESP-IDF 组件概述)
- [ 2、组件类型](# 2、组件类型)
-
- [ 1.内置组件(Core/Built-in Components):](# 1.内置组件(Core/Built-in Components):)
- [ 2.项目组件(Project Components):](# 2.项目组件(Project Components):)
- [ 3.外部组件(External/Managed Components):](# 3.外部组件(External/Managed Components):)
- [ 3、组件结构](# 3、组件结构)
-
- [ 1.源代码](# 1.源代码)
- [ 2.头文件](# 2.头文件)
- [ 3.CMakeLists.txt](# 3.CMakeLists.txt)
- [ 4.idf_component.yml](# 4.idf_component.yml)
- [ 4、项目中的组件](# 4、项目中的组件)
-
- [ 1.main/](# 1.main/)
- [ 2.components/](# 2.components/)
- [ 3.managed_components/](# 3.managed_components/)
- [ 4.idf_component.yml](# 4.idf_component.yml)
- [ 5.dependencies.lock](# 5.dependencies.lock)
- [ 5、使用内置 GPIO 组件](# 5、使用内置 GPIO 组件)
-
- [ 1.搭建电路](# 1.搭建电路)
- [ 2.包含 GPIO 库](# 2.包含 GPIO 库)
- [ 3.示例代码](# 3.示例代码)
- [ 4.构建并烧录代码](# 4.构建并烧录代码)
- [ 5.代码解析](# 5.代码解析)
- [ 6、使用外部 Button 组件](# 6、使用外部 Button 组件)
-
- [ 1.搭建电路](# 1.搭建电路)
- [ 2.包含 button 组件](# 2.包含 button 组件)
- [ 3.示例代码](# 3.示例代码)
- [ 4.构建并烧录代码](# 4.构建并烧录代码)
- [ 5.代码解析](# 5.代码解析)
- [ 7、自定义组件](# 7、自定义组件)
-
- [ 1.VS Code 命令创建组件](# 1.VS Code 命令创建组件)
- [ 2.idf.py命令创建组件](# 2.idf.py命令创建组件)
- [ 3.在main/CMakeLists.txt加入组件目录](# 3.在main/CMakeLists.txt加入组件目录)
- [ 4.清理缓存](# 4.清理缓存)
- [ 5.在main目录中添加自定义.c.h文件](# 5.在main目录中添加自定义.c.h文件)
- [ 8、板级支持包 (BSP)](# 8、板级支持包 (BSP))
- 三、工程目录
-
- [ 1、标准工程目录](# 1、标准工程目录)
- [ 2、顶层目录](# 2、顶层目录)
-
- [ 1. 顶层 CMakeLists.txt](# 1. 顶层 CMakeLists.txt)
- [ 2. sdkconfig](# 2. sdkconfig)
- [ 3. dependencies.lock](# 3. dependencies.lock)
- [ 4. build/ 编译目录](# 4. build/ 编译目录)
- [ 5. managed_components/](# 5. managed_components/)
- [ 6. .vscode/settings.json](# 6. .vscode/settings.json)
- [ 3、main主目录](# 3、main主目录)
-
- [ 1. main.c](# 1. main.c)
- [ 2. main/CMakeLists.txt](# 2. main/CMakeLists.txt)
- [ 3. main 内自定义.c/.h](# 3. main 内自定义.c/.h)
- [ 4、components 自定义组件目录](# 4、components 自定义组件目录)
- [ 5、managed_components外部组件目录](# 5、managed_components外部组件目录)
-
- [ 1. 手动添加远程组件(自动写入 yml 并下载)](# 1. 手动添加远程组件(自动写入 yml 并下载))
- [ 2. 更新所有第三方组件至符合版本区间的最新版](# 2. 更新所有第三方组件至符合版本区间的最新版)
- [ 3. 清空所有下载的第三方组件(缓存错乱时)](# 3. 清空所有下载的第三方组件(缓存错乱时))
- [ 4. 查看当前工程所有组件依赖树](# 4. 查看当前工程所有组件依赖树)
一、创建工程
1、从模版创建工程
1.新项目向导
打开 VS Code,点击 ESP-IDF 扩展,在 "Advanced" 中打开 "新项目向导"。

2.选择 ESP-IDF 的版本。

3.选择模版
在 "ESP-IDF Templates" 中选择 "sample_project" 模板,然后点击 "Create project using template sample_project"。

4.项目设置
设置项目名称,存放位置以及相关参数。开发板相关参数可以在创建项目后修改。
设置完成后点击 "Create Project"。
注意:
项目路径中不要包含空格、中文或特殊符号。

5.打开新项目
点击 "Open Project" 打开新项目。

2、编写代码
ESP-IDF 会为项目生成许多文件和文件夹。
1.main.c 文件:
c
#include <stdio.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const gpio_num_t led_pin = GPIO_NUM_7;
void app_main(void)
{
gpio_reset_pin(led_pin); // 重置引脚
gpio_set_direction(led_pin, GPIO_MODE_OUTPUT); // 设置引脚为输出模式
while (1)
{
gpio_set_level(led_pin, 1); // 设置引脚为高电平,点亮 LED
printf("LED state: ON\n"); // 打印 LED 的当前状态
vTaskDelay(pdMS_TO_TICKS(1000)); // 任务延时 1000 毫秒 (1秒)
gpio_set_level(led_pin, 0); // 设置引脚为低电平,熄灭 LED
printf("LED state: OFF\n"); // 打印 LED 的当前状态
vTaskDelay(pdMS_TO_TICKS(1000)); // 再次任务延时 1000 毫秒 (1秒)
}
}
2.代码导航与语法高亮
对于代码导航和 C/C++ 语法高亮,推荐使用 Microsoft C/C++ 扩展。
安装该插件后,VS Code 通常能自动进行代码高亮并识别宏定义、库变量等。
3.运行 idf.py reconfigure 任务
在编写代码过程中相关库出现红色波浪线提示未定义等问题,一般构建一次项目即可解决,也可以通过下面的方法修复:
C/C++ 语言扩展依赖于一个名为 compile_commands.json 的文件,该文件位于项目构建目录中。
可以使用 ESP-IDF: 运行 idf.py reconfigure 任务 来生成此文件。
使用快捷键 Ctrl + Shift + P 打开 VS Code 命令面板。然后运行 ESP-IDF: 运行 idf.py reconfigure 任务。

3、构建并烧录代码
1.配置烧录选项
首先,在构建和烧录之前,请务必检查并设置正确的目标设备、串口和烧录方式。

2.一键自动构建烧录
点击 工具栏火焰图标 一键构建烧录监视 一键自动依次执行构建、烧录和监视这三个步骤。
3.执行结果
烧录完成后,您会看到 LED 开始闪烁。同时,串口监视器会启动并输出如下日志信息:

4、代码解析
1.首先包含所需的库:
c
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
stdio.h: C 语言标准输入输出库,我们用它来调用 printf 函数向串口打印信息。
driver/gpio.h: ESP-IDF 提供的 GPIO 驱动库,包含了配置和操作 GPIO 引脚所需的函数,例如设置引脚方向、读写引脚电平等。
freertos/FreeRTOS.h 和 freertos/task.h: ESP-IDF 使用 FreeRTOS 作为其实时操作系统。这两个头文件提供了操作系统核心和任务管理相关的 API。在这里,我们主要使用 vTaskDelay 函数来实现精确的延时。
2.GPIO 引脚定义
c
static const gpio_num_t led_pin = GPIO_NUM_7;
这行代码定义了一个常量 led_pin 来表示 LED 所连接的 GPIO 引脚号。
gpio_num_t 是 ESP-IDF 中用于表示 GPIO 编号的枚举类型。
GPIO_NUM_7 是该枚举中的一个取值,表示编号为 7 的通用 IO 引脚(GPIO7)。从可读性与类型安全角度,建议使用 GPIO_NUM_7 而不是直接写字面值 7。
备注
不同芯片对 GPIO7 的可用性与限制不同,请检查所用你的开发板的引脚定义。
3.GPIO 初始化
c
gpio_reset_pin(led_pin); // 重置引脚
gpio_set_direction(led_pin, GPIO_MODE_OUTPUT); // 设置引脚为输出模式
在 app_main 函数内部,首先对 GPIO 引脚进行初始化设置。
gpio_reset_pin(led_pin);: 在配置引脚前先将其重置为默认状态,可以避免一些意外问题。
gpio_set_direction(led_pin, GPIO_MODE_OUTPUT);: 这行代码将选定的 led_pin (GPIO 7) 设置为输出模式。
4.无限循环 while(1)
c
在 FreeRTOS 任务函数中,通常会使用 while(1) 实现任务的持续运行。这样任务会一直被 FreeRTOS 调度器管理,除非主动调用 vTaskDelete() 删除任务,否则不会退出。无限循环配合 vTaskDelay() 等函数,可以让任务周期性地执行操作,同时让出 CPU 给其他任务,实现多任务并发。
while (1)
{
gpio_set_level(led_pin, 1); // 设置引脚为高电平,点亮 LED
printf("LED state: ON\n"); // 打印 LED 的当前状态
vTaskDelay(pdMS_TO_TICKS(1000)); // 任务延时 1000 毫秒 (1秒)
gpio_set_level(led_pin, 0); // 设置引脚为低电平,熄灭 LED
printf("LED state: OFF\n"); // 打印 LED 的当前状态
vTaskDelay(pdMS_TO_TICKS(1000)); // 再次任务延时 1000 毫秒 (1秒)
}
gpio_set_level(led_pin, 1);:
设置 led_pin 引脚的输出高电平。
printf():
ESP-IDF 支持标准 C 语言的 printf(),可将信息输出到串口终端,便于调试和状态监控。
vTaskDelay(pdMS_TO_TICKS(1000));:
该函数让当前 FreeRTOS 任务延时指定的 tick 数,延时期间任务进入阻塞态,释放 CPU 使用权。
pdMS_TO_TICKS(1000) 宏将 1000 毫秒转换为 tick 数,确保延时为 1000 毫秒,此过程是非阻塞的。
二、使用组件
1、ESP-IDF 组件概述

ESP-IDF 采用组件化设计,将系统的各项功能(如操作系统、网络协议栈、驱动、外设支持等)划分为独立的"组件"(Component)。
每个组件是一个可复用、独立的代码包,专注于实现某一特定功能。
在项目构建时,组件会被编译为静态库,并由主应用程序或其他组件进行链接和调用。
在此架构基础上,开发者可以灵活地添加自定义组件或集成第三方组件(如特定的云服务、协议或驱动)。
通过将外部组件与 ESP-IDF 的内部核心组件相结合,可实现项目功能的扩展与定制,进而达成项目整体的模块化与高效复用。
带来的优势包括:清晰的分层与依赖管理、代码复用、易于扩展与更新,从而降低项目复杂度,加快开发迭代。
2、组件类型
在 ESP-IDF 项目中,组件主要分为以下三类:
1.内置组件(Core/Built-in Components):
这些是 ESP-IDF 框架自带的核心组件,位于 ESP-IDF 安装目录下的 components 文件夹中,提供底层驱动、网络协议栈、FreeRTOS 操作系统等基础功能。
开发者可以直接在代码中包含其头文件并使用,无需额外配置。
2.项目组件(Project Components):
这些是开发者在项目根目录下的 components 文件夹中创建的组件,适合存放项目特有、可复用的功能模块,有助于保持主逻辑 main 的整洁和项目的模块化。
3.外部组件(External/Managed Components):
这些组件由社区或第三方开发者创建,并发布到 ESP-IDF 组件注册表 (ESP Component Registry)。
可以通过 IDF 组件管理器 自动下载和集成到项目中,下载后会存放在项目根目录下的 managed_components 文件夹中。
3、组件结构
一个完整的 ESP-IDF 组件通常包含以下内容:
1.源代码
组件实现的核心功能代码文件。
2.头文件
对外暴露的接口声明,供其他组件或主程序调用。
3.CMakeLists.txt
定义源代码和头文件的编译方式。
声明组件依赖关系。
注册组件到构建系统。
配置可选特性。
作为 CMake 构建描述文件,指示编译器如何编译、链接和构建该组件。
4.idf_component.yml
组件管理器描述文件,列出该组件依赖的其他组件及其版本信息。ESP-IDF 组件管理器会根据此文件自动下载和集成所需依赖,确保依赖关系满足。
4、项目中的组件
一个包含组件的项目的目录结构示例如下:
c
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/
1.main/
项目主组件目录,包含项目的主要源代码。
main 目录下通常有自己的 CMakeLists.txt 和可选的 idf_component.yml,用于声明主组件的依赖关系。
应用程序必须包含一个 main 组件(名称可更改),这是保存应用程序逻辑的主要组件。
2.components/
项目自定义组件目录。每个子目录为一个组件,包含源代码、头文件、CMakeLists.txt、Kconfig 等。
可用于组织可复用代码或引入第三方组件。
若有同名组件,优先使用 components/ 下的版本。
3.managed_components/
由 IDF 组件管理器 自动创建,用于存放通过组件管理器下载的托管组件。
每个托管组件通常包含 idf_component.yml,定义组件元数据和依赖关系。
请勿手动修改该目录内容,如需修改,可将组件复制到 components/ 目录下。
4.idf_component.yml
组件管理器描述文件,声明组件的元数据及其依赖项。
可存在于 main/、components/ 下的每个组件目录,以及 managed_components/ 下的托管组件目录。
该文件是可选的,仅在需要声明依赖时才需要。
5.dependencies.lock
由 IDF 组件管理器 自动生成,记录当前项目使用的所有托管组件及其精确版本。
请勿手动修改,只有当项目中存在 idf_component.yml 文件时才会生成该文件。
5、使用内置 GPIO 组件
通过使用 ESP-IDF 内置的 esp_driver_gpio 组件 来读取按钮的电平状态。
1.搭建电路
需要使用的器件有:
按钮 * 1
面包板 * 1
导线
ESP32 开发板
ESP32-S3 引脚:
3v3<-按钮->gpio7
2.包含 GPIO 库
创建一个项目。
在包含 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)
3.示例代码
将以下代码复制到 main/main.c 中:
c
#include <stdio.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define BUTTON_GPIO GPIO_NUM_7 // 按钮连接的 GPIO 引脚
void app_main(void)
{
// 配置 GPIO
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << BUTTON_GPIO), // 选中 GPIO7
.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); // 读取 GPIO 电平
printf("Button value: %d\n", level);
vTaskDelay(pdMS_TO_TICKS(20)); // 延时 20 毫秒,避免独占 CPU
}
}
4.构建并烧录代码
配置烧录选项:
检查并设置正确的目标设备、串口和烧录方式。
烧录:
一键自动依次执行构建、烧录和监视这三个步骤。
执行结果:
烧录完成后,串口监视器会开始打印信息。
当按钮未按下时,由于内部上拉电阻的作用,GPIO7 读取到高电平,串口监视器输出 Button value: 1。
当按钮被按下时,GPIO7 被连接到 GND,读取到低电平,串口监视器输出 Button value: 0。

5.代码解析
c
包含头文件:
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
driver/gpio.h: 包含了配置和操作 GPIO 所需的函数声明和类型定义。
freertos/FreeRTOS.h 和 freertos/task.h: 提供 FreeRTOS 操作系统 API,我们使用 vTaskDelay 来实现非阻塞延时。
定义 GPIO 引脚:
#define BUTTON_GPIO GPIO_NUM_7
使用宏定义将按钮连接的引脚(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 组件
1.搭建电路
需要使用的器件有:
按钮 * 1
面包板 * 1
导线
ESP32 开发板
ESP32-S3 引脚:
3v3<-按钮->gpio7
2.包含 button 组件
对于外部库(button),使用组件管理器和注册表。
1)创建一个项目。
2)前往 组件注册表(ESP Component Registry)。
https://components.espressif.com/
3)搜索 "button" 组件(espressif/button)
espressif/button
4)复制右侧的指令:
idf.py add-dependency "espressif/button^4.2.0"

5)点击 打开 ESP-IDF 终端,粘贴命令。

注意:
添加依赖后,需要执行一次完整清理,使旧的构建缓存失效。
下次构建时,构建系统会重新运行 CMake 配置,自动下载新组件并注册其头文件路径。
在 VS Code 中执行 > ESP-IDF: Full Clean Project,或在终端执行:
idf.py fullclean
6)包含头文件
在代码中包含相应的头文件并调用组件文档和文件夹中提供的函数。
3.示例代码
c
#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 (ret != ESP_OK)
{
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));
}
}
4.构建并烧录代码
配置烧录选项:
首先,在构建和烧录之前,请务必检查并设置正确的目标设备、串口和烧录方式。
烧录:
点击 一键自动依次执行构建、烧录和监视这三个步骤。
执行结果:
烧录完成后,串口监视器会开始打印信息。
当单击按钮时,监视器将输出:I (xxxx) button_example: BUTTON_SINGLE_CLICK
当快速双击按钮时,监视器将输出:I (xxxx) button_example: BUTTON_DOUBLE_CLICK

5.代码解析
c
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 (ret != ESP_OK)
{
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、自定义组件
1.VS Code 命令创建组件
使用快捷键 Ctrl + Shift + P 打开 VS Code 命令面板。然后运行 > ESP-IDF: 创建新 ESP-IDF 组件。

2.idf.py命令创建组件
idf.py create-component <组件名>
idf.py create-component Test2
进入组件目录,否则就会建立在当前目录下。
3.在main/CMakeLists.txt加入组件目录
idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
INCLUDE_DIRS ".../components/Test/include" //组件Test头文件目录
INCLUDE_DIRS ".../components/Test2/include"//组件Test1头文件目录
REQUIRES esp_driver_gpio)
4.清理缓存
每次创建或下载新组件后,都需要执行一次完整清理,使旧的构建缓存失效。
下次构建时,构建系统会重新运行 CMake 配置,正确识别并集成新组件。
在 VS Code 中执行 > ESP-IDF: Full Clean Project,或在终端执行:
idf.py fullclean
5.在main目录中添加自定义.c.h文件
main文件中直接添加头文件即可使用。
c
s3_demo/
├─ main/
│ ├─ main.c // 主程序、FreeRTOS任务入口
│ ├─ led_driver.c // 自定义源文件
│ └─ led_driver.h // 对应头文件
├─ CMakeLists.txt // 工程编译配置
└─ sdkconfig
8、板级支持包 (BSP)
板级支持包(BSP,Board Support Package)是在 ESP-IDF 中以组件形式提供的、针对特定开发板的硬件抽象与初始化包。
它封装了板载外设(如显示屏、触摸、音频编解码器、SD 卡、LED、按钮等)的引脚配置与驱动初始化,提供统一的 API,便于快速上手、跨板复用代码并减少配置错误。
像任何 ESP-IDF 组件一样,BSP 可以通过组件管理器使用 idf_component.yml 集成到项目中。
三、工程目录
1、标准工程目录
c
esp32s3_demo/ # 工程根目录(VSCode必须打开此文件夹)
├─ .vscode/ # VSCode IDE配置,不参与编译
│ ├─ settings.json # EIM路径、目标芯片、串口、插件参数
│ ├─ launch.json # OpenOCD/JTAG调试配置
│ └─ extensions.json # 推荐插件清单
├─ components/ # 自定义本地组件(模块化开发核心)
│ └─ led_driver/
│ ├─ idf_component.yml # 组件版本、依赖、芯片兼容声明
│ ├─ CMakeLists.txt # 编译注册脚本
│ ├─ led_driver.c # 驱动实现
│ └─ led_driver.h # 对外头文件
├─ main/ # 程序唯一入口目录(强制存在)
│ ├─ main.c # app_main() 上电入口
│ ├─ custom_func.c # main内自定义源文件
│ ├─ custom_func.h
│ └─ CMakeLists.txt # main编译脚本(高频修改)
├─ build/ # 编译产物(自动生成,fullclean可清空)
│ ├─ *.bin # 烧录固件:bootloader、分区表、app.bin
│ ├─ *.elf # JTAG调试镜像
│ ├─ ninja构建缓存、obj目标文件
│ └─ config/ # sdkconfig编译缓存
├─ managed_components/ # 组件管理器自动拉取的第三方远程组件
├─ sdkconfig # menuconfig全局配置存储文件(日志/USB-JTAG/FreeRTOS)
├─ sdkconfig.defaults # 工程默认配置模板,批量量产统一参数
├─ dependencies.lock # 第三方组件版本锁定文件(v6新增强化依赖管理)
├─ CMakeLists.txt # 工程顶层编译入口(固定模板)
└─ README.md # 工程说明文档
2、顶层目录
1. 顶层 CMakeLists.txt
v6.0.1 模板无改动,固定写法,仅修改工程名:
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(esp32s3_demo) # 工程名
作用:加载 IDF 编译工具链,定义固件输出名称。
2. sdkconfig
v6.0.1 日志、USB-JTAG、FreeRTOS、双核调度、PSRAM 全部配置保存在此;
禁止手动编辑,统一使用 idf.py menuconfig 修改;
切换 IDF 版本 / 芯片型号建议删除,重建配置避免缓存冲突。
3. dependencies.lock
v5.x 无强制锁文件,v6.0.1 组件管理器强制支持版本锁定;
执行 idf.py update-dependencies 生成,多人协作、量产固定第三方组件版本,防止自动升级导致编译报错。
4. build/ 编译目录
v6 编译逻辑优化,缓存结构调整:
一键清理:idf.py fullclean,完整删除 build 文件夹;
核心烧录文件:build/esp32s3_demo.bin(合并分区表 + bootloader + 应用);
.elf 文件:USB-JTAG 硬件断点调试必备。
5. managed_components/
v6 组件管理器大幅优化,idf.py add-dependency 拉取的第三方库全部存放于此;
依赖规则由 components/xxx/idf_component.yml 定义。
6. .vscode/settings.json
存储 idf.eimIdfJsonPath 路径、默认芯片esp32s3、串口 COM;
报错 Cannot read properties of undefined (reading 'idfPath') 根源:路径错误 / 未打开工程文件夹 / EIM 未激活 IDF。
3、main主目录
1. main.c
唯一入口函数 void app_main(void),芯片上电自动执行;
所有硬件初始化、FreeRTOS 任务创建、业务逻辑起点。
2. main/CMakeLists.txt
核心函数 idf_component_register(),新增.c必须写入SRCS,否则报undefined reference:
idf_component_register(
SRCS "main.c" "custom_func.c"
INCLUDE_DIRS "."
REQUIRES driver freertos log usb esp_usb_jtag
)
参数说明:
SRCS:参与编译的所有.c源文件;
INCLUDE_DIRS:头文件搜索路径,.代表当前 main 文件夹;
REQUIRES:依赖 IDF 内置组件(v6 内置组件名无变更)。
3. main 内自定义.c/.h
适合小型工程;多外设、多任务大型项目推荐拆分至components。
4、components 自定义组件目录
每个独立功能模块单独文件夹,配套 4 个文件:
xxx.c:函数实现,内部变量加static私有化;
xxx.h:对外接口、宏、结构体,全局可#include "xxx.h";
CMakeLists.txt:编译注册,控制源码、头文件、本地依赖;
idf_component.yml:v6 强化组件管理,声明版本、芯片兼容、远程依赖。
5、managed_components外部组件目录
managed_components 是 ESP Component Manager(ECM 组件管理器) 自动创建、自动维护的第三方组件存放目录,完全自动生成,禁止手动修改、手动新增文件。
1. 手动添加远程组件(自动写入 yml 并下载)
idf.py add-dependency esp_lcd^1.4.5
执行后:
自动在对应组件的 idf_component.yml 添加依赖;
自动下载代码到 managed_components/esp_lcd;
更新 dependencies.lock。
2. 更新所有第三方组件至符合版本区间的最新版
idf.py update-dependencies
3. 清空所有下载的第三方组件(缓存错乱时)
Remove-Item -Recurse -Force managed_components
idf.py fullclean
idf.py build
4. 查看当前工程所有组件依赖树
idf.py component-deps