Getting started - Polidea/cmake-nRF5x GitHub Wiki

Installing the required tools and nRF5 SDK

Opposite to IDEs, nRF5 CMake is not a standalone application but rather a solution which integrates multiple separate tools in order to create a lightweight and versatile development environment. Follow these steps to make sure you have these installed:

1. Install CMake

Proceed to CMake Download page, download the distribution specific to your OS and install it. You should use CMake version 3.14 or newer. Also, make sure to add cmake to your PATH variable.

2. Download the GNU Arm Embedded Toolchain

Go to GNU Arm Embedded Toolchain Downloads page and download the toolchain meant for your operating system. We recommend using a non-installer version. Extract the archive and save it under an easy accessible path.

3. Install the J-Link Software and Documentation Pack

Go to J-Link Software and Documentation Pack page, download the package meant for your operating system and install it.

4. Download the nRF5 SDK

Go to nRF5 SDK page, download it and extract it under a path that is easy to access.

IMPORTANT: Be sure to download a version that is supported by the nRF5 CMake. Currently supported versions are 15.3.0 and 16.0.0.

5. Install nRF Command Line Tools

Download the nRF Command Line Tools. Its Windows distribution requires you to run an installer. Make sure that nrfjprog is added to your PATH variable.

6. Install the build system you want to use (optional)

As you're probably aware, in general, CMake does not build software directly but rather generates input files for build systems like make or Ninja. If the desired build system is already installed on your machine, you can skip this step.

IMPORTANT: This step is required if you want to use nRF5 CMake on Windows since the popular make is obviously not natively available. When builing on Windows, we recommend using Ninja. Go to Ninja page, download a Windows binary and add it to your PATH. Alternatively, you can use make available as a package in Cygwin or MSYS2 projects.

Setting up a project and building it with nRF5 CMake

Using the nRF5 CMake, we're going to set a up a simple project based on one of the official examples from the nRF5 SDK - the BSP Example. We're going to build it for the nRF52 DK board with the nRF52832 SoC (PCA10040).

Setting up a project structure

Let's start by creating a basic project directory structure. Create a new empty directory where we will keep the project files. Let's call it nrf5_example_bsp.

Instead of writing code from scratch, copy the main.c file of the BSP Example found in your nRF5 SDK, in the examples/peripheral/bsp directory, to the main directory of your project.

Alongside source code, we're going to need 2 more files specific to the nRF52-DK board (PCA10040):

  • Linker script - examples/peripheral/bsp/pca10040/blank/armgcc/bsp_gcc_nrf52.ld
  • SDK configuration file - examples/peripheral/bsp/pca10040/blank/config/sdk_config.h

In your project directory, create a config subdirectory and pca10040 within it and copy those 2 files there.

In the end, your project directory should look like this:

nrf5_example_bsp
├── config
│   └── pca10040 
│       ├── bsp_gcc_nrf52.ld
│       └── sdk_config.h
└── main.c

Adding nRF5 CMake to your project

Once you have all the tools and the SDK installed, you need to add the nRF5 CMake to your project. The simplest way to do that would be to download the ZIP archive of this repository, extract it and only copy the cmake folder from the main repo directory into your project. The cmake directory contains all the files you're going to need if you want to setup a new project from scratch.

Alternatively, you can add the nRF5 CMake to your project as a git submodule. The advantage of doing that is the fact that you can conveniently keep the nRF5 CMake up-to-date. This time however, we're just going to copy the cmake directory into our project directory. After that, it is supposed to look like this:

nrf5_example_bsp
├── config
├── cmake 
└── main.c

Writing CMakeLists.txt

Create a CMakeLists.txt in the main project directory. It's going to define what we're going to build.

We start by declaring a minimum CMake version required and by declaring the project:

cmake_minimum_required(VERSION 3.14)
project(nrf5_example_bsp LANGUAGES C ASM)

Next, we need include the nrf5 CMake module which will provide the essential functionality of the nRF5 CMake to the project but before we can do that, we need to append the cmake subdirectory to the CMAKE_MODULE_PATH so that CMake can find it:

list(APPEND CMAKE_MODULE_PATH "cmake")
include("nrf5")

After that, we can create a CMake target representing our binary executable. We specify the only source file of this project (main.c):

add_executable(nrf5_example_bsp "main.c")

Next, we need to call the nrf5_target() function available in the nrf5.cmake script on our executable target. This function configures some necessary link options (e.g. linker script), adds the required sources like startup and system files and also adds additional targets to the build system that will allow us to conveniently program our board once the project is built:

nrf5_target(nrf5_example_bsp)

Finally, we have to link the libraries from the nRF5 SDK which are used in this example. In nRF5 CMake every such library is represented as CMake target. Normally, you would link such targets to your executable by using the standard target_link_libraries() CMake clause. However, we're going to use an alternative nrf5_target_link_libraries() provided by the nRF5 CMake. The reason for doing that will be explained later.

As you can see in the main.c source file and by inspecting the SDK configuration file (sdk_config.h), the BSP Example utilizes the following libraries:

  • Application error (app_error) - commonly used for error-handling
  • Board Support Package (bsp) - for handling button presses and LED indication
  • Logger with UART backend (nrf_log) - for printing log entries on UART

We can link them to our executable target by writing:

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
)

As you can see, we actually need some additional Logger-related libraries i.e. nrf5_log_backend_serial and nrf5_log_default_backends but that's due to the example configuration in sdk_config.h and because of how the nRF Logger is structured.

The complete CMakeLists.txt for our project should look like this:

cmake_minimum_required(VERSION 3.14)
project(nrf5_example_bsp LANGUAGES C ASM)

list(APPEND CMAKE_MODULE_PATH "cmake")
include("nrf5")

add_executable(nrf5_example_bsp "main.c")
nrf5_target(nrf5_example_bsp)
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
)

Generating input files for the build system

Once our CMakeLists.txt is complete, we are ready to call CMake on it in order to generate input files for the build system. This will require us to pass multiple parameters (CMake cache entries) to properly configure the project. Because of that, we recommend that you create a script for that purpose. Depending on your operating system, it can be a Shell or a Batch script.

This is a sample Shell script to call CMake for this project:

cmake \
   -DCMAKE_TOOLCHAIN_FILE=cmake/arm-none-eabi.cmake                            \
   -DTOOLCHAIN_PREFIX=~/tools/gcc-arm-none-eabi                                \
   -DNRF5_SDK_PATH=~/tools/nRF5SDK160098a08e2                                  \
   -DNRF5_BOARD=pca10040                                                       \
   -DNRF5_SOFTDEVICE_VARIANT=blank                                             \
   -DNRF5_SDKCONFIG_PATH=config/pca10040                                       \
   -DNRF5_LINKER_SCRIPT=config/pca10040/bsp_gcc_nrf52.ld                       \
   -G "Unix Makefiles"                                                         \
   -B build                                                                    \
   -S .

We're going to break down CMake variables defined in this script as well as other CMake options:

  • -DCMAKE_TOOLCHAIN_FILE=cmake/arm-none-eabi.cmake

    Here we specify so called toolchain file which is a CMake script that specifies which compiler, linker and other tools should be used when building the project. It also specifies the set of flags and other options that should be used when calling those programs. Configuring those is necessary due to the fact that when creating a binary for an nRF52 SoC, we are cross-compiling. You can read more about cross-compilation and toolchains in CMake documentation: cmake-toolchain(7).

    nRF5 CMake provides a cmake/arm-non-eabi.cmake toolchain file for the GNU Arm Embedded Toolchain.

  • -DTOOLCHAIN_PREFIX=~/tools/gcc-arm-none-eabi-9-2019-q4-major-win32

    Once CMake knows which compiler, linker and tools to use, it has to know where those are located on the local machine. Here, we specify a path to the GNU Arm Embedded Toolchain we previously downloaded and installed.

  • -DNRF5_SDK_PATH=~/tools/nRF5SDK160098a08e2

    We specify a path to the nRF5 SDK. In this case, we're using version 16.0.0 of the SDK.

  • -DNRF5_BOARD=pca10040

    We specify the nRF52 board we're building the binary for. In this case it's the nRF52-DK with the nRF52832 SoC. If you have a custom board or you simply do not wish to specify the board, you can use the NRF5_TARGET variable, for instance: -DNRF5_TARGET=nrf52832_xxaa.

  • -DNRF5_SOFTDEVICE_VARIANT=blank

    When using nRF5 CMake, you need to specify which SoftDevice is used by your application e.g. S132, S140 etc. Our application does not use SoftDevice at all so we're setting this variable to blank.

  • -DNRF5_SDKCONFIG_PATH=config/pca10040

    We are setting the directory where the SDK configuration file (sdk_config.h) is located.

  • -DNRF5_LINKER_SCRIPT=config/pca10040/bsp_gcc_nrf52.ld

    We specify the linker script to use by the linker. Among other things, it defines the address ranges of Flash and RAM memories.

  • -G "Unix Makefiles"

    This CMake option tells CMake which build system to generate input files for. If you're using Windows, please specify "Ninja" instead (assuming you have it installed).

  • -B build

    This specifies the CMake build directory where CMake will put the generated input files. If it doesn't exists, it will be created.

  • -S .

    This specifies the CMake source directory where the CMakeLists.txt file is located.

Run the script you created. You should now have the input files in the build directory.

Building the project

Once you have the input files for your build system, open a terminal, navigate to the build directory and run the build system by typing make or ninja depending on which system you chose. If everything went alright, you should see a short summary with the size of your binary:

$ ninja
[43/43] Linking C executable nrf5_example_bsp
   text    data     bss     dec     hex filename
  30824     288    2208   33320    8228 nrf5_example_bsp

Programming the board

If you have the nRF52-DK board with the nRF52832 SoC, connect it to the computer and from the build directory of your project, build the flash target e.g. make flash or if you're using Ninja: ninja flash.

This should load the binary into your board. You can now verify if the example is functioning correctly.

What's the next step?

Hopefully, you have learned how to build a simple nRF52 project using the nRF5 CMake. As an exercise, you can create a configuration of this project for the PCA10056 board with the nRF52840 board.

We encourage you to take a look at the CMakeLists.txt we provide for the official examples from the nRF5 SDK so they can be built using the nRF5 CMake.

In our opinion the best way to learn nRF5 CMake is learning-by-doing so consider using it for your next nRF52 project or perhaps, try to migrate your existing project.