CMake and Kconfig tricks for common code in Zephyr apps

One of the best tools in the Zephyr ecosystem is the ability to include different code modules in the build configuration. This feature leverages CMake and Kconfig, two tools that are core to Zephyr. But these tools aren’t limited to officially approved code modules. Today we’ll cover some CMake and Kconfig tricks for including common code in your own Zephyr applications.

The problem: common code across different variations of your app

Earlier this month we posted about the developer training that Golioth offers. It centers around a hardware development board called the MagTag, for which we’ve put together a code repository with many different examples. This includes code to drive the ePaper display, update four ws2812 LEDs, service button presses, and parse JSON objects.

The problem is that we need to use a common set of code in most, but not all of the training samples.

Luckily we’ve seen this problem before. The samples in the Golioth Zephyr SDK use common code for networking and shell settings features. Each feature from that common code has its own KConfig symbol, like GOLIOTH_SAMPLE_WIFI_SETTINGS, that is selected to include it in the build. By defining these in the board-specific conf files, libraries are only built for the devices that actually need them. For instance, an Ethernet-connected device doesn’t need WiFi settings.

The example from the Golioth SDK is a good one to study, but the MagTag implementation is a bit simpler so let’s walk through that code.

Golioth’s Developer Training is a self-guided experience that you can explore at your own pace. If you are interested in setting up a training with Golioth staff for your organization, please contact our Developer Relations team, we’d love to chat!

Directory Structure

Directory structure for a Zephyr project with multiple appsFrom the directory structure of the training you can see that we have five different Zephyr programs in this repository, each with their own boards and src subdirectories.

Common code for each app is placed in the magtag-common directory. Header files for each library are located in the the magtag-common/include/magtag-common directory. That might seem a bit convoluted, but it makes for sensible include names like #include "magtag-common/buttons.h".

Creating Kconfig symbols

Common code is individually enabled with a Kconfig symbol. There are a few ways to approach this. One of the easiest is to assign one symbol to indicate your app uses the common code, and then one symbol for each specific library in the common folder. This is done with a Kconfig file inside of the magtag-common directory.

menuconfig MAGTAG_COMMON
    bool "Common helper code for Golioth MagTag Demo"
    help
      Build and link common code that is shared across MagTag samples.

if MAGTAG_COMMON

config MAGTAG_ACCELEROMETER
    bool "Handle accelerometer readings"
    help
      Get accel from DeviceTree and write readings to shared sensor struct

config MAGTAG_BUTTONS
    bool "Process button reads"
    help
      Configure buttons for interrupts with callbacks

config MAGTAG_EPAPER
    bool "2.9\" grayscale ePaper driver"
    help
      Hardware driver for ePaper, including text and partial writes

config MAGTAG_WS2812
    bool "ws2812 helper functions"
    help
      Intialize and update LED color and state

endif # MAGTAG_COMMON

From this code snippet you can see the library-specific symbols are only defined if the MAGTAG_COMMON symbol has been selected.

Using CMake to build in the libraries

Now that we’ve created the Kconfig symbols, a CMakeLists.txt file in the magtag-common directory is used to build in the code based.

zephyr_library_sources_ifdef(CONFIG_MAGTAG_ACCELEROMETER accelerometer/accel.c)
zephyr_library_sources_ifdef(CONFIG_MAGTAG_BUTTONS buttons/buttons.c)
zephyr_library_sources_ifdef(CONFIG_MAGTAG_EPAPER epaper/magtag_epaper.c)
zephyr_library_sources_ifdef(CONFIG_MAGTAG_EPAPER epaper/magtag_epaper_hal.c)
zephyr_library_sources_ifdef(CONFIG_MAGTAG_WS2812 ws2812/ws2812_control.c)

zephyr_include_directories(include)

The include directory is added by default, but the source files are only added if the associated symbol is defined.

Using the common code in a Zephyr application

So far, the common code is completely separate from the each of the apps we want to build. Let’s use the golioth-demo app as an example. To tell the app about the common code, the subdirectory needs to be added to the CMakeLists.txt file in the application subfolder.

cmake_minimum_required(VERSION 3.20.0)

list(APPEND OVERLAY_CONFIG "../credentials.conf")

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(lightdb)

add_subdirectory_ifdef(CONFIG_MAGTAG_COMMON ../magtag-common magtag-common)

target_sources(app PRIVATE src/main.c)

This codes makes sure that the common directory is only added to the build when the MAGTAG_COMMON symbol is selected. This completes the plumbing work. In this example, the app selects the common code in the prj.conf file:

# MagTag Common Files
CONFIG_MAGTAG_COMMON=y
CONFIG_MAGTAG_EPAPER=y
CONFIG_MAGTAG_WS2812=y
CONFIG_MAGTAG_ACCELEROMETER=y
CONFIG_MAGTAG_BUTTONS=y

And of course, you must include the header files to access the library functions. Here’s an excerpt of the main.c from the golioth-demo app:

/* MagTag specific hardware includes */
#include "magtag-common/magtag_epaper.h"
#include "magtag-common/ws2812_control.h"
#include "magtag-common/accel.h"
#include "magtag-common/buttons.h"

Wrapping up

MagTag Kconfig options shown in menuconfig

Abstracting your common code makes it a lot easier to maintain. This approach also adds entries in menuconfig for each of the libraries. This is a user-friendly feature as it allows you to convey more details in the help file. For more complex uses (like the Golioth samples common libs) it also makes it easier to see the symbol dependency hierarchy.

This example shows the libraries as a part of the repository, however this will work just as well if you commit them to their own repo. From there, they can be included as a git submodule, or with a little creativity you can get your west manifest file to pull the repo whenever west update is run. This is the approach that we’ll be taking with future development. Doing so allows us to lock the project to a specific hash of the common code so that future changes don’t break application code.

Mike Szczys
Mike Szczys
Mike is a Firmware Engineer at Golioth. His deep love of microcontrollers began in the early 2000s, growing from the desire to make more of the BEAM robotics he was building. During his 12 years at Hackaday (eight of them as Editor in Chief), he had a front-row seat for the growth of the industry, and was active in developing a number of custom electronic conference badges. When he's not reading data sheets he's busy as an orchestra musician in Madison, Wisconsin.

Post Comments

No comments yet! Start the discussion at forum.golioth.io

More from this author

Related posts

spot_img

Latest posts

Golioth Design Partners: IoT Solutions for Constrained Devices | 2025 Partner Network

We regularly refer Golioth users to our trusted Design Partners to help design, test, and deploy hardware out into the world. The Golioth Design Partner program has been going for more than 2 years and continues growing. In 2025, we reached 20 listed partners, with others in the wings.

Adding Golioth Example Code to Your ESP-IDF Project

In our previous blog post, we demonstrated how to add the Golioth Firmware SDK to an ESP-IDF project. As you start integrating Golioth into...

Tracking Our CEO at CES

We used Golioth Location to build an application that keeps up with Golioth CEO Jonathan Beri at CES 2025.

Want to stay up to date with the latest news?

We would love to hear from you! Please fill in your details and we will stay in touch. It's that simple!