1.1 GNU Arm Embedded Toolchain

Arm maintains a GCC cross-compiler available as binaries that run on Linux and Windows.
It is available on their arm developer site.

I want to develop for AArch32 bare-metal target (arm-none-eabi).

Installation on Windows

So far there are only win32 versions available for download either as .exe installers or .zip archives. File naming convention helps to identify the type of release (preview, major or update).

By default each release installs to its own directory instead of upgrading the previous one. This way several releases can coexist and you can select which one you use for a specific project. One downside to this is that the directory and filename convention is heavy. For practical use, you need to configure an IDE or encapsulate those paths and names in Makefile variables.

### Build environment selection

GCCDIR = "D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi"

BINPFX  = $(GCCDIR)/bin/arm-none-eabi-
CC      = $(BINPFX)gcc

### Build rules

.PHONY: version

version:
    $(CC) --version

$ make
"D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-m
ingw-w64-i686-arm-none-eabi"/bin/arm-none-eabi-gcc --version
arm-none-eabi-gcc.exe (Arm GNU Toolchain 13.3.Rel1 (Build arm-13.24)) 13.3.1 202
40614
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Installation on Linux

Installation on Linux means downloading the Linux x86_64 tarball and extracting it. I use the ~/Packages folder for this type of distribution. This means that the Makefile on Linux will be the same as the Windows one except for the value of the GCCDIR variable.
GCCDIR = $(HOME)/Packages/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi
By selecting the path based on the development environment, there is no need to make changes while switching between OS. Gmake has the built-in variable MAKE_HOST that can be tested for this.
### Build environment selection

ifeq (linux, $(findstring linux, $(MAKE_HOST)))
 GCCDIR = $(HOME)/Packages/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi
else
 GCCDIR = "D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi"
endif

BINPFX  = $(GCCDIR)/bin/arm-none-eabi-
CC      = $(BINPFX)gcc

### Build rules

.PHONY: version

version:
    $(CC) --version

I use the path prefix $(HOME)/Packages instead of ~/Packages when defining GCCDIR as some sub-processes called by gmake may have issues with ~ expansion (in this case ld). This way gmake will handle the expansion before calling the sub-processes.

Toolchain’s chain of events

In order to generate a file that can be loaded in the micro-controller, I need to sketch the chain of events that will take place.
  1. Compile source codes (.c) to object modules (.o)
  2. Link all the object modules (.o) together into an executable (.elf)
  3. Convert the executable (.elf) into a format suitable for loading or flashing (.bin or .hex).

1. Compile

Gmake has default rules to built .o files out of .c files. As I have already defined with CC the command to compile, I can make a simple test of this step by creating an empty .c file and checking what happens when I try to compile it.
$ touch empty.c

$ make empty.o
"D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-m
ingw-w64-i686-arm-none-eabi"/bin/arm-none-eabi-gcc    -c -o empty.o empty.c
Compilation is succesful and empty.o file is generated.

2. Link

To link the object module generated in the first step, I need to

There are sample link scripts that come with the toolchain, they are located in the subfolder share/gcc-arm-none-eabi/samples/ldscripts. For now I can use the simplest script: mem.ld.

### Build environment selection

ifeq (linux, $(findstring linux, $(MAKE_HOST)))
 GCCDIR = $(HOME)/Packages/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi
else
 GCCDIR = "D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi"
endif

BINPFX  = $(GCCDIR)/bin/arm-none-eabi-
CC      = $(BINPFX)gcc
LD      = $(BINPFX)ld

LD_SCRIPT = $(GCCDIR)/share/gcc-arm-none-eabi/samples/ldscripts/mem.ld

### Build rules

%.elf: %.o
    $(LD) -T$(LD_SCRIPT) -o $@ $<

$ make empty.elf
"D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-m
ingw-w64-i686-arm-none-eabi"/bin/arm-none-eabi-ld -T"D:/Program Files (x86)/GNU 
Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi"
/share/gcc-arm-none-eabi/samples/ldscripts/mem.ld -o empty.elf empty.o
Link terminates successfully and creates empty.elf.

3. Convert

Finally, I use the command objcopy to convert the executable .elf file into binary or intel hex format suitable to load in the micro-controller.
### Build environment selection

ifeq (linux, $(findstring linux, $(MAKE_HOST)))
 GCCDIR = $(HOME)/Packages/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi
else
 GCCDIR = "D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi"
endif

BINPFX  = $(GCCDIR)/bin/arm-none-eabi-
CC      = $(BINPFX)gcc
LD      = $(BINPFX)ld
OBJCOPY = $(BINPFX)objcopy

LD_SCRIPT = $(GCCDIR)/share/gcc-arm-none-eabi/samples/ldscripts/mem.ld

### Build rules

%.elf: %.o
    $(LD) -T$(LD_SCRIPT) -o $@ $<

%.bin: %.elf
    $(OBJCOPY) -O binary $< $@

%.hex: %.elf
    $(OBJCOPY) -O ihex $< $@

Now, if I start in a directory that contains only this Makefile and an empty empty.c file, I can successfully build.
$ make empty.bin empty.hex
"D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-m
ingw-w64-i686-arm-none-eabi"/bin/arm-none-eabi-gcc    -c -o empty.o empty.c
"D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-m
ingw-w64-i686-arm-none-eabi"/bin/arm-none-eabi-ld -T"D:/Program Files (x86)/GNU 
Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi"
/share/gcc-arm-none-eabi/samples/ldscripts/mem.ld -o empty.elf empty.o
"D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-m
ingw-w64-i686-arm-none-eabi"/bin/arm-none-eabi-objcopy -O binary empty.elf empty
.bin
"D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-m
ingw-w64-i686-arm-none-eabi"/bin/arm-none-eabi-objcopy -O ihex empty.elf empty.h
ex
rm empty.o empty.elf
Notice that gmake automatically removes the intermediary .o and .elf files on completion.

The generated empty.bin is empty.

Cleaning up

I want to keep the output of the build easy to understand without the clutter of the long command names. Also I need a way to clean the working directory back to its initial state.
### Build environment selection

ifeq (linux, $(findstring linux, $(MAKE_HOST)))
 GCCDIR = $(HOME)/Packages/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi
else
 GCCDIR = "D:/Program Files (x86)/GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi"
endif

BINPFX  = @$(GCCDIR)/bin/arm-none-eabi-
CC      = $(BINPFX)gcc
LD      = $(BINPFX)ld
OBJCOPY = $(BINPFX)objcopy

LD_SCRIPT = $(GCCDIR)/share/gcc-arm-none-eabi/samples/ldscripts/mem.ld

### Build rules

.PHONY: clean

clean:
    @echo CLEAN
    @rm -f *.o *.elf *.bin *.hex

%.elf: %.o
    @echo $@
    $(LD) -T$(LD_SCRIPT) -o $@ $<

%.bin: %.elf
    @echo $@
    $(OBJCOPY) -O binary $< $@

%.hex: %.elf
    @echo $@
    $(OBJCOPY) -O ihex $< $@

$ make clean
CLEAN

$ make empty.bin empty.hex
empty.elf
empty.bin
empty.hex
rm empty.o empty.elf

Checkpoint

At this stage, I have a working toolchain and I am able to build from an empty source file (empty.c) to an empty binary file (empty.bin).

Next, I will select a micro-controller from the STM32 family and generate a binary file that it can execute.


© 2020-2024 Renaud Fivet