3.6 Toolchain Update

When a new release of GNU ARM Embedded Toolchain comes out, I have to do some adaptations when switching to it.

By example, to switch from release 9 update to release 10 major, I made three changes to Makefile.

● Update the Linux base directory location:

#GCCDIR = $(HOME)/Packages/gcc-arm-none-eabi-9-2020-q2-update
GCCDIR = $(HOME)/Packages/gcc-arm-none-eabi-10-2020-q4-major
● Update the Windows base directory location:
#GCCDIR = D:/Program Files (x86)/GNU Arm Embedded Toolchain/9 2020-q2-update
GCCDIR = D:/Program Files (x86)/GNU Arm Embedded Toolchain/10 2020-q4-major
● Update the library subdirectory location:
#LIBDIR  = $(GCCDIR)/lib/gcc/arm-none-eabi/9.3.1/thumb/v6-m/nofp
LIBDIR  = $(GCCDIR)/lib/gcc/arm-none-eabi/10.2.1/thumb/v6-m/nofp
In the case of release 10 major, unfortunately while doing some regression testing by recompiling the projects so far, I found that the new release optimizes further the C startup clearing of BSS data by calling memset() from the distribution libraries.
$ make
f030f4.elf from startup.txeie.o adc.o adcmain.o
D:\Program Files (x86)\GNU Arm Embedded Toolchain\10 2020-q4-major\bin\arm-none-
eabi-ld.exe: startup.txeie.o: in function `Reset_Handler':
D:\Renau\Documents\Projects\stm32bringup\docs/startup.txeie.c:126: undefined ref
erence to `memset'
make: *** [mk8:61: f030f4.elf] Error 1
So I had to add libc.a and its location on top of libgcc.a to the list of libraries.
LIBS = -l$(LIBSTEM) -lc -lgcc
LIB_PATHS = -L. -L$(GCCDIR)/arm-none-eabi/lib/thumb/v6-m/nofp -L$(LIBDIR)
with libc.a the link phase complete successfully.
f030f4.elf from startup.txeie.o adc.o adcmain.o
   text    data     bss     dec     hex filename
   2644       0      16    2660     a64 f030f4.elf

As I don’t want to turn off size optimization and I am not willing to always pay the full 180 bytes for a production ready memset() when it is called only once at startup to clear a few bytes, I ended up adding my own version of memset() to my local library.

#include <string.h>

void *memset( void *s, int c, size_t n) {
    char *p = s ;
    while( n--)
        *p++ = c ;

    return s ;
}
LIBOBJS = printf.o putchar.o puts.o memset.o
Link succeed with a reduction of 152 bytes of code.
f030f4.elf from startup.txeie.o adc.o adcmain.o
   text    data     bss     dec     hex filename
   2492       0      16    2508     9cc f030f4.elf

GCC front end handling the libraries selection

As I was investigating the compilation flags to find if there was a better way to solve this issue, I figure out I could let gcc handle the distribution libraries selection and their location based on the CPU type. So I changed the linker invocation accordingly and got rid of LD, LIBDIR and LIB_PATHS definitions.
#   $(LD) -T$(LD_SCRIPT) $(LIB_PATHS) -Map=$(PROJECT).map -cref -o $@ $(OBJS) $(LIBS)
    $(CC) $(CPU) -T$(LD_SCRIPT) -L. -Wl,-Map=$(PROJECT).map,-cref \
        -nostartfiles -o $@ $(OBJS) -l$(LIBSTEM)
As the compiler front end is now controlling the libraries selection it is possible to give it a hint how to select a better optimized memset(). The libc library comes in two flavors: regular and nano.
OBJS = $(SRCS:.c=.o)
LIBOBJS = printf.o putchar.o puts.o # memset.o

CPU = -mthumb -mcpu=cortex-m0 --specs=nano.specs
memset() included in the nano version of libc occupies the same space as my own implementation.
f030f4.elf from startup.txeie.o adc.o adcmain.o
   text    data     bss     dec     hex filename
   2492       0      16    2508     9cc f030f4.elf

PATH based command selection

Finally, I revised the way I specify the commands location by updating the PATH environment variable in the Makefile instead of giving the full path of each command.
On Windows, I make sure that drive specification matches the development environment in use (Cygwin, MSYS2 and other).
### Build environment selection

ifeq (linux, $(findstring linux, $(MAKE_HOST)))
 INSTALLDIR = $(HOME)/Packages
#REVDIR = gcc-arm-none-eabi-10-2020-q4-major
 REVDIR = arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi
else
 DRIVE = d
ifeq (cygwin, $(findstring cygwin, $(MAKE_HOST)))
 OSDRIVE = /cygdrive/$(DRIVE)
else ifeq (msys, $(findstring msys, $(MAKE_HOST)))
 OSDRIVE = /$(DRIVE)
else
 OSDRIVE = $(DRIVE):
endif
 INSTALLDIR = $(OSDRIVE)/Program Files (x86)
#REVDIR = GNU Arm Embedded Toolchain/10 2020-q4-major
 REVDIR = GNU Arm Embedded Toolchain/arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi
endif

GCCDIR = $(INSTALLDIR)/$(REVDIR)
export PATH := $(GCCDIR)/bin:$(PATH)

BINPFX  = @arm-none-eabi-
AR      = $(BINPFX)ar
CC      = $(BINPFX)gcc
OBJCOPY = $(BINPFX)objcopy
OBJDUMP = $(BINPFX)objdump
SIZE    = $(BINPFX)size
Switching back to latest version of the toolchain at the time of writing this, the link shows further improvement of the code size. The optimization via memset() has been skipped by the compiler.
f030f4.elf from startup.txeie.o adc.o adcmain.o
   text    data     bss     dec     hex filename
   2464       0      16    2480     9b0 f030f4.elf

Checkpoint

Invoking the compiler instead of the linker gives more flexibility in case the toolchain directory structure changes or if I target a different core. The compiler is aware of the location of the toolchain libraries while the linker need explicit parameters to handle those changes.

Next, I will (re)build to execute code in RAM instead of FLASH.


© 2020-2024 Renaud Fivet