Skip to main content

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

prj.conf
# Enable the I2C bus and sensor subsystem (for BME280)
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 files in order:

  1. SoC defaults — from the chip definition (esp32s3.conf)
  2. Board defaults — from the board definition (esp32s3_devkitc.conf)
  3. Your prj.conf — your application overrides
  4. Driver Kconfig — for every symbol you enable (e.g. CONFIG_SENSOR=y), every matching sensor driver in the tree becomes available. The overlay 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.

info

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 BME280 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 BME280 at zephyr/drivers/sensor/bosch/bme280/Kconfig:

menuconfig BME280
bool "BME280/BMP280 sensor"
default y
depends on DT_HAS_BOSCH_BME280_ENABLED
select I2C if $(dt_compat_on_bus,$(DT_COMPAT_BOSCH_BME280),i2c)
select SPI if $(dt_compat_on_bus,$(DT_COMPAT_BOSCH_BME280),spi)
help
Enable driver for BME280 I2C-based or SPI-based temperature
and pressure sensor.

Three things to notice:

  • default yBME280 is auto-enabled the moment its dependency is met. You don't write CONFIG_BME280=y yourself.
  • depends on DT_HAS_BOSCH_BME280_ENABLED — this symbol is auto-generated by Zephyr when it finds a DTS node with compatible = "bosch,bme280" in your overlay. The overlay triggers the Kconfig.
  • select I2C if … on-bus i2c — the driver pulls in the I2C subsystem only if your BME280 is actually on an I2C bus. Wire it via SPI and it pulls in SPI instead. This is select doing conditional work.

This is why CONFIG_I2C=y + CONFIG_SENSOR=y is enough to bring up the BME280 — everything driver-specific resolves automatically from your overlay.



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 BME280 Kconfig file is the clearest example — every keyword Kconfig supports appears at least once:

menuconfig BME280
bool "BME280/BMP280 sensor"
default y
depends on DT_HAS_BOSCH_BME280_ENABLED
select I2C if $(dt_compat_on_bus,$(DT_COMPAT_BOSCH_BME280),i2c)
help
Enable driver for BME280 I2C- or SPI-based environmental sensor.

if BME280

choice BME280_MODE
prompt "BME280 sampling mode"
default BME280_MODE_NORMAL

config BME280_MODE_NORMAL
bool "normal"

config BME280_MODE_FORCED
bool "forced"
endchoice

config BME280_TEMP_OVER
int "Temperature oversampling factor"
default 2
range 1 16

endif # BME280

Five things to notice:

  • menuconfig vs configmenuconfig BME280 doubles as a collapsible group header in menuconfig. Children sit under it visually.
  • if BME280 … endif — applies an implicit depends on BME280 to every symbol inside. Sub-options disappear entirely if BME280 is n.
  • choice … endchoice — defines a mutually-exclusive group. Exactly one of BME280_MODE_NORMAL / BME280_MODE_FORCED may be y.
  • Indentation is convention, not syntax — but every Kconfig file follows the same 4-space style.
  • A Kconfig file rarely lives alone — each subsystem has its own Kconfig, and source lines higher up in the tree pull them together.


File-level directives

These structure the file itself:

DirectivePurpose
config NAMEDeclare a single symbol (CONFIG_NAME)
menuconfig NAMEDeclare a symbol that also acts as a collapsible menu header in menuconfig
choiceendchoiceMutually-exclusive group — exactly one option may be y
menu "Title"endmenuGroup symbols under a labeled section in the UI
if EXPRendifApply 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:

AttributeExampleWhat it does
typebool / int / hex / string / tristateOne 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 VALUEdefault 0x200 if FOOInitial value. Multiple default lines pick the first matching condition.
depends on EXPRdepends on I2C && !ZEPHYR_QEMUSymbol disappears unless EXPR is true. The symbol cannot be y if its dependencies aren't met.
select OTHERselect I2SWhen this symbol is y, force OTHER to yignores OTHER's own depends on. Use sparingly.
imply OTHERimply USB_KEYBOARDSuggest OTHER=y, but respect its dependencies and allow the user to override. The polite version of select.
range LOW HIGHrange 0 99Constrain int/hex to a valid range.
help(multi-line block)Documentation shown in menuconfig and the Kconfig HTML reference.
visible if EXPRvisible if EXPERTHide 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.

DirectionRespects 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 configsNo
imply"Suggest X be on"YesYes — 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.

warning

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:

FileLives inPurpose
KconfigEvery subsystem and driver directoryDefines symbols (declarative — the schema)
Kconfig.defconfigBoards and SoCsSets defaults for invisible symbols — invisible to menuconfig but applied at build time
<board>_defconfigboards/<vendor>/<board>/Initial values for visible board symbols (the board's "factory settings")
prj.confYour app rootYour config overrides — what you actually edit day-to-day
boards/<board>.confYour app root, optionalBoard-specific overrides that merge with prj.conf only when building for that board
sysbuild.confYour app root, Sysbuild onlyCross-image settings (e.g. SB_CONFIG_BOOTLOADER_MCUBOOT=y)
.configbuild/zephyr/ (generated)The fully-resolved result of merging everything above

Precedence (later wins): SoC Kconfig.defconfig → board Kconfig.defconfig<board>_defconfigprj.confboards/<board>.conf → CLI -DCONFIG_* flags.



Common Kconfig types

Symbol typeExampleMeaning
boolCONFIG_BME280=yEnable/disable a feature
intCONFIG_BME280_TEMP_OVER=2Numeric value
stringCONFIG_BOOT_BANNER_STRING="my fw"String value
hexCONFIG_SRAM_SIZE=0x40000Hex value
info

Reference: Kconfig — Tips and Best Practices and the Kconfig setting guide in the Zephyr docs.



Browsing available symbols

# Launch the Kconfig GUI (requires Python and tkinter)
west build -t menuconfig

# Or the simpler ncurses version
west build -t guiconfig

Both show the full tree of available symbols with their current values and help text.



Next: actually read the BME280

The four layers are now in place — overlay, binding, Kconfig, and the in-tree driver they pull in. Time to use it. The next page, Sensors, calls sensor_sample_fetch against the BME280 you've just configured.