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.
Compilation
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 -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
Installing tools
To cross-compile for Cortex-M4 family of microcontrollers, which are placed on discovery boards, you will need the
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
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-
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_cross_compilation 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_cross_compilation, CMSIS, Linker, STM32F4xx_HAL_Driver, etc. The source files for this tutorial
Tutorial 1 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-
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, –
There are few operations defined in the Makefile – all (compiles all files and links
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
*** 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
st-flash write tut1.bin 0x8000000
OR
make reflash
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
[4] Beginner’s Guide to Linkers
[5] Linker Scripts