Inspirel banner

3. First Program

There is a long tradition in programming tutorials to present some variant of the "Hello World" example as the first program.

This makes sense in desktop programming, where there is a huge hardware and software infrastructure already prepared that we can take for granted and where a single line of code seems to be a "basic" programming statement.

This does not work in the embedded world.

Before we will come to the point where we will be able to display a "Hello World" message, we will be already pretty advanced in the use of Ada on ARM Cortex-M. For now, let's start with something simple - as simple as an empty program that does absolutely nothing. We will use it to exercise all development tools.

Our program will be composed of two files: the specification (similar to a header file in C or C++) and a body. The specification (in file program.ads) will be very short:

package Program is

   procedure Run;
   pragma Export (C, Run, "run");

end Program;

Ada uses packages for organizing program elements and here we have package Program that will act as a logical container for everything else. That "everything else" is just a single procedure called Run - this procedure has no parameters and does not produce any value and is exactly what we need for a starting point of our program.

Note that you can use different names for both the package and the startup procedure, there is no obligation to use any specific name. You could, for example, use Main as a name for the startup procedure.

Note also that there is a pragma that defines an external name for the Run procedure (the external name will be run, but you can use anything else) - the external name is something that other tools will be able to see and refer to. That is, there will be a single procedure in the program, but it will be visible under two different names depending on the point of view: in Ada the procedure will be visible as Run and external tools will see the same procedure as run.

Another source file is needed with the body of the procedure Run - this file, named program.adb, contains:

package body Program is

   procedure Run is
   begin
      loop
         null;
      end loop;
   end Run;

end Program;

The procedure Run is the only entity in the package Program and is itself very simple: it contains an empty loop (the only statement in the loop is a null statement), which prevents the procedure from returning. We do not want this procedure to return anywhere, as it is a startup procedure, the root of the whole call graph in our program.

Just to help you with some analogies, the C programmer might write a similar procedure like this:

void run(void)
{
    for (;;)
    {
    }
}

I hope that the meaning of Ada keywords above is now well understood.

Note that it is not strictly true that procedure Run does nothing at all: actually, it is very busy wasting energy by endlessly spinning in the loop, millions of times per second. This is not the optimal solution, especially for battery-powered devices, but we will keep it like that until later chapters.

It is not necessary to know assembly language, but we will occasionally have a look at the assembly output for our program. You can ask the compiler to generate the assembly output with this command:

$ gcc -S -mcpu=cortex-m0 -mthumb -mfloat-abi=soft program.adb

or

$ gcc -S -mcpu=cortex-m3 -mthumb program.adb

(Remember that with cross-compilation tools, your compiler will have a different name, for example it will be arm-eabi-gcc with GNAT on Windows.)

The first variant above is appropriate for devices with the Cortex-M0 chip, which does not support floating-point instructions, the second variant will be used for Cortex-M3 or better.

The -S parameter above tells the compiler to prepare the assembly output instead of an object file. The -mcpu and -mthumb options are needed to limit the set of instructions to those understood by the target microcontroller - in the first variant above, the Cortex-M0 chip was selected, which does not support floating-point instructions, and the second variant will be used for Cortex-M3 or better. When working with a specific chip, it makes sense to use values that correspond with the target device - thanks to this, the compiler will be free to use more elaborate instruction sets, leading to executables that are both smaller and faster.

If the above command was executed without errors, a new file (program.s) should be created in the same directory. Its content might be similar to this:

	.cpu cortex-m0
	.eabi_attribute 27, 3
	.fpu vfp
	.eabi_attribute 20, 1
	.eabi_attribute 21, 1
	.eabi_attribute 23, 3
	.eabi_attribute 24, 1
	.eabi_attribute 25, 1
	.eabi_attribute 26, 2
	.eabi_attribute 30, 6
	.code	16
	.file	"program.adb"
	.global	program_E
	.data
	.type	program_E, %object
	.size	program_E, 1
program_E:
	.space	1
	.text
	.align	2
	.global	run
	.code	16
	.thumb_func
	.type	run, %function
run:
	push	{r7, lr}
	add	r7, sp, #0
.L4:
	mov	r8, r8
.L2:
	mov	r8, r8
	b	.L4
	.size	run, .-run
	.ident	"GCC: (Debian 4.6.3-8+rpi1) 4.6.3"
	.section	.note.GNU-stack,"",%progbits

It is not important to understand everything in this file, but there is one thing that is worth noting: the run symbol that marks the beginning of our Run function. The run name is exactly what we have used as external procedure name and this was propagated to the assembly output.

Let's try to compile the same program to create an object file:

$ gcc -c -mcpu=cortex-m0 -mthumb -mfloat-abi=soft program.adb

The -c option tells the compiler to only compile the given file, without attempting to link a full executable. A new file should be created in the same directory, called program.o. It is a binary file.

We can check the list of all symbols used in this file with this command:

$ nm program.o

The output of this command might look like:

00000000 D program_E
00000000 T run

We need not worry about the program_E symbol (although you might want to check the assembly output above and see that it was there as well), but we will be happy to see run in this object file - this means that it contains our startup procedure.

Now, we can try to link our program into an executable file. The linking process will be controlled by a very short linker script, which I will explain in the next chapter - here, in order not to lose the flow (and assuming Arduino M0 as a target), let's invoke the linker with this command:

$ ld -T flash.ld -o program.elf program.o 

As a result, the program.elf file should be created in the same directory. This file is an executable file, but we have no means to use it directly on the board. Still, it contains all the information that was accumulated so far - in particular, you can check its symbols:

$ nm program.elf

This time the output is a bit different:

0000410c D program_E
00004100 T run

Note that before (with the program.o file) the addresses on the left were all 0 and now the run symbol has address 4100 - the importance of this fact will be clarified in the next chapter, but for the time being keep in mind that these addresses are related to the memory layout of the target device and for each microcontroller can be different.

I have mentioned that there is no direct way to use this executable file on the board, so we need to convert it to the raw binary image that will contain exactly the bytes that we want to upload to the flash memory in the microcontroller. This command will do it:

$ objcopy -O binary program.elf program.bin

The last and final file, program.bin, contains the raw binary image that we want to upload to the board.

An alternative format that can be used for data transfer - and the one that we will use for Arduino M0 - is called Intel Hex and can be obtained with:

$ objcopy -O ihex program.elf program.hex

We will use extensions .bin and .hex to distinguish the final file formats.

The steps needed to actually upload such images to the flash memory are different for each board.

Try to become comfortable with all the steps presented above and in the following sections (and don't hesitate to experiment a bit with them), they will be regularly repeated with each new program.

Arduino M0

Connect the board to the USB port in your computer.

In order to reduce the effort needed to discover all the details, I have uploaded a very simple sketch from Arduino IDE to the board and carefully observed the commands that were printed in the output window. The last of these commands referred to the Avrdude tool with a dedicated configuration script - as already mentioned, it makes sense to copy them to some more comfortable place and after doing so, it should be possible to upload the executable file with this command:

$ ./avrdude -Cavrdude.conf -v -v -patmega2560 -cstk500v2
            -P/dev/cu.usbmodem0041 -b57600 -Uflash:w:program.hex:i

Note that avrdude.cfg is a configuration file from the Arduino IDE installation. The program.hex file is the image file created earlier.

The above command should be executed shortly after pressing the RESET button on the connected board - the bootloader that is already installed in the device waits for the first couple of seconds for the data transmission (the LED on the board blinks rapidly during that time) and these few seconds of time window is when the upload should be initiated from the host computer (note that during that short time window the device can be visible in the system under some temporary name like /dev/cu.usbmodem0041 - use the Arduino IDE output to check that name on your particular host system), and if no upload is initiated during that time, the board will automatically transition to normal operation.

The result of executing the above command might look similar to this:

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################ | 100% 0.00s

avrdude: Device signature = 0x1e9801
avrdude: NOTE: "flash" memory has been specified, an erase cycle
         will be performed
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "program.hex"
avrdude: writing flash (16748 bytes):

Writing | ################################################ | 100% 0.00s

avrdude: 16748 bytes of flash written
avrdude: verifying flash memory against program.hex:
avrdude: load data flash data from input file program.hex:
avrdude: input file program.hex contains 16748 bytes
avrdude: reading on-chip flash data:

Reading | ################################################ | 100% 0.00s

avrdude: verifying ...
avrdude: 16748 bytes of flash verified

avrdude: safemode: Fuses OK (H:00, E:00, L:00)

avrdude done.  Thank you.

The program is now in the flash memory and will start executing automatically or after each reset and power on.

Arduino/Genuino Zero

The Arduino Zero and Arduino M0 boards come from different vendors (which are in disagreement with regard to the brand name) and even though they share a lot in terms of design, they use different loaders.

The Zero board can be programmed with the use of OpenOCD, which, after analyzing the Arduino IDE output and copying the involved files to some more accessible place, can be invoked with this command:

$ ./openocd -s scripts/ -f arduino_zero.cfg -c 'telnet_port disabled;
            program program.bin verify reset 0x00002000; exit'

Note that arduino_zero.cfg is a configuration file from the Arduino IDE installation. This file, together with a whole set of other scripts, is located in the scripts directory. The program.bin file is the binary executable created earlier and the 0x00002000 value above is an offset that will be explained in the next chapter.

The result of executing the above command might look similar to this:

Open On-Chip Debugger 0.9.0-gd4b7679 (2015-06-10-19:16)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : only one transport option; autoselect 'swd'
adapter speed: 500 kHz
adapter_nsrst_delay: 100
cortex_m reset_config sysresetreq
Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : CMSIS-DAP: FW Version = 02.01.0157
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 1 TDO = 1 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : clock speed 500 kHz
Info : SWD IDCODE 0x0bc11477
Info : at91samd21g18.cpu: hardware has 4 breakpoints, 2 watchpoints
target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x81000000 pc: 0x00002208 msp: 0x20008000
** Programming Started **
auto erase enabled
Info : SAMD MCU: SAMD21G18A (256KB Flash, 32KB RAM)
Warn : Adding extra erase range, 00000000 to 0x00001fff
wrote 8192 bytes from file program.bin in 1.193115s (6.705 KiB/s)
** Programming Finished **
** Verify Started **
verified 269 bytes in 0.076565s (3.431 KiB/s)
** Verified OK **
** Resetting Target **

The program is now in the flash memory and will start executing after reset (which is ensured by the OpenOCD at the end) or after each power on.

Note that since the M0 and Zero boards share the same microcontroller and pin mapping, later examples will focus on only one of these boards.

Arduino Due

Connect the board to the USB port in your computer and try to figure out how it was detected, exactly in the same way as you would do with Arduino IDE.

The Arduino IDE uses some tricks with USB transmission speed to force the board to reset in the loader mode. We will not attempt to repeat these tricks and instead will do the upload manually.

First, press the ERASE button on the board (it is in the area below descriptions for pins 15 and 16); after that press RESET (in the corner) and execute the bossac command:

$ bossac -p tty.usbmodemfa141 -U false -e -w -v -b program.bin -R

You should see something like this:

Erase flash
Write 269 bytes to flash
[==============================] 100% (2/2 pages)
Verify 269 bytes of flash
[==============================] 100% (2/2 pages)
Verify successful
Set boot flash true

After that your program is in the flash memory of the board and from now on every time the board is reset (or powered on), the program will automatically start execution.

STM32 Nucleo-32

Connect the board to the USB port in your computer.

We will use the OpenOCD tool, which in its standard package already contains the configuration script for STM32F0x boards. The appropriate command is:

$ ./openocd -s scripts/ -f board/st_nucleo_f0.cfg
            -c 'program program.bin reset exit 0x08000000'

The program.bin file is the binary executable created earlier and the 0x08000000 value above is an offset that will be explained in the next chapter.

The result of executing the above command might look similar to this:

Open On-Chip Debugger 0.9.0-gd4b7679 (2015-06-10-19:16)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control.
        The results might differ compared to plain JTAG/SWD
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
srst_only separate srst_nogate srst_open_drain connect_deassert_srst
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v24 API v2 SWIM v11 VID 0x0483 PID 0x374B
Info : using stlink api v2
Info : Target voltage: 3.253649
Info : stm32f0x.cpu: hardware has 4 breakpoints, 2 watchpoints
target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0xc1000000 pc: 0x08000100 msp: 0x20001000
** Programming Started **
auto erase enabled
Info : device id = 0x10006444
Info : flash size = 32kbytes
target state: halted
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20001000
wrote 1024 bytes from file program.bin in 0.117594s (8.504 KiB/s)
** Programming Finished **
** Resetting Target **
shutdown command invoked

The program is now in the flash memory and will start executing after reset (which is ensured by the OpenOCD at the end) or after each power on.

STM32 Nucleo-144

Connect the board to the USB port in your computer.

We will use the OpenOCD tool, which from version 0.10 should contain the configuration script for STM32F7x boards, but since this tutorial was written before the official release of this version, some tips might be helpful if you try to use some earlier variant.

Check the scripts/board/st_nucleo_f7.cfg file and if it does not exist, create it as a copy of st_nucleo_f4.cfg in the same directory and replace the line:

source [find target/stm32f4x.cfg]

with:

source [find target/stm32f7x.cfg]

With this modification it should be possible to run the command:

$ ./openocd -s scripts/ -f board/st_nucleo_f7.cfg
            -c 'program program.bin reset exit 0x08000000'

The program.bin file is the binary executable created earlier and the 0x08000000 value above is an offset that will be explained in the next chapter.

The result of executing the above command might look similar to this:

Open On-Chip Debugger 0.10.0-dev-00247-g73b676c (2016-03-09-23:22)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control.
        The results might differ compared to plain JTAG/SWD
adapter speed: 2000 kHz
adapter_nsrst_delay: 100
srst_only separate srst_nogate srst_open_drain connect_deassert_srst
srst_only separate srst_nogate srst_open_drain connect_deassert_srst
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : clock speed 1800 kHz
Info : STLINK v2 JTAG v25 API v2 SWIM v13 VID 0x0483 PID 0x374B
Info : using stlink api v2
Info : Target voltage: 3.233618
Info : stm32f7x.cpu: hardware has 8 breakpoints, 4 watchpoints
stm32f7x.cpu: target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x08000128 msp: 0x20010000
** Programming Started **
auto erase enabled
Info : device id = 0x10016449
Info : flash size = 1024kbytes
stm32f7x.cpu: target state: halted
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x20000046 msp: 0x20010000
wrote 32768 bytes from file program.bin in 0.961748s (33.273 KiB/s)
** Programming Finished **
** Resetting Target **
shutdown command invoked

The program is now in the flash memory and will start executing after reset (which is ensured by the OpenOCD at the end) or after each power on.

Previous: Documentation and Tools, next: Linking and Booting.
See also Table of Contents.