Using nRF52 with CLion
Are you interested in embedded development? In this guest blog post Nick Brook, founder at NRB Tech, shows how to use CLion for nRF52.
IoT consultant and founder at NRB Tech. Nick founded NRB Tech in 2018 to build useful and sustainable IoT products through a combination of in-depth hardware, software and user experience expertise. |
Nordic’s nRF52 series of Bluetooth Low Energy System-on-Chips (SoCs) are versatile and widely used in IoT products, supporting many different applications. Nordic provides a very complete SDK which contains many example applications and good documentation, making it easy to get started and build IoT devices. Some of the example projects available in the SDK include:
- Beacons
- Blood pressure monitoring
- Cycling speed and cadence
- Glucose monitoring
- Heart rate
- HID Keyboard + Mouse
- Running speed and cadence
- Many other technical example projects
These provide great starting points to develop a Bluetooth Low Energy device of your own.
At NRB Tech we use the Nordic nRF52 series SoCs to build products such as AirTurn’s range of foot pedals, which act as a keyboard, mouse, MIDI device, or use a proprietary protocol.
Bluetooth products are often a combination of a low power device and an App. It’s useful to be able to share code across those platforms, so that you can write and test the code just once for both the firmware and the App.
CLion is a great IDE for C and embedded development, and CMake provides many advantages such as the ability to create libraries that can be shared across platforms and to integrate those libraries into an application easily. However, the nRF52 SDK does not support development with CMake/CLion out of the box, but the Nordic nRF5 Mesh SDK does use CMake – although it does not support external projects.
So we built nRF5-cmake-scripts, which uses the mesh SDK scripts and provides CMake functions to add your own targets. It also makes adding the DFU bootloader very simple and provides many macros for adding the nRF5 SDK libraries to your project as you need them. Let’s set up an example project, using CLion as our IDE.
Setting up
This tutorial assumes you are using an nRF52 DK. If you are using different nRF5 hardware, you’ll need to modify the variables at the top of the root CMakeLists.txt later in the tutorial.
You will need the following dependencies:
- Nordic command line tools (
nrfjprog
andmergehex
) by Nordic Semiconductor – Wrapper utility around JLink.- This also includes the JLink installer – install it.
- Python 3.
- Nordic nrfutil by Nordic Semiconductor – a utility for generating DFU packages.
- After installing Python, install using
pip install nrfutil
.
- After installing Python, install using
- ARM GNU Toolchain by ARM and the GCC Team – a compiler toolchain for embedded ARM chips.
- On a Mac, this can be installed with homebrew:
brew tap ArmMbed/homebrew-formulae
brew install arm-none-eabi-gcc
- On other platforms, you can download it from the GNU-ARM toolchain page.
- On a Mac, this can be installed with homebrew:
- git – A version control system.
- CMake – A build tool.
- On Mac and Linux, your PATH is probably automatically configured correctly. But if some executables cannot be found, then edit your
~/.profile
file and addexport PATH="<additional paths, semicolon separated>;$PATH"
. - On Windows you will need to ensure that all binaries are in your PATH (instructions for editing). Your PATH additions might look something like this:
C:\Program Files (x86)\GNU Tools Arm Embedded\9 2019-q4-major\bin
C:\Program Files\Nordic Semiconductor\nrf-command-line-tools\bin
%USERPROFILE%\AppData\Local\Programs\Python\Python37\Scripts
C:\Program Files\Git\cmd
C:\Program Files (x86)\SEGGER\JLink
Creating a nRF52 project
Once the dependencies have been installed, clone the base project in a terminal/command window to set up the project structure:
git clone --recurse-submodules https://github.com/NRB-Tech/nRF5-cmake-scripts-example-base.git .
Run a script to get the project ready for your own use (on Windows, run in git bash by right clicking in the directory > “Git Bash here”):
./cleanup.sh
Then, copy the example CMakeLists.txt as recommended in the nRF5-cmake-scripts
readme:
cmake -E copy nRF5-cmake-scripts/example/CMakeLists.txt .
Note: You may also need to edit some of the variables in this file for your platform, such as by setting NRFJPROG
, MERGEHEX
, NRFUTIL
and PATCH_EXECUTABLE
manually if they are not in your PATH
.
At this point, you can open the project in CLion. Build the download target to fetch the dependencies.
Using the Terminal tab in CLion, copy over some files from an SDK example project:
cmake -E copy toolchains/nRF5/nRF5_SDK_16.0.0_98a08e2/examples/ble_peripheral/ble_app_template/pca10040/s132/armgcc/ble_app_template_gcc_nrf52.ld src/gcc_nrf52.ld
cmake -E copy toolchains/nRF5/nRF5_SDK_16.0.0_98a08e2/examples/ble_peripheral/ble_app_template/pca10040/s132/config/sdk_config.h src/
Add a file src/main.c, and then add some source code. Here we add some simple code to log a message.
#include "nrf_log_default_backends.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
int main(void) {
ret_code_t err_code = NRF_LOG_INIT(NULL);
APP_ERROR_CHECK(err_code);
NRF_LOG_DEFAULT_BACKENDS_INIT();
NRF_LOG_INFO("Hello world");
while(true) {
// do nothing
}
}
Create an src/app_config.h file to override some of the default configurations in sdk_config.h:
#define NRF_LOG_BACKEND_RTT_ENABLED 1 // enable rtt
#define NRF_LOG_BACKEND_UART_ENABLED 0 // disable uart
#define NRF_LOG_DEFERRED 0 // flush logs immediately
#define NRF_LOG_ALLOW_OVERFLOW 0 // no overflow
#define SEGGER_RTT_CONFIG_DEFAULT_MODE 2 // block until processed
We are going to include the DFU bootloader too, so we need to generate keys. In the terminal/command prompt:
nrfutil keys generate keys/dfu_private.key
nrfutil keys display --key pk --format code keys/dfu_private.key --out_file keys/dfu_public_key.c
Now we need to create a file src/CMakeLists.txt to build our targets:
set(NRF5_LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/gcc_${NRF_FAMILY})
# DFU requirements
# List the softdevice versions previously used, or use FALSE if no previous softdevices
set(PREVIOUS_SOFTDEVICES FALSE)
# Set the location to the DFU private key
set(PRIVATE_KEY ${CMAKE_CURRENT_SOURCE_DIR}/../keys/dfu_private.key)
set(PUBLIC_KEY ${CMAKE_CURRENT_SOURCE_DIR}/../keys/dfu_public_key.c)
# Set the App validation type. [NO_VALIDATION|VALIDATE_GENERATED_CRC|VALIDATE_GENERATED_SHA256|VALIDATE_ECDSA_P256_SHA256]
set(APP_VALIDATION_TYPE NO_VALIDATION)
# Set the Soft Device validation type. [NO_VALIDATION|VALIDATE_GENERATED_CRC|VALIDATE_GENERATED_SHA256|VALIDATE_ECDSA_P256_SHA256]
set(SD_VALIDATION_TYPE NO_VALIDATION)
# The bootloader version (user defined)
set(BOOTLOADER_VERSION 1)
# The DFU version string (firmware version string)
set(DFU_VERSION_STRING "${VERSION_STRING}")
# Set the target name
set(target example)
# add the required libraries for this example
nRF5_addLog()
nRF5_addSeggerRTT()
nRF5_addAppError()
# include files
list(APPEND SOURCE_FILES main.c)
list(APPEND INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}")
nRF5_addExecutable(${target} "${SOURCE_FILES}" "${INCLUDE_DIRS}" "${NRF5_LINKER_SCRIPT}")
# make sdk_config.h import app_config.h
target_compile_definitions(${target} PRIVATE USE_APP_CONFIG)
# Here you can set a list of user variables to be defined in the bootloader makefile (which you have modified yourself)
set(bootloader_vars "")
# add the secure bootloader build target
nRF5_addSecureBootloader(${target} "${PUBLIC_KEY}" "${bootloader_vars}")
# add the bootloader merge target
nRF5_addBootloaderMergeTarget(${target} ${DFU_VERSION_STRING} ${PRIVATE_KEY} ${PREVIOUS_SOFTDEVICES} ${APP_VALIDATION_TYPE} ${SD_VALIDATION_TYPE} ${BOOTLOADER_VERSION})
# add the bootloader merged flash target
nRF5_addFlashTarget(bl_merge_${target} "${CMAKE_CURRENT_BINARY_DIR}/${target}_bl_merged.hex")
# Add the Bootloader + SoftDevice + App package target
nRF5_addDFU_BL_SD_APP_PkgTarget(${target} ${DFU_VERSION_STRING} ${PRIVATE_KEY} ${PREVIOUS_SOFTDEVICES} ${APP_VALIDATION_TYPE} ${SD_VALIDATION_TYPE} ${BOOTLOADER_VERSION})
# Add the App package target
nRF5_addDFU_APP_PkgTarget(${target} ${DFU_VERSION_STRING} ${PRIVATE_KEY} ${PREVIOUS_SOFTDEVICES} ${APP_VALIDATION_TYPE})
# print the size of consumed RAM and flash - does not yet work on Windows
if(NOT ${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Windows")
nRF5_print_size(${target} ${NRF5_LINKER_SCRIPT} TRUE)
endif()
Reload the CMake project (Tools | CMake | Reload CMake Project), then we are ready to build and run our example. First, build the START_JLINK_RTT
target to open an RTT console, then build the flash_bl_merge_example
target. You should see the “Hello world” log output in the RTT console! From here you can add source code and include SDK libraries with the macros provided in nRF5-cmake-scripts/includes/libraries.cmake.
To debug this code, we need to do a little more work to set up CLion.
Debugging in CLion
First, we need to add a toolchain to use the ARM debugger client. In CLion Preferences/Settings | Build, Execution, Deployment | Toolchains, create a new toolchain named arm-none-eabi and set the Debugger to arm-none-eabi-gdb:
Then we need to use that toolchain for our CMake profile. In CLion Preferences/Settings | Build, Execution, Deployment | CMake set the Toolchain to arm-none-eabi:
Next we just need to create the build configuration for debugging. On the top right of the CLion window, click the configuration drop down and choose Edit Configurations. Then Add a new Embedded GDB Server configuration:
Configure as shown:
- Set a name.
- Share through VCS to share this configuration with other CLion users of your project.
- Select the flash_bl_merge_<target> target.
- Select your executable.
- Set “Download executable” to “None.”
- Set ‘target remote’ args to
tcp:localhost:2331.
- Set GDB Server to
- Mac: /usr/local/bin/JLinkGDBServer.
- Windows: C:\Program Files (x86)\SEGGER\JLink\JLinkGDBServerCL.exe.
- Or the appropriate path for your system.
- 1. Set GDB Server args to
-device nrf52 -strict -timeout 0 -nogui -if swd -speed 1000 -endian little -s
.
Now we can build or debug this target, which will build the firmware and bootloader then flash to the device. When debugging, if your breakpoint is not hit when the debugger starts, just press the reset button and continue:
You can also view the state of peripherals on the device when debugging. When debugging, click the Peripherals tab and click “Load .svd file”:
Browse to toolchains/nRF5/nRF5_SDK_16.0.0_98a08e2/modules/nrfx/mdk and select the .svd file for your hardware (for nRF52832 use nrf52.svd), then select the peripherals you want to monitor. When the debugger is paused you will then be able to inspect the values of peripheral registers:
This tutorial was originally posted at NRB Tech blog.
Now you are ready to give it a try!
Update: Part II – Tutorial: Using nRF52 With nRF Connect SDK, CMake, and CLion