If you’ve been following the course, you already have your first “Blinky” running. But Zephyr is not just about turning an LED on and off. The real power of this operating system lies in its modularity.
Imagine you want to add Bluetooth to your project. In other environments, you’d have to find the library, include the .h files, add the .c files to the Makefile… a real hassle. In Zephyr, it’s often as simple as writing one line: CONFIG_BT=y.
Today, we’re going to talk about the heart of Zephyr customization: the Kconfig system.
What is Kconfig?
Kconfig (Kernel Configuration) is the same configuration system used by the Linux Kernel.
Zephyr is a massive operating system with thousands of features (drivers, network stacks, file systems, shells, logs…). If we compiled everything at once, the binary would occupy gigabytes and wouldn’t fit on any microcontroller.
Kconfig allows us to define which parts of the operating system we want to compile and how they should behave.
Key difference:
- Devicetree (.dts): Describes the Hardware (which board I have, which pins I use).
- Kconfig (.conf): Describes the Software (which OS features I want to enable).
The prj.conf File
In your project, configuration is mainly defined in the prj.conf file. It’s a simple text file where we assign values to configuration variables.
The syntax is very straightforward: CONFIG_VARIABLE=value.
Types of values:
- Boolean (
y/n): Enable or disable a feature. - Integers: Define buffer sizes, priorities, stack sizes, etc.
- Strings: Device names, default texts.
Practical Example
Suppose we want to use the console to print messages (printk), but we also want to use Zephyr’s advanced Logging system and increase the Stack size of the main thread.
Our prj.conf would look like this:
# Enable console support
CONFIG_PRINTK=y
# Enable the Logging subsystem
CONFIG_LOG=y
# Configure the main thread stack size to 2048 bytes
CONFIG_MAIN_STACK_SIZE=2048When we run west build, the system reads this file, resolves dependencies, and generates a C header file (autoconf.h) with all these #define statements.
Automatic Dependencies
Kconfig evaluates dependencies and default values before accepting an option. Enabling CONFIG_BT=y enables the core subsystem, but the role, the driver, and other capabilities still depend on the board and the selected options.
Sometimes this can be confusing. If an assignment is ignored, check its dependencies in menuconfig. If an option appears active without you requesting it, another option might have selected it, or it might have that default value.
Interactive Tools: menuconfig and guiconfig
No one (not even Zephyr maintainers) memorizes the thousands of configuration options (CONFIG_...). Fortunately, we have visual tools to explore what we can enable.
From the terminal, inside the project folder, run:
west build -t menuconfigThis opens a text-based interface inside the terminal. You can navigate through menus, search for options with /, and view their help with ?.
To open the graphical interface, use:
west build -t guiconfigCaution! Changes made in menuconfig are temporary for that build. If you do a west build -p always, they will be lost.
Use these tools to discover the symbol, for example CONFIG_I2C, and then write it in prj.conf to keep it for future builds.
How Does This Affect My C Code?
Everything you define in Kconfig is translated into macros available in your C code. The prefix is always CONFIG_.
If in prj.conf you have:
CONFIG_MY_FEATURE=yIn your main.c you can do:
#include <zephyr/kernel.h>
int main(void) {
#ifdef CONFIG_MY_FEATURE
printk("The feature is active!\n");
#endif
return 0;
}This is very powerful for writing portable code that adapts based on the project configuration.