I had some initial luck copying the vast majority of the zfp-lm3s RTS and modifying it for the nRF51.
Linker File ChangesThey have different memory maps, but luckily the nRF51 map is very simple:
- SEARCH_DIR(.) GROUP(-lgcc)
- MEMORY { FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 256k RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16k }
- OUTPUT_FORMAT ("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
- ENTRY(Reset_Handler)
- SECTIONS {.text : { KEEP((.Vectors)) *(.text)
KEEP(*(.init))
KEEP(*(.fini))
/* .ctors */
*crtbegin.o(.ctors)
*crtbegin?.o(.ctors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
*(SORT(.ctors.*))
*(.ctors)
/* .dtors */
*crtbegin.o(.dtors)
*crtbegin?.o(.dtors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
*(SORT(.dtors.*))
*(.dtors)
*(.rodata*)
*(.eh_frame*)
. = ALIGN(4);
} > FLASH
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab
} > FLASH
__exidx_start = .;
.ARM.exidx :
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
. = ALIGN(4);
} > FLASH
__exidx_end = .;
__etext = .;
.data : AT (__etext)
{
__data_start__ = .;
*(vtable)
*(.data*)
. = ALIGN(4);
/* preinit data */
PROVIDE_HIDDEN (__preinit_array_start = .);
*(.preinit_array)
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
/* init data */
PROVIDE_HIDDEN (__init_array_start = .);
*(SORT(.init_array.*))
*(.init_array)
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
/* finit data */
PROVIDE_HIDDEN (__fini_array_start = .);
*(SORT(.fini_array.*))
*(.fini_array)
PROVIDE_HIDDEN (__fini_array_end = .);
*(.jcr)
. = ALIGN(4);
/* All data end */
__data_end__ = .;
} > RAM
.bss :
{
. = ALIGN(4);
__bss_start__ = .;
*(.bss*)
*(COMMON)
. = ALIGN(4);
__bss_end__ = .;
} > RAM
.heap (COPY):
{
__end__ = .;
end = __end__;
*(.heap*)
__HeapLimit = .;
} > RAM
/* .stack_dummy section doesn't contains any symbols. It is only
* used for linker to calculate size of stack sections, and assign
* values to stack symbols later */
.stack_dummy (COPY):
{
*(.stack*)
} > RAM
/* Set stack top to end of RAM, and stack limit move down by
* size of stack_dummy section */
__StackTop = ORIGIN(RAM) + LENGTH(RAM);
__StackLimit = __StackTop - SIZEOF(.stack_dummy);
PROVIDE(__stack = __StackTop);
/* Check if data + heap + stack exceeds RAM limit */
ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed with stack")
} ~~~
In which Shawn cannot use a WebUIWell, that first update was crap. Hopefully we are allowed to edit these down before the contest is over.
More lm3s / nrf51 differencesRTS changes, continuedI copied the initialization code from the Nordic nRF SDK v10.0; it is permissively licensed. Most of the non-memory map changes in the linker file are differing symbol names (__heap_start vs. HeapStart).
The assembly to bootstrap the microcontrollers are quite similar. The nRF has a manipulation to ensure that all the RAM banks are powered at reset and no PLL machinery. Otherwise, it's just the standard:
- Point us to the top of stack
- Show us where our vectors live
- Copy in the bss segment from flash
- Branch to the main routine
I used the linker file to define some registers and was able to turn on one of the boards LEDs.
Next time- A Delay routine
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:
ada procedure WFI is use System.Machine_Code; begin Asm (Template => "wfi", Volatile => True); return; end WFI;
Efficient (Power) delay routineInitialization 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).
ada ... -- 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).
ada 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 IRQHandlerThe 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.
ada procedure RTC1_IRQHandler is ... begin -- Clear the flag so the main execution path continues Delay_Active := False; -- Stop and reset RTC and acknowledge 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 instalment; we'll figure out why and fix it.
Hosting Build Log on my BlogThe Make with Ada site's support for markdown is incomplete (please enable editing and code blocks). For easier reading, I am posting them at my blog as well.
ZFP RTS AvailabilityI've worked on the zfp RTS for nrf51 and it's mostly complete. It currently lives here: https://github.com/nocko/zfp-nrf51 and is licensed GPLv2 or later.
I am planning to submit a pull request against https://github.com/AdaCore/embedded-runtimes; but currently I include a driver for the interrupt handler in the RTS (where as Adacore puts similar drivers in https://github.com/AdaCore/AdaDriversLibrary. I'll have to coordinate with Adacore for a good solution.
GNAT 2016 support for arm-v6mI've updated my blog with a post about troubleshooting bad instructions in GNAT 2016 output for nrf51 and a fix (that requires rebuilding GNAT 2016):
https://nocko.se/blog/gnat-2016s-multilib-isnt-multi-enough/
I've updated my blog with a new post about the ZFP runtime for nrf51: https://nocko.se/blog/zero-footprint-profile-runtime-system-ada-for-nrf51/
Ada iBeaconI've written a BLE iBeacon demo app in Ada. It's licensed GPLv2+. The code is available here: https://github.com/nocko/Nrf51IbeaconAda/.
I've written a blog post with some explanation and additional annotation: https://nocko.se/blog/nrf51-ibeacon-in-ada/
Final Project UpdateI posted a few thoughts on my Make with Ada experience: https://nocko.se/blog/make-with-ada-redux/.
Thank you for putting together what has been a very positive experience for me.
Bluetooth BeaconsHaving created an initial RTS for the NRF51822, I want to play with the 2.4GHz radio. Initial low hanging fruit is iBeacon, followed by Eddystone-EID.
Project LogIn which armv7-m (lm3s) is much like armv6-m (nRF51822)I had some initial luck copying the vast majority of the zfp-lm3s RTS and modifying it for the nRF51.
Linker File ChangesThey have different memory maps, but luckily the nRF51 map is very simple: ~~~
- SEARCH_DIR(.) GROUP(-lgcc)
- MEMORY { FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 256k RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16k }
- OUTPUT_FORMAT ("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
- ENTRY(Reset_Handler)
- SECTIONS {.text : { KEEP((.Vectors)) *(.text)
KEEP(*(.fini))
/* .ctors */
*crtbegin.o(.ctors)
*crtbegin?.o(.ctors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
*(SORT(.ctors.*))
*(.ctors)
/* .dtors */
*crtbegin.o(.dtors)
*crtbegin?.o(.dtors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
*(SORT(.dtors.*))
*(.dtors)
*(.rodata*)
*(.eh_frame*)
. = ALIGN(4);
} > FLASH
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
. = ALIGN(4);
} > FLASH
__exidx_start = .;
.ARM.exidx :
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
. = ALIGN(4);
} > FLASH
__exidx_end = .;
__etext = .;
.data : AT (__etext)
{
__data_start__ = .;
*(vtable)
*(.data*)
. = ALIGN(4);
/* preinit data */
PROVIDE_HIDDEN (__preinit_array_start = .);
*(.preinit_array)
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
/* init data */
PROVIDE_HIDDEN (__init_array_start = .);
*(SORT(.init_array.*))
*(.init_array)
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
/* finit data */
PROVIDE_HIDDEN (__fini_array_start = .);
*(SORT(.fini_array.*))
*(.fini_array)
PROVIDE_HIDDEN (__fini_array_end = .);
*(.jcr)
. = ALIGN(4);
/* All data end */
__data_end__ = .;
} > RAM
.bss :
{
. = ALIGN(4);
__bss_start__ = .;
*(.bss*)
*(COMMON)
. = ALIGN(4);
__bss_end__ = .;
} > RAM
.heap (COPY):
{
__end__ = .;
end = __end__;
*(.heap*)
__HeapLimit = .;
} > RAM
/* .stack_dummy section doesn't contains any symbols. It is only
* used for linker to calculate size of stack sections, and assign
* values to stack symbols later */
.stack_dummy (COPY):
{
*(.stack*)
} > RAM
/* Set stack top to end of RAM, and stack limit move down by
* size of stack_dummy section */
__StackTop = ORIGIN(RAM) + LENGTH(RAM);
__StackLimit = __StackTop - SIZEOF(.stack_dummy);
PROVIDE(__stack = __StackTop);
/* Check if data + heap + stack exceeds RAM limit */
ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed with stack")
} ~~~
In which Shawn cannot use a WebUIWell, that first update was crap. Hopefully we are allowed to edit these down before the contest is over.
More lm3s / nrf51 differencesRTS changes, continuedI copied the initialization code from the Nordic nRF SDK v10.0; it is permissively licensed. Most of the non-memory map changes in the linker file are differing symbol names (__heap_start vs. HeapStart).
The assembly to bootstrap the microcontrollers are quite similar. The nRF has a manipulation to ensure that all the RAM banks are powered at reset and no PLL machinery. Otherwise, it's just the standard:
- Point us to the top of stack
- Show us where our vectors live
- Copy in the bss segment from flash
- Branch to the main routine
I used the linker file to define some registers and was able to turn on one of the boards LEDs.
Next time- A Delay routine
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: ada procedure WFI is use System.Machine_Code; begin Asm (Template => "wfi", Volatile => True); return; end WFI;
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).
ada ... -- 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).
ada 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.
ada procedure RTC1_IRQHandler is ... begin -- Clear the flag so the main execution path continues Delay_Active := False; -- Stop and reset RTC and acknowledge the interrupt TASKS_STOP := 1; TASKS_CLEAR := 1; EVENTS_COMPARE (0) := 0; end RTC1_IRQHandler;
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 instalment; we'll figure out why and fix it.
Hosting Build Log on my BlogThe Make with Ada site's support for markdown is incomplete (please enable editing and code blocks). For easier reading, I am posting them at my blog as well.
ZFP RTS AvailabilityI've worked on the zfp RTS for nrf51 and it's mostly complete. It currently lives here: https://github.com/nocko/zfp-nrf51 and is licensed GPLv2 or later.
I am planning to submit a pull request against https://github.com/AdaCore/embedded-runtimes; but currently I include a driver for the interrupt handler in the RTS (where as Adacore puts similar drivers in https://github.com/AdaCore/AdaDriversLibrary. I'll have to coordinate with Adacore for a good solution.
GNAT 2016 support for arm-v6mI've updated my blog with a post about troubleshooting bad instructions in GNAT 2016 output for nrf51 and a fix (that requires rebuilding GNAT 2016):
https://nocko.se/blog/gnat-2016s-multilib-isnt-multi-enough/
I've updated my blog with a new post about the ZFP runtime for nrf51: https://nocko.se/blog/zero-footprint-profile-runtime-system-ada-for-nrf51/
Ada iBeaconI've written a BLE iBeacon demo app in Ada. It's licensed GPLv2+. The code is available here: https://github.com/nocko/Nrf51IbeaconAda/.
I've written a blog post with some explanation and additional annotation: https://nocko.se/blog/nrf51-ibeacon-in-ada/
Final Project UpdateI posted a few thoughts on my Make with Ada experience: https://nocko.se/blog/make-with-ada-redux/.
Thank you for putting together what has been a very positive experience for me.
Comments
Please log in or sign up to comment.