written by Shawn Nock on 2016-08-30

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.

Like many before me, I chose to blink some lights. You can follow along here: https://github.com/nocko/Nrf51_Led_Demo_Ada. You can blink LEDs by spinning in a tight do nothing loop, but that's no fun.

As it turns out, to blink the LED efficiently, you need to write a few peripheral drivers. GPIO (duh), but also RTC (so you can sleep the uController), NVIC (interrupt controller) and support for the sleep mechanisms as well.

The sleep mechanism on Cortex-m0 is really easy, it's an instruction (WFI or WFE), we can implement this in Ada with inline assembly:

 procedure WFI is
      use System.Machine_Code;
   begin
      Asm (Template => "wfi", Volatile => True);
      return;
end WFI;

Efficent (Power) delay routine

Initialization requires starting the 32kHz xtal oscillator, configuring the RTC prescaler and finally configuring the RTC to interrupt on match of the the CC0 register (and enabling interrupt in the NVIC).

...
-- Start the 32kHz xtal oscillator
if EVENTS_LFCLKSTARTED /= 1 then
            LFCLKSRC.SRC := Xtal;
            TASKS_LFCLKSTART := 1;
            loop
               --  Waiting for the LF Oscillator to start
               exit when EVENTS_LFCLKSTARTED = 1;
            end loop;
end if;
...
-- Initialize (clear) the RTC peripheral, enable interrupt on CC0
PRESCALER := Delay_Timer_Prescaler;
TASKS_STOP := 1;
TASKS_CLEAR := 1;
INTENSET.COMPARE.Arr (0) := Set;
Interrupts.Enable (RTC1_IRQ);

The delay routine has two parts; set the RTC to interrupt after the specified time and finally wait-for and detect the interrupt (while saving as much power as possible).

procedure Delay_MS (Milliseconds : Natural) is
      TASKS_START : nrf51.Word renames nrf51.RTC.RTC1_Periph.TASKS_START;
      CC0 : UInt24 renames nrf51.RTC.RTC1_Periph.CC (0).COMPARE;
   begin
      CC0 := MS_To_Ticks (Milliseconds);
      -- Set a flag (to be cleared by the IRQHandler)
      Delay_Active := True;
      -- Start the RTC
      TASKS_START := 1;
      loop
         -- Keep sleeping until the IRQHandler indicates that our time has come
         WFI;
         exit when Delay_Active = False;
      end loop;
   end Delay_MS;

RTC IRQHandler

The ISR is simple. Clear the flag set above so Delay_MS will return; then stop the RTC, ack the interrupt and clear the RTC peripheral compare registers.

procedure RTC1_IRQHandler is
   ...
   begin
      -- Clear the flag so the main execution path continues
      Delay_Active := False;
      -- Stop and reset RTC and acknowlege the interrupt
      TASKS_STOP := 1;
      TASKS_CLEAR := 1;
      EVENTS_COMPARE (0) := 0;
end RTC1_IRQHandler;

Does it work?

It doesn't work on GNAT 2016, and we'd not have known if we used a busy loop to blink our LED. In our next exciting installment; we'll figure out why and fix it.