Monadnock Systems

The end of Make with Ada is upon us. I wanted to take a moment to summarize my experience.


Annotated source code for an iBeacon in Ada


In order to develop software written in Ada for a embedded devices; a runtime system (RTS) is needed. An RTS in Ada is somewhere between a standard library in other languages and a subset of the full Ada language. It defines the language features that the program will have access to at runtime. There are standard groupings of Ada features called profiles.

In Ada, there are several common standardized profiles. The simplest profile is the Zero Footprint Profile (ZFP). It doesn't include, for example, the Ada tasking features or dynamic memory allocation. It's a good match to bare-board targets with limited resources. So I ported the lm3s-zfp to nrf51.


The ZFP Runtime for nrf51 is available for download from my Github: The repo is licensed GPLv3 or later (it is a derived work of zfp-lm3s).

Porting Process

Porting the RTS from lm3s was simple. I'll discuss the highlights below.

Build Options

The lm3s zfp has two interchangable linking profiles. The nrf51822 doesn't have enough ram for a ram loader to be useful. I added only a ROM profile. I also changed the -mcpu= argument to cortex-m0.

diff -u zfp-lm3s/runtime.xml /home/nock/devel/Nrf51_Led_Demo_Ada/zfp-nrf51/runtime.xml
--- zfp-lm3s/runtime.xml        2012-12-18 08:04:18.000000000 -0500
+++ /home/nock/devel/Nrf51_Led_Demo_Ada/zfp-nrf51/runtime.xml   2016-07-05 09:17:52.564315292 -0400
@@ -4,13 +4,9 @@
-   type Loaders is ("ROM", "RAM");
-   Loader : Loaders := external ("LOADER", "ROM");
    package Compiler is
       Common_Required_Switches := ("-mlittle-endian", "-msoft-float",
-         "-mcpu=cortex-m3", "-mthumb");
+         "-mcpu=cortex-m0", "-mthumb");
       for Leading_Required_Switches ("Ada") use
          Compiler'Leading_Required_Switches ("Ada") &amp;
@@ -26,15 +22,9 @@
         ("${RUNTIME_DIR(ada)}/adalib/libgnat.a") &amp;
         Compiler.Common_Required_Switches &amp;
         ("-nostdlib", "-lgcc");
-      case Loader is
-         when "ROM" =>
-           for Required_Switches use Linker'Required_Switches &amp;
-           ("-T", "${RUNTIME_DIR(ada)}/arch/lm3s-rom.ld");
-         when "RAM" =>
-           for Required_Switches use Linker'Required_Switches &amp;
-           ("-T", "${RUNTIME_DIR(ada)}/arch/lm3s-ram.ld");
-      end case;
+       for Required_Switches use Linker'Required_Switches &amp;
+           ("-T", "${RUNTIME_DIR(ada)}/arch/nrf51_xxab.ld");

Linker Script

I replaced the lm3s linker script with a pretty standard script based based on the nRF51 SDK.

Startup Code

The startup code for the nRF51 series is very simple. There's no PLL/FLL; all that's needed is to setup the interrupt vectors (standard ARM Cortex), configure RAMON (a register that controls power to RAM), copy the .data section into RAM, and zero .bss. The hardest part was homogenizing symbol naming of the interrupt vectors and pointing the end of the Reset vector to the Ada entry point.

Additions / Next Steps

I also added drivers for the NVIC (Nested Vector Interrupt Controller), GPIO, RTC and radio peripheral to the runtime. I am not convinced that this is the correct place for these drivers to live... but initially it's workable enough.

Since I wrote the runtime, AdaCore has established a repository of runtimes. I'd like for my runtime to live there. However, this migration must be part of a larger effort to port my drivers to the AdaCore Ada Drivers Libray and included HAL. I've picked up a new client in the last 8 weeks; which has significantly delayed my personal projects, but hope to pickup this work again in the next week or two.

In the last post, I described a comprehensive blink example for the nrf51 zero footprint runtime system. Here's the rub, though: it doesn't work.


I came up with a clever way to allocate manage persistent logs in flash via GNU linker files. This method was working great without softdevice / bootloader... but when flashing via DFU (with the appropriate memory map) my custom linker section was being mangled.

The key was that only my section was failing. The .text section was located properly and the application was running. I use the ALIGN() macro in the linker file to align my section on a flash page boundary and then objcopy to generate ihex from elf. The alignment offset was large enough that objcopy decided to save a bit of space by putting a gap in the ihex file



The address space above is not contiguous; it jumps from 0x18170—0x18400. This is a perfectly valid thing to do in an ihex file and all the tools I've ever used handled this without issue...

I noticed that my section (after loading via bootloader) was landing at 0x18170... which is familiar. It's the first address of the gap. After consulting the objcopy man page, the --gap-fill flag can be used to insert a fixed value into the gap areas. After filling the gap, the code works as expected.

This means the DFU bootloader from SDK 6.1 is very stupid. I haven't checked the source; but seems to load the firmware at a fixed address; stripping all the addressing prefixes from the transferred file.

A project for a rainy day is to improve this code and/or take a look at bootloaders from subsequent SDKs. Anywho, if your bootloaded application isn't where you expected it'd be; it may be worth checking that your hex file is contiguous. This nugget would've saved me 3 hours.

Having ported a zero footprint profile RTS to nrf51 (from the lm3s one available in the GNAT 2016 distribution); it was time to write some code.


I've been working on a magnetic door lock controller for Unlab (the hackerspace in London, ON). It may end up being deployed on a Raspberry Pi, but those are quite expensive for the use case. In any case, I develop on a normal linux machine and wanted to avoid the RPi.GPIO module that is specific to the Pi.

For testing I wanted to use the FT232R on my Adafruit FTDI Friend via the sysfs GPIO interface. There are several patches (of various qualities) that support this, but none of them are on track to be mainlined. I chose the best looking one and found that the gpiolib interface has changed somewhat. I forward ported Sacha Silbe's patch to 4.7; it's available at github:

GNAT GPL 2016 (arm-eabi) does not support armv6-m. So maybe you want this support or to customize something else about the installation.


Signing my first zone was quite difficult and needlessly so.