GNAT 2016 and nrf51822: Blinking like you mean it
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/Nrf51LedDemo_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;
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.