Chapter 24: Component Configuration and KConfig System
Chapter Objectives
- Understand the concept of ESP-IDF components and their role in modular development.
- Learn about the Kconfig system used for configuration management in ESP-IDF.
- Understand the purpose and usage of the
menuconfig
tool. - Learn the basic syntax of Kconfig files (
config
,menuconfig
,choice
,bool
,int
,hex
,string
,depends on
,default
). - Understand how configuration options are stored in
sdkconfig
and accessed viasdkconfig.h
. - Learn how to add custom configuration options to your own components.
- Understand the role of
Kconfig.projbuild
files.
Introduction
As our ESP32 projects grow in complexity, managing configuration settings becomes increasingly important. While simple #define
directives in header files might suffice for small projects, they quickly become unwieldy. How do we manage settings for different hardware variants? How do we easily enable or disable features? How do libraries or reusable modules (components) expose their own configuration options without cluttering the main application?
ESP-IDF solves these challenges using a powerful and flexible configuration system called Kconfig, originally developed for the Linux kernel. Kconfig allows developers to define configuration options in dedicated files, provides a text-based menu interface (menuconfig
) to easily view and modify these options, and automatically generates header files (sdkconfig.h
) to make these settings accessible in C code.
This chapter explores the structure of ESP-IDF components, delves into the Kconfig language and its associated tools, and shows you how to create and manage configuration settings for your own custom components, leading to more modular, maintainable, and configurable firmware.
Theory
ESP-IDF Components
ESP-IDF promotes a modular design philosophy based on components. A component is essentially a self-contained module of code, typically residing in its own directory, that provides specific functionality. Examples include drivers (like GPIO, WiFi), libraries (like FreeRTOS, NVS, lwIP), and even parts of your own application logic.
Key characteristics of components:
- Directory Structure: Usually located under the
components/
directory in your project or within the ESP-IDF framework itself. - Build Integration: Each component typically has its own
CMakeLists.txt
file that tells the build system how to compile its source files and link them into the final application. - Configuration: Components can define their own configuration options using Kconfig files, allowing users to customize the component’s behavior via
menuconfig
. - Dependencies: Components can declare dependencies on other components.
my_project/
├── main/
│ ├── CMakeLists.txt
│ ├── Kconfig.projbuild (We'll create this)
│ └── main.c
├── components/
│ └── custom_module/
│ ├── CMakeLists.txt (We'll create this)
│ ├── Kconfig (We'll create this)
│ ├── Kconfig.projbuild (We'll create this)
│ └── custom_module.c (We'll create this)
│ └── include/
│ └── custom_module.h (We'll create this)
├── CMakeLists.txt
├── partitions.csv
└── sdkconfig
Using components makes projects more organized, facilitates code reuse, and allows for independent configuration of different modules.
The Kconfig System
Kconfig is the backbone of ESP-IDF’s configuration mechanism. It involves several parts:
- Kconfig Files: Plain text files containing definitions of configuration options. These files use a specific syntax to define symbols (options), their types, default values, dependencies, help text, and how they are organized into menus.
menuconfig
Tool: An interactive, text-based menu (ncurses
) interface that reads the Kconfig files and presents the options to the user in a structured way (menus and submenus). Users navigate this interface to enable/disable features, set values, and choose options. You run it usingidf.py menuconfig
.sdkconfig
File: A plain text file located in the project’s root directory. When you save your configuration inmenuconfig
, the chosen settings (only those differing from defaults or explicitly set) are saved to this file in the formatCONFIG_OPTION_NAME=value
. This file should generally not be edited manually; letmenuconfig
manage it. It should be added to version control (like Git) to ensure reproducible builds.sdkconfig.h
File: An automatically generated C header file located in thebuild/config/
directory. It translates the settings from thesdkconfig
file into C preprocessor macros (e.g.,#define CONFIG_OPTION_NAME value
). You include this header in your C/C++ code to access the configuration values. This file is generated during the build process and should not be edited manually or added to version control.- Build System Integration: The ESP-IDF build system (CMake) processes the Kconfig files, generates the
sdkconfig.h
header, and uses the configuration settings to conditionally compile code or pass definitions to the compiler.
%%{ init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#DBEAFE', 'primaryTextColor': '#1E40AF', 'primaryBorderColor': '#2563EB', /* Blue */ 'secondaryColor': '#FEF3C7', 'secondaryTextColor': '#92400E', 'secondaryBorderColor': '#D97706', /* Amber */ 'tertiaryColor': '#D1FAE5', 'tertiaryTextColor': '#065F46', 'tertiaryBorderColor': '#059669', /* Green */ 'lineColor': '#A78BFA', 'textColor': '#1F2937', 'mainBkg': '#FFFFFF', 'nodeBorder': '#A78BFA', 'fontFamily': '"Open Sans", sans-serif' } } }%% graph TD subgraph "Definition Phase" KFiles["Kconfig Files<br>(In Components / IDF)"]:::kconfigDef; KProjBuild["Kconfig.projbuild Files"]:::kconfigDef; end subgraph "Configuration Phase (User)" Menuconfig["idf.py menuconfig<br>(Interactive Tool)"]:::menuTool; end subgraph "Storage & Generation Phase (Build System)" Sdkconfig["sdkconfig File<br>(Project Root, User Settings)"]:::configFile; BuildProcess["Build Process<br>(CMake/Ninja)"]:::buildSystem; SdkconfigH["sdkconfig.h<br>(build/config/, Generated Header)"]:::generatedFile; end subgraph "Usage Phase (Code)" CCode["C/C++ Source Code<br>(#include <i>sdkconfig.h</i>)"]:::sourceCode; AppBinary[("Application Binary<br>(.elf / .bin)")]:::appBinary; end KFiles -- Defines Options --> Menuconfig; KProjBuild -- Locates Kconfig --> Menuconfig; Menuconfig -- Saves Choices --> Sdkconfig; Sdkconfig -- Input --> BuildProcess; BuildProcess -- Generates --> SdkconfigH; SdkconfigH -- Included By --> CCode; CCode -- Compiled With Config --> AppBinary; %% Styling Definitions classDef kconfigDef fill:#FEF3C7,stroke:#D97706,stroke-width:1.5px,color:#92400E; classDef menuTool fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6,font-weight:bold; classDef configFile fill:#FEF9C3,stroke:#F59E0B,stroke-width:1px,color:#B45309; classDef buildSystem fill:#BFDBFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef generatedFile fill:#D1FAE5,stroke:#059669,stroke-width:1.5px,color:#065F46; classDef sourceCode fill:#FFFFFF,stroke:#6B7280,stroke-width:1px,color:#1F2937; classDef appBinary fill:#E5E7EB,stroke:#6B7280,stroke-width:2px,color:#374151,font-weight:bold;
Kconfig File Syntax Basics
Kconfig files define configuration symbols and their properties. Here are some common elements:
config SYMBOL_NAME
: Defines a configuration symbol (variable). The convention is to use uppercase names.bool "Prompt"
: Defines a boolean symbol (can be enabledy
or disabledn
). The string is the prompt displayed inmenuconfig
.int "Prompt"
: Defines an integer symbol.hex "Prompt"
: Defines a hexadecimal symbol.string "Prompt"
: Defines a string symbol.default <value>
: Sets the default value for the symbol. Defaults can depend on other symbols (e.g.,default y if OTHER_SYMBOL
).depends on <OTHER_SYMBOL>
ordepends on <SYMBOL1> && <SYMBOL2>
: Makes the current symbol visible/configurable only if the dependency symbol(s) are enabled (y
).help
: Starts a block of help text explaining the symbol, displayed inmenuconfig
. The indentation matters.menu "Menu Title"
…endmenu
: Groups related options under a submenu inmenuconfig
.choice <CHOICE_NAME>
…endchoice
: Defines a group where only one option can be selected (like radio buttons). Individual options within the choice are defined usingconfig
.source "path/to/other/Kconfig"
: Includes another Kconfig file, typically used to bring in component configurations.
Component Kconfig Files
Each component that needs to be configurable should contain a Kconfig
file in its root directory. This file defines the configuration options specific to that component.
Project Kconfig Files
main/Kconfig
(Optional): If your main application logic needs project-specific configuration options not tied to a reusable component, you can create aKconfig
file in themain
directory.Kconfig.projbuild
: This file, located in the component’s directory (ormain/
), plays a crucial role. It tells the main build configuration process where to find the component’sKconfig
file. Its typical content is:# Kconfig.projbuild for component 'my_component' include $(COMPONENT_KCONFIG)
or for the main component:# Kconfig.projbuild for main component include Kconfig
The build system automatically finds theseKconfig.projbuild
files and uses them to aggregate all Kconfig definitions before runningmenuconfig
.
Accessing Configuration in Code
Once you have configured your project using menuconfig
and built it, the settings are available in your C code by including the generated header:
#include "sdkconfig.h" // Include the generated config header
// ... later in your code ...
#ifdef CONFIG_MY_FEATURE_ENABLE // Check if a boolean option is enabled
// Code related to the feature
ESP_LOGI(TAG, "My Feature is enabled.");
#endif
int buffer_size = CONFIG_MY_BUFFER_SIZE; // Access an integer value
ESP_LOGI(TAG, "Using buffer size: %d", buffer_size);
const char* device_name = CONFIG_MY_DEVICE_NAME; // Access a string value
ESP_LOGI(TAG, "Device name: %s", device_name);
#if CONFIG_MY_MODE_OPTION_A // Check a choice option
ESP_LOGI(TAG, "Mode A selected");
#elif CONFIG_MY_MODE_OPTION_B
ESP_LOGI(TAG, "Mode B selected");
#endif
Important: The
CONFIG_
prefix is automatically added to the symbol names defined in Kconfig when generatingsdkconfig.h
.
Accessing Kconfig Settings in C Code (via sdkconfig.h)
Remember to #include "sdkconfig.h"
Kconfig Type | C Code Access Example |
---|---|
Boolean (bool ) |
Use preprocessor directives #ifdef , #if , or #ifndef . The macro is defined (usually to 1) if the option is enabled (y).
|
Integer (int ) / Hex (hex ) |
The macro expands directly to the configured integer or hex value.
|
String (string ) |
The macro expands to a C string literal (including quotes).
|
Choice (choice ) |
Only the macro corresponding to the selected option within the choice group will be defined (usually to 1). Use #if / #elif / #else / #endif .
|
Practical Examples
Project Setup:
- Start with a standard ESP-IDF project template (e.g.,
hello_world
). - We will create a custom component.
Example 1: Creating a Configurable Custom Component
Let’s create a simple component called custom_module
with some configuration options.
Create Component Directory Structure:
Inside your project’s root directory, create the following structure:
my_project/
├── main/
│ ├── CMakeLists.txt
│ ├── Kconfig.projbuild (We'll create this)
│ └── main.c
├── components/
│ └── custom_module/
│ ├── CMakeLists.txt (We'll create this)
│ ├── Kconfig (We'll create this)
│ ├── Kconfig.projbuild (We'll create this)
│ └── custom_module.c (We'll create this)
│ └── include/
│ └── custom_module.h (We'll create this)
├── CMakeLists.txt
├── partitions.csv
└── sdkconfig
Create components/custom_module/Kconfig:
This file defines the configuration options for our component.
# Kconfig file for Custom Module component
menu "Custom Module Configuration"
config CUSTOM_MODULE_ENABLE
bool "Enable Custom Module Feature"
default y
help
Enable this option to activate the core functionality
of the custom module.
config CUSTOM_MODULE_BUFFER_SIZE
int "Data Buffer Size (bytes)"
depends on CUSTOM_MODULE_ENABLE
default 1024
range 128 4096
help
Specifies the size of the internal data buffer used by the module.
Must be between 128 and 4096 bytes.
config CUSTOM_MODULE_DEVICE_NAME
string "Device Nickname"
depends on CUSTOM_MODULE_ENABLE
default "My ESP32 Device"
help
A user-friendly name for this device, used in logs or UI.
choice CUSTOM_MODULE_MODE
prompt "Operating Mode"
depends on CUSTOM_MODULE_ENABLE
default CUSTOM_MODULE_MODE_A
help
Select the primary operating mode for the module.
config CUSTOM_MODULE_MODE_A
bool "Mode A (Standard)"
config CUSTOM_MODULE_MODE_B
bool "Mode B (Low Power)"
endchoice # CUSTOM_MODULE_MODE
endmenu # Custom Module Configuration
Create components/custom_module/Kconfig.projbuild:
This tells the build system about the component’s Kconfig file.
# Kconfig.projbuild for component 'custom_module'
include $(COMPONENT_KCONFIG)
Create components/custom_module/include/custom_module.h:
Header file for our component’s functions.
#ifndef CUSTOM_MODULE_H
#define CUSTOM_MODULE_H
#include "esp_err.h"
/**
* @brief Initializes the custom module based on Kconfig settings.
*
* @return esp_err_t ESP_OK on success, or an error code.
*/
esp_err_t custom_module_init(void);
/**
* @brief Performs the main action of the custom module.
*/
void custom_module_do_work(void);
#endif // CUSTOM_MODULE_H
Create components/custom_module/custom_module.c:
Implementation file, accessing Kconfig values.
#include "custom_module.h"
#include "sdkconfig.h" // Include generated configuration
#include "esp_log.h"
#include <stdio.h> // For snprintf
static const char *TAG = "CUSTOM_MODULE";
// Static buffer, size determined by Kconfig
#ifdef CONFIG_CUSTOM_MODULE_ENABLE
static char internal_buffer[CONFIG_CUSTOM_MODULE_BUFFER_SIZE];
#endif
esp_err_t custom_module_init(void)
{
#ifndef CONFIG_CUSTOM_MODULE_ENABLE
ESP_LOGW(TAG, "Custom module is disabled in configuration.");
return ESP_OK; // Not an error, just disabled
#else
ESP_LOGI(TAG, "Initializing Custom Module...");
ESP_LOGI(TAG, "Feature Enabled: Yes");
ESP_LOGI(TAG, "Buffer Size: %d bytes", CONFIG_CUSTOM_MODULE_BUFFER_SIZE);
ESP_LOGI(TAG, "Device Nickname: '%s'", CONFIG_CUSTOM_MODULE_DEVICE_NAME);
#if CONFIG_CUSTOM_MODULE_MODE_A
ESP_LOGI(TAG, "Operating Mode: A (Standard)");
#elif CONFIG_CUSTOM_MODULE_MODE_B
ESP_LOGI(TAG, "Operating Mode: B (Low Power)");
#else
ESP_LOGW(TAG, "Operating Mode: Unknown/Not Set!");
#endif
// Initialize the buffer (example)
snprintf(internal_buffer, sizeof(internal_buffer), "Initialized for %s", CONFIG_CUSTOM_MODULE_DEVICE_NAME);
ESP_LOGD(TAG, "Internal buffer initialized.");
ESP_LOGI(TAG, "Custom Module Initialized Successfully.");
return ESP_OK;
#endif // CONFIG_CUSTOM_MODULE_ENABLE
}
void custom_module_do_work(void)
{
#ifndef CONFIG_CUSTOM_MODULE_ENABLE
// Do nothing if disabled
return;
#else
ESP_LOGI(TAG, "Custom module doing work...");
// Access the buffer or use other config values here
ESP_LOGD(TAG, "Current buffer content: '%s'", internal_buffer);
// Simulate work based on mode
#if CONFIG_CUSTOM_MODULE_MODE_A
ESP_LOGI(TAG, "Performing standard work...");
#elif CONFIG_CUSTOM_MODULE_MODE_B
ESP_LOGI(TAG, "Performing low power work...");
#endif
#endif // CONFIG_CUSTOM_MODULE_ENABLE
}
Create components/custom_module/CMakeLists.txt:
Build instructions for the component.
# CMakeLists.txt for component custom_module
idf_component_register(SRCS "custom_module.c"
INCLUDE_DIRS "include"
REQUIRES main) # Example dependency
Modify main/main.c:
Include the component header and call its functions.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "custom_module.h" // Include our custom component header
static const char *TAG = "MAIN";
void app_main(void)
{
ESP_LOGI(TAG, "Main application started.");
// Initialize the custom module
esp_err_t init_result = custom_module_init();
if (init_result != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize custom module!");
// Handle error, maybe restart or halt
return;
}
// Main loop periodically calls the module's work function
while (1) {
custom_module_do_work();
vTaskDelay(pdMS_TO_TICKS(5000)); // Do work every 5 seconds
}
}
Create main/Kconfig.projbuild:
This ensures the build system finds the main component’s dependencies (even though main doesn’t have its own Kconfig in this example, this file structure is standard).
# Kconfig.projbuild for main component
# No custom Kconfig for main in this example, but file is needed
# if main has dependencies that need configuration processing.
(Note: Often, main
doesn’t need a Kconfig.projbuild
if it has no Kconfig itself and its dependencies are handled correctly by the build system finding their respective Kconfig.projbuild
files. However, including it is safe.)
Modify CMakeLists.txt (Project Root):
Ensure the components directory is known to CMake (this is usually default if using idf.py create-project or examples). If not, you might need to add set(EXTRA_COMPONENT_DIRS components) near the top.
Build, Configure, Flash, Monitor:
- Configure: Run
idf.py menuconfig
. Navigate to the newComponent config
->Custom Module Configuration
menu. You should see the options you defined:- Enable/disable the feature.
- Set the buffer size.
- Change the device nickname.
- Select Mode A or Mode B.
- Try changing some values. Save and exit.
- Build:
idf.py build
. Observe the build output – it should compile yourcustom_module.c
. - Flash:
idf.py -p <YOUR_PORT> flash
- Monitor:
idf.py -p <YOUR_PORT> monitor
Expected Output:
The monitor output should show the logs from custom_module_init
, reflecting the configuration values you set in menuconfig
. The custom_module_do_work
logs will then appear periodically. If you disabled the module in menuconfig
, the initialization log will indicate it’s disabled, and custom_module_do_work
will do nothing.
# Example output if enabled with defaults
I (xxx) MAIN: Main application started.
I (xxx) CUSTOM_MODULE: Initializing Custom Module...
I (xxx) CUSTOM_MODULE: Feature Enabled: Yes
I (xxx) CUSTOM_MODULE: Buffer Size: 1024 bytes
I (xxx) CUSTOM_MODULE: Device Nickname: 'My ESP32 Device'
I (xxx) CUSTOM_MODULE: Operating Mode: A (Standard)
I (xxx) CUSTOM_MODULE: Custom Module Initialized Successfully.
I (xxx) MAIN: Main loop started.
I (xxx) CUSTOM_MODULE: Custom module doing work...
I (xxx) CUSTOM_MODULE: Performing standard work...
... (repeats every 5 seconds) ...
If you change settings in menuconfig
(e.g., set buffer size to 512, name to “Test Device”, mode to B) and rebuild/reflash, the output will change accordingly:
# Example output after changing config
I (xxx) MAIN: Main application started.
I (xxx) CUSTOM_MODULE: Initializing Custom Module...
I (xxx) CUSTOM_MODULE: Feature Enabled: Yes
I (xxx) CUSTOM_MODULE: Buffer Size: 512 bytes
I (xxx) CUSTOM_MODULE: Device Nickname: 'Test Device'
I (xxx) CUSTOM_MODULE: Operating Mode: B (Low Power)
I (xxx) CUSTOM_MODULE: Custom Module Initialized Successfully.
I (xxx) MAIN: Main loop started.
I (xxx) CUSTOM_MODULE: Custom module doing work...
I (xxx) CUSTOM_MODULE: Performing low power work...
... (repeats every 5 seconds) ...
Variant Notes
The Kconfig system, menuconfig
tool, sdkconfig
file generation, and the method for accessing configuration values via sdkconfig.h
are consistent across all ESP32 variants supported by ESP-IDF (ESP32, S2, S3, C3, C6, H2). The underlying mechanism does not change based on the specific chip.
While the available configuration options will differ significantly between variants (e.g., options related to PSRAM, specific peripherals, number of cores), the way you define, configure, and access these options remains the same.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Forgetting Kconfig.projbuild Component Kconfig exists, but link file is missing. |
Component options don’t appear in menuconfig . Build might succeed, but CONFIG_ macros are undefined or have wrong defaults. |
Ensure every component with a Kconfig file has a corresponding Kconfig.projbuild file containing include $(COMPONENT_KCONFIG) . |
Manual Edits to sdkconfig / sdkconfig.h Editing configuration outside menuconfig . |
Changes overwritten on next menuconfig save or build. Inconsistent configuration. |
Always use idf.py menuconfig to change settings. Commit sdkconfig to version control; add sdkconfig.h and build/ to .gitignore . |
Incorrect Kconfig Syntax Typos, bad indentation, wrong keywords. |
menuconfig fails, shows errors, or displays menus incorrectly. Build may fail at configuration stage. |
Check syntax carefully against examples/docs. Pay attention to help indentation. Run menuconfig to validate after edits. |
Case Mismatch / Typos in CONFIG_ MacrosUsing wrong name in C code. |
Compilation errors (undefined macro). Code might be silently excluded if using #ifdef without #else . |
Verify exact symbol name from Kconfig . Remember CONFIG_ prefix is added, rest usually uppercase. Check generated build/config/sdkconfig.h if unsure. |
Missing #include "sdkconfig.h" Forgetting the header in C files. |
Compilation errors (undefined macro CONFIG_... ). |
Add #include "sdkconfig.h" to the top of any C/C++ file needing access to configuration values. |
Incorrect depends on LogicDependency expression is wrong or uses CONFIG_ prefix. |
Options appear/disappear unexpectedly in menuconfig based on other selections. |
Use correct Kconfig symbol names (without CONFIG_ prefix) in depends on statements. Use && (AND), || (OR), and parentheses () for complex logic. |
Exercises
- Add String Length Limit: Modify the
custom_module/Kconfig
file. Add a new integer configuration optionCUSTOM_MODULE_NAME_MAX_LEN
thatdepends on CUSTOM_MODULE_ENABLE
. Give it a default value (e.g., 32) and a reasonable range (e.g., 8-64). Modifycustom_module.c
to use this value when initializing theinternal_buffer
withsnprintf
to prevent potential buffer overflows if theCONFIG_CUSTOM_MODULE_DEVICE_NAME
string is longer than expected (or longer than the new max length). Re-runmenuconfig
, build, and test. - Add Component Dependency Config: Imagine
custom_module
needs a specific feature from the standard ESP-IDF “ESP Timer” component to be enabled. Find the Kconfig option for enabling the ESP Timer high-resolution timer (CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD
or similar, check underComponent config -> ESP System Settings -> High resolution timer
inmenuconfig
). Incustom_module/Kconfig
, makeCUSTOM_MODULE_ENABLE
alsodepends on
this ESP Timer option. Runmenuconfig
, disable the high-resolution timer, and observe that the “Custom Module Configuration” menu becomes greyed out or hidden. Re-enable it to make your component configurable again. (Note: You don’t need to change the C code for this exercise, just the Kconfig dependency).
Summary
- ESP-IDF uses components for modular code organization.
- The Kconfig system provides a robust way to manage configuration options for the project and its components.
idf.py menuconfig
is the tool used to interactively view and modify Kconfig settings.- Configuration choices are saved in the
sdkconfig
file (add to version control). - The build system generates
sdkconfig.h
containing C preprocessor macros (CONFIG_...
) corresponding to the settings (do not add to version control). - Components define their own options in a
Kconfig
file and make it known to the build system viaKconfig.projbuild
. - Kconfig supports various types (
bool
,int
,hex
,string
,choice
), dependencies (depends on
), defaults, ranges, help text, and menus. - Access configuration in C code by including
sdkconfig.h
and using theCONFIG_...
macros.
Further Reading
- ESP-IDF Programming Guide – Build System: https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-guides/build-system.html (Covers components and build process).
- ESP-IDF Programming Guide – Kconfig: https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/kconfig.html (Detailed reference for Kconfig options and syntax).
- Kconfig Language Documentation (Linux Kernel): https://www.kernel.org/doc/html/latest/kbuild/kconfig-language.html (The original source, ESP-IDF uses a very similar syntax).
