Polidea Labs #1: A Minimal Toolchain Setup for nRF5x Chips
The most basic need
Taking control of the build process is one of the most basic needs a programmer has to satisfy in order to succeed. It’s not only about the obvious translation of source code into binary. In most cases, build scripts also include tooling that take the burden of repetitive tasks from the developer. Simply put: if the process of flashing a developer board with new firmware always requires the same 10 parameters command, why would you want to type it each time by hand? Consequently, the choice of the right build system and setup has a tremendous influence on your productivity. In this article, I will present our build setup for nRF5x targeted firmware projects. You will learn all about the components involved - why we chose them and how it ended.
How did we arrive here?
Historically, here at Polidea, we had been focusing on writing only the mobile-app part of the projects that included peripheral devices. The natural border line had been marked by the Bluetooth Low Energy schema. Over the years, many of our customers had been asking if we could also help with firmware development. We had usually hesitated as this was “not in our DNA”. Finally 2017 came and we decided to cross the border into a yet unexplored land. It seemed like a natural thing to do since we had already gained some experience in BLE projects. The first platform we set to explore is the nRF5 series by Nordic Semiconductor. This was dictated by both the market situation (it’s very popular) and the needs of our clients (almost all of our past and present BLE projects use it).
The most important part of the setup is of course the SDK provided by Nordic Semiconductor. It makes a very good first impression, and more importantly, does support various toolchains across multiple operating systems including a GCC variant provided by ARM that runs just fine on a plain macOS system. This is not something taken for granted in a world dominated by Windows only tools. All in all, we only need 4 software packages to start: 1. arm-none-eabi-gcc by the Free Software Foundation and ARM – this is a variant of the popular GCC compiler targeting ARM processors running without a OS (hence none-eabi), 2. nRF5 SDK by Nordic Semiconductor – the actual SDK containing libraries, bootstrap code, and examples, 3. J-Link by SEGGER – interfacing software for the J-Link family of programmers and debuggers, 4. nrfjprog utility by Nordic Semiconductor – wrapper utility for the J-Link software.
Installation is a snap. J-Link is provided as a standard macOS installer. The rest just needs to be downloaded and unpacked in a convenient place. After that, only two last polishing touches are necessary: 1. setting the PATH to include the nrfjprog directory, 2. editing the Makefile.posix file found in the toolchains/gcc directory of the SDK the point to the right compiler install directory and version.
To check if everything is set up correctly so far, we can access the SDK’s extensive example catalog. It shows various aspects of the platform. From basic GPIO access, through peripherals like timers, up to high-level Bluetooth Low Energy services. They supplement the documentation nicely. Let’s start with the “hello world” equivalent for embedded apps, and make some LED’s blink. The ‘blinky’ example is located under ‘examples/peripheral/blinky’. At its root there is the main source file (main.c), a license, and a IAR Workbench project file. Then, in individual sub-directories there are makefiles/project files for the different compiler and board combinations. We also have a selection of a bare-metal and a softdevice based configuration (we will talk about softdevices shortly). So, assuming we want to build for the nRF51 DK (model name PCA10028) with softdevice under arm-none-eabi-gcc, we would go to the ./pca10028/s130/armgcc subdirectory. Nordic decided to stick with the standard UNIX make tool for their build scripts. This makes building very easy. Just type:
make– builds the firmware,
make erase– clears the SoC flash,
make flash_softdevice– writes the softdevice into the SoC flash,
make flash– writes the actual firmware into the SoC flash,
make clean– removes all build products. Can be used to force a complete fresh build cycle.
Invoking 1—4 in order will compile and flash the development board with the firmware. If you did everything correctly during setup, you should now observe LED1 cycling in an infinite on-off loop.
Nordic uses the concept of softdevices in their SDK. Simply put, those are ready to use implementations of higher level radio stacks. They come in numerous versions for different chip families (nRF51 vs. nRF52), and various radio protocols (Bluetooth Low Energy, ANT, etc). In contrast to the rest of the SDK, they are provided in a binary form and without source code. Designing the firmware has a number of implications:
- the softdevice can be certified to conform to a target protocol, and as such, it’s easier to pass certification with the final device,
- the Over-The-Air Device Firmware Update process can be simplified, as the actual application binary can be swapped out separately from the softdevice,
- Nordic protects their IP.
The only real downside for the developers is the need to choose the right softdevice for the job.
Yearn to be more
So far so good. I applaud Nordic for choosing standard makefiles for their example projects. This reduces the number of dependencies, and is well known among programmers. But they have their downsides. Some of them are inherent to make. Others are the consequences of design decisions made by Nordic. To name a few:
- The scripts contain a number of long and ugly relative paths. For example
SDK_ROOT := ../../../../../...
- The sources and headers for different components of the SDK are intermixed with each other. It’s not immediately apparent which of them belong together, depend on each other or are unused at all.
- For each target SoC there is a separate makefile.
- They are tightly coupled with a makefile bundled with the SDK.
- They assume the nrfjprog tool is in the PATH.
- Makefiles have more in common with bash scripts, than with programming languages.
Can we do better? Of course we can. Enter CMake. The build script tool that generates build scripts. It provides a nice syntax, and enables organizing code into smaller modules. Exactly what we would expect in 2017. The only problem is that we need to port the makefiles to it.
The process is straightforward, although time consuming. The basic idea is to enable verbose output in the original makefiles (using VERBOSE=1) and save the build output. Then iterate on your cmake files, until you get exactly the same compiler and linker calls. Of course once done this work can be reused, with minimal modification, for other examples and completely new projects.
My starting point was the BLE Heart Rate service example for the nRF51 DK board. In the first step I copied over the source file and header lists directly from the makefiles. Thanks to this, I could concentrate on compiler and linker flags. This was easy with only one exception. By default CMake appends CMAKECFLAGS to the linker call. Which in case of ARM GCC is not a good idea. Luckily the CMAKECLINKEXECUTABLE and CMAKECXXLINKEXECUTABLE variables can be overridden to change this behaviour. As soon as the firmware compiled and burned successfully to the board, I went on removing excess source files and headers. The last touch was to compare the nRF51 and nRF52 makefiles, and incorporate the differences (like the Cortex-M0 vs. Cortex-M4 CPU architecture) into our cmake script. The end result is on Polidea’s GitHub (MIT license) found here.
To sum up, by moving to cmake we:
- achieved a clear separation of project and platform sources,
- can switch quickly between different Nordic Semiconductor SoCs,
- have predefined macros for adding common SDK libraries.
Last missing part
Ok, there is one last part missing. The IDE. One option is Eclipse CDT. In fact, you will find it in most of the instructions for using the nRF+GCC combo available online. But as I’m a huge fan of Jetbrains, I decided to go with CLion instead. In fact, I originally learned CMake due to CLion.
And there you have it. A setup for building nRF based firmware projects using GCC on macOS with support for a decent IDE.