#zephyrrtos
Explore tagged Tumblr posts
Text
Configuring Zephyr: A Deep Dive into Kconfig
We presented The Zephyr Project RTOS and illustrated a personal best practice for beginning with "Zephyr" in an earlier blog post. A custom West manifest file is a great way to guarantee that your code is always at a known baseline when you begin development, as you saw in that blog post. Following the creation of your custom manifest file and the establishment of your baseline repositories using West, what comes next in your Zephyr journey?
Enabling particular peripherals, features, and subsystems is one of the first steps in putting embedded software into practice. Some MCU manufacturers, like STM32, Microchip, and TI, have tools in their IDEs that let developers add subsystems to their codebase and enable peripherals in their projects. These tools, however, are closely related to the MCUs that the vendors sell. Applying these tools' functionality to other MCUs is challenging, if not impossible.
However, we can enable a specific MCU subsystem or feature using a vendor-neutral mechanism provided by The Zephyr Project RTOS. For people like me who don't like GUIs, this mechanism can be used with a command line. The name of this utility is "Kconfig." I'll go over what Kconfig is, how it functions, and the various ways we can use it to incorporate particular features and subsystems into our Zephyr-based project in this blog post.
WHAT IS KCONFIG?
Kconfig is still utilized today as a component of the kernel compilation process, having been initially created as part of the Linux kernel. Kconfig has a particular grammar. Although fascinating, the specifics of how Kconfig is implemented in the Linux kernel are outside the purview of this blog post. Alternatively, if you're interested, you can read my article here: (https://www.linux-magazine.com/Issues/2021/244/Kconfig-Deep-Dive), which walks through the Kconfig source code. However, after seeing an example, it's simple to become familiar with the format of a "Kconfig"—the slang term for a specific configuration option. The Kconfig system consists of three primary components.
First, there is the collection of Kconfig files scattered across different OS codebase directories. For example, if we look under "drivers/led" within the Zephyr codebase, we see a file named Kconfig with the following contents: menuconfig LED bool "Light-Emitting Diode (LED) drivers" help Include LED drivers in the system configurationif LED...config LED_SHELL bool "LED shell" depends on SHELL help Enable LED shell for testing.source "drivers/led/Kconfig.gpio"...endif # LED
Using the if statement, the line that begins with "menuconfig" tells the Kconfig system that "LED" contains a number of feature options that are only visible if the "LED" feature is enabled. The user can then activate the "LED_SHELL" option if the "LED" feature is enabled. The result of this configuration option is a Boolean, which determines whether this feature is enabled or disabled, as the line that follows shows. If a configuration option refers to a particular configuration parameter, the result can also be an integer in addition to a Boolean. The line that starts with "depends" indicates that in order for the "LED_SHELL" feature to be visible, the "SHELL" feature needs to be enabled. As a result, only after the "LED" and "SHELL" features have been enabled will the "LED_SHELL" feature become visible. A more detailed explanation of the feature can be found in the two lines that begin with "help". Last but not least, the final line before the "endif" lets us refer to additional Kconfig files, which aids in classifying components. As though they were copied and pasted, the features of the referenced file are present in the current file. It is crucial to remember that the path to "source" comes from the Zephyr codebase's root.
HOW SHOULD YOU USE KCONFIG?
A collection of applications that enable users to enable or disable the features listed in all Kconfig files make up the second component of the Kconfig infrastructure. Zephyr provides a Visual Studio Code extension that enables users to carry out this task with a graphical user interface. For command line enthusiasts like myself, the VS Code extension provides an alternative to utilizing a graphical user interface. In order to configure Zephyr appropriately, the extension can accept a file, which is the final component of the Kconfig infrastructure and contains a set of configuration options that can be turned on or off. The following snippet shows an example. CONFIG_BT=yCONFIG_BT_PERIPHERAL=yCONFIG_BT_GATT_CLIENT=yCONFIG_BT_MAX_CONN=1CONFIG_BT_L2CAP_TX_MTU=250CONFIG_BT_BUF_ACL_RX_SIZE=254
There is nothing complicated about the file format. "CONFIG_" appears at the start of each line, and then the configuration option's name. After the "=" symbol, the line either ends with a "y" to activate the feature or a "n" to deactivate it. In the example above, we configure the stack parameters and activate the Bluetooth stack in Zephyr along with specific stack features. "prj. conf," which contains user-defined features, is the default file in the majority of Zephyr-based applications.
CONCLUSION
The Zephyr Project RTOS provides a robust, vendor-neutral mechanism called the Kconfig infrastructure that allows us to fully configure our entire application. It can be used to control particular subsystems and peripherals within the MCU in addition to turning on or off individual stacks within the RTOS and setting configuration parameters.
Ready to bring your embedded systems to life with optimized configurations and robust solutions? We specialize in hardware design and software development tailored to your project needs. Whether you're configuring peripherals or diving deeper into Kconfig for your Zephyr-based applications, our experts are here to support you every step of the way.
👉 Contact Us Today and let's transform your embedded ideas into reality!
2 notes
·
View notes
Text
The tiny little EOS S3 is the tiny chip in the centre containing a 2.4K LUT #FPGA And a 80Mhz Cortex-M4 #Arm #MCU running #ZephyrRTOS
0 notes
Text
Defining Hardware Capabilities: Devicetree Bindings
We discovered in a previous blog post how the hardware on the device could be described by a devicetree in an embedded software application based on The Zephyr Project. We saw an example of how the devicetree can be used to describe the four LEDs found on a nRF52840 development kit (https://www.nordicsemi.com/Products/Development-hardware/nrf52840-dk). We discovered that a complete board can be created by combining several devicetree files. In order to comprehend how to reference elements in the devicetree, we lastly went over some source code. We will discover how The Zephyr Project uses the devicetree in this blog post.
Zephyr Is Not Linux!
One of the main points of the previous blog post was that, despite the fact that the devicetree concept is derived from Linux, Zephyr's application is very different. As part of the boot process in Linux, the kernel reads the devicetree in binary form from somewhere in RAM and, based on the devices that are present and enabled, calls the relevant driver functions. But in Zephyr, the devicetree is used to create header files that are used with the Zephyr kernel and drivers, as well as the source code that makes up the finished application. Therefore, Zephyr uses the devicetree during compile-time, while the Linux kernel uses it during run-time. Zephyr's build infrastructure incorporates particular mechanisms to interface with the devicetree as a result of this distinction.
Devicetree Bindings
“Devicetree bindings” are the basis of Zephyr’s mechanism to allow the C portion of the application to reference the devicetree source file. The following graphic from The Zephyr Project’s documentation (https://docs.zephyrproject.org/latest/build/dts/intro-scope-purpose.html) demonstrates this mechanism:
The conventional devicetree files covered in the last blog post are the "Devicetree sources." The contents of the devicetree, including the data types of the nodes, are described by the "Devicetree bindings." The source files and bindings are finally combined into a C header file by the Zephyr build infrastructure. The "devicetree.h" header file, which is utilized by the application and Zephyr source files, abstracts the contents of the generated header file.
Through The Looking Glass
An outstanding illustration of how devicetree bindings function in Zephyr is "custom_dts_binding" under samples/basic. To navigate devicetree bindings, we surprisingly only need to pay attention to the following three lines in the sample's main.c:#if !DT_NODE_EXISTS(DT_NODELABEL(load_switch))#error “Overlay for power output node not properly defined.”#endif
First, we’ll need to build the application using the following invocation (assuming “zephyr_main” is where we cloned the latest west manifest): $> west build -p always -b nucleo_l073rz zephyr_main/zephyr/samples/basic/custom_dts_binding
The C source files in Zephyr and the applications that use it ultimately use devicetree.h, as seen in the above image. The generated header files are included close to the top of the devicetree.h file when we open it under zephyr_main/zephyr/include/zephyr/:#ifndef DEVICETREE_H#define DEVICETREE_H#include <devicetree_generated.h>...#endif /* DEVICETREE_H */
Where is “devicetree_generated.h”? It’s not in the Zephyr repository but in the build directory!
If we return to the relevant lines in main.c and search for the “DT_NODELABEL” in devicetree.h, we find the following definition:#define DT_NODELABEL(label) DT_CAT(DT_N_NODELABEL_, label)
If we further search for DT_CAT, we find the following definition: #define DT_CAT(a1, a2) a1 ## a2
These two macros will convert “DT_NODELABEL(load_switch)” from main.c into “DT_N_NODELABEL_load_switch”. If we search for DT_NODE_EXISTS, we find the following definition:#define DT_NODE_EXISTS(node_id) IS_ENABLED(DT_CAT(node_id, _EXISTS))
The IS_ENABLED macro is defined using the following clever macros in the util_macro.h and util_internal.h header files under zephyr_main/zephyr/include/zephyr/sys:#define IS_ENABLED(config_macro) Z_IS_ENABLED1(config_macro)#define Z_IS_ENABLED1(config_macro) Z_IS_ENABLED2(_XXXX##config_macro)#define _XXXX1 _YYYY,#define Z_IS_ENABLED2(one_or_two_args) Z_IS_ENABLED3(one_or_two_args 1, 0)#define Z_IS_ENABLED3(ignore_this, val, ...) val
If we work through the macros using “DT_N_NODELABEL_load_switch,” the first one expands to the following: IS_ENABLED(DT_N_NODELABEL_load_switch) --> IS_ENABLED(DT_N_S_load_switch_EXISTS)
Where is “DT_N_NODELABEL_load_switch_EXISTS” defined? It’s (ultimately) in devicetree_generated.h!#define DT_N_S_load_switch_EXISTS 1...#define DT_N_NODELABEL_load_switch DT_N_S_load_switch
The second macro will cause “DT_N_NODELABEL_load_switch” to expand to “DT_N_S_load_switch” and the first macro will result in the expansion to “DT_N_S_load_switch_EXISTS”! If we step through the expansion of the series starting with “IS_ENABLED” macros, we see the following (and remembering that DT_N_S_load_switch_EXISTS expands to “1”): IS_ENABLED(DT_N_S_load_switch_EXISTS) --> Z_IS_ENABLED1(1)Z_IS_ENABLED1(1) --> Z_IS_ENABLED2(_XXXX1)
Now, since “_XXX1” expands to “_YYY,” (paying close attention to the comma), Z_IS_ENABLED2 expands to “Z_IS_ENABLED3(_YYY, 1, 0)”. Finally, the last macro expands to: Z_IS_ENABLED3(_YYY, 1, 0, ...) --> 1
And there we have it! Let’s say the DT_N_S_load_switch_EXISTS macro was set to “0” instead. Then we wouldn’t be able to leverage the macro which expands “_XXX1” to “_YYY,” and rather, we’d have the following chain of macros: IS_ENABLED(DT_N_S_load_switch_EXISTS) --> Z_IS_ENABLED1(0)Z_IS_ENABLED1(0) --> Z_IS_ENABLED2(_XXX0)Z_IS_ENABLED2(_XXX0) --> Z_IS_ENABLED3(_XXX0 1, 0)
>p>And the lack of the comma in Z_IS_ENABLED3 will result in that macro expanding to 0 (since the macro is extracting the value after the first comma): Z_IS_ENABLED3(_XXX0 1, 0, ...) --> 0
And ultimately, the original macro in main.c will result in a compilation error. You can try and see for yourself. Change the DT_N_S_load_switch_EXISTS macro in devicetree_generated.h from a “1” to a “0” and rebuild the application using the following invocation (take note that we’re not performing a pristine build since that will regenerate the header file):
Conclusion
This blog post demonstrated how the application source could use the generated devicetree header files in conjunction with a few header files from The Zephyr Project's core repository to identify the existence of particular nodes during compilation. We observed how the Linux kernel, which makes use of this data during run-time, is different from this. To gain insight into this process, we traced the expansion of certain clever macros using a sample from the Zephyr repository. To verify the anticipated outcomes, we then disabled the node's existence in the devicetree header file that was generated. We will examine in more detail how the Zephyr build infrastructure integrates the devicetree sources and bindings to create the generated devicetree header files in a subsequent blog post.
Understanding and implementing devicetree bindings is crucial for efficient hardware configuration in embedded systems. At Silicon Signals, we specialize in optimizing your hardware and software integration with Zephyr-based projects. Whether you're tackling devicetree bindings or enhancing your embedded system's performance, our team is equipped to support you through the development cycle.
👉 Contact Us Today to get started and take your embedded solutions to the next level with expert hardware design and custom software development!
0 notes