Skip to content

Using nRF5 CMake

Borys Jeleński edited this page Jul 14, 2020 · 8 revisions

This section provides a more in-depth insight into the inner workings of nRF5 CMake. It's supposed to help you understand how it works so you can make the best use of it. Before proceeding, make sure to read the Getting started section.

The subjects covered in this section are not meant to be read in any particular order. Feel free to learn only what you think is important for you at the moment.

nRF5 SDK libraries

As we already mentioned in the Getting started section, every library found in the nRF5 SDK is represented as a CMake target. This allows us to model dependecies between libraries by taking advantage of CMake property propagation system. Each library target defines its source files, include directories required to build it and use it, compile defines and other libraries it depends on.

We currently use two types of CMake targets:

  • OBJECT - for libraries that consists of at least one source file
  • INTERFACE - for libraries that are header-only

You can read about different CMake target types in CMake documentation: cmake-buildsystem(7).

Here is a sample library definition of the widely-used Application Timer found in the nrf5_app.cmake script in the cmake directory:

# Application Timer
add_library(nrf5_app_timer OBJECT EXCLUDE_FROM_ALL
  "${NRF5_SDK_PATH}/components/libraries/timer/app_timer.c"
)
target_include_directories(nrf5_app_timer PUBLIC
  "${NRF5_SDK_PATH}/components/libraries/timer"
)
target_link_libraries(nrf5_app_timer PUBLIC
  nrf5_app_scheduler
  nrf5_app_util_platform
  nrf5_delay
  nrf5_nrfx_hal
)

Using nRF5 libraries in your project

Being CMake targets, the nRF5 SDK libraries can be linked to your executable target by using the standard target_link_libraries() clause. However, you might remember that in our BSP Example in the Getting started section we used the nrf5_target_link_libraries() function that is provided with the nRF5 CMake like this:

nrf5_target_link_libraries(nrf5_example_bsp PRIVATE
  nrf5_app_error
  nrf5_log
  nrf5_log_backend_serial
  nrf5_log_backend_uart
  nrf5_log_default_backends
  nrf5_bsp
)

If you simply replace the nrf5_target_link_libraries() with the target_link_libraries() here, you will notice that you can still successfully generate input files for the build system, but upon running the build system you'll see a staggering number of undefined reference linker errors.

The reason for that is due to the fact that in the nRF5 CMake, many of the nRF5 libraries are represented as CMake OBJECT libraries and the CMake documentation on Object Libraries states that when linking a target that uses object libraries as dependencies (directly or inderectly), only object files from the object libraries that this target directly depends upon are linked. The nrf5_target_link_libraries() function works in this case because as part of the nRF5 CMake, we track the dependecies between nRF5 SDK libraries and whenever a library is specified within nrf5_target_link_libraries(), the other libraries it depends on are automatically added as if they were specified directly. In case of duplicates, they are removed.

This provides a convenient mechanism for adding libraries to the project. In a way, it attempts to overcome the limitations of CMake itself. However, when compared to the standard target_link_libraries(), the nrf5_target_link_libraries() function has some drawbacks. The problem is that in nRF5 SDK, whether a library actually depends on some other library is determined by the project configuration - the SDK configuration file (sdk_config.h) more specifically. For instance, the app_timer library only depends on the app_scheduler library if the following definitions is present in the sdk_config.h:

#define APP_TIMER_CONFIG_USE_SCHEDULER 1

A drawback of using the nrf5_target_link_libraries() function for adding a library X is that it will add all the other libraries the library X possibly depends on regardless of the current project configration and whether those libraries are actually used. This may seem harmless and it often is but in some cases, it might cause you trouble compiling the project. That is because such unused libraries will still be compiled and if their configuration is missing in the SDK configuration file, the compilation may fail. As a rule of thumb, in order to mitigate this, we recommend that you use the SDK configuration files found in the config/<nrf52_target>/config directory of the nRF5 SDK as your starting point when configuring a new project. They are supposed to contain all the configuration entries for all the libraries available in the current version of the SDK.

On the other hand, using the standard CMake target_link_libraries() clause allows for a fine control over which libraries are pulled into the project so you can compile and link only those libraries that are actually used. We generally recommend using the nrf5_target_link_libraries() for nRF52 beginners. More experienced nRF52 developers may prefer using the standard target_link_libraries() to have more control over the libraries added to the project.

SDK configuration header file

In nRF5 SDK, libraries are configured by using the compile-time defines found in the SDK configuration file sdk_config.h. As a result, when pulling libraries into your project, only specifying them (and their dependencies) by using the target_link_libraries() clause or the nrf5_target_link_libraries() function is generally not enough. Additionally, you have to make sure that the configuration in the sdk_config.h file is correct for the libraries you intend to use. You can find more information in the nRF5 SDK documentation: SDK configuration header file.

In particular, when using a library, be sure to define the XXX_ENABLED to 1 where XXX is the name of your library, for example:

#ifndef APP_TIMER_ENABLED
#define APP_TIMER_ENABLED 1
#endif

The XXX_ENABLED define usually determine whether the contents of the source file for a particular library is compiled. If you forget to set to 1, you may encouter some undefined reference errors during linking.

You also have to put other configuration defines in your sdk_config.h related to your library and set them to proper values. Failing to do so may lead to compile errors when compiling sources for the library, either due to missing definitions or consistency checks failing.

In order to avoid these problems, we generally recommend setting up the project incrementally by adding one library (or a set of related librarires) at the time and making sure the project builds correctly. As a starting point, it is a good idea to use an sdk_config.h file from one of the official nRF5 examples which resembles your project the most.

Using app_config.h configuration file

When setting up a project, aside from using a template sdk_config.h and modifying it according to your needs you can also use the SDK configuration file (either from an example or a generic one) in its pristine form and only override the configuration entries you need. You can achieve that by creating the app_config.h file and putting there all the definitions you wish to override.

In nRF5 CMake you can utilize the app_config.h file by setting the NRF5_APPCONFIG_PATH CMake variable to a path where you app_confih.h resides when configuring your project with CMake.

You can use this mechanism for creating multiple customized configurations of your project for different boards, debugging scenarios etc. Each configuration would be defined by a separate app_config.h file. Of course, you could achive the same by using multiple sdk_config.h files. However, it is likely that in such case these SDK configuration files would differ in only relatively small number of configuration entries. Using app_config.h files in this case would result in a cleaner solution where each of the files would only contain the customization points.