Kconfig Tutorial

This tutorial provides a comprehensive guide to using the Kconfig configuration system in the Nexus Embedded Platform, from basic concepts to advanced techniques.

Introduction

What is Kconfig?

Kconfig is a configuration language originally developed for the Linux kernel. It provides:

  • Hierarchical Configuration: Organize options in menus and submenus

  • Type Safety: Boolean, integer, string, and hexadecimal types with validation

  • Dependencies: Express relationships between configuration options

  • Default Values: Platform-specific and conditional defaults

  • Validation: Range constraints, symbol dependencies, and consistency checks

See also

Configuration Workflow

The following diagram shows the complete Kconfig configuration workflow:

        flowchart TD
    START([Start Configuration]) --> KCONFIG[Kconfig Files]
    KCONFIG --> DEFCONFIG{Use defconfig?}

    DEFCONFIG -->|Yes| LOADDEF[Load defconfig]
    DEFCONFIG -->|No| MENUCONFIG[Interactive menuconfig]

    LOADDEF --> MERGE[Merge with Kconfig]
    MENUCONFIG --> MERGE

    MERGE --> VALIDATE[Validate Configuration]
    VALIDATE --> VALID{Valid?}

    VALID -->|No| ERROR[Show Errors]
    ERROR --> MENUCONFIG

    VALID -->|Yes| SAVE[Save .config]
    SAVE --> GENERATE[Generate nexus_config.h]
    GENERATE --> HEADER[C Header File]
    HEADER --> BUILD[Build System Uses Config]
    BUILD --> DONE([Configuration Complete])

    style START fill:#e1f5ff
    style KCONFIG fill:#fff4e1
    style MENUCONFIG fill:#ffe1f5
    style VALIDATE fill:#e1ffe1
    style GENERATE fill:#f5e1ff
    style HEADER fill:#ffe1e1
    style DONE fill:#e1f5ff
    style ERROR fill:#ffcccc
    

Why Use Kconfig?

Benefits:

  • Compile-Time Configuration: All configuration resolved before compilation

  • Type Safety: Prevents invalid configurations

  • Dependency Management: Automatic handling of option dependencies

  • Multiple Platforms: Single configuration system for all platforms

  • Validation: Built-in validation prevents configuration errors

  • Documentation: Self-documenting with help text

Use Cases:

  • Platform selection (Native, STM32, GD32, ESP32)

  • Peripheral configuration (UART, GPIO, SPI, I2C, Timer, ADC)

  • OSAL backend selection (FreeRTOS, RT-Thread, bare-metal)

  • Memory layout configuration

  • Debug and optimization settings

Basic Concepts

Configuration Symbols

A configuration symbol is a named option that can be set to a value:

config UART_ENABLE
    bool "Enable UART support"
    default y
    help
      Enable UART peripheral support in the HAL.

Symbol Types:

  • bool: Boolean (y/n)

  • tristate: Three-state (y/m/n) - rarely used in embedded

  • int: Integer value

  • hex: Hexadecimal value

  • string: String value

Symbol Naming

Naming Conventions:

PLATFORM_<NAME>                      # Platform selection
<PLATFORM>_<PERIPHERAL>_ENABLE       # Peripheral enable
INSTANCE_<PLATFORM>_<PERIPHERAL>_<N> # Instance enable
<PERIPHERAL><N>_<PARAMETER>          # Instance parameter

Examples:

config PLATFORM_STM32                # Platform
config STM32_UART_ENABLE             # Peripheral enable
config INSTANCE_STM32_UART_1         # Instance enable
config UART1_BAUDRATE                # Instance parameter

Dependencies

Use depends on to make options conditional:

config UART1_DMA_ENABLE
    bool "Enable DMA for UART1"
    depends on INSTANCE_STM32_UART_1
    depends on STM32_DMA_ENABLE
    help
      Enable DMA transfers for UART1.

Multiple dependencies are AND’ed together. The option is only visible when all dependencies are satisfied.

Default Values

Provide sensible defaults for all options:

config UART1_BAUDRATE
    int "UART1 baud rate"
    default 115200
    help
      Baud rate for UART1 in bits per second.

Conditional Defaults:

config OSAL_HEAP_SIZE
    int "Heap size (bytes)"
    default 32768 if OSAL_FREERTOS
    default 16384 if OSAL_BAREMETAL
    default 65536
    range 4096 1048576

The first matching default is used.

Help Text

Always provide help text explaining the option:

config UART1_TX_BUFFER_SIZE
    int "UART1 TX buffer size"
    default 256
    range 16 4096
    help
      Size of the UART1 transmit buffer in bytes.

      Larger buffers can improve throughput but consume
      more RAM. Must be a power of 2 for optimal performance.

      Recommended values:
      - 128 bytes: Low-bandwidth applications
      - 256 bytes: General purpose (default)
      - 512+ bytes: High-bandwidth applications

Basic Configuration Workflow

Step 1: Select Platform

Choose your target platform:

# .config file
CONFIG_PLATFORM_STM32=y

Or use a default configuration:

# Copy platform defconfig
cp platforms/STM32/defconfig_stm32f4 .config

Step 2: Select Chip

For STM32, select chip family and variant:

CONFIG_STM32F4=y
CONFIG_STM32F407=y

Step 3: Configure Peripherals

Enable and configure required peripherals:

# Enable UART
CONFIG_STM32_UART_ENABLE=y
CONFIG_INSTANCE_STM32_UART_1=y
CONFIG_UART1_BAUDRATE=115200
CONFIG_UART1_MODE_DMA=y

Step 4: Select OSAL Backend

Choose your RTOS:

CONFIG_OSAL_FREERTOS=y
CONFIG_OSAL_TICK_RATE_HZ=1000
CONFIG_OSAL_HEAP_SIZE=32768

Step 5: Generate Configuration

Generate the C header file:

python scripts/Kconfig/generate_config.py --config .config

This creates nexus_config.h:

#define NX_CONFIG_PLATFORM_STM32 1
#define NX_CONFIG_STM32F407 1
#define NX_CONFIG_UART1_BAUDRATE 115200
#define NX_CONFIG_OSAL_FREERTOS 1

Step 6: Build Project

Build with the generated configuration:

CMake -B build
CMake --build build

Intermediate Concepts

Choice Groups

Use choice for mutually exclusive options:

choice
    prompt "UART1 transfer mode"
    default UART1_MODE_DMA

config UART1_MODE_POLLING
    bool "Polling"
    help
      Use polling mode for UART1 transfers.
      Simple but blocks CPU.

config UART1_MODE_INTERRUPT
    bool "Interrupt"
    help
      Use interrupt mode for UART1 transfers.
      Non-blocking but requires ISR handling.

config UART1_MODE_DMA
    bool "DMA"
    help
      Use DMA mode for UART1 transfers.
      Most efficient for large transfers.

endchoice

Only one option in the choice group can be selected.

Range Constraints

Use range to constrain integer values:

config UART1_BAUDRATE
    int "UART1 baud rate"
    default 115200
    range 9600 921600
    help
      Baud rate must be between 9600 and 921600 bps.

Conditional Ranges:

config TIMER_PRESCALER
    int "Timer prescaler"
    default 1
    range 1 65536 if STM32F4
    range 1 32768 if STM32L4
    help
      Timer prescaler value.
      Range depends on chip family.

Select Statements

Use select to automatically enable dependencies:

config UART1_DMA_ENABLE
    bool "Enable DMA for UART1"
    depends on INSTANCE_STM32_UART_1
    select STM32_DMA_ENABLE
    help
      Enable DMA transfers for UART1.
      Automatically enables DMA subsystem.

When UART1_DMA_ENABLE is set, STM32_DMA_ENABLE is automatically enabled.

Warning: Use select sparingly. It can create circular dependencies and override user choices.

Imply Statements

Use imply to suggest but not force dependencies:

config UART_ENABLE
    bool "Enable UART support"
    imply DMA_ENABLE
    help
      Enable UART peripheral support.
      DMA is recommended but not required.

imply sets the default but allows the user to override it.

Advanced Concepts

Conditional Compilation

Use if blocks for conditional sections:

if PLATFORM_STM32

source "platforms/STM32/Kconfig_chip"
source "platforms/STM32/Kconfig_peripherals"

menu "STM32 Platform Settings"
    # STM32-specific options
endmenu

endif # PLATFORM_STM32

Everything inside the if block is only visible when PLATFORM_STM32 is enabled.

Visibility Conditions

Control option visibility with visible if:

config ADVANCED_FEATURE
    bool "Enable advanced feature"
    visible if EXPERT_MODE
    help
      This option is only visible in expert mode.

Source Directives

Include other Kconfig files:

# Absolute path from root
source "platforms/STM32/Kconfig"

# Relative path (rsource)
rsource "adapters/*/Kconfig"

rsource supports wildcards for including multiple files.

Environment Variables

Access environment variables:

config CUSTOM_PLATFORM
    string "Custom platform name"
    default "$(PLATFORM_NAME)"
    help
      Platform name from environment variable.

Derived Symbols

Create symbols derived from other symbols:

config UART_COUNT
    int
    default 1 if INSTANCE_STM32_UART_1
    default 2 if INSTANCE_STM32_UART_2
    default 3 if INSTANCE_STM32_UART_3
    default 0

This symbol has no prompt and is automatically calculated.

Platform-Specific Configuration

Native Platform

Quick Start:

cp platforms/native/defconfig .config
python scripts/Kconfig/generate_config.py

Key Options:

CONFIG_PLATFORM_NATIVE=y
CONFIG_NATIVE_ENABLE_LOGGING=y
CONFIG_NATIVE_LOG_LEVEL=3
CONFIG_NATIVE_DMA_CHANNELS=8
CONFIG_NATIVE_ISR_SLOTS=64

Peripheral Configuration:

# UART
CONFIG_NATIVE_UART_ENABLE=y
CONFIG_INSTANCE_NATIVE_UART_1=y
CONFIG_UART1_BAUDRATE=115200

# GPIO
CONFIG_NATIVE_GPIO_ENABLE=y
CONFIG_INSTANCE_NATIVE_GPIO_A=y
CONFIG_GPIO_A_PIN_COUNT=16

STM32F4 Platform

Quick Start:

cp platforms/STM32/defconfig_stm32f4 .config
python scripts/Kconfig/generate_config.py

Key Options:

CONFIG_PLATFORM_STM32=y
CONFIG_STM32F4=y
CONFIG_STM32F407=y
CONFIG_STM32_CHIP_NAME="STM32F407xx"

Memory Configuration:

CONFIG_LINKER_RAM_START=0x20000000
CONFIG_LINKER_RAM_SIZE=0x00020000
CONFIG_LINKER_FLASH_START=0x08000000
CONFIG_LINKER_FLASH_SIZE=0x00100000

STM32H7 Platform

Quick Start:

cp platforms/STM32/defconfig_stm32h7 .config
python scripts/Kconfig/generate_config.py

Key Options:

CONFIG_PLATFORM_STM32=y
CONFIG_STM32H7=y
CONFIG_STM32H743=y
CONFIG_STM32_CHIP_NAME="STM32H743xx"

Memory Configuration:

CONFIG_LINKER_RAM_START=0x24000000
CONFIG_LINKER_RAM_SIZE=0x00080000
CONFIG_LINKER_FLASH_START=0x08000000
CONFIG_LINKER_FLASH_SIZE=0x00200000

GD32 Platform

Key Options:

CONFIG_PLATFORM_GD32=y
CONFIG_GD32F30X=y
CONFIG_GD32F303=y

ESP32 Platform

Key Options:

CONFIG_PLATFORM_ESP32=y
CONFIG_ESP32_WROOM_32=y

Configuration Workflow

Interactive Configuration

While Nexus doesn’t currently provide a menuconfig UI, you can edit .config directly:

# Edit configuration
nano .config

# Validate changes
python scripts/Kconfig/validate_kconfig.py Kconfig

# Generate header
python scripts/Kconfig/generate_config.py

Automated Configuration

Use scripts for automated configuration:

#!/usr/bin/env python3
# configure.py

import subprocess

def configure_platform(platform, chip=None):
    config_lines = [
        f"CONFIG_PLATFORM_{platform.upper()}=y"
    ]

    if chip:
        config_lines.append(f"CONFIG_{chip.upper()}=y")

    with open('.config', 'w') as f:
        f.write('\n'.join(config_lines))

    # Generate header
    subprocess.run([
        'python', 'scripts/Kconfig/generate_config.py'
    ])

# Usage
configure_platform('STM32', 'stm32f407')

Configuration Templates

Create reusable configuration templates:

# templates/uart_debug.config
CONFIG_STM32_UART_ENABLE=y
CONFIG_INSTANCE_STM32_UART_1=y
CONFIG_UART1_BAUDRATE=115200
CONFIG_UART1_MODE_INTERRUPT=y
CONFIG_HAL_DEBUG_ENABLE=y
CONFIG_HAL_DEBUG_LEVEL=4

Merge with base configuration:

cat platforms/STM32/defconfig_stm32f4 templates/uart_debug.config > .config
python scripts/Kconfig/generate_config.py

Configuration Validation

Syntax Validation

Validate Kconfig syntax:

python scripts/Kconfig/validate_kconfig.py Kconfig

Checks:

  • Block structure (if/endif, menu/endmenu, choice/endchoice)

  • Source file existence

  • Symbol definitions

  • Syntax errors

Dependency Validation

Check for dependency issues:

python scripts/Kconfig/validate_kconfig.py --check-deps Kconfig

Checks:

  • Circular dependencies

  • Undefined symbol references

  • Unsatisfiable dependencies

Value Validation

Validate configuration values:

python scripts/Kconfig/validate_kconfig.py --check-values .config

Checks:

  • Range constraint violations

  • Invalid type values

  • Missing required symbols

Configuration Comparison

Compare Two Configurations

Show differences between configurations:

python scripts/Kconfig/kconfig_diff.py old.config new.config

Output:

Added symbols:
  CONFIG_NEW_FEATURE=y

Removed symbols:
  CONFIG_OLD_FEATURE=y

Changed symbols:
  CONFIG_UART1_BAUDRATE: 9600 -> 115200

JSON Output

Generate machine-readable diff:

python scripts/Kconfig/kconfig_diff.py \
    --format json \
    old.config new.config \
    --output diff.json

Configuration Migration

Version Migration

Migrate configuration to new version:

python scripts/Kconfig/kconfig_migrate.py \
    --input old.config \
    --output new.config \
    --version 2.0

Migration Rules:

  • Renamed symbols are automatically updated

  • Deprecated symbols are removed

  • New required symbols are added with defaults

  • Migration log is generated

Symbol Renaming

Handle renamed symbols:

# migration_rules.py
SYMBOL_RENAMES = {
    'OLD_UART_ENABLE': 'STM32_UART_ENABLE',
    'OLD_BAUDRATE': 'UART1_BAUDRATE',
}

Best Practices

Naming Conventions

Follow consistent naming:

✓ CONFIG_PLATFORM_STM32
✓ CONFIG_STM32_UART_ENABLE
✓ CONFIG_INSTANCE_STM32_UART_1
✓ CONFIG_UART1_BAUDRATE

✗ CONFIG_STM32_PLATFORM
✗ CONFIG_ENABLE_UART_STM32
✗ CONFIG_STM32_UART1_INSTANCE
✗ CONFIG_BAUDRATE_UART1

Organization

Group related options:

menu "UART Configuration"

menuconfig STM32_UART_ENABLE
    bool "Enable UART support"

if STM32_UART_ENABLE

menu "UART Instances"
    # Instance configurations
endmenu

menu "UART Global Settings"
    # Global settings
endmenu

endif # STM32_UART_ENABLE

endmenu

Documentation

Provide comprehensive help:

config UART1_BAUDRATE
    int "UART1 baud rate"
    default 115200
    range 9600 921600
    help
      Baud rate for UART1 in bits per second.

      Common values:
      - 9600: Low-speed serial
      - 19200: Moderate speed
      - 38400: Higher speed
      - 57600: High speed
      - 115200: Very high speed (default)
      - 230400, 460800, 921600: Ultra high speed

      Note: Higher baud rates require more accurate
      clock configuration and may not work reliably
      on all platforms.

Defaults

Provide sensible defaults:

config UART1_TX_BUFFER_SIZE
    int "UART1 TX buffer size"
    default 256 if UART1_MODE_DMA
    default 128 if UART1_MODE_INTERRUPT
    default 64 if UART1_MODE_POLLING
    default 256
    range 16 4096

Dependencies

Use depends on for hard requirements:

config UART1_DMA_ENABLE
    bool "Enable DMA for UART1"
    depends on INSTANCE_STM32_UART_1
    depends on STM32_DMA_ENABLE

Use select sparingly:

# Good: select for internal dependencies
config UART_DRIVER
    bool
    select CLOCK_ENABLE
    select GPIO_ENABLE

# Bad: select for user-visible options
config UART_ENABLE
    bool "Enable UART"
    select DMA_ENABLE  # Don't force DMA on users

Troubleshooting

Common Issues

Issue: Symbol not defined

Error: Symbol 'UART_ENABLE' is not defined

Solution:

# Find where symbol should be defined
grep -r "config UART_ENABLE" .

# Check if Kconfig file is sourced
grep -r "source.*uart.*Kconfig" .

Issue: Circular dependency

Error: Circular dependency detected: A -> B -> A

Solution:

# Bad: circular dependency
config A
    bool "Option A"
    depends on B

config B
    bool "Option B"
    depends on A

# Good: remove circular dependency
config A
    bool "Option A"

config B
    bool "Option B"
    depends on A

Issue: Range violation

Error: Value 1000000 is outside range [9600, 921600]

Solution:

# Fix the value in .config
CONFIG_UART1_BAUDRATE=115200

Issue: Configuration not applied

# Regenerate configuration header
python scripts/Kconfig/generate_config.py --config .config

# Clean and rebuild
rm -rf build
CMake -B build
CMake --build build

Debugging Configuration

Enable verbose output:

python scripts/Kconfig/generate_config.py --verbose

Check generated header:

cat nexus_config.h | grep UART

Validate dependencies:

python scripts/Kconfig/validate_kconfig.py --check-deps Kconfig

Next Steps

See Also