ARM-ELF Topics and Tutorial

This appendix describes topics relevant to GNAT for bareboard ARM and also presents a tutorial on building, running, and debugging an Ada application on an embedded ARM board.

Supported Processors and Run-Time Libraries

GNAT for bareboard ARM supports processors from the ARMv7 architecture family and provides both pre-built generic and microcontroller/processor specific runtimes.

Generic core runtimes

Generic Light run-times are provided for the following ARM Cortex cores:

  • Cortex M0: light-cortex-m0

  • Cortex M0+: light-cortex-m0p

  • Cortex M3: light-cortex-m3

  • Cortex M4: light-cortex-m4

  • Cortex M4 with single precision floating point: light-cortex-m4f

  • Cortex M7 with single precision floating point: light-cortex-m7f

  • Cortex M7 with double precision floating point: light-cortex-m7d

To use these generic runtimes you are required to provide your own microcontroller specific startup code and linker scripts. To help create these files you can use the Startup-gen tool as documented in Startup-gen User’s Guide

These generic Light run-times provide a minimal Ada.Text_IO implementation using ARM semihosting. See to your debug probe documentation on enabling semihosting output.

Microcontroller runtimes

Run-time support is provided for the following processor families:

  • ST Microelectronics STM32F4

  • Texas Instruments TMS570LC

  • Texas Instruments TMS570LS

  • Xilinx Zynq-7000

For these processor families, pre-built run-time libraries are provided that represent subsets of the Ada language appropriate for the intended application domain and capabilities of the processors. Due to the range of individual processors within these families and the different configurations options available for a particular processor, these pre-built libraries target a specific processor or developer board.

For the ST Microelectronics STM32F4, the run-time libraries are targeted at the STM32F4-Discovery and STM32F429I-Discovery kits. The following pre-built run-time libraries are available:

  • light-stm32f4

  • light-tasking-stm32f4

  • embedded-stm32f4

For the Texas Instruments TMS570LS, the following pre-built run-time libraries are available:

  • light-tms570

  • light-tasking-tms570

  • embedded-tms570

For the Texas Instruments TMS570LC, the following pre-built run-time libraries are available:

  • light-tms570lc

  • light-tasking-tms570lc

  • embedded-tms570lc

For the Xilinx Zynq-7000, the following pre-built run-time libraries are available:

  • light-zynq7000

  • light-tasking-zynq7000

  • embedded-zynq7000

All pre-built run-time libraries include the necessary low-level hardware startup and initialization functionality required to run applications on the developer board. In many cases, the pre-built run-time libraries will run on other developer boards and processors if they share a similar configuration to these pre-built run-times. If your processor or board uses a different configuration you can modify and rebuild the pre-built run-time to meet your needs, or you wish to target a different processor within the supported Cortex families you can re-target the run-time. The run-time libraries are largely independent of the underlying hardware, simplifying this process.

Getting Started

Developing with GNAT for bareboard ARM is similar to native GNAT development with two important differences. First, when building for a bareboard target you need to specify both the target processor and the run-time library. This can be defined in project file (either directly or via the project properties dialog in GNAT Studio) or on the command line. For details on how to specify the target and run-time library for your project see Introduction to GNAT for Cross Platforms. For all supported ARM boards, the target ‘arm-eabi’ needs to be specified.

For example, the following project file fragment shows both the target and the run-time library specified via their respective attributes. In this example an ARM target and the ‘Zero Foot Print’ STM32F4 run-time library is specified:

project Demo is
   ...
   for Runtime ("Ada") use "light-stm32f4";
   for Target use "arm-eabi";
   ...
end Demo;

The second difference is how programs are run and debugged. This is dependent on your setup and the tools you use. For example, a debug probe can be used to download programs to an ARM microcontroller’s embedded flash or RAM. In this instance refer to the documentation accompanying the probe. In many cases, GNAT Studio can be used to debug your program if your debug probe provides a GDB server. See Debugging for details.

If you are using the ST-LINK debug probe (embedded in the STM32F4x Discovery Kits), GNAT Studio integrates support that allows you to download programs to STM32 microcontrollers and debug them direct from GNAT Studio. The following guide Getting Started with the STM32F429-Discovery provides an introduction to setting up the required software to interact with the ST-LINK debug probe and how to download and debug programs on STM32F microcontrollers. While the guide uses the STM32F429 Discovery Kit, this introduction is applicable for other STM Discovery Kits or other boards utilising STM32F microcontrollers.

Getting Started with the STM32F429-Discovery

This guide describes how to setup, build, run, and debug an Ada application on the STM32F4x Discovery kits. Setup for Windows and Linux hosts are covered. While this section focuses on the STM32F429-Discovery Kit, much of the steps contained within this section are independent of the application and the target hardware itself.

The example used by this guide, led_flasher-stm32f4, can be found in the directory:

<GNAT_install_directory>/share/examples/gnat-cross/led_flasher-stm32f4

This simple example flashes the two LEDs on the developer board, toggling between a standard flashing rate and a quicker flashing rate at the press of the blue User button on the board. The example provides a demonstration of the Ravenscar Run-Time, protected procedures as interrupt handlers and how to access the registers of a microcontroller. While the example targets the STM32F429-Discovery Kit, the embedded-stm32f4 run-time the example uses supports other STM32F4 Discovery Kits. Thus, this example can easily be run on other Discovery Kits by modifying the example to reflect the LED pin assignments for your Discovery Kit. See the lights.ads file on how to perform this modification.

To begin this guide, it is assumed GNAT for bareboard ARM has been installed and the install bin directory is on the PATH environment variable. To complete the setup of the developer tools, the drivers and tools needed to interact with the debug probe included in the Discovery Kits need to be install as described in the following sections.

Host Setup

To program and debug the STM32F429-Discovery Kit you will need to install the open-source stlink project and add it to your PATH. You can obtain the latest version of stlink from its project page at https://github.com/stlink-org/stlink. On Windows, you will also need to install the ST-LINK debug probe USB driver from https://www.st.com/en/development-tools/stsw-link009.html.

Opening the Led_Flasher Project

The led_flasher-stm32f4 example can be found in the directory:

<GNAT_install_directory>/share/examples/gnat-cross/led_flasher-stm32f4

and has the following layout:

led_flasher-stm32f4:
 |--> README.txt
 |--> flasher.gpr
 |--> gnat.adc
 |--> obj:
 \--> src:
       |--> button.adb
       |--> button.ads
       |--> led_flasher.adb
       |--> lights.adb
       |--> lights.ads
       |--> st.ads
       |--> st-stm32f4.ads
       |--> st-stm32f4-exti.ads
       |--> st-stm32f4-gpio.ads
       \--> st-stm32f4-rcc.ads

If you have installed GNAT in a shared location you may first need to copy the example to a directory that you have write access to (e.g. in you home directory).

The example has a typical project layout, capturing the sources for the project within the src directory while the object and other generated files are stored in the obj directory. The gnat.adc file contains the configuration pragmas for the project, while the README.txt provides an introduction to the example.

flasher.gpr is the GPR project file for the example that captures the project settings, including the target and run-time used by the project. On Windows you can open the project in GNAT Studio by double clicking on the project file in Windows Explorer. Otherwise you can launch GNAT Studio from the Start Menu and open the project from within GNAT Studio. On Linux you can open the project with the following shell command from the led_flasher-stm32f4.adc directory:

$ gnatstudio -P flasher.gpr

Note if this is your first time running GNAT Studio, GNAT Studio will create a configuration directory and will alert you via a pop-up dialog box. Press ‘OK’ to close that box and continue. A ‘Tip of the Day’ dialog will appear. Press Close to dismiss it. A Welcome to GNAT Studio page will appear in the center of the GNAT Studio work area. Use the ‘x’ that will appear to the right of the title in the tab to close that page.

You can now explore the project in the Project View on the left side of GNAT Studio.

Building

Within GNAT Studio, build the project using either the Build Main button on the toolbar or the Build/Project->led_flasher.adb menu entry. In the latter case, if a pop-up window appears, press the Execute button in order to build with the default settings.

As GNAT Studio builds the project it will display the Messages view at the bottom of the GNAT Studio window to show information about the build. On a successful build, the Memory Usage view will graphically display the memory usage of the led_flasher program, unless the preference is disabled. (See the preference Build Targets/Project/Build Main page, among others, containing the Display memory usage check-box.)

Running the Program

To run the compiled program, connect the STM Discovery Kit to the computer with a USB cable. Then click on the Flash to Board button on the toolbar. This will download the program to the STM Discovery Kit. Once the program has been downloaded the Message view will report:

Flashing image to board.
Flashing complete. You may need to reset (or cycle power).

Press the black Reset button on the Discovery Kit to reset the board and run the program. The two user LEDs should now flash at different rates. Pressing the blue User button will cause the LEDs to flash at twice their rate. Pressing the User button again will cause the LEDs to flash at their normal rate.

Debugging the Program

With the board connected, click on the Debug on Board button on the toolbar. This will start a GNAT Studio debug session and launch the necessary tools required to debug the program on the board from GNAT Studio. The program will also be automatically loaded onto the microcontroller. Debugging a program on a board through GNAT Studio is similar to debugging a native program:

  • The program can continue to the next break point by pressing the Continue button on the toolbar or by using the Debug/Continue menu item.

  • The program can be stepped through statements using the corresponding menu items in Debug menu and buttons on the toolbar.

  • The program can be interrupted by pressing <ctrl>-\ or though the Debug/Interrupt menu.

  • Breakpoints can be set via the Breakpoints view, by clicking on the line numbers in the source editor while the program is interrupted or by right-clicking on the desired line and selecting Set breakpoint on line ... from the Debug menu.

  • Variables can be queried from the Variables view or by hovering the mouse over a variable with the program interrupted.

GNAT Studio also provides a Debugger Console view that allows shell access to the underlying GDB client that is debugging the program. The GDB commands that correspond to the above debugging tasks can be entered into the console directly if desired.

The program can be restarted by either using the black reset button on the board or via the Debugger Console with the command:

monitor reset

The debugging session can be exited via the Debug/Terminate menu or issuing the quit command in the Debugger Console.

Debugging Walkthrough

If you do not have an active GNAT Studio debug session, click on the Debug on Board button on the toolbar. (Remember that you can hover the mouse cursor over the buttons to see their names, and that there is a preference in General/Custom Styles to have the button names displayed along with the button icons on the toolbar.) When the GNAT Studio session loads, the editor will switch to the main procedure in led_flasher.adb and the Debugger Console will report the initial output of gdb:

file /GNAT/share/examples/gnat-cross/led_flasher-stm32f4/obj/led_flasher
Reading symbols from GNAT/share/examples/gnat-cross/led_flasher-stm32f4/obj/led_flasher...done.
remote debugging using localhost:4242
(gdb) load
Loading section .text, size 0xafa4 lma 0x8000000
Loading section .ARM.extab, size 0x8c0 lma 0x800afa4
Loading section .ARM.exidx, size 0xf28 lma 0x800b864
Loading section .rodata, size 0x1ff4 lma 0x800c790
Loading section .data, size 0xe3c lma 0x800e784
Start address 0x80036fc, load size 62908
Transfer rate: 14 KB/sec, 8986 bytes/write.
(gdb) monitor reset halt

Here GDB has loaded the example program led_flasher and connected to the remote GDB server (the program relaying the debugging commands from GDB to the board). Once GDB is connected with the board, GNAT Studio issues the load command to load the program onto the board and follows with the monitor reset halt command to reset the microcontroller to a debuggable state. If the Debugging Console does not display the above, there is a communication problem with the board. If this occurs, quit GNAT Studio and reconnect the Developer Kit to your computer.

Staying within the Debugger View, at the GDB command prompt (gdb) type

c

to run the program. The LEDs on the Developer Kit will flash at different speeds. Press the blue User button to change the speed of the LEDs.

Now press <ctrl>-\ to interrupt the program. When you do this the program on the microcontroller stops running and the Debugger Console will report the instruction address and subprogram the microcontroller has stopped on:

Program received signal SIGTRAP, Trace/breakpoint trap.
0x080035ec in __gnat_sys_tick_trap ()

GNAT Studio will switch the Editor view to the line of code where the program stopped if it can locate the source files. Since led_flasher program is compiled with the debug flag this will occur when the debugger stops inside the program code. However, in the above case the program has stopped in the run-time code that has not been compiled with the debug flag and thus GNAT Studio will not switch to the run-time source code.

We will now stop the debugger in a line in the example’s source code. With the Project view visible expand the src directory and open lights.adb. Scroll to line 47 and click on the line number. A mark will appear over the line number to indicate that the breakpoint has been set. You can also confirm the breakpoint in the Breakpoints View on the lower left of the GNAT Studio window.

You can also add breakpoints from the Breakpoints view. In the view, click on the plus icon. In the Breakpoint editor windows that appears, enter button.adb into the file field and change the line field to 29. Click the OK button to close the window and to add the breakpoint. Double-clicking on the new breakpoint in the Breakpoints view will open the Button package and highlight the line the breakpoint is set on. For now in the Breakpoints view deselect the Enable checkbox next to the newly created breakpoint 2.

In the Debugger Console enter the command c to run the program to the next break point or press the continue button on the toolbar. The program will run to the selected breakpoint and GNAT Studio will switch the editor to the file location the program has stopped on (lights.adb:47 in this case). With the program stopped, you can query parts of the program state. For example, you can query the value of the local Divider object on line 51 by placing the cursor over the variable name. The value of the object will appear in a tooltip next to the variable (in this case it will show 1). You can also query the state of the protected object Speed_Button on line 47 by the same method, where it will show the Speed_State component as normal.

Now press the blue User button on the board and continue the program. When the program stops, look at the state of the protected object: it should now be double.

You can step through the statements of the program by pressing the step or next statement buttons on the GNAT Studio toolbar or by entering the s n commands into Debugger Console. Step (s) cause the debugger to stop at a new line of source code, while Next (n) will cause the debugger to step over subprogram calls. Try using these commands now and notice how the editor highlights the current position of the program.

We will now change how long one of the LEDs on the board stay on for. First delete the breakpoints we created by selecting the breakpoints in the Breakpoints view and pressing the minus button on the view’s toolbar. Next, open lights.ads and go to line 45. This task object declaration creates a task that flashes the user LED PG13. Change the On_Time discriminate of the task to 75. Build the application again using the build button on the toolbar. Once the program has been built, we now need to load the updated program onto the microcontroller. Go back to the Debugger Console view and enter at the GDB prompt:

load

If the program has been successfully downloaded the console will print

`/GNAT/share/examples/gnat-cross/led_flasher-stm32f4/obj/led_flasher' has changed; re-reading symbols.
Loading section .text, size 0xafa4 lma 0x8000000
Loading section .ARM.extab, size 0x8c0 lma 0x800afa4
Loading section .ARM.exidx, size 0xf28 lma 0x800b864
Loading section .rodata, size 0x1ff4 lma 0x800c790
Loading section .data, size 0xe3c lma 0x800e784
Start address 0x80036fc, load size 62908
Transfer rate: 14 KB/sec, 8986 bytes/write.

Now you can reset the microcontroller and run the program by entering the following:

monitor reset
c

Notice now that LED PG13 stay on for longer. To conclude the walkthrough, exit GNAT Studio’s debugging session by either typing quit in the Debugger Console or by using the Debug/Terminate menu item.

Changing Time Resolution

On the ARM Cortex-M boards, the Ravenscar run-time libraries use the ARM SysTick interrupt to implement Ada semantics for time, e.g., delay statements and package Ada.Real_Time. These clock tick interrupts occur at a given, regular rate determined by the run-time. As a result, the maximum resolution for time (e.g., in delay statements) is set by this interval because that is how often the run-time is invoked to examine the time and react as necessary.

The interval is set in the System.BB.Board_Support package body, located within the run-time in the gnarl/s-bbbosu.adb file:

 1with ...
 2
 3package body System.BB.Board_Support is
 4
 5   ...
 6
 7   -----------------------
 8   -- Sys_Tick Handling --
 9   -----------------------
10
11   --  We use the Sys_Tick timer as a periodic timer with 1 kHz rate. This
12   --  is a trade-off between accurate delays, limited overhead and maximum
13   --  time that interrupts may be disabled.
14
15   Tick_Period : constant Time.Timer_Interval := Clock_Frequency / 1000;

As you can see on line 15, the resolution for delays etc. is one millisecond. Applications can request smaller values but won’t actually get them because the hardware is not configured to do so. That one millisecond resolution is a good default, representing the trade-off described in the comments, but it may not be good enough for your application.

The System.BB.Board_Support package files are located in the gnarl subdirectory. Therefore, we copy the body (not the spec) for the package to the gnarl_user subdirectory and make the modification there. We can change the divisor to whatever is required, keeping in mind that shorter intervals entail more frequent interrupt handling overhead. Then we rebuild the run-time. See the Customized Bareboard Run-Time Libraries annex for how to modify and build run-time libraries.

Target Restrictions

Task Priority Restriction

Due to a restriction stemming from the hardware model of interrupt handling and context switching in the ARMv7-M architecture, Ada tasks cannot have a base priority specified that is an interrupt priority value.

This restriction applies only to ARMv7-M processors. It does not apply to the ARMv7-A or ARMv7-R architectures.

Debugging

The means for debugging applications on bareboard targets vary with the target board and debugging probe used. This section addresses both general GNAT Studio debugging topics and the use of the debugging probe embedded on the ST Microelectronics Discovery Kits.

Debugging with GNAT Studio

GNAT Studio offers a similar debugging experience when targeting an embedded ARM board as a native target, with GNAT Studio using the GDB client included with GNAT. The difference between native and remote debugging is remote debugging requires a GDB server to pass commands and data between the debug probe and GDB. An example of a GDB server is the st-util utility that communicates with the ST-LINK debug probe found on the STM Discovery Kits and used by GNAT Studio in the Getting Started tutorial.

GNAT Studio can be set to automatically connect to your probe’s GDB server when you launch a debugging session in GNAT Studio on a per-project basis. To do this open the properties for your project in GNAT Studio by clicking on the pencil icon in the Project view and navigate to the Embedded properties sheet. Under Communication Settings enter the host address and port of your GDB server in the Program Host field as detailed by your probe’s documentation. For example, if you are using st-util as your GDB server and running it on your computer you would enter:

localhost:4242

In the Protocol field choose remote from the drop down menu.

If you are using the st-util GDB server, GNAT Studio provides built-in support for launching st-util when the user initializes a debug session. You can tell GNAT Studio that you are using this tool by selecting it from the Connection Tool drop down menu. Otherwise, you will need to ensure that you have your GDB server running before starting a debugging session in GNAT Studio.

ST Microelectronics Discovery Kits

This section provides details on using the ST-Link debugging probe embedded in ST Microelectronics Discovery Kits that enables symbolic debugging with GDB (on the command line or within GNAT Studio) over USB. Normally, GNAT Studio will handle the details of launching and connecting to the GDB server and thus this section is useful if you are not using GNAT Studio or run into problems while using GNAT Studio.

Interaction with the on-board JTAG converter is accomplished by a stand-alone command-line executable named st-util that runs on the host. This executable is a third-party utility that provides a number of capabilities, including that of a GDB server. It is pre-built on the Windows hosted ARM-ELF toolchain but you must build it on Linux hosts. See Host Setup in the Getting Started section on installing and setting up st-util.

Using st-util involves manually invoking it on the command line, without parameters, in a command shell. You must invoke it before GDB connects to the board, each time a connection is to be made. This includes the case in which a previous invocation and connection has terminated, because terminating the debugger session disconnects the gdb-server that ‘st-util’ provides.

Note that if you just want to download your application to the board and let it run immediately, without debugging it, you can do so with another utility named st-flash that writes binary files to the FLASH memory on the board. As for st-util there is a version of st-flash pre-built in the Windows hosted toolchain.

Note that st-flash requires that the executable produced by the toolchain be converted to a ‘binary’ format, which can be accomplished via the arm-eabi-objcopy command. For example, assuming we have a project that creates an executable named led_flasher in an obj subdirectory, we could build the project and then invoke the arm-eabi-objcopy command, as shown in the following:

$ gprbuild -P flasher.gpr
$ arm-eabi-objcopy -O binary obj\led_flasher obj\led_flasher.bin

The result is the file led_flasher.bin that can be specified to st-flash on the command line. If you just enter the name st-flash on the command line it will print usage information. Usage information is also widely available on the Internet.

Automatic Board Reset For Debugging

When debugging a bare ARM board, you may need to reset the board after initially connecting the debugger. A reset is suggested because the previously running application may have put the processor and peripherals in an inconvenient state for debugging. If, when running an application under the debugger, initial execution seems to have no effect, a reset is likely in order.

This frequently can be the case with the ST Microelectronics Discovery boards. You can use the dedicated on-board button to reset the board after GDB connects to the board. Alternatively, you can automate the reset action using a GDB command file script.

The command file below will cause a Cortex-M3 or -M4 board to automatically reset immediately after a GDB load command executes. Note that GNAT Studio issues that command as part of the Debug/Initialize menu invocation. Manual GDB load command invocations will also trigger the action.

define hookpost-load
echo Resetting the processor and peripherals...\n
set *0xE000ED0C := 0x05FA0004
echo Reset complete\n
end

To use the script, copy the contents below into a file named .gdbinit (note the leading period in the name). Create the file if you do not already have one defined.

You can put the file in the same directory as the project gpr file, but note that other locations are possible. In particular, other locations can be convenient, for example if you want to use the same initialization for every project, globally. See the GDB documentation for the possible locations and their effects.

If you do place the .gdbinit file in the same directory as the project file, you will also need to enable local instances of the file so that GDB will not ignore it. To do so, create another .gdbinit file in your HOME directory (either on Linux or on Windows) and place the following inside it:

set auto-load safe-path /

Of course, if you already had a .gdbinit in your HOME directory, just add that line to it. That line will enable local instances of .gdbinit and so GDB will execute their content.

The board reset is achieved by writing to the ‘Application Interrupt and Reset Control’ register located at address 0xE000ED0C. Both the processor and the peripherals can be reset by writing a value of 0x05FA0004. That value will write to the SYSRESETREQ bit. If you want to avoid resetting the peripherals, change the value to 0x05FA0001. That value will write to the VECTRESET bit. Do not use a value that sets both bits.

For further details see the book by Joseph Yiu: The Definitive Guide to the ARM Cortex-M3 and Cortex-M4 Processors, 3rd edition, pp. 262-263.

In both cases, any on-board debug hardware is not reset. For boards with a JTAG interface, you can use the GDB command:

monitor jtag_reset

to reset it. This command can be issued interactively at the GDB prompt, or could be part of another GDB macro.

Troubleshooting

Here are some recommendations when you encounter unexpected behavior:

  • Sometimes, when first starting a debugging session, invoking the debugger’s Run or stepping commands may not have any effect. This can happen when prior execution leaves the board in an uncommunicative state. (Remember the board starts executing a valid loaded application whenever power is applied.) In such cases the board probably just needs to be reset. You can push the black button on the board to do so, and then try to load the executable (‘Debug’ => ‘Initialize demo’) again. If that doesn’t suffice, try disconnecting the board from the USB cable in order to cycle power, reconnect, and then try the full debugging steps again.

  • If st-util can communicate with the board but does not properly interact with it as described above, it may be that the firmware on the board has been updated. If you cannot reinstate the prior version of the firmware, you will need to invoke an additional utility each time you power-up the board. Download the STM32 ST-Link utility from the same ST Micro web page above by selecting the ‘STSW-LINK004’ part number. Selecting ‘STSW-LINK004’ will present a file named stsw-link004.zip for download. Unzip and install it. Put the binaries on the host system path. Then, before calling st-util, each time after first applying power to the board, invoke the following on the command line: st-link_cli -c

    If the firmware is the problem this can allow the board and st-util to work together properly.