Kconfig
Kconfig is the configuration system Zephyr inherited from the Linux kernel. It controls which drivers, subsystems, and features are compiled into your firmware.
Every Zephyr feature — I2C, BLE, USB, logging — is gated behind a CONFIG_ symbol. If CONFIG_I2C=n, the I2C driver is not compiled and takes zero flash space.
Your project's prj.conf
# Enable the I2C bus and sensor subsystem (for the SHT30)
CONFIG_I2C=y
CONFIG_SENSOR=y
# Logging
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3 # 0=off 1=err 2=warn 3=info 4=debug
How are Kconfig values resolved?
Zephyr merges multiple Kconfig sources in order:
- SoC defaults — from the chip's
Kconfig.defconfig - Board defaults — from the board's
<board>_defconfig(e.g.efz_esp32s3_procpu_defconfig) - Your
prj.conf— your application overrides - Driver Kconfig — for every symbol you enable (e.g.
CONFIG_SENSOR=y), every matching sensor driver in the tree becomes available. The devicetree decides which one actually compiles in.
Values in prj.conf override everything upstream. If you set CONFIG_LOG_DEFAULT_LEVEL=4, it overrides the board default.
After a build, cat build/zephyr/.config shows every resolved Kconfig value — the definitive record of what is compiled into your firmware.
Dependency-driven Kconfig — the SHT30 example
Some symbols have depends on — if a driver requires another subsystem, you must enable the full chain or the build will fail.
To understand what a driver requires, read its Kconfig file directly. For the SHT30 (driven by Zephyr's SHT3x driver) at zephyr/drivers/sensor/sensirion/sht3xd/Kconfig:
menuconfig SHT3XD
bool "SHT3xD Temperature and Humidity Sensor"
default y
depends on DT_HAS_SENSIRION_SHT3XD_ENABLED
select I2C
select CRC
help
Enable driver for SHT3xD temperature and humidity sensors.
Three things to notice:
default y—SHT3XDis auto-enabled the moment its dependency is met. You don't have to writeCONFIG_SHT3XD=yyourself (the course sample does anyway, so the dependency is visible when you readprj.conf).depends on DT_HAS_SENSIRION_SHT3XD_ENABLED— this symbol is auto-generated by Zephyr when it finds an enabled DTS node withcompatible = "sensirion,sht3xd"— in your overlay on the DevKitC, or already in the board DTS on the EFZ-ESP32S3. The devicetree triggers the Kconfig.select I2C/select CRC— the driver forces on the I2C subsystem (the SHT30 only speaks I2C) and the CRC library it uses to check every measurement.
This is why CONFIG_SENSOR=y is enough to bring up the SHT30 — everything driver-specific resolves automatically from the devicetree. Here's what a real build for the EFZ-ESP32S3 resolved (build/zephyr/.config, filtered):
CONFIG_DT_HAS_SENSIRION_SHT3XD_ENABLED=y
CONFIG_I2C=y
CONFIG_SENSOR=y
CONFIG_SHT3XD=y
CONFIG_SHT3XD_TRIGGER_NONE=y
CONFIG_SHT3XD_REPEATABILITY_HIGH=y
CONFIG_SHT3XD_PERIODIC_MODE=y
CONFIG_SHT3XD_MPS_1=y
CONFIG_CRC=y
Every SHT3XD_* line there is a default you never typed.
Anatomy of a Kconfig file
A Kconfig file is a plain-text description of available config symbols, written in a small declarative language. The real SHT3x Kconfig file is a good tour — most of the keywords you'll meet appear in it. An excerpt:
menuconfig SHT3XD
bool "SHT3xD Temperature and Humidity Sensor"
default y
depends on DT_HAS_SENSIRION_SHT3XD_ENABLED
select I2C
select CRC
help
Enable driver for SHT3xD temperature and humidity sensors.
if SHT3XD
choice SHT3XD_MEASUREMENT_MODE
prompt "Measurement mode"
default SHT3XD_PERIODIC_MODE
config SHT3XD_SINGLE_SHOT_MODE
bool "single shot"
config SHT3XD_PERIODIC_MODE
bool "periodic data acquisition"
endchoice
choice SHT3XD_MPS
prompt "Measurements per second"
default SHT3XD_MPS_1
depends on SHT3XD_PERIODIC_MODE
help
Number of measurements per second.
config SHT3XD_MPS_05
bool "0.5"
config SHT3XD_MPS_1
bool "1"
# ... SHT3XD_MPS_2, _4, _10
endchoice
config SHT3XD_THREAD_PRIORITY
int "Thread priority"
depends on SHT3XD_TRIGGER_OWN_THREAD
default 10
help
Priority of thread used by the driver to handle interrupts.
endif # SHT3XD
Five things to notice:
menuconfigvsconfig—menuconfig SHT3XDdoubles as a collapsible group header inmenuconfig. Children sit under it visually.if SHT3XD … endif— applies an implicitdepends on SHT3XDto every symbol inside. Sub-options disappear entirely ifSHT3XDisn.choice … endchoice— defines a mutually-exclusive group. Exactly one ofSHT3XD_SINGLE_SHOT_MODE/SHT3XD_PERIODIC_MODEmay bey. TheSHT3XD_MPSchoice adds its owndepends on: a measurement rate only makes sense in periodic mode.- Indentation is convention, not syntax — Zephyr's Kconfig files use a tab for attributes and a tab plus two spaces for
helptext. - A
Kconfigfile rarely lives alone — each subsystem has its ownKconfig, andsourcelines higher up in the tree pull them together.
The same file also has a SHT3XD_REPEATABILITY choice (_LOW / _MEDIUM / _HIGH, default high) and a SHT3XD_TRIGGER_MODE choice. The two thread-based trigger modes carry depends on $(dt_compat_any_has_prop,$(DT_COMPAT_SENSIRION_SHT3XD),alert-gpios) — they only exist if your devicetree wires the sensor's ALERT pin. Neither course board does, so only SHT3XD_TRIGGER_NONE is available: devicetree reaching into Kconfig again.
Switch the SHT30 to single-shot mode — the sensor then sleeps between reads instead of measuring once a second on its own. Add to prj.conf:
CONFIG_SHT3XD_SINGLE_SHOT_MODE=y
Rebuild and check build/zephyr/.config: CONFIG_SHT3XD_PERIODIC_MODE is now unset, and every CONFIG_SHT3XD_MPS_* line has vanished — its depends on is no longer met.
File-level directives
These structure the file itself:
| Directive | Purpose |
|---|---|
config NAME | Declare a single symbol (CONFIG_NAME) |
menuconfig NAME | Declare a symbol that also acts as a collapsible menu header in menuconfig |
choice … endchoice | Mutually-exclusive group — exactly one option may be y |
menu "Title" … endmenu | Group symbols under a labeled section in the UI |
if EXPR … endif | Apply a shared depends on to all enclosed symbols |
source "path/Kconfig" | Include another Kconfig file (absolute from ZEPHYR_BASE) |
rsource "Kconfig" | Same as source, but relative to the current file |
comment "Text" | Static text shown in menuconfig (e.g. section dividers) |
mainmenu "Title" | Root menu title (only valid in the top-level Kconfig) |
Config block attributes
These appear inside a config or menuconfig block:
| Attribute | Example | What it does |
|---|---|---|
| type | bool / int / hex / string / tristate | One per block. Determines what values are valid. |
prompt "Text" | bool "Enable LED strip" (combined form) | User-facing label shown in menuconfig. Without a prompt, the symbol is hidden and only settable via defaults or select. |
default VALUE | default 0x200 if FOO | Initial value. Multiple default lines pick the first matching condition. |
depends on EXPR | depends on I2C && !ZEPHYR_QEMU | Symbol disappears unless EXPR is true. The symbol cannot be y if its dependencies aren't met. |
select OTHER | select I2S | When this symbol is y, force OTHER to y — ignores OTHER's own depends on. Use sparingly. |
imply OTHER | imply USB_KEYBOARD | Suggest OTHER=y, but respect its dependencies and allow the user to override. The polite version of select. |
range LOW HIGH | range 0 99 | Constrain int/hex to a valid range. |
help | (multi-line block) | Documentation shown in menuconfig and the Kconfig HTML reference. |
visible if EXPR | visible if EXPERT | Hide the prompt unless EXPR is true, but still let defaults apply. |
select vs imply vs depends on — the one thing that trips people up
These three keywords all relate one symbol to another, and getting them wrong is the most common source of "why is this driver not building" headaches.
| Direction | Respects target's depends on? | Override-able? | |
|---|---|---|---|
depends on | "I need X to be on" | n/a (it's the constraint) | n/a |
select | "Force X to be on" | No — can cause invalid configs | No |
imply | "Suggest X be on" | Yes | Yes — user can set X=n |
Use depends on for hard requirements (HW driver requires its bus subsystem to be selectable). Use imply when you'd like a feature on by default but it's not critical. Reach for select only when the target symbol has no user-facing prompt and exists solely to be turned on by other symbols.
selecting a symbol that has its own depends on can produce a config where the target is y but its dependency is n — Kconfig will warn but still build, and you'll get cryptic link errors. If you find yourself doing this, the right answer is almost always depends on instead.
The Kconfig file family in a Zephyr build
You won't write a Kconfig file very often — but you'll encounter half a dozen related files. Knowing which is which saves a lot of time:
| File | Lives in | Purpose |
|---|---|---|
Kconfig | Every subsystem and driver directory | Defines symbols (declarative — the schema) |
Kconfig.defconfig | Boards and SoCs | Sets defaults for invisible symbols — invisible to menuconfig but applied at build time |
<board>_defconfig | boards/<vendor>/<board>/ | Initial values for visible board symbols (the board's "factory settings") |
prj.conf | Your app root | Your config overrides — what you actually edit day-to-day |
boards/<board>.conf | Your app root, optional | Board-specific overrides that merge with prj.conf only when building for that board |
sysbuild.conf | Your app root, Sysbuild only | Cross-image settings (e.g. SB_CONFIG_BOOTLOADER_MCUBOOT=y) |
.config | build/zephyr/ (generated) | The fully-resolved result of merging everything above |
Precedence (later wins): SoC Kconfig.defconfig → board Kconfig.defconfig → <board>_defconfig → prj.conf → boards/<board>.conf → CLI -DCONFIG_* flags.
Common Kconfig types
| Symbol type | Example | Meaning |
|---|---|---|
bool | CONFIG_SHT3XD=y | Enable/disable a feature |
int | CONFIG_SHT3XD_THREAD_PRIORITY=10 | Numeric value |
string | CONFIG_BOOT_BANNER_STRING="my fw" | String value |
hex | CONFIG_SRAM_SIZE=0x40000 | Hex value |
Reference: Kconfig — Tips and Best Practices and the Kconfig setting guide in the Zephyr docs.
Browsing available symbols
# The terminal (ncurses) interface
west build -t menuconfig
# Or the graphical version (requires Python and tkinter)
west build -t guiconfig
Both show the full tree of available symbols with their current values and help text.
Next: actually read the SHT30
The four layers are now in place — devicetree, binding, Kconfig, and the in-tree driver they pull in. Time to use it. The next page, Sensors, calls sensor_sample_fetch against the SHT30 you've just configured.