Configuration Guide
no-OS decides which drivers, platform code, and framework features go into a
build through a Kconfig system — the same mechanism the Linux kernel uses.
The Getting Started tutorial referred to build variants (the .conf
files), and Architecture introduced Kconfig in passing. This page is the
full treatment: what the option system is, how the layered defconfigs combine
into one configuration, how to change options, and how the result reaches your
C code.
This is a companion to the per-platform build guides under No-OS Build Guide, which cover toolchain setup. Here we cover configuration itself.
The mental model
Every configurable choice in no-OS is a Kconfig symbol named
CONFIG_<SOMETHING> — CONFIG_SPI, CONFIG_IIO, CONFIG_ADC_DEMO,
CONFIG_MAXIM, and so on. A build's configuration is nothing more than the
set of these symbols and their values.
Symbols come from three places, and a concrete build is produced by layering three sources of defconfig (default-configuration) fragments on top of the Kconfig defaults:
┌─ project board config projects/<proj>/boards/<variant>/<board>.conf
│ board tweaks for this project+variant (pin maps, extra options)
├─ project variant config projects/<proj>/<variant>.conf
│ what the application needs: drivers, frameworks, examples
├─ board config board_configs/<platform>/<board>_defconfig
│ what the MCU provides: platform peripheral drivers
└─ Kconfig defaults Kconfig files across the tree
the baseline (mostly "disabled")
Higher layers override lower ones. The board config turns on the platform peripheral implementations the MCU has; the variant config turns on the application pieces (device drivers, IIO, the chosen example); the optional per-variant board config adds anything specific to that project on that board.
Where the pieces live
Kconfig files (the option definitions)
Symbols are declared in Kconfig files, which form a tree:
the root
Kconfig— the hardware-interface abstractions (CONFIG_SPI,CONFIG_I2C,CONFIG_UART,CONFIG_GPIO, ...);drivers/**/Kconfig— one option per driver and platform backend (CONFIG_ADC_DEMO,CONFIG_SPI_MAXIM, ...);projects/<proj>/Kconfig— options private to a project.
A declaration looks like this (from the root Kconfig):
config SPI
bool "Enable SPI"
default n
help
Enable Serial Peripheral Interface (SPI) support.
API: drivers/api/no_os_spi.c, include/no_os_spi.h
Projects can also declare their own structured choices. The IIO Demo, for
example, uses a choice so exactly one example variant is selected, plus a
bool that selects a dependency:
choice IIO_DEMO_EXAMPLE
prompt "Example variant"
default IIO_DEMO_IIO_EXAMPLE
config IIO_DEMO_IIO_EXAMPLE
bool "Basic IIO example"
config IIO_DEMO_IIO_TIMER_TRIGGER_EXAMPLE
bool "IIO timer trigger example"
endchoice
config IIO_DEMO_NETWORKING
bool "Enable ESP8266 Wi-Fi networking"
default n
select WIFI_NETWORKING
Board configs (what the MCU provides)
board_configs/<platform>/<board>_defconfig enables the platform peripheral
drivers a given board's MCU offers. For example
board_configs/maxim/max32650fthr_defconfig:
CONFIG_PLATFORM_DRIVERS=y
CONFIG_MAXIM=y
CONFIG_UART_MAXIM=y
CONFIG_SPI_MAXIM=y
CONFIG_I2C_MAXIM=y
CONFIG_GPIO_MAXIM=y
CONFIG_TIMER_MAXIM=y
...
These are the concrete platform_ops implementations from
Architecture — turning on CONFIG_SPI_MAXIM is what compiles the
Maxim SPI backend into the build.
Variant configs (what the application needs)
projects/<proj>/<variant>.conf is the defconfig for one build variant. The
IIO Demo's iio.conf:
CONFIG_UART=y
CONFIG_IRQ=y
CONFIG_TIMER=y
CONFIG_IIO=y
CONFIG_ADC=y
CONFIG_ADC_DEMO=y
CONFIG_ADC_IIO_DEMO=y
CONFIG_DAC=y
CONFIG_DAC_DEMO=y
CONFIG_DAC_IIO_DEMO=y
CONFIG_IIO_DEMO_IIO_EXAMPLE=y
Note it enables the abstract interfaces (CONFIG_UART, CONFIG_SPI) and
the device/framework code — but not any platform backend. That is the board
config's job. This split is the whole point: the variant config is
platform-independent, so the same iio.conf builds for Maxim, STM32, ADuCM,
and Pico. Each variant of a project is exactly this — a named defconfig — which
is why iio, iio_usb_uart, and iio_wifi are separate variants:
iio_wifi.conf = iio.conf
+ CONFIG_IIO_DEMO_NETWORKING=y
+ CONFIG_WIFI_SSID="..."
+ CONFIG_WIFI_PWD="..."
Per-variant board configs (project + board specifics)
Optionally, projects/<proj>/boards/<variant>/<board>.conf carries settings
needed only for a specific project on a specific board — for instance a pin-mux
file or an extra peripheral. From the IIO Demo Wi-Fi build on the ADuCM board:
CONFIG_ADUCM_PINMUX_PATH="pinmux_config.c"
CONFIG_TIMER=y
The build utility layers the matching one automatically based on the board you select; you rarely edit these by hand.
How the layers become one configuration
When you run the build utility from Getting Started:
python tools/scripts/no_os_build.py build --project iio_demo --variant iio --board max32650fthr
it resolves the three layers for you:
--board max32650fthrselects the CMake preset (which points atboard_configs/maxim/max32650fthr_defconfig);--variant iioselectsprojects/iio_demo/iio.confas thePROJECT_DEFCONFIG;if
projects/iio_demo/boards/iio/max32650fthr.confexists, it is layered on top.
Under the hood, tools/scripts/generate_config.py loads the Kconfig tree and
merges each defconfig on top of it (later fragments win, mirroring the layering
above). It then emits two artifacts into the build directory:
a CMake file (
config.cmake) withset(CONFIG_FOO <value>)lines, which the build system uses to decide which source files to compile;a C header (
no_os_config.h, guarded byNO_OS_CONFIG_H) with#define CONFIG_FOO <value>for every symbol, which your source code can test.
Changing the configuration
There are three ways to adjust options, from most permanent to most transient.
Edit a defconfig
To make a change part of a variant, edit its .conf file and rebuild with
--clean so the configuration is regenerated:
# e.g. set your network for the Wi-Fi variant
# projects/iio_demo/iio_wifi.conf
# CONFIG_WIFI_SSID="MyNetwork"
# CONFIG_WIFI_PWD="MyPassword"
python tools/scripts/no_os_build.py build --project iio_demo --variant iio_wifi --board eval-adicup3029 --clean
This is the right place for changes you want to keep. To add a whole new variant,
add a new <variant>.conf (and, if needed, boards/<variant>/<board>.conf
files) — the build utility discovers variants by globbing *.conf in the
project directory, so it will appear in list automatically.
Override on the CMake command line
For a one-off manual configure, pass -DCONFIG_FOO=... directly. The
No-OS Build Guide CMake pages document the full manual flow (selecting the
preset with --preset and the variant with -DPROJECT_DEFCONFIG=).
External library dependencies
Some projects pull in external libraries — lwIP, FreeRTOS, mbedTLS, LVGL, the
MQTT client, the Azure SDK, the Pico SDK, and a few others. These are not stored
in the repository; they are cloned with Git on demand during the CMake configure
step, at the exact version the build asks for. The requested version of each
library is itself a Kconfig symbol (CONFIG_FREERTOS_VERSION,
CONFIG_LWIP_VERSION, CONFIG_LVGL_VERSION, ...), so it layers and can be
overridden through the same defconfig/menuconfig/command-line mechanisms as any
other option.
For each library the source is resolved in this order:
Explicit override. If
NO_OS_DEP_<LIB>_PATHis set (as a CMake variable or an environment variable), it is used verbatim — no version check, no cloning. Point it at a local working copy when you are developing against a library or need a version the defconfig does not offer:export NO_OS_DEP_LWIP_PATH=~/src/lwip
The library names follow the version symbols above —
NO_OS_DEP_LWIP_PATH,NO_OS_DEP_FREERTOS_PATH,NO_OS_DEP_MBEDTLS_PATH,NO_OS_DEP_LVGL_PATH,NO_OS_DEP_PICO_SDK_PATH, and so on.Managed clone. Otherwise the build clones the requested version once and reuses it. By default the clone lands in-tree under
libraries/and is reused on every later configure. IfNO_OS_CACHE_DIRis set, the clone goes into a shared, version-keyed store under that directory instead — so several builds (or CI pipeline runs) reuse the same download rather than re-cloning:export NO_OS_CACHE_DIR=~/.cache/no-os-deps
An existing clone is reused as-is when it already sits on the requested version,
so switching between builds that ask for different versions re-fetches only what
changed. The in-tree libraries/ clones are git-ignored.
Using configuration in code
The generated header makes every symbol available as a CONFIG_* macro, so
source and driver code can compile conditionally:
#ifdef CONFIG_IIO_DEMO_NETWORKING
/* set up the ESP8266 Wi-Fi transport */
#else
/* use the plain UART transport */
#endif
Boolean symbols that are disabled are still defined (as empty), which is why the
codebase guards with #ifdef CONFIG_FOO rather than testing a value. String
and integer symbols (CONFIG_WIFI_SSID, buffer sizes, ...) expand to their
literal value.
At the build-system level, the CONFIG_* values in config.cmake gate
which files are added to the compile — e.g. CONFIG_SPI_MAXIM=y is what pulls
drivers/platform/maxim/.../maxim_spi.c into the build. This is how a
disabled option costs nothing: its code is never compiled in.
Troubleshooting
- An option I set in the ``.conf`` did not take effect.
The configuration is generated at configure time. After editing a defconfig, rebuild with
--clean(or delete thebuild/directory) so it is regenerated.- A driver's symbol is on but the platform backend is missing (link errors).
You enabled the abstract interface (e.g.
CONFIG_SPI) in the variant config but the board config does not provide the backend (e.g.CONFIG_SPI_MAXIM). Interfaces come from the variant; backends come from the board defconfig — check both layers.- ``menuconfig`` changes disappeared.
menuconfigedits the configuration inside the build directory, not the source.conf. Copy the change into the appropriate defconfig to keep it.- A ``select``ed option is unexpectedly on.
Kconfig
selectforces a dependency on. For exampleCONFIG_IIO_DEMO_NETWORKINGselectsCONFIG_WIFI_NETWORKING— you cannot turn the latter off while the former is on. Follow theselectchain in theKconfigfiles.
Where to go next
Getting Started — the build utility and variants in practice.
Architecture — how
CONFIG_*selection maps to the platform_ops the code dispatches through.No-OS Build Guide — per-platform toolchain setup and the manual CMake flow.