2  Hello World - Blinker

This chapter is based on the projectlet:

- 01_button

2.1 Projectlet Goals

  • Setup the development environment targeting the chosen hardware namely the STM32F4 Discovery board. The ultimate resource for this is of course the AdaCore documentation and we will not address these here. For convenience there are Docker containers that are available from a companion repository devops which has been used extensively in developing this book. The appendix provides an overview of the approach.
  • Experiment with the basic toolset for building, flashing the application and testing.

2.2 Applications

The equivalent of the well known “Hello World” program in the embedded world is the “blinker” - essentially a demonstration of how to interact with the GPIO pins - specifically to blink an LED. Leaving the hardware details aside, the software simply needs to set the output of the GPIO pin to High and Low to turn the LED on and off. Blinking then is achieved by adding a delay between the High and Low states.

In this project, we apply the Ada Driver Library adl to this problem. The library itself supports many microcontroller families and boards, and we focus in this project on the STM32F4 Discovery board. The git repository is setup as a submodule of this repository for easy access.

Three variations on this theme are provided for illustration:

  • blinker - the basic blinker application dealing with the LEDs on the board itself.
  • blinker_ext - essentiall the same as above except the LED itself is external to the board
  • blinker_morse - adds a different blinking pattern to the mix - in this case a Morse code pattern. Such an application is used in the real world to signal different challenges faced by the application.

2.3 Code walkthrough

2.3.1 Basic Blinker

The environmetal setup for this simple application - including the necessary parts of the adl are specified:

Listing 2.1: Basic environment
File : blinker.adb

0002 | --   The packages referenced here are part of the Ada Driver Library. 
0003 | --   Only the STM32 relevant packages will be pulled in by the build system 
0004 | --   though the library itself is quite large and supports quite a few microcontroller families. 
0005 | with STM32.Board;  
0006 | with STM32.GPIO; 
0007 | with STM32.Device;

In particular the package STM32.Board indicates LEDs available on the board - ie the 4 LEDS - blue, green, red and orange. In this example, the LEDs are lit in turns forming an anticlockwise pattern as we view the board:

Listing 2.2: Primary function loop
File : blinker.adb

0018 |    STM32.Board.Initialize_LEDs;                    -- Onboard LEDs are initialized.
0019 |    stm32.board.All_LEDs_Off;                  
0020 |    loop
0021 |       -- Toogle the state of one LED and after a delay, toggle it again and then
0022 |       -- onto the next LED.
0023 |       stm32.gpio.Toggle (stm32.Board.Green_LED);
0024 |       delay 0.5;
0025 |       stm32.gpio.Toggle (stm32.Board.Green_LED);
0026 |       stm32.gpio.Toggle (stm32.Board.Blue_LED);
0027 |       delay 0.5;
0028 |       stm32.gpio.Toggle (stm32.Board.Blue_LED);
0029 |       stm32.gpio.Toggle (stm32.Board.Red_LED);
0030 |       delay 0.5;
0031 |       stm32.gpio.Toggle (stm32.Board.Red_LED);
0032 |       stm32.gpio.Toggle (stm32.Board.Orange_LED);
0033 |       delay 0.5;
0034 |       stm32.gpio.Toggle (stm32.Board.Orange_LED);
0035 |    end loop;

The library STM32.GPIO supports manipulating the GPIO pins.

2.3.2 External LED Blinker

Tbe board provides numerous GPIO pins and many (if not most) of them can be used to connect other devices. In this example we use an external LED and attempt to blink the LED. Of course in this case, we need to tell the system the specific pins where the LED is connected and initialize those explicitly:

Listing 2.3: External LED specification
File : status.ads

0006 | package status is
0007 |    LED : STM32.Board.User_LED renames STM32.Device.PC1 ;
0008 |    procedure Initialize( dev : in out STM32.Board.User_LED);
0009 |    procedure Toggle( dev : in out STM32.Board.User_LED) ;
0010 | end status ;

In this and related projects, the moniker status is used to refer to the external LED. The initialization similar to the OnBoard LEDs :

Listing 2.4: Initialization of the external LED
File : status.adb

0004 |    procedure Initialize( dev : in out STM32.Board.User_LED) is
0005 |    begin
0006 |       Enable_Clock (dev) ;
0007 |       stm32.GPIO.Configure_IO
0008 |         (dev,
0009 |          (stm32.gpio.Mode_Out,
0010 |           Resistors   => stm32.gpio.Floating,
0011 |           Output_Type => stm32.gpio.Push_Pull,
0012 |           Speed       => stm32.gpio.Speed_100MHz));
0013 |       stm32.GPIO.Clear(dev) ;
0014 |    end Initialize;

Once initialized, the external LED is dealt with in the same way as any other LED.

Listing 2.5: Primary loop with external LED
File : blinker_ext.adb

0012 |    status.Initialize( status.LED );
0013 |    loop
0014 |       delay 0.5;
0015 |       status.Toggle ( status.LED );
0016 |    end loop;

During development of course, a proto board is a flexible testing solution:

External LED

2.3.3 Morse Code Blinker

Introducing some variations, given a character, its representation in morse code is signaled in this project. The key parameter for this application is the duration of On and Off patterns:

Listing 2.6: Morse code using an LED
File : morsecode.adb

0143 |    procedure Generate (C : Character; led : in out STM32.Board.User_LED)
0144 |    is
0145 |       use STM32.GPIO ;
0146 |       -- The letter translates into a sequence of symbols in this code.
0147 |       code : Letter_Representation := Translate (C);
0148 |    begin
0149 |       for a in code'Range loop
0150 |          case code (a) is
0151 |          -- Each symbol dot and dash are distinguished by the duration when an LED is set
0152 |          -- and after each symbol is a delay when the LED is cleared.
0153 |             when Dot =>
0154 |                Set (led) ;
0155 |                delay Span (Dot);
0156 |                Clear( led );
0157 |                delay Span (minigap);
0158 |             when Dash =>
0159 |                Set (led);
0160 |                delay Span (Dash);
0161 |                Clear(led);
0162 |                delay Span (minigap);
0163 |             when others =>
0164 |                null;
0165 |          end case;
0166 |       end loop;
0167 |       -- Once all the symbols of a letter are generated, there is an inter letter gap 
0168 |       -- before the next letter is generated.
0169 |       delay Span (gap);
0170 |    end Generate;

A simple application signals just one character :

Listing 2.7: Potential application
File : blinker_morse.adb

0007 | procedure Blinker_morse is
0008 |    message : constant String := "A" ;
0009 | begin
0010 |    status.Initialize( status.LED );
0011 |    for c of message loop
0012 |       Morsecode.Generate (c, status.LED);
0013 |    end loop;
0014 | end Blinker_morse ;

In real life examples, particularly when there is a lack of convenient output, this can be used to signal an internal condition.

2.4 Project Setup

Standard environment created by alire is modified a little for this project. The pattern is used in the initial set of projects in this group till a more flexible approach is evolved.

File : blinker.gpr

0002 | with "../../adl/boards/stm32f407_discovery/stm32f407_discovery_full.gpr";
0003 | with "../common.gpr" ;
0004 | with "config/blinker_config.gpr";
0005 | 
0006 | project Blinker is
0007 | 
0008 |    for Target use "arm-eabi" ;
0009 |    for Runtime ("Ada") use "embedded-stm32f4" ;
0010 | 
0011 |    for Source_Dirs use ("src/", "config/");
0012 |    for Object_Dir use "obj/" & Blinker_Config.Build_Profile;
0013 |    for Create_Missing_Dirs use "True";
0014 |    for Exec_Dir use "bin";
0015 |    for Main use ("blinker.adb" , "blinker_ext.adb" , "blinker_morse.adb") ;
0016 |    package Compiler is
0017 |       for Default_Switches ("Ada") use common.Compiler_Switches ; 
0018 |    end Compiler;

Only a subset of the entire ADL is referenced here - the parts relevant to STM32F4 Discovery boards.

In addition, all the projects in this group will use standard compiler settings which are specified in the common.gpr file included here.

2.4.1 Building the project

With the setup outlined above, building the project is straightforward:

alr build

which produces the elf images for all the 3 applications.

2.4.2 Flashing the binary to the board

In this group of projects, openocd is the tool used for flashing the binary. In the case of STM32 boards, st-link is also a possibility.

openocd -f interface/stlink.cfg \
        -f target/stm32f4x.cfg \
        -c 'init; reset halt; program bin/blinker verify reset exit'

In the example above, the elf file generated by the linker is the input for openocd. The elf file contains all the information such as the load address that is needed to flash. In addition the debug symbols are also embedded in the elf file and can be used for debugging.