written by Shawn Nock on 2016-09-16

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: https://github.com/nocko/zfp-nrf51. 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.