Build Configurations & Kconfig in nRF Connect SDK

Developing embedded applications often means balancing flexibility, performance, and resource usage. With the nRF Connect SDK (NCS) and Zephyr RTOS, this balance is achieved through a highly configurable build system powered by Kconfig.

If you’re new to Zephyr or NCS, you’ve probably encountered the mysterious prj.conf file or run into the menuconfig command. These tools can feel confusing at first, but once you understand how they work together, they become one of the most powerful parts of the SDK.

In this post, we’ll explore:

  • What Kconfig is and how it fits into the build system
  • How to configure your project with prj.conf
  • How to explore options with menuconfig
  • Common configurations (logging, peripherals, Bluetooth Low Energy)
  • A practical example: enabling debug logging and tuning BLE stack parameters
  • Best practices for managing build configurations

By the end, you’ll be comfortable navigating and customizing build options in your own projects.

Why Build Configurations Are Important

When working on microcontrollers, every byte of flash and RAM matters. You don’t want unused features bloating your firmware image or draining power.

Build configurations allow you to:

  • Enable only the features you need (e.g., I2C but not SPI)
  • Adjust resource usage (e.g., Bluetooth buffer sizes, number of simultaneous connections)
  • Control debugging and logging levels to trade performance for visibility
  • Switch between development and production builds without rewriting code

Instead of hardcoding constants in source files, you use Kconfig-driven build configurations that make your project modular and flexible.

Kconfig: The Backbone of Configuration

The Kconfig system originated in the Linux kernel and was adopted by Zephyr. It provides a declarative way to define build-time options.

Each subsystem — whether it’s logging, Bluetooth, or a peripheral driver — comes with a Kconfig file that lists available options. These options are exposed to the user through either:

  • Text-based files (prj.conf, overlays)
  • Interactive menus (menuconfig)

During the build process, Kconfig generates:

  • .config → the resolved configuration with all options (including defaults)
  • autoconf.h → a header file that lets your code use #ifdef CONFIG_OPTION for conditional compilation

This means your firmware is literally built around the options you’ve enabled.

Configuring with prj.conf

The simplest way to configure your project is via the prj.conf file, which lives in the root of your application directory.

This file uses a key=value format to define Kconfig symbols. Here’s an example:

# Enable logging
CONFIG_LOG=y

# Set logging level to debug
CONFIG_LOG_DEFAULT_LEVEL=4

# Enable Bluetooth stack with peripheral role
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="My_Nordic_Device"

The syntax is simple:

  • CONFIG_FEATURE=y → enable a feature
  • CONFIG_FEATURE=n → disable a feature
  • CONFIG_FEATURE=value → set an integer or string

Because prj.conf is just a plain text file, it’s easy to track changes in version control.

Configuring with menuconfig

Sometimes you don’t know what options are available or what their possible values are. That’s where menuconfig comes in.

Run:

west build -t menuconfig

This opens an ncurses-based menu interface where you can navigate through categories such as:

  • Kernel
  • Device drivers
  • Logging
  • Networking
  • Bluetooth

Inside, you can toggle options, change integer values, or set strings.

When you save and exit, your changes are written back into a .config file and can optionally be exported into a prj.conf overlay. This way, you can experiment interactively and then lock down your configuration in source control.

If you prefer a graphical UI, there’s also guiconfig:

west build -t guiconfig

Common Configurations You’ll Use

Let’s explore some frequently used build options.

Logging

Logging is one of the first features you’ll configure. It’s essential for debugging but comes with trade-offs in memory and performance.

# Enable logging
CONFIG_LOG=y

# Default logging level
CONFIG_LOG_DEFAULT_LEVEL=3  # Info
CONFIG_LOG_DEFAULT_LEVEL=4  # Debug

# Choose a backend
CONFIG_LOG_BACKEND_RTT=y    # Use Segger RTT
CONFIG_LOG_BACKEND_UART=y   # Use UART

Log levels:

  • 0 → None
  • 1 → Error
  • 2 → Warning
  • 3 → Info
  • 4 → Debug

Peripherals

Peripherals are optional and must be explicitly enabled. For example:

# Enable I2C driver
CONFIG_I2C=y

# Enable UART
CONFIG_SERIAL=y
CONFIG_UART_NRFX_UART0=y

This ensures unused drivers don’t waste memory.

Bluetooth Low Energy (BLE)

The BLE stack is one of the most configurable parts of NCS. You can control device roles, connection limits, security, and data throughput.

# Enable BLE and peripheral role
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y

# Set device name
CONFIG_BT_DEVICE_NAME="My_Nordic_Device"

# Maximum simultaneous connections
CONFIG_BT_MAX_CONN=2

# Increase ATT MTU size for higher throughput
CONFIG_BT_BUF_ACL_RX_SIZE=251
CONFIG_BT_BUF_ACL_TX_SIZE=251
CONFIG_BT_DATA_LEN_UPDATE=y

These options are critical when tuning applications for speed, reliability, or power efficiency.

Practical Example: Debug Logging + BLE Tuning

Let’s combine what we’ve learned into a real-world scenario.

Imagine you’re developing a BLE peripheral device and need:

  • Verbose debug logging during development
  • Larger BLE buffers for faster data transfers
  • Support for two simultaneous connections

Here’s what your prj.conf might look like:

# Logging
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=4       # Debug
CONFIG_LOG_BACKEND_RTT=y         # Use RTT for efficient logging

# Bluetooth
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="Debug_Device"

# BLE performance tuning
CONFIG_BT_MAX_CONN=2             # Allow 2 connections
CONFIG_BT_BUF_ACL_RX_SIZE=251
CONFIG_BT_BUF_ACL_TX_SIZE=251
CONFIG_BT_DATA_LEN_UPDATE=y      # Enable data length extensions

With this setup:

  • RTT provides non-intrusive logging without blocking UART.
  • Debug-level logs give you full insight during development.
  • BLE throughput improves with larger buffer sizes and data length extensions.
  • The device can handle two connections simultaneously (useful for testing with multiple phones).

If you later want a release build, you can create a separate config overlay prj_release.conf that disables debug logging and shrinks BLE buffers to save memory.

Best Practices for Build Configurations

Managing configurations can get messy as projects grow. Here are some guidelines:

  • Keep prj.conf minimal: Only include options relevant to your project. Defaults handle the rest.
  • Use overlays for variants: Maintain prj_debug.conf, prj_release.conf, etc. and select them with west build -c -b nrf52840dk_nrf52840 -- -DCONF_FILE=prj_debug.conf.
  • Version control configs: Track prj.conf and overlays in Git to ensure reproducible builds.
  • Leverage menuconfig to explore options: Especially when you’re learning or tuning advanced features.
  • Understand the trade-offs: Every feature has a cost in flash, RAM, and power consumption.

Conclusion

The combination of Kconfig, prj.conf, and menuconfig gives you complete control over your application’s build in the nRF Connect SDK.

By learning how to configure logging, peripherals, and Bluetooth options, you can create firmware that’s optimized for both development and production.

Once you’re comfortable with these tools, you’ll find yourself quickly iterating on features, debugging efficiently, and building lean, production-ready applications.

Mastering build configurations isn’t just about toggling switches — it’s about making conscious design choices for your embedded system.