There are few things to know before you can compile a piece of software for the target processor. First of all, when you compile the standard ‘Hello World’ application on your machine, the compilation is targeted at the CPU architecture that you have on your machine. However, when you want to program a different device, the compiler must come from the toolchain that is relevant to the architecture of the target device. Also, when you want to use some third-party libraries, they all need to be compiled for the target architecture. Let’s take a look at what exactly happens during the compilation, which is shown in the picture below.

The compiler first generates the object files (.o) by compiling the source files with .c extension (or .cpp in case of C++ code). To be more precise, the pre-stage of compilation is done by a preprocessor which prepares the code for compilation by generating code from macros, conditional compilation instructions or other directives. The object files can be compiled using the gcc for the host machine, which is the same way as you would normally compile.

gcc -c source1.c -o source1.o gcc -c source2.c -o source2.o

In this case, two object files source1.o and source2.o are compiled, which contain machine code. When compiling for a microcontroller, it is common to see another source file with a .s extension which is written in processor’s assembly. That file contains code specific to the microcontroller such as definitions of first instructions, stack pointer or interrupts’ declarations. This will be explained in more detail on an actual example later on in this post. The assembly file (.s) can be compiled using the assembler provided with the toolchain.

The last stage involves linking all object files along with the linker script and generating an executable file. The linker script allows mapping all the instructions defined in the object files onto the memory map of a processor outlined in the linker script. Therefore, it is expected to have a specific linker script for different processors. They define memory regions such as stack, RAM, ROM or peripheral regions.

Compiling for a specific target device is exactly the same as compiling for your Windows or Linux environment. The only differences are the parameters that your software build system takes as inputs. For your own machine, much of the compilation process is already automated and the specific details of building a C file are hidden. You can check it yourself by compiling a C/C++ in the command line with a –verbose parameter.

Installing tools

To cross-compile for Cortex-M4 family of microcontrollers, which are placed on discovery boards, you will need the gcc cross-compiler from ARM toolchain and a utility to flash an executable on a microcontroller called st-link. Here is a short instruction on how to download and set up both tools using the command line.

ARM Toolchain

Download the latest toolchain from ARM

wget https://armkeil.blob.core.windows.net/developer/Files/downloads/gnu-rm/8-2018q4/gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2

Unpack the toolchain in /opt directory

sudo tar -jxvf gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2 -C /opt rm -r gcc-arm-none-eabi-8-2018-q4-major-linux.tar.bz2

Now add the directory with the cross-compiler and other available tools such as assembler and linker to the PATH by appending the following line to the ~/.bashrc file and running the bashrc script immediately after

echo "export PATH=\$PATH:/opt/gcc-arm-none-eabi-8-2018-q4-major/bin/" >> ~/.bashrc source ~/.bashrc

The toolchain is added to the PATH every time you open the terminal. You can test that by typing arm-none- eabi – and pressing double tab to expand. If you see all available tools such as on the screenshot below then you successfully have the toolchain installed.

ST-Link V2

ST-Link is the utility that allows you to reflash the chip or debug your software through the USB interface. The Windows version of the utility is provided by the STMicroelectronics, but luckily there is an open-source version of that available for Linux.

Let’s make sure you have some prerequisite software installed before downloading st-link

sudo apt-get install libusb-1.0-0-dev sudo apt-get install git sudo apt-get install make

Now, let’s clone the repository with the st-link and build it

git clone https://github.com/texane/stlink.git cd stlink make

If you see a bunch of compilation messages and no errors then hopefully the whole process succeeded and you can now use st-link utilities!

sudo mkdir /opt/stlink sudo cp build/Release/st-* /opt/stlink/ sudo cp build/Release/src/gdbserver/st-util /opt/stlink/

To make your life a little bit easier, we will move the binaries to the /opt directory and add them to the PATH so you no longer have to search for it.

echo "export PATH=\$PATH:/opt/stlink/" >> ~/.bashrc source ~/.bashrc

Now that you have all the essentials installed, we can move on to the actual cross-compilation and reflashing the chip

Let’s cross-compile Blinky!

The first program you usually write while learning most of the programming languages is the Hello World. It is a bit more difficult to do that on an embedded system as it would involve sending data over a communication channel or displaying the message on some screen. However, a good way of testing the first embedded program is to make an LED turn on, and that is what this paragraph will be all about. As mentioned earlier, in addition to having the actual application program, we will need a linker script for STM32F407xx and the startup assembly code. All the necessary files are provided in the tut1 directory in the git repository provided. You should download my Github repository with all the files before continuing.

git clone https://github.com/woookey/embeddedTutorial.git

Once you clone the repository you should find a few directories – tut1, CMSIS, Linker, and STM32F4xx_HAL_Driver. The source files for this tutorial are included in the tut1 and they use the some code included in the CMSIS, which is provided by STMicroelectronics and defines the peripheral addresses such as GPIOs, USARTs, USB, etc. The STM32F4xx_HAL_Driver (HAL – Hardware Abstraction Layer) library provides the implementation of functions operating on hardware. They will be extensively used in other parts of the tutorial.

The tut1 includes three main files – system_stm32f4xx.c, main.c and startup_stm32f407xx.s. The Makefile is provided to automate building the software and linking together the object files. Although this tutorial is not an introduction to Makefiles, I will briefly explain the basics so it is understood what instructions are performed during the cross-compilation. The provided Makefile has a few variables defined. At the top of the file, you can see a definition of CC (compiler), AS (assembler) and OBJCOPY (tool for converting compilation outcome to other formats such as .axf to .bin). Those variables define the tools we installed from the ARM toolchain, for example, CC is equal to the ARM compiler – arm-none- eabi – gcc . Each of the variables can be invoked by typing $(VAR). Therefore, writing $(CC) main.c will be translated to arm-none- eabi – gcc main.c.

There are also some flags defined specifically for a compiler and a linker – CFLAGS and LFLAGS, respectively. Let’s take a closer look at both flags

CFLAGS = -Wall -g --specs=nosys.specs -mcpu=cortex-m4 -Os CFLAGS += -DSTM32F407xx LFLAGS = -L../Linker -TSTM32F407VGTx_FLASH.ld

The very first flag, – Wall, indicates showing all warning such a declared variable not being used or a wrong variable type being used, which most often happens during casting from one type to another. The second flag, -g, generates debug information used for gdb (debugger). The next flag, –specs=nosys.specs, tells the compiler to use a default library to implement system calls. These system calls are invoked in the startup, for example, you can see it in the startup_stm32f407xx.s file at line 113: bl __libc_init_array. This calls the function __libc_init_array that calls static constructors. Since we do not need that function it will be linked to a default implementation. The next flags, -mcpu=cortex-m4 -Os, specify the processor to be Cortex-M4 and turn the compiler’s optimization level for code size. The last flag, – DSTM32F407xx, defines an STM32F407xx variable, which makes the CMSIS library include headers containing definitions relevant to STM32F407xx microcontrollers. In a similar way, LFLAGS set the path to the location of the linker script with -L../Linker and set the linker script name to STM32F407VGTx_FLASH.ld using a -TSTM32F407VGTx_FLASH. ld flag. The current linker script is targeted for STM32F407VGT but if the microcontroller on your discovery board is STM32F407IE then you need to change it in the Makefile .Also , if the microcontroller you are using does not come from STM32F407xx family then you will have to change the-DSTM32F407xx flag.

There are few operations defined in the Makefile – all (compiles all files and links them, and reflashes the target device automatically), reflash (reflashes the actual microcontroller) and clean (removes temporary files). You can invoke any operation by typing make <target>. Compiling any .c file is performed using the pattern rule – %.o : %.c. Effectively, the pattern rule takes the filename with .o extension and sets the filename with .c extension as the dependency of the .o file. Everytime a pattern rule is called, it runs the command specified in the line below, which in this case is $(CC) $(CFLAGS) $(INC) -c $< -o $@. The two parameters in the command – $< $@, are calling the first dependency (.c file) and the target (.o file). By running an all target, you can compile all the dependencies defined as shown below and link them together with the linker script. I added some references if you want to learn a bit more about Makefiles[1][2][3]



DEPENDENCIES = main.o DEPENDENCIES += startup_stm32f407xx.o DEPENDENCIES += system_stm32f4xx.o

Those three object files are the only necessary code files to cross-compile for STM32F407xx. The main.c file just turns the clock for GPIOD and switches the led on (the green LED or LD4 on discovery board is connected to pin 12 of D port). If you take a look at the startup file – startup_stm32f407xx.s, you will notice the definition of the stack, weak aliases for interrupt handlers and a call to the main routine.

*** startup_stm32f407xx.s *** ... [110] /* Call the clock system intitialization function.*/ [111] bl SystemInit //- defined in system_stm32f4xx.c (clock setup) [112] /* Call static constructors */ [113] bl __libc_init_array [114] /* Call the application's entry point.*/ [115] bl main //- branches to main ... [166] .word FLASH_IRQHandler /* FLASH */ [167] .word RCC_IRQHandler /* RCC */ [168] .word EXTI0_IRQHandler /* EXTI Line0 */ [169] .word EXTI1_IRQHandler /* EXTI Line1 */

Once all the dependencies are compiled, the executable can be generated by the linker. This linker script provides the set of instructions for linker to map each and every function or variables to the correct regions of the processor. You can also notice that it defines the entry point to be the Reset_Handler, defines the stack address, memory sizes and their origins as well as the size of the heap. To understand the linker a bit more I recommend checking some documentation and tutorials [4][5].



*** STM32F407VGTx_FLASH.ld *** ... [32] /* Entry Point */ [33] ENTRY(Reset_Handler) [34] [35] /* Highest address of the user mode stack */ [36] _estack = 0x20020000; /* end of RAM */ [37] /* Generate a link error if heap and stack don't fit into RAM*/ [38] _Min_Heap_Size = 0x200;; /* required amount of heap */ [39] _Min_Stack_Size = 0x400;; /* required amount of stack */ [40] [41] /* Specify the memory areas */ [42] MEMORY [43] { [44] FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K [45] RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K [46] CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K [46] }

Once the linking is performed, the generated output is of an .axf format, which is the ARM’s object file format containing object code and debugging information. You can convert it into a binary file using the arm -none- eabi – objcopy tool. The all target in the included Makefile automatically converts .axf to .bin format after a successful compilation. You can reflash your microcontroller by using an st-flash tool, which was installed earlier. The command format is as following st-flash write <target> <address>. The process of downloading the compiled code to the process’s memory is done by the internal bootloader which communicates with the host device over a serial interface. You can re-flash your discovery board (or any processor you are using) by calling the following command



st-flash write tut1.bin 0x8000000

If the compilation and reflashing succeed, you should have the green LED on a discovery board lit up! In the next part of this tutorial, I will explain how to set up clocks and the System Timer (SysTick) using HAL libraries

References

[1] GNU Make

[2] Make and Makefiles Tutorial

[3] Unix Makefile Tutorial

[4] Beginner’s Guide to Linkers

[5] Linker Scripts