About
These are rough notes about my experience.
The Nordic tutorial. This post elaborates, because the tutorial is somewhat incomplete, and assumes you are using Eclipse on a Windows host.
Broad overview of the development environment
Nordic seems to support this development environment (Linux, Eclipse, GNU, SEGGER).
They provide example apps that include Makefiles for this environment.
The Nordic tutorial includes Eclipse projects. The projects use soft file links (“linked resources” in Eclipse terminology.)
The toolchain is not very modern with respect to finding and declaring dependencies: you must maintain your own Makefile (starting from one provided by Nordic in their examples) and you must create new soft linked resources in the project. (When you change your project to call additional functions in the SDK.)
Components:
- IDE: Eclipse
- compiler: GNU gcc compiler for ARM
- debugger: GNU gdb for ARM
- interface to the dev board: SEGGER tools, JLink
- flashing: nrfjprog command line tool
My host platform is a clean Ubuntu 16.04.1 installation.
My target is the nrf52 family (nrf52832 chip) on a development board from Nordic, which includes a SEGGER brand debugger probe (JTAG interface.)
Overview
These are the rough steps:
- Preliminaries: install your usual but optional tools like git and Dropbox
- install a JRE (Java runtime needed by Eclipse)
- install Eclipse installer
- install Eclipse C/C++ (an IDE)
- install ARM plugin for Eclipse
- install GNU ARM tools (gcc-arm-none-eabi and arm-none-eabi-gdb, needed by ARM plugin for Eclipse)
- install the Nordic nRF5x SDK (libraries used by your app)
- install SEGGER JLink tools (interface to the debugger probe on the target development board)
- install nRF5x command line tools (to flash the target)
- import an example project into Eclipse
- choose a Dev pack for the project (a dev pack is a description of the target, optional for debugging)
- modify the project and its Makefile
- build
- flash
- debug your example project
Details
These are selected details about the steps. (But I gloss over other details that are easily searched.)
- Since I keep my projects in local git repositories and use the “Team” functions of Eclipse:
sudo apt-get install git
- Eclipse is written in Java:
sudo apt-get install default-jre
- At the Eclipse website you first download a small installer app. (“Ubuntu Software” app will let you install an older Eclipse.)
- Then you run that app and choose from a variety of environments, here C/C++
- A third party plugin adds function to Eclipse for developing embedded ARM. The Nordic chip has an ARM mcu. Search for “Eclipse ARM plugin” and find the page where you can just drag an icon into Eclipse. Alternatively, you can use the more usual method of installing Eclipse plugins, that is, choose Help>Install New Software, then enter a URL to the place where this Eclipse plugin lives, etc.
- There is a gcc compiler and gdb debugger for ARM that are not installed with the usual development tools (such as make) that are already installed on many Linux hosts.
sudo apt-get install gcc-arm-none-eabi
sudo apt-get install gdb-arm-none-eabi
- The Nordic SDK includes many libraries (in source form) and example projects (including Makefiles for this dev environment.) This toolchain uses soft links to various files in the SDK (the source is not copied to the project.)
- The development board includes a SEGGER brand of hardware debugger probe (a JTAG interface to the target) so you are entitled to use SEGGER’s tools to talk to it from Eclipse. See below.
- The nRF5x command line tools are a separate download from the Nordic site. It yields nrfjprog, a tool to flash your app to the target. I am not sure you need it, you can also use JFlash, a SEGGER tool?
- Outdated example projects are attached to the tutorial as zip files. The example projects contains an “Eclipse project file”, i.e. it is not just the source files, but a file that configures Eclipse with many soft links to the SDK. Example Makefiles and .ld files are in the SDK. E.G. $(NRF_SDK_ROOT)/examples/peripheral/blinky/pca10040/blank/armgcc/Makefile, but using those you must create your own Eclipse project.
- ARM Eclipse plugin has support for Packages Manager. It is optional, but especially needed if you want to use a debugger and examine registers and other memory locations. See below.
- See below
- At this point, an example project might build successfully.
- See below
- See below
More details of installing SEGGER tools (step 8, 15)
The ARM Eclipse plugin has support for the SEGGER debugger probe, but you need to download the SEGGER tools separately from the SEGGER website. It comes as a .deb package. Clicking on it opens the “Ubuntu Software” app. Choose the “Install” button. It installs executables to /usr/bin.
To test SEGGER tools, plugin your dev board, and at a terminal:
JLinkGDBServer
Expect many lines to scroll by, such as:
Connecting to J-Link...
J-Link is connected.
Firmware: J-Link OB-SAM3U128-V2-NordicSemi compiled Jul 5 2016 08:42:09
This means the host communicated with the probe side of the dev board. But it will quit because it cannot connect to the app process running on the target side of the dev board?
The Nordic tutorial describes configuring the GNU ARM Eclipse plugin for debugging.
The GNU ARM Plugin site gives a better tutorial for configuring a “Run/Debug Configuration” for SEGGER JLink. Note that for non-embedded C programming, a debug configuration usually exists already, but here you must create one.
After you create a debug configuration, make sure you choose the correct one: the arrow to the right of the Debug icon lists the configurations. You will have two after you create another one. If you choose the wrong one (the C/C++ Application configuration) you get:
nrf52832_xxaa.out: cannot execute binary file: Exec format error
Note that the “Main>C/C++ Application” field is “_build/nrf52832.out”, not .elf. Other than that, I only entered the device name field, “nRF53832_xxAA”. (Since I had not already loaded a dev pack it was not filled in automatically?)
Do not choose “Connect to running target” because you usually want to reset the mcu before you start debugging.
When you debug (for example by clicking on the Debug icon), expect Eclipse to change to the debug perspective and for the debugger to break at main(). This worked for me even though I had not flashed the test program, i.e. it stopped at the standard location for main.
More details of installing packs
Follow ARM Eclipse plugin has support for Packages Manager.
In Eclipse, switch to the Packs perspective. Click the refresh button at the top of the “Packs” pane. It takes many minutes to refresh (from the web.) If it fails to refresh packs, try deleting the Packages folder. Expect a list to appear in the Packs pane.
The left pane is for filtering the Packvoid RTC1_IRQHandler(void);s pane. If you select an entry there, it filters the list in the Packs pane.
I selected the “Device” tab in the left, filtering pane, and selected “Nordic Semiconductor>nRF52 Series.” Then in the “Packs” pane I expanded the “nRF_DeviceFamilyPack” item, and right-mouse-button clicked on the latest version and chose “Install” from the pop-up menu.
I don’t understand what the “Board” tab does, and I didn’t use it.
After you install a device pack, the debugger magically (without you needing to change the debug configuration) starts showing more information, such as dissassembly and registers. Until you do this, they are empty or blank.
More details of futzing with the project (step 12)
Fix path variables in the project
In Project>Properties>Resource>Linked Resources>Path Variables, add a new entry “NRF_SDK_ROOT” to point to where you installed the SDK.
In …Linked Resource>Linked Resources, edit each entry that is broken (has an exclamation point) to use the NRF_SDK_ROOT path variable you just defined.
Fix the path to the compiler in the Makefile
(This is described in the Nordic tutorial, but obliquely, for Windows.)
The Makefile in the example project includes a Makefile.posix from the SDK. Makefile.posix defines the path to the gcc compiler for ARM. Edit $(NRF_SDK_ROOT)/examples/toolchain/gcc/Makefile.posix. Change the first definition (of GNU_INSTALL_ROOT) to have the value “/usr”
Fix capitalization errors (case) in the SDK
The SDK was developed on a Windows host, which is case-insensitive to filenames. There are some capitalization changes (bugs that Nordic is aware of) needed:
$(NRF_SDK_ROOT)/components/toolchain/gcc/gcc_startup_nrf52.S => .s
In the Makefile:
CMSinclude/Include => cmsis/include
Define path variables in the Makefile
Do something like
NRF_SDK_ROOT = /home/bootch/nrf5_sdk
TEMPLATE_PATH = /home/bootch/nrf5_sdk/components/toolchain/gcc
More details on a Flash target (step 14)
The Makefile has a target (something you can ask to make) for flashing. The target invokes the Nordic command line tool nrfjprog. (In more modern toolchains, for non-embedded programming, you neither maintain the Makefile nor invoke more than one make target. For desktop app, other targets might package an app. For embedded, you must flash the app.)
You can configure Eclipse so that you can click an icon to make that target (and flash your app to the dev board): in the C/C++ perspective, in the right pane, select the “Make Target” tab, next to the “Outline” tab. Then select your project in the body of the pane. Then choose the “New Make Target” icon (an icon of green, concentric rings with a plus sign.) Expect a dialog to open.
Enter a “Target name” (extracted from the Makefile), in this case “flash”.
Choose OK. Expect the dialog to close and a new entry named “flash” in the body of the pane.
To flash your app, double-click that new entry . Expect the console to show messages about the progress of the flash.
The tool nrfjprog must be in your shell path, or you can modify the makefile to invoke it using an absolute path to its installed location.
More about debugging (step 15)
When you click on the Debug icon (and choose the proper debug configuration) the perspective changes to the Debug perspective and the program is halted at main() (a default of the configuration?) You can choose from the icons to Resume, Step Into, etc.
If you have installed a dev pack, the Dissassembly tab of the right, middle pane will show assembly code (sometimes?) The Outline tab of the same pane will show the context of the current program counter? Sometimes it shows that you have wandered into an exception handler (a green dot?)
The left, middle pane has an arrow in the left margin showing where the program counter is in the source code. If the PC is in a macro such as APP_TIMER_INIT, that is not very useful.
If you are stepping along and the console shows “Starting target CPU…” and the blue arrow does not advance in the source code, it means you have thrown an exception and entered an infinite loop in the default exception handlers (see startup_nrf52.s). Click the Pause icon to see where you are looping. Then the Debug pane in the upper left will show you the call stack and you can examine it to see where in the code you were when the exception was thrown.
Debugging using NRF_LOG
This section is work in progress, not working yet because NRF_LOG has C linkage, not C++
This section is highly dependent on SDK version, here using v12.
The two usual techniques for debugging are:
- use a debugger e.g. gdb
- use “printf” or “log” statements sprinkled through your code
NRF_LOG is a logging library from Nordic. A logging library is glorified printf: it adds the capability to specify a logging level such as: INFO, DEBUG, etc. The level can change for each module.
Configuring your project for logging
NRF_LOG requires use of a UART peripheral. In your sdk_config.h (if SDK v12) file:
#define USE_UART 1
Inserting logging code into your source
At the top of each file (module) where you want to call logging functions to write to the log:
#define NRF_LOG_ENABLED 1#include "nrf_log.h"
Note the order is important, define before include?
At the top of main.c, where you initialize logging:
#define NRF_LOG_ENABLED 1
#include "nrf_log_ctrl.h"
That might require to add more linked resources to your project (depending on whether you are already using a UART) as evidenced by compiler or linker warnings.
Early in main.c, insert and call from main():
static void initLogging(void)
{
// Initialize logging library.
__attribute__((unused)) uint32_t err_code = NRF_LOG_INIT(NULL);
// APP_ERROR_CHECK(err_code);
}
Here NULL means: without timestamping. I chose to ignore any error return.
Logging to the Eclipse console
TODO Does this work???? The log can go to the Eclipse console. The log statements will be interspersed with messages from the SEGGER GDB Server (e.g. “Reading registers…”)
To arrange that in Eclipse, click on the arrow next to the Debug icon. Expect a pop-up menu. Choose “Debug Configurations.” Expect a dialog. Select your configuration in the left pane. Expect the right pane to refresh with the attributes of your configuration. In the right pane, choose the “Startup” tab. Choose the “Enable semihosting” checkbox and the “GDB client” checkbox. Choose “Apply” and “Close.”
Logging to SEGGER RTT
You can also use a faster solution, SEGGER RTT. Faster means it takes less cpu cycles away from say your ISR routines. It is faster because the logging goes directly from your target mcu to a terminal app on your host, without going through the GDB Server on your host?
Described by E. Styger’s post. That requires more setup, and requires a SEGGER probe (which your nrf52 DK board has.)
Also described by this post.
Also a Nordic tutorial.
Debugging off in the weeds, lost in never never land
Especially if you are debugging your own code (versus an example project), you might end up with the program state “Running” but not hitting any of your breakpoints you expect to hit. When you click the “Pause” icon, it might show the PC in:
- WDT_IRQHandler
- HardFault_Handler
I think this means an interrupt occurred for which you did not define a handler. Default handlers are provided in startup_nrf52.s. You can also see them in the .map file.
Each of the internal interrupts (for exceptions such as invalid op code, invalid memory address, etc.) has a separate default handler defined. They are all just infinite loops, but at different addresses.
The default handlers for IRQ (external interrupts) are all just one, same handler (at the same address) that is an infinite loop (assembly code: “BR . ” that is, branch to the value of the current PC program counter register, i.e. a loop.)
WDT_IRQHandler
The debugger will say it is the WDT_IRQHandler, because that is the last name that points to this location. But it might not be the WDT device that interrupted, and started looping in the default handler. Once you are paused, to know what IRQ device interrupt occurred, examine the XPSR register. One of its sub-registers is the IPSR register, the low-order 9 bits. (For unknown reasons, SEGGER and gdb did not show the IPSR sub-register by itself, and showed the contents of the full XPSR register in decimal.) Convert to hex (using a calculator) and mask off all but the low-order 9 bits. From that value, subtract decimal 16 (hex 10). That gives you the index of the interrupt. Then examine /components/device/nrf52.h for its list of the interrupt indices. In my case, I got zero, meaning the “POWER_CLOCK” IRQ.
Then you can define your own handler in C, just a function with the same name e.g. WDT_IRQHandler. The default handlers are defined as “weak” in assembly language, so defining the same name function in C overrides the default.
Here, I am using C++ compiler so I am careful to specify C linkage so that the compiler doesn’t mangle the name and the linker then overrides the default, weak definition:
extern "C" {
void WDT_IRQHandler() {
while (1);
}}
In my case, the interrupt I was not handling was one used by app_timer. In the SDK /components/drivers_nrf/clock/nrf_drv_clock.h I added this (so that the C++ compiler did not mangle the name and thus fail to override the weak default):
#ifdef __cplusplus
extern "C" {
#endif
void POWER_CLOCK_IRQHandler(void);
#ifdef __cplusplus
}
#endif
Also in components/libraries/timer/app_timer.c
#ifdef __cplusplus
extern "C" {
#endif
void SWI0_IRQHandler(void);
void RTC1_IRQHandler(void);
#ifdef __cplusplus
}
#endif
This is only because I used app_timer from the Nordic library. In general, if you use more from the library that might use interrupts, you should examine your .map file for any “…IRQHandler” whose name is mangled by the C++ compiler.
If you fail to make these changes (and you are using C++ and app_timer) the symptoms will be: your app compiles but halts, showing a stack trace that ends in some IRQHandler, after a call to an app_timer function.
Note that Nordic does NOT fully support C++. Thus you may find instances such as above, where you need to alter the SDK code to support C++. There are many places in the SDK code that seem to support C++ (i.e. #ifdef __cplusplus) , but the support seems to be incomplete.
(Here I rant: I hate C++ but I like object oriented programming. I use C++ in a highly constrained way. I don’t want to use a C standard dated to 1999. I need all the help I can get from a compiler. I wish Nordic would embrace C++, or even some other modern language like Python or Rust. )
HardFault_Handler
See here.
A short summary is: many bugs (programming errors) such as bad pointers end up as hard faults. (And not necessarily an op code exception or a memory exception, depending on how exceptions are enabled?) The debugger often does not show you the call stack when a hard fault occurs. So install a handler for this exception that helps the debugger show you the call stack when the exception occurred. A few statements/instructions before the last call is often the errant code.
After thoughts
The build system takes much hands on maintenance of the Makefile, and is not as smart as some build systems about avoiding redoing steps that don’t need doing:
- build, it only compiles.
- flash, it compiles then flashes
- debug, it builds, then starts the debugger, without flashing!
Some of the dependencies are defined in the Makefiles which you are maintaining. If you create a dependency error in the Makefile, it might build with stale code.
Other build steps are specified in the Eclipse configuration.
If the debugger ever seems lost, start over by cleaning, building, flashing, and debugging.