下面的内容编写时间跨度有点大,乱了得一团,也没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 内置组件,如driver
、esp_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" // 单独指定每一个参与编译的 .cINCLUDE_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 开发框架中,Kconfig
和 Kconfig.projbuild
是两种不同用途的配置文件格式,它们的主要区别在于 作用范围 和 设计目的。以下是详细解释:
1. Kconfig
(组件级配置)
- 作用对象:用于 单个组件(Component) 内部的配置。
- 功能:定义组件自身需要的可配置选项(如硬件参数、功能开关等)。
- 文件位置:位于组件的根目录(如
components/your_component/Kconfig
)。 - 特点:
- 仅在组件的编译过程中生效。
- 选项对其他组件不可见(除非显式暴露)。
- 遵循 ESP-IDF 的组件隔离原则,保证模块化。
示例场景:
比如一个 SPI 驱动组件可能需要配置默认时钟频率,此时会在 Kconfig
中定义:
config SPI_CLOCK_SPEEDint "SPI clock speed (Hz)"default 1000000helpSet the SPI master clock frequency.
2. Kconfig.projbuild
(项目级全局配置)
- 作用对象:影响 整个项目 或 多个组件 的全局配置。
- 功能:覆盖或扩展项目范围的配置(如 SDK 工具链选项、系统级参数)。
- 文件位置:可存在于组件目录或项目根目录(优先级高于普通
Kconfig
)。 - 特点:
- 配置选项会直接出现在顶层
menuconfig
菜单中。 - 可能破坏组件隔离性,需谨慎使用。
- 通常用于硬件适配或特殊需求(如切换目标芯片型号)。
- 配置选项会直接出现在顶层
示例场景:
项目需要强制指定所有组件的日志级别,可以在 Kconfig.projbuild
中:
config LOG_DEFAULT_LEVELint "Global log level"default 3range 0 4
关键区别总结
特性 | Kconfig | Kconfig.projbuild |
---|---|---|
作用范围 | 组件内部 | 整个项目全局 |
使用场景 | 组件私有配置 | 跨组件或系统级配置 |
隔离性 | 严格隔离 | 可能影响其他组件 |
推荐使用频率 | 高频(标准做法) | 低频(特殊需求) |
为什么设计两种格式?
-
模块化需求:
ESP-IDF 强调组件化开发,Kconfig
确保每个组件的配置独立,避免命名冲突。 -
灵活性的平衡:
Kconfig.projbuild
为特殊场景(如硬件适配、全局优化)提供“后门”,但需手动启用。 -
优先级控制:
当两者冲突时,Kconfig.projbuild
的配置会覆盖普通Kconfig
,方便项目定制。
最佳实践建议
- 优先使用
Kconfig
:保持组件解耦。 - 慎用
Kconfig.projbuild
:仅在需要全局覆盖时使用,并添加详细注释说明。 - 命名约定:在组件中避免定义全局选项(如以
CONFIG_
开头的选项应谨慎)。
通过这种设计,ESP-IDF 实现了配置的灵活性和模块化的平衡。
- 修改
Kconfig
文件,一定要删除build
文件才会同步sdkmenuconfig
菜单,否则最多只是刷新了宏定义,不会刷新菜单选项。 - 尝试修改例程,添加以下测试代码。分别添加全局和组件配置,然后刷新菜单。就能看到宏定义被创建,菜单也出现选项。
/* main/Kconfig.projbuild 文件内容 */
menu "Example Configuration main.c"config MAIN_TESTint "main.c test"range 10 3600000default 1000helpDefine the main test.endmenu/* components/button_test/Kconfig 文件内容 */
menu "Example Configuration button.c"config BUTTON_TESTint "button.c test"range 10 3600000default 1000helpDefine the button test.endmenu/* components/led_test/Kconfig 文件内容 */
menu "Example Configuration led.c"config LED_TESTint "led.c test"range 10 3600000default 1000helpDefine 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_TESTint "main.c test 中文" # 名字 range 10 3600000default 1000helpmain.c test 中文. # 帮助endmenu
问 DS:
source
、rsource
和orsource
的区别?
以下内容貌似瞎说的,我试了而一下觉得路径没成功,相对路径成功了。而另一个工程使用source
搭配相对路径也成功了。所以有点疑惑。
在 Kconfig 系统中,source
、rsource
和 orsource
是用于导入其他 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,然后查电阻本寻找合适的电阻,然后再计算实际触发电压,然后编写注册按钮的程序。