[Zephyr ® Series] Part 4: Overview and Practical Applications of Kconfig and Device Trees (Japanese Blog)
From this point on, we'll be moving on to the advanced topics of Zephyr.
This session will cover an overview of Kconfig and device trees, followed by hands-on programming exercises to help you utilize them effectively.
One of the key features of Zephyr RTOS, as mentioned previously, is its software scalability (reusability), which makes it easy to reuse software developed once in other projects or derivative products, enabling rapid development.
Furthermore, to support a wide range of hardware platforms, Zephyr employs a powerful configuration system called "Kconfig" and "Devicetree".
This allows you to port programs to different microcontroller boards simply by changing configuration files, without having to rewrite the source code in the same C/C++ language.
This article explains the basic mechanisms of Kconfig and the device tree. As a practical application, we will modify the hardware-independent LED blinking program created in Part 3 to run on two different microcontroller boards: " FRDM-MCXA153 " and " FRDM-MCXN947 ".
To run the same application on different boards (microcontrollers and processors), we will explain practical programming methods using Kconfig and the device tree.
table of contents
Preparation
Kconfig Basics
Device tree fundamentals
Best practices for improving software reusability
Kconfig and the Device Tree in Practice
Create a simple program (hands-on)
1. Program Specifications
2. Directory structure
3. Create Kconfig and prj.conf
4. Creating device tree overlays and board-specific settings
5. Hardware-independent common code (main.c) Create
6. Build and run
summary
Preparation
Hardware preparation
This article will primarily use the following boards to create and test programs.
FRDM-MCXA153 (main use)
Additionally, the following boards will be used as supplementary tools to verify the portability of the program you have created.
FRDM-MCXN947
SW preparation
This guide assumes that you have already set up the Zephyr development environment (Zephyr SDK, West command, etc.). If you haven't set it up yet, please refer to the second article on environment setup.
[Zephyr ® Series] Part 2: First Build and Testing (Japanese Blog)
We will be using the LED blinking program created in the third installment of the Zephyr series. If you haven't created it yet, we recommend creating it by referring to the previous article.
[Zephyr ® Series] Part 3: First Steps in Blinking an LED and Software Reusability (Japanese Blog)
Kconfig Basics
Kconfig is a configuration system also used in the Linux kernel. In Zephyr, it is used to manage whether to "enable or disable" software features, or "what parameters to set," such as kernel functions, device drivers, subsystems, and application-specific settings.
The following two files are important for Kconfig:
The "Kconfig" file defines the selectable configuration items (symbols), their default values, and dependencies.
"prj.conf" file *: This file is where application developers specify the values they want to set for items defined in "Kconfig" (such as enabling them with "y" or providing specific numerical values).
Using Kconfig, you can exclude unnecessary code from compilation and optimize memory usage.
Furthermore, the Kconfig and prj.conf files are all written in text format.
How to enable the feature
The prj.conf file enables features for the entire project.
For example, the ADC, DAC, and OPAMP drivers are defined in Kconfig. When using the functions defined in Kconfig, you declare them by adding "CONFIG_" to the beginning of prj.conf.
Kconfig:ADCの定義Kconfig: ADC definition
prj.conf例prj.conf example
Device tree fundamentals
Devicetree例Example of a device tree
The device tree is a text file that describes what hardware (CPU, memory, peripherals, pin settings, etc.) a microcontroller supports, as well as the settings and configuration of that hardware.
These settings and configurations are then expanded into macros.
Instead of directly writing hardware addresses into C code (hardcoding), information described in the device tree can be read through Zephyr macros, enabling hardware-independent programming.
Nodes and Properties: Each element of hardware is represented as a "node" in a hierarchical structure, and register addresses, interrupt numbers, etc., are described as "properties."
".dts" and ".dtsi": Standard hardware configurations for each microcontroller and board are predefined within the Zephyr repository in ".dts" (Devicetree Source) and ".dtsi" (Include) files.
dts: Described as the device tree of the board.
dtsi: Describes the device tree of an SoC/microcontroller and is provided by the device manufacturer.
".overlay" file: This file is created when you want to override application-specific wiring (e.g., connecting an LED to a specific GPIO pin) or default settings.
Best practices for improving software reusability
To enhance software reusability in Zephyr, it is important to follow the following design principles:
Separation of hardware-dependent and hardware-independent parts: C code (`main.c`) For example, avoid directly writing about specific microcontroller register operations or pin numbers.
Leverage device tree aliases: Instead of directly referencing actual hardware nodes (e.g., `&red_led` or `&gpioa`), applications should reference abstract names defined in the `aliases` node (e.g., `led0`). This allows you to adapt to different boards simply by changing what the alias points to.
Prepare a board-specific device tree (overlay) : When there are differences in some functions, such as in derivative products, you can overwrite (overlay) only the parts with hardware differences for each board.
Switching features with Kconfig : Application behavior parameters and the on/off status of specific features are controlled using Kconfig symbols instead of C language "#define".
Kconfig and the Device Tree in Practice
From here, we will learn how to use Kconfig and the device tree by creating a simple program so that we can actually use them in practice .
Create a simple program (hands-on)
The program will be created by modifying the LED blinking program we created last time, and will have the following specifications.
Here, as a practical exercise, we will create a common application that runs on both "FRDM-MCXA153" and "FRDM-MCXN947".
1. Program Specifications
Source code : Use the code from "Part 3: Your First LED Blinking Program" and make the following modifications.
LED blinking speed: The blinking interval can be set using Kconfig.
Button Function (Enable/Disable): The button function can be enabled or disabled via Kconfig settings. When enabled, pressing the button will toggle between blinking and constant illumination of the LED.
Outputting board name: At startup, the "device (board) name" configured in Kconfig will be output to standard output (terminal).
2. Directory structure
The project directory structure should be as follows:
Add the Kconfig file and boards folder to the "my_hello" folder of the LED blinking program you created last time. Any method of adding the files is fine. On Windows, use the PowerShell `ni` command or a text editor to create a new file and save it in the `my_hello` folder. In Linux, you can create a new, empty file using the `touch` command.
The files under "boards/" handle hardware differences and unique settings specific to each board.
my_hello/
├── CMakeLists.txt
├── Kconfig <- 新規追加:アプリ独自のKconfig
├── prj.conf <- アプリの共通設定
├── src/
│ └── main.c <- ハードウェア非依存の共通コード
└── boards/ <- 新規作成フォルダ
├── frdm_mcxa153.overlay <- 新規作成:FRDM-MCXA153用のデバイスツリー設定
├── frdm_mcxa153.conf <- 新規作成:FRDM-MCXA153用のKconfig設定
├── frdm_mcxn947_cpu0.overlay <- 新規作成:FRDM-MCXN947用のデバイスツリー設定
└── frdm_mcxn947_cpu0.conf <- 新規作成:FRDM-MCXN947用のKconfig設定
Note : By creating specific files within your application's directory, Zephyr's build system (West) will automatically recognize them and apply the settings.
Adding Kconfig: You can add your own configuration symbols by placing a "Kconfig" file in your Applications folder.
Board-specific settings ("boards/" directory): By creating a "boards" directory within your application and placing "[board name].overlay" or "[board name].conf" files there, the overlay and Kconfig overrides will be automatically applied only when building with that board as the target.
3. Create Kconfig and prj.conf
First, create your own "Kconfig" in the application root directory and define application-specific parameters.
my_hello/Kconfig
mainmenu "my LED blink"
config CUSTOM_BLINK_RATE_MS
int "LED blink rate in milliseconds"
default 1000
help
Set LED blink frequency. #LEDの点滅周期(ミリ秒)を設定します
config ENABLE_BUTTON_TOGGLE
bool "Enable button to toggle LED state"
default y
help
Enable button to toggle LED state. # ボタン入力によるLEDの点滅/点灯状態>の切り替え機能を有効にします。
config BOARD_NAME_STRING
string "Board Name String"
default "Unknown Board"
help
Set board name for printf. # 標準出力に表示するボード名を設定します。
source "Kconfig.zephyr"
Next, as a common setting for the entire application, there is "prj.conf". This will be written.
In this prj.conf file, use the Kconfig symbol you just created to configure it as follows:
my_hello/prj.conf
# GPIOの有効化
CONFIG_GPIO=y
# アプリケーションの共通設定
CONFIG_CUSTOM_BLINK_RATE_MS=500
CONFIG_ENABLE_BUTTON_TOGGLE=y
4. Creating device tree overlays and board-specific settings
Create a directory called "boards" and prepare the necessary files for each board.
Main board for FRDM-MCXA153
We map the button on the board (`sw2`) so that it can be accessed from the application using the standard alias `sw0`. `led0` is already in the board definition so it can be omitted here, but it can be explicitly overridden if needed.
my_hello/boards/frdm_mcxa153.overlay
/ {
aliases {
sw0 = &user_button_2; /* FRDM-MCXA153のユーザーボタン */
};
};
my_hello/boards/frdm_mcxa153.conf
CONFIG_BOARD_NAME_STRING="FRDM-MCXA153 Board"
By referencing the symbols in this .conf (board-specific Kconfig) within the application (main.c), it becomes possible to output the board name to standard output using the printf function.
For FRDM-MCXN947
Similarly, we define "sw0" in the FRDM-MCXN947. This handles any differences in the hardware names of the buttons.
In fact, if the hardware names (which differ depending on the peripheral or instance) vary across boards, you assign the actual hardware to the alias node in the device tree.
my_hello/boards/frdm_mcxn947_cpu0.overlay
/ {
aliases {
sw0 = &user_button_3; /* FRDM-MCXN947のユーザーボタン */
};
};
my_hello/ boards/frdm_mcxn947_cpu0.conf
I'll try overriding the LED blinking speed to 250ms only when building with MCXN947.
CONFIG_BOARD_NAME_STRING="FRDM-MCXN947 Board"
CONFIG_CUSTOM_BLINK_RATE_MS=250 The application code is the same for FRDM-MCXA153 and FRDM-MCXN947, but you can configure the blinking behavior of the LED separately here.
5. Hardware-independent common code (main.c) Create
Write the following in "my_hello/src/main.c":
The LED control section reuses the hardware-independent code (using the "led0" alias) created in the previous article, and incorporates Kconfig and button control into it.
my_hello/ src/main.c
#include
#include
#include
/* Devicetreeのエイリアスを参照する */
/* どのボードでも、一番目のLEDは通常 "led0" と定義されています */
#define LED0_NODE DT_ALIAS(led0)
#define SW0_NODE DT_ALIAS(sw0)
/* エイリアスからGPIO仕様(ポート、ピン、フラグ)を取得 */
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
/* ボタン機能がKconfigで有効化されている場合のみコンパイルされる部分 */
#ifdef CONFIG_ENABLE_BUTTON_TOGGLE
static const struct gpio_dt_spec sw = GPIO_DT_SPEC_GET(SW0_NODE, gpios);
static struct gpio_callback button_cb_data;
static bool is_blinking = true;
void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
is_blinking = !is_blinking;
if (!is_blinking) {
/* 点滅オフ時はLEDを点灯させた状態にする */
gpio_pin_set_dt(&led, 1);
}
}
#endif //CONFIG_ENABLE_BUTTON_TOGGLE
int main(void)
{
int ret;
/* Kconfigで設定されたボード名を出力 */
printf("Starting application on %s\n", CONFIG_BOARD_NAME_STRING);
/* デバイスの準備確認 */
if (!gpio_is_ready_dt(&led)) {
return -1;
}
/* ピンの設定 (Devicetreeで定義された初期状態などを考慮して設定) */
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return -1;
}
#ifdef CONFIG_ENABLE_BUTTON_TOGGLE
if (!gpio_is_ready_dt(&sw)) {
return -1;
}
ret = gpio_pin_configure_dt(&sw, GPIO_INPUT);
if (ret < 0) {
return -1;
}
ret = gpio_pin_interrupt_configure_dt(&sw, GPIO_INT_EDGE_TO_ACTIVE);
if (ret < 0) {
return -1;
}
gpio_init_callback(&button_cb_data, button_pressed, BIT(sw.pin));
gpio_add_callback(sw.port, &button_cb_data);
#endif //CONFIG_ENABLE_BUTTON_TOGGLE
while (1) {
#ifdef CONFIG_ENABLE_BUTTON_TOGGLE
if (is_blinking) {
ret = gpio_pin_toggle_dt(&led);
}
#else
/* ピンの状態を反転 (ボタン機能が無効な場合は常に点滅) */
ret = gpio_pin_toggle_dt(&led);
#endif //CONFIG_ENABLE_BUTTON_TOGGLE
/* Kconfigで設定された点滅間隔で待機 */
k_msleep(CONFIG_CUSTOM_BLINK_RATE_MS);
}
return 0;
}
6. Build and run
Now, let's actually test its functionality. Please refer to the previous article for instructions on setting up the build environment and enabling the west command.
First, navigate to the directory of your installed zephyrproject repository as shown in the command instructions below, enable west, and then proceed.
Operation confirmed with FRDM-MCXA153 (main)
Build using the following command and write it to the FRDM-MCXA153.
## ホームディレクトリからZephyrprojectディレクトリに移動
cd ~/zephyrproject
## west環境を有効化
source .venv/bin/activate
## zephyr v4.3をチェックアウトしていない場合は、前回(第3回 初めてのLチカとソフトウェアの再利用性)を参考にv4.3をチェックアウトしてください。
west build -b frdm_mcxa153 my_hello
west flash
Execution result
コンソール出力Console output
LED点滅、点灯モード切り替えLED flashing and steady light mode switching
The message "Starting application on FRDM-MCXA153 Board" will appear in the terminal.
The LED blinks at 500ms intervals ("prj.conf"). (Settings).
Pressing SW2 ("custom-sw") will turn it on, and pressing it again will return it to blinking.
Operation confirmed with FRDM-MCXN947
We'll build the project using the exact same C source code, only changing the board specification.
# -pオプションを使用し、frdm_mcxa153のビルド情報をクリーンしてビルドします。
west build -p -b frdm_mcxn947//cpu0 my_hello
west flash
Execution result
The contents of "boards/frdm_mcxn947_cpu0.conf" will be automatically applied, and the terminal will display "Starting application on FRDM-MCXN947 Board".
The LED blinks rapidly at 250ms intervals (configured in "prj.conf").
Pressing SW3 (the button mapped to "user_button_3" on the MCXN947) will similarly switch between LED illumination and blinking.
While the FRDM-MCXA153 and FRDM-MCXN947 use different GPIOs for controlling LEDs and switch buttons, the device tree effectively absorbs these differences, demonstrating how cleanly the application program and hardware are separated.
summary
In this session, we learned the basics of Kconfig and device trees in Zephyr, and practiced techniques to separate hardware-dependent parts from C code by utilizing them.
I believe you've experienced a powerful mechanism for reusing the same source code across multiple different boards, where hardware settings are absorbed by the device tree overlay (".overlay"), application parameters can be flexibly changed and features can be easily enabled or disabled using board-specific Kconfig files (".conf").
==========================
We are currently unable to respond to comments left in the "Comment" section of this post. We apologize for the inconvenience, but please refer to "Technical Questions to NXP - How to Contact Us (Japanese Blog)" when making inquiries. (If you are already an NXP distributor or have a relationship with NXP, you may ask your representative directly.)
This document provides an overview of the device tree and Kconfig, features designed to enhance Zephyr's software reusability. It then outlines the steps required to utilize these features in practice.
After reading through this Zephyr series, from the first installment to the fourth, you will be able to write programs using the Zephyr RTOS. General Purpose Microcontrollers MCX Japanese Blog
記事全体を表示