STM32Cube Library – Part 3 Compiling

Okay, I’m finally going to finish this tutorial! This part will cover writing a Makefile to handle compilation and flashing code to the microcontroller.

This is the third part in a multi-part series.

To start off, here is the contents of the Makefile I use to compile my projects. This is somewhat unnecessary with the new release of STM32CubeMX version 4.22.

# Put your STM32F4 library code directory here
SRC_DIR=Src
INC_DIR=Inc

# Binaries will be generated with this name (.elf, .bin, .hex, etc)
PROJ_NAME=My_Project

#microcontroller platform that your project targets (should be lowercase)
TARGET=stm32f4xx
CPU_TARGET=cortex-m4

#user defined CFLAGS
CFLAGS += -Os -Wall -g

# If your main.c is in a directory other than SRC_DIR
MAINFILE=

# Linker file created by StmCubeMX
LINKER_FILE=STM32F407VG_FLASH.ld

# this will grab all .c files in your SRC_DIR directory
USR_SRC := $(SRC_DIR)/*.c

# Normally you shouldn't need to change anything below this line!
################################################################

CC=arm-none-eabi-gcc
AR=arm-none-eabi-ar
OBJCOPY=arm-none-eabi-objcopy

#get the uppercase version of the target
TARGET_UPPER = $(shell echo $(TARGET) | tr [:lower:] [:upper:] | tr X x)

STM_LIB_SRC = Drivers/${TARGET_UPPER}_HAL_Driver
CMSIS_STM32 = Drivers/CMSIS/Device/ST/${TARGET_UPPER}
CMSIS_CORE = Drivers/CMSIS/Include
STM_USB_CORE = Middlewares/ST/STM32_USB_Device_Library/Core
STM_USB_CDC = Middlewares/ST/STM32_USB_Device_Library/Class/CDC

STM_SRC := $(STM_LIB_SRC)/Src/$(TARGET)*.c
STM_SRC += $(STM_USB_CORE)/Src/*.c
STM_SRC += $(STM_USB_CDC)/Src/*.c
STM_SRC += $(CMSIS_STM32)/Source/Templates/system_$(TARGET).c
# Startup file for the microcontroller you are targeting
STARTUP := $(CMSIS_STM32)/Source/Templates/gcc/*.s

CFLAGS += --std=gnu11 --specs=nosys.specs -mthumb -mcpu=$(CPU_TARGET)
CFLAGS += -T${LINKER_FILE}
CFLAGS += -fdata-sections -ffunction-sections -Wl,--gc-sections

# Include files from STM libraries
INCLUDE += -I$(INC_DIR)
INCLUDE += -I$(CMSIS_CORE)
INCLUDE += -I$(CMSIS_STM32)/Include
INCLUDE += -I$(STM_LIB_SRC)/Inc
INCLUDE += -I$(STM_USB_CORE)/Inc
INCLUDE += -I$(STM_USB_CDC)/Inc

STM_SRC_EXP := $(wildcard $(STM_SRC))
STM_OBJ := $(STM_SRC_EXP:.c=.o)

STM_STARTUP_EXP := $(wildcard $(STARTUP))
STM_OBJ += $(STM_STARTUP_EXP:.s=.o)

USR_SRC_EXP := $(wildcard $(USR_SRC))
USR_OBJ := $(USR_SRC_EXP:.c=.o)

#list all targets that don't have files as dependencies
.PHONY: all clean size flash stflash

.c.o:
    $(CC) $(INCLUDE) $(CFLAGS) -c $< -o $@

.s.o:
    $(CC) $(INCLUDE) $(CFLAGS) -c $< -o $@

all: main size

main: $(STM_OBJ) $(USR_OBJ)
    $(CC) $(CFLAGS) $(INCLUDE) $(MAINFILE) $(USR_OBJ) $(STM_OBJ) -o $(PROJ_NAME).elf
    $(OBJCOPY) -O binary $(PROJ_NAME).elf $(PROJ_NAME).bin
 
clean:
    rm -f *.o $(USR_OBJ) $(STM_OBJ)  $(PROJ_NAME)*.elf $(PROJ_NAME)*.bin

size: 
    arm-none-eabi-size $(PROJ_NAME)*.elf

# Flash the STM32
flash: main
    dfu-util -d 0483:df11 -c 1 -i 0 -a 0 -s 0x08000000:leave -D $(PROJ_NAME).bin

stflash: main
    st-flash write $(PROJ_NAME).bin 0x08000000

If you just try and run this right out of the gate you will probably get an error about not having a % in the target pattern or some such nonsense. This is because WordPress substitutes spaces for tabs. To fix this you will have to go through all the indents and replace the four spaces with a tab. There are not too many of these so it shouldn’t be an awful lot of work.

The next issue that we run into is that the HAL library needs us to define what microcontroller we are using. There are several ways we can handle this. The first, less elegant way is to simply uncomment a line in the stm32fxxx file in the CMSIS/Device/../Include directory

/* Uncomment the line below according to the target STM32 device used in your 
   application  
  */ 
#if !defined (STM32F405xx) && !defined (STM32F415xx) && !defined (STM32F407xx) && !defined (STM32F417xx) && \ 
    !defined (STM32F427xx) && !defined (STM32F437xx) && !defined (STM32F429xx) && !defined (STM32F439xx) && \ 
    !defined (STM32F401xC) && !defined (STM32F401xE) && !defined (STM32F410Tx) && !defined (STM32F410Cx) && \ 
    !defined (STM32F410Rx) && !defined (STM32F411xE) && !defined (STM32F446xx) && !defined (STM32F469xx) && \ 
    !defined (STM32F479xx) 
  /* #define STM32F405xx */   /*!< STM32F405RG, STM32F405VG and STM32F405ZG Devices */ 
  /* #define STM32F415xx */   /*!< STM32F415RG, STM32F415VG and STM32F415ZG Devices */ 
  /* #define STM32F407xx */   /*!< STM32F407VG, STM32F407VE, STM32F407ZG, STM32F407ZE, STM32F407IG  and STM32F407IE Devices */ 
  /* #define STM32F417xx */   /*!< STM32F417VG, STM32F417VE, STM32F417ZG, STM32F417ZE, STM32F417IG and STM32F417IE Devices */ 
  /* #define STM32F427xx */   /*!< STM32F427VG, STM32F427VI, STM32F427ZG, STM32F427ZI, STM32F427IG and STM32F427II Devices */ 
  /* #define STM32F437xx */   /*!< STM32F437VG, STM32F437VI, STM32F437ZG, STM32F437ZI, STM32F437IG and STM32F437II Devices */ 
  /* #define STM32F429xx */   /*!< STM32F429VG, STM32F429VI, STM32F429ZG, STM32F429ZI, STM32F429BG, STM32F429BI, STM32F429NG,  
                                   STM32F439NI, STM32F429IG  and STM32F429II Devices */ 
  /* #define STM32F439xx */   /*!< STM32F439VG, STM32F439VI, STM32F439ZG, STM32F439ZI, STM32F439BG, STM32F439BI, STM32F439NG,  
                                   STM32F439NI, STM32F439IG and STM32F439II Devices */ 
  /* #define STM32F401xC */   /*!< STM32F401CB, STM32F401CC, STM32F401RB, STM32F401RC, STM32F401VB and STM32F401VC Devices */ 
  /* #define STM32F401xE */   /*!< STM32F401CD, STM32F401RD, STM32F401VD, STM32F401CE, STM32F401RE and STM32F401VE Devices */ 
  /* #define STM32F410Tx */   /*!< STM32F410T8 and STM32F410TB Devices */ 
  /* #define STM32F410Cx */   /*!< STM32F410C8 and STM32F410CB Devices */ 
  /* #define STM32F410Rx */   /*!< STM32F410R8 and STM32F410RB Devices */ 
  /* #define STM32F411xE */   /*!< STM32F411CC, STM32F411RC, STM32F411VC, STM32F411CE, STM32F411RE and STM32F411VE Devices */ 
  /* #define STM32F446xx */   /*!< STM32F446MC, STM32F446ME, STM32F446RC, STM32F446RE, STM32F446VC, STM32F446VE, STM32F446ZC,  
                                   and STM32F446ZE Devices */ 
  /* #define STM32F469xx */   /*!< STM32F469AI, STM32F469II, STM32F469BI, STM32F469NI, STM32F469AG, STM32F469IG, STM32F469BG,  
                                   STM32F469NG, STM32F469AE, STM32F469IE, STM32F469BE and STM32F469NE Devices */ 
  /* #define STM32F479xx */   /*!< STM32F479AI, STM32F479II, STM32F479BI, STM32F479NI, STM32F479AG, STM32F479IG, STM32F479BG  
                                   and STM32F479NG Devices */ 
#endif 
    
/*  Tip: To avoid modifying this file each time you need to switch between these 
        devices, you can define the device in your toolchain compiler preprocessor. 
  */

Alternatively the more elegant solution is that we can put a -D option in the CFLAGS of the makefile

CFLAGS += -Os -Wall -g -DSTM32F407xx

Take note that just putting a #define in the main.c file before anything else will not work because the file that throws the error message is processed first regardless.

To use the makefile to compile the source code run

make

in bash in the same directory as the Makefile.

To flash the code onto a microcontroller run either

make flash

or

make stflash

The former command uses the usb bootloader on most of the stm32 microcontrollers that support usb. The latter command is for using the stlink (the programmer/debugger on the stm32f4Discovery board). If you are using the discovery board, this is the option you want to use.

Now that the use of the Makefile has been covered lets go more in depth about what it is doing.

The first part is a list of constants that you might want to change when changing projects

# Put your STM32F4 library code directory here
SRC_DIR=Src
INC_DIR=Inc

# Binaries will be generated with this name (.elf, .bin, .hex, etc)
PROJ_NAME=My_Project

#microcontroller platform that your project targets
TARGET=stm32f4xx
CPU_TARGET=cortex-m4

#user defined CFLAGS
CFLAGS += -Os -Wall -g -DSTM32F407xx

# If your main.c is in a directory other than SRC_DIR
MAINFILE=

# Linker file created by StmCubeMX
LINKER_FILE=STM32F407VG_FLASH.ld

# this will grab all .c files in your SRC_DIR directory
USR_SRC := $(SRC_DIR)/*.c

The linker file sets up the memory layout of the microcontroller (what addresses are RAM, what areas are Flash, etc)

The last line is in this section if you don’t want all of the things in the $(SRC_DIR) directory compiled.

This next section are for constants that are necessary for compiling the Stm HAL library, in some cases they are based on user constants.

CC=arm-none-eabi-gcc
AR=arm-none-eabi-ar
OBJCOPY=arm-none-eabi-objcopy

#get the uppercase version of the target
TARGET_UPPER = $(shell echo $(TARGET) | tr [:lower:] [:upper:] \
| tr X x)

STM_LIB_SRC = Drivers/${TARGET_UPPER}_HAL_Driver
CMSIS_STM32 = Drivers/CMSIS/Device/ST/${TARGET_UPPER}
CMSIS_CORE = Drivers/CMSIS/Include
STM_USB_CORE = Middlewares/ST/STM32_USB_Device_Library/Core
STM_USB_CDC = Middlewares/ST/STM32_USB_Device_Library/Class/CDC

STM_SRC := $(STM_LIB_SRC)/Src/$(TARGET)*.c
STM_SRC += $(STM_USB_CORE)/Src/*.c
STM_SRC += $(STM_USB_CDC)/Src/*.c
STM_SRC += $(CMSIS_STM32)/Source/Templates/system_$(TARGET).c
# Startup file for the microcontroller you are targeting
STARTUP := $(CMSIS_STM32)/Source/Templates/gcc/*.s

Other than the confusing paths that ST picked for their code, this is fairly straightforward as well. The one mysterious piece of this segment is this line

#get the uppercase version of the target
TARGET_UPPER = $(shell echo $(TARGET) | tr [:lower:] [:upper:] \
| tr X x)

This invokes the shell to run the tr program with $(TARGET) (the contents of the target variable) as its input. It converts the lowercase characters in the input and outputs an uppercase version of it. This output is piped to another invocation of this program that changes the x’s back to lowercase. All this chicanery is done to convert the change the $(TARGET) environment variable into a format that can be directly injected into the paths for the HAL library.

The next section sets up flags for the compiler and various include paths for the headers in the HAL library.

CFLAGS += --std=gnu11 --specs=nosys.specs -mthumb -mcpu=$(CPU_TARGET)
CFLAGS += -T${LINKER_FILE}
CFLAGS += -fdata-sections -ffunction-sections -Wl,--gc-sections

# Include files from STM libraries
INCLUDE += -I$(INC_DIR)
INCLUDE += -I$(CMSIS_CORE)
INCLUDE += -I$(CMSIS_STM32)/Include
INCLUDE += -I$(STM_LIB_SRC)/Inc
INCLUDE += -I$(STM_USB_CORE)/Inc
INCLUDE += -I$(STM_USB_CDC)/Inc

This expands any wildcard characters in the path variables and changes the .c files into .o files and puts them into a variable.

STM_SRC_EXP := $(wildcard $(STM_SRC))
STM_OBJ := $(STM_SRC_EXP:.c=.o)

STM_STARTUP_EXP := $(wildcard $(STARTUP))
STM_OBJ += $(STM_STARTUP_EXP:.s=.o)

USR_SRC_EXP := $(wildcard $(USR_SRC))
USR_OBJ := $(USR_SRC_EXP:.c=.o

Finally we get to the meat of the Makefile, where the actual compiling is done.

#list all targets that don't have files as dependencies
.PHONY: all clean size flash stflash

#how to turn a .c file into a .o file
.c.o:
    $(CC) $(INCLUDE) $(CFLAGS) -c $< -o $@

#how to turn a .s file into a .o file
.s.o:
    $(CC) $(INCLUDE) $(CFLAGS) -c $< -o $@

# make all runs make main and make size
all: main size

main: $(STM_OBJ) $(USR_OBJ)
    $(CC) $(CFLAGS) $(INCLUDE) $(MAINFILE) $(USR_OBJ) $(STM_OBJ) -o $(PROJ_NAME).elf
    $(OBJCOPY) -O binary $(PROJ_NAME).elf $(PROJ_NAME).bin
 
clean:
    rm -f *.o $(USR_OBJ) $(STM_OBJ) $(PROJ_NAME)*.elf $(PROJ_NAME)*.bin

size: main
    arm-none-eabi-size $(PROJ_NAME)*.elf

# Flash the STM32
flash: main
    dfu-util -d 0483:df11 -c 1 -i 0 -a 0 -s 0x08000000:leave -D $(PROJ_NAME).bin

stflash: main
    st-flash write $(PROJ_NAME).bin 0x08000000

The .PHONY line tells the makefile that the listed commands do not use filenames as their input variable, but rather other commands, it doesn’t change anything unless one of the commands is the same as a filename.

.c.o and .s.o lines tell Make how to change a .c or .s file into a .o file.

the main: line takes a list of .o files we need to complete the rule, this rule takes all the individual .o files into a single binary. Because we defined rules to turn .c and .s files into .o files earlier, Make will look for .c and .s files that are the same (except for their file extension) to the files that are needed then use the rules to turn them into .o files.

Lastly the flash and stflash commands will program a microcontroller connected to the computer. The use of these commands was covered earlier in the series.

Well, that pretty much wraps this up. I have finally finished this tutorial series, and its only taken a year!

Soli Deo Gloria

 

Advertisements

One thought on “STM32Cube Library – Part 3 Compiling

  1. Pingback: STM32Cube library – Part 1 Toolchain – Nebk Electronics

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s