下面的内容编写时间跨度有点大,乱了得一团,也没ai整理。食之无味,弃之可惜。

推荐笔记:ESP32 之 ESP-IDF 教学(十八)—— 组件配置(KConfig)
推荐笔记:Kconfig 拓展
乐鑫组件库,网页在线

一、准备工程

  • 先重新弄一个工程,也算是复习一下上一节课的内容。这次换一种方式创建。

在这里插入图片描述

  • 选择模板

在这里插入图片描述

  • 最后结果如下图. 编译下载监听调试都没有问题。再开始下一步。

在这里插入图片描述

二、添加或创建组件

1) 添加组件

  • 乐鑫官方有一个在线库,可以方便 添加组件 ,使用以下指令打开主页。
ESP-IDF: 乐鑫组件注册表 
ESP-IDF: Show ESP Component Registry
  • 添加 点灯组件 led_strip 和 按钮组件 button ,注意版本,不同版本直接差别可能较大!!!
  • 两个组件的官方介绍页面为:LED 指示灯按键

在这里插入图片描述

  • 官方组件添加很方便,直接下载即可。编译没有问题。
  • 虽然只添加了两个组件,但是下载了三个,其中一个应该是依赖,被一同下载了。
  • 官方组件是不允许修改的,每次编译都会检查和确保哈希值。一般创建自己的组件,然后依赖,在其基础上实现功能或改动。应该是不建议直接复制一份然后修改

在这里插入图片描述

2) 创建组件

  • 下面使用vscode的一键 创建组件 功能,快速弄两个模板组件。

在这里插入图片描述

  • 如何就可以看到大致结构如下,创建两个组件实现对提示灯和按键的测试或调用。
  • 创建多一些测试文件,最后结构如下:

在这里插入图片描述

自动创建的 .h 头文件居然没有 ifndef 宏定义保护。我个人还是喜欢加保护。这样以防万一。在个人工程里,嵌套调用还是很频繁的。

  • 往代码里填写一些测试内容,按钮的测试内容如下,闪灯的内容类似。
  • 注意 CMakeLists.txt 简单理解,添加编译路径,指定参与编译的文件。
/* 以下是 CMakeLists.txt 内容 */
idf_component_register(SRCS "button_test.c" "examples/button_examples.c" 
                    INCLUDE_DIRS "include") 
// 因为 CMakeLists.txt 中没有添加 examples 文件路径, 所以下面使用相对路径

/* 以下是 button_examples.c 内容 */
#include <stdio.h>
#include "button_examples.h"
#include "esp_log.h"

static const char *TAG = "button_examples.c";

void button_examples_func(void)
{
    ESP_LOGI(TAG, "button_examples_func Start!");

}

/* 以下是 button_test.c 内容 */
#include <stdio.h>
#include "button_test.h"
#include "examples/button_examples.h" // 注意这里是使用了相对路径 
#include "esp_log.h" // 组件默认包含了 esp-idf 库, 直接引用

static const char *TAG = "button_test.c";

void button_test_func(void)
{
    ESP_LOGI(TAG, "button_test_func Start!");
    button_examples_func();
}

/* 以下是 main.c 内容 */
#include <stdio.h>
#include "esp_log.h"

#include "led_test.h"
#include "button_test.h"
// #include "examples/button_examples.h" 无法导入 button_examples.h 文件,起到隔离保护作用

const char *TAG = "main.c";

void app_main(void)
{
    ESP_LOGI(TAG, "app_main Start!");

    led_test_func();
    button_test_func();
}

/* 编译运行监听内容如下 */
I (236) main_task: Started on CPU0
I (246) main_task: Calling app_main()
I (246) main.c: app_main Start!
I (246) led_test.c: led_test_func Start!
I (246) led_examples.c: led_examples_func Start!
I (256) button_test.c: button_test_func Start!
I (256) button_examples.c: button_examples_func Start!
I (266) main_task: Returned from app_main()

四、组件路径依赖

本文只介绍 ESP-IDF 推荐的规范格式,有一些非规范的操作也可以灵活实现目的,并不推荐也不介绍

1) 组件路径/目录

  • ESP-IDF 默认仅从以下位置查找组件:

    • $IDF_PATH/components/(ESP-IDF 内置组件,如 driveresp_wifi 等)
    • $PROJECT_PATH/components/(当前项目的 components 目录)
    • $PROJECT_PATH/managed_components/(当前项目的 managed_components 目录)
  • 有一些第三方组件(开发板的驱动),不合适重复放在工程中,也不能放在 ESP-IDF安装目录里,就可以在 CMakeLists.txt 中显式设置 EXTRA_COMPONENT_DIRS ,该定义默认是空,不会添加任何额外的组件目录。

# ⭐ 必须在 项目根目录的 CMakeLists.txt 中设置
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)  # 必须放在 set 之前

list(APPEND EXTRA_COMPONENT_DIRS     	# 额外组件路径(可以是绝对路径或相对于项目目录的路径)
    # "../shared_components"         	# 上级目录的共享组件
    # "C:/your/custom_components"   	# 绝对路径组件目录
)

# ⭐ EXTRA_COMPONENT_DIRS 必须在这里设置(在 project() 之前)
project(hello_component)  # 初始化 ESP-IDF 项目
  • 重名组件会被替换,优先级从小到大,和局部变量类似。
  • 初始编译时所有组件都会被编译,所以需要很久

2) 组件依赖

  • 重点:永远通过组件名而非路径建立依赖关系,这是 ESP-IDF 模块化设计的核心原则!
  • 组件之间的依赖通过 CMakeLists.txt 修改。
  • 例如上例中,按钮组件依赖闪灯组件,就可以导入闪灯组件的头文件,调用函数。
idf_component_register(
    SRCS 
    	"led_test.c" // 单独指定每一个参与编译的 .c
                    
    INCLUDE_DIRS 
    	"include" // 只限定对外开放 这个文件夹内的头文件
                    
    # REQUIRES 
    	# button_test  # 明确依赖的组件名(非路径)
    PRIV_REQUIRES 
    	button_test  # 仅当前组件内部使用,对外不可见
    	# "$($ENV{IDF_TARGET})"  # 根据目标芯片选择依赖,相当于可配置更改
    )            
  • 头文件不添加在 INCLUDE_DIRS : 用相对路径,作用域仅限于当前组件内部。
  • 头文件添加在 INCLUDE_DIRS : 将路径变为公共 API,允许跨组件直接引用。
  • 黄金法则最小化暴露原则——仅将必要的头文件路径加入 INCLUDE_DIRS,内部实现细节用相对路径隔离
  • 为什么 .c 文件需要逐个指定? .c 文件在编译时会经历以下过程:
    • 编译(Compile):每个 .c 文件都会独立编译成对象文件(.o)。
    • 链接(Link):所有 .o 文件合并生成最终的库或固件。
  • 因此需要 显式列出每个 .c 文件;
    • 避免误编译:确保只编译需要参与构建的源文件(而不是目录下所有文件)。
    • 精确控制依赖:可以针对特定 .c 文件设置编译选项(如 CFLAGS)。
    • 构建系统优化:仅重新编译修改过的 .c 文件。

五、菜单配置项

问DS:为什么esp中,有Kconfig和Kconfig.projbuild两种不同的格式?

在 ESP-IDF 开发框架中,KconfigKconfig.projbuild 是两种不同用途的配置文件格式,它们的主要区别在于 作用范围设计目的。以下是详细解释:


1. Kconfig(组件级配置)

  • 作用对象:用于 单个组件(Component) 内部的配置。
  • 功能:定义组件自身需要的可配置选项(如硬件参数、功能开关等)。
  • 文件位置:位于组件的根目录(如 components/your_component/Kconfig)。
  • 特点
    • 仅在组件的编译过程中生效。
    • 选项对其他组件不可见(除非显式暴露)。
    • 遵循 ESP-IDF 的组件隔离原则,保证模块化。

示例场景
比如一个 SPI 驱动组件可能需要配置默认时钟频率,此时会在 Kconfig 中定义:

   config SPI_CLOCK_SPEED
       int "SPI clock speed (Hz)"
       default 1000000
       help
           Set the SPI master clock frequency.

2. Kconfig.projbuild(项目级全局配置)

  • 作用对象:影响 整个项目多个组件 的全局配置。
  • 功能:覆盖或扩展项目范围的配置(如 SDK 工具链选项、系统级参数)。
  • 文件位置:可存在于组件目录或项目根目录(优先级高于普通 Kconfig)。
  • 特点
    • 配置选项会直接出现在顶层 menuconfig 菜单中。
    • 可能破坏组件隔离性,需谨慎使用。
    • 通常用于硬件适配或特殊需求(如切换目标芯片型号)。

示例场景
项目需要强制指定所有组件的日志级别,可以在 Kconfig.projbuild 中:

   config LOG_DEFAULT_LEVEL
       int "Global log level"
       default 3
       range 0 4

关键区别总结

特性 Kconfig Kconfig.projbuild
作用范围 组件内部 整个项目全局
使用场景 组件私有配置 跨组件或系统级配置
隔离性 严格隔离 可能影响其他组件
推荐使用频率 高频(标准做法) 低频(特殊需求)

为什么设计两种格式?

  1. 模块化需求
    ESP-IDF 强调组件化开发,Kconfig 确保每个组件的配置独立,避免命名冲突。

  2. 灵活性的平衡
    Kconfig.projbuild 为特殊场景(如硬件适配、全局优化)提供“后门”,但需手动启用。

  3. 优先级控制
    当两者冲突时,Kconfig.projbuild 的配置会覆盖普通 Kconfig,方便项目定制。


最佳实践建议

  • 优先使用 Kconfig:保持组件解耦。
  • 慎用 Kconfig.projbuild:仅在需要全局覆盖时使用,并添加详细注释说明。
  • 命名约定:在组件中避免定义全局选项(如以 CONFIG_ 开头的选项应谨慎)。

通过这种设计,ESP-IDF 实现了配置的灵活性和模块化的平衡。


  • 修改 Kconfig 文件,一定要删除 build 文件才会同步 sdkmenuconfig 菜单,否则最多只是刷新了宏定义,不会刷新菜单选项。
  • 尝试修改例程,添加以下测试代码。分别添加全局和组件配置,然后刷新菜单。就能看到宏定义被创建,菜单也出现选项。
/* main/Kconfig.projbuild 文件内容 */
menu "Example Configuration main.c"

    config MAIN_TEST
        int "main.c test"
        range 10 3600000
        default 1000
        help
            Define the main test.
            
endmenu

/* components/button_test/Kconfig 文件内容 */
menu "Example Configuration button.c"

    config BUTTON_TEST
        int "button.c test"
        range 10 3600000
        default 1000
        help
            Define the button test.
            
endmenu

/* components/led_test/Kconfig 文件内容 */
menu "Example Configuration led.c"

    config LED_TEST
        int "led.c test"
        range 10 3600000
        default 1000
        help
            Define the led test.
            
endmenu
  • main 配置菜单出现在一级菜单,而组件配置菜单位于 Component config 下面的二级菜单。
  • 如果希望本地组件的菜单在 main 菜单下面,可以修改为以下内容。可以输入中文,但终端显示错位,VSCode 中就能显示正常。
menu "Example Configuration main.c"
	
    rsource "../components/button_test/Kconfig" # 相对路径
    rsource "../components/led_test/Kconfig"
    
    config MAIN_TEST
        int "main.c test 中文" # 名字 
        range 10 3600000
        default 1000
        help
            main.c test 中文. # 帮助

endmenu

在这里插入图片描述

在这里插入图片描述


问 DS:sourcersourceorsource 的区别?
以下内容貌似瞎说的,我试了而一下觉得路径没成功,相对路径成功了。而另一个工程使用source搭配相对路径也成功了。所以有点疑惑。

在 Kconfig 系统中,sourcersourceorsource 是用于导入其他 Kconfig 文件的命令,但它们的行为和用途有所不同。以下是它们的详细解释和区别:


1. source

  • 功能:从指定的路径导入另一个 Kconfig 文件。
  • 路径解析规则
    • 绝对路径:直接使用给定的路径(如 source "/path/to/Kconfig")。
    • 相对路径:路径是**相对于 $srctree(源码根目录)**解析的,而不是当前文件所在目录。
  • 典型用途
    用于导入项目中或 ESP-IDF 内置的组件 Kconfig 文件。
  • 失败行为
    如果文件不存在,会报错并终止解析(如你遇到的问题)。

2. rsource

  • 功能相对路径导入(Relative-source),路径是相对于当前 Kconfig 文件所在目录解析的。
  • 路径解析规则
    相对路径基于当前文件的位置,而非 $srctree
    (这是与 source 的关键区别!)
  • 典型用途
    当需要从当前组件目录中导入子模块的 Kconfig 文件时。
  • 失败行为
    文件不存在时会报错。

3. orsource

  • 功能可选导入(Optional-source),如果文件不存在,则静默忽略,不会报错。
  • 路径解析规则
    source 相同(相对于 $srctree)。
  • 典型用途
    导入可能不存在的可选配置文件,避免因文件缺失导致构建失败。
  • 失败行为
    文件不存在时不会报错。

三者的对比总结

命令 路径解析基准 文件不存在时的行为 典型场景
source $srctree 报错并终止 强制依赖的组件或核心配置
rsource 当前文件目录 报错并终止 组件内部的子模块配置
orsource $srctree 静默忽略 可选的自定义或平台特定配置

  • 最后,如果修改了配置文件,为了避免每次重复配置,可以使用指令 idf.py save-defconfig
    保 存当前的已修改配置,留作下次使用。
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) 5.4.1 Project Minimal Configuration
#
CONFIG_IDF_TARGET="esp32s3"
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_BUTTON_TEST=999
CONFIG_LED_TEST=666
CONFIG_MAIN_TEST=555

六、实战

  • 将组件的示例内容实现,路径类似:managed_components\espressif__button\test_apps\main。里面有多个例子,拷贝出来进行仿制,然后对组件进行二次打包,作为个人组件方便调用。搭配 Kconfig 配置就更加规范了。

在这里插入图片描述

  • 提一句按钮的使用,单个按键没啥好讲的,主要是adc按键,使用 excel 表格方便计算,举例3000mV,分成5个按钮。
  • 参考例程给的思路,分成 n+1,已知所需电压,假设上拉电阻R0,计算理论R1~R5,然后查电阻本寻找合适的电阻,然后再计算实际触发电压,然后编写注册按钮的程序。

在这里插入图片描述

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐