Make build system

6. Build system & version control setup

Since we're getting closer to the point of 'platform independent' code, I thought it's time to setup a git repository for the car dashboard project before it gets out of hand. This required rewriting the Makefile and restructuring directories to accommodate BL2 and a simple operating system. BL2 will be the bootloader that is responsible for loading the OS. In a more productive setup, I would not be writing Makefiles by hand, and will probably use Autotools with nested Makefiles for recursive building, but I promised in the introductory post of this series that I will only use make for building the whole project, besides, I believe knowing your way around Makefiles is an essential skill that would come in handy when your make based build system starts acting up.

Project Structure

The directories are intuitively named, but you have to understand that the central piece of the software stack is BL1, the following command shows the directories structure:

korena@korena-solid:~/Development/Projects/Embedded/tiny210$ ls -R
asm  bl2  host_bin  host_scripts  host_tools  include  Makefile  src  target_bin
imageMaker.c  readFileBytes.c

The top level source directories belong to BL1, because it is the primary focus here. A sub directory was created for BL2, and another will be created for the operating system when we come to the OS part of the project, the reason why we treat BL2 and the OS as sub parts of the project is because we plan to use u-boot as bootloader, which will replace BL2, and Linux as a substitute for the simple OS we'll build, the only part that will be kept from this project setup is BL1.
Finally, a Git repository was created for version control, you can clone the project from my public github account.


Only a single top level Makefile is used here, which is not what you'd want to use in a similar real life project, because of the fact that this project encompasses three separate modules, and it would be wiser to have a Makefile for each sub module (BL1, BL2 and OS), preferably using an auto build system. The reason I went with a single Makefile is the fact that maintaining Makefiles manually is something I dread.


# for home
#CROSS_COMPILE= /home/korena/Development/Compilers/gcc-arm-none-eabi-4_7-2013q1/bin/arm-none-eabi-
# for work
CROSS_COMPILE =arm-none-eabi-
AS        = $(CROSS_COMPILE)as
LD        = $(CROSS_COMPILE)ld
CC        = $(CROSS_COMPILE)gcc
AR        = $(CROSS_COMPILE)ar
NM        = $(CROSS_COMPILE)nm
HEX  = $(OBJCOPY) -O ihex
BIN  = $(OBJCOPY) -O binary -S
PRJ_ROOT_DIR := $(shell pwd)
PROJECT_HOME := $(shell pwd)
# add include directories here ...
##### bootloader related (second stage) #####
BL2_INC_DIR = $(BL2_ROOT_DIR)/include
# Target Name
PROJECT_TYPE := embedded_app
# Version Number
MAJOR := 1
MINOR := 00

IMGMAKE  =$(HOST_BIN_DIR)/imageMaker
BL1_LDS   =
BL2_LDS   = $(BL2_ROOT_DIR)/
#BL1 source files expressions ... 
BL1_SRCs_ASM += $(wildcard $(ASM_SRC_DIR)/*.s)
BL1_SRCs_C += $(wildcard $(SRC_DIR)/*.c)
OBJS_BL1 = $(BL1_SRCs_ASM:.s=.o) $(BL1_SRCs_C:.c=.o)
# BL1 include files expression ...
ASM_INCLUDES += $(wildcard $(INCLUDES_DIR)/*.inc)
C_INCLUDES += $(wildcard $(INCLUDES_DIR)/*.h)

#BL2 source files expressions ...
BL2_SRCs_ASM += $(wildcard $(BL2_ASM_DIR)/*.s)
BL2_SRCs_C += $(wildcard $(BL2_C_SRC_DIR)/*.c)
OBJS_BL2 = $(BL2_SRCs_ASM:.s=.o) $(BL2_SRCs_C:.c=.o)
CFLAGS =  -Os -nostdlib
all:  $(IMGMAKE) $(BL1_BIN).boot  $(BL1_BIN) $(BL2_BIN) 
$(BL1_BIN):      $(OBJS_BL1)
      $(LD) -T $(BL1_LDS) -o $(BL1_ELF) -Map $(OBJS_BL1)
      $(OBJCOPY) -O binary $(BL1_ELF) $(BL1_BIN)
$(BL1_BIN).boot: $(BL1_BIN) $(IMGMAKE)
      $(IMGMAKE) ./$(BL1_BIN) ./$(BL1_BIN).boot
$(BL2_BIN):   $(OBJS_BL2)
       $(LD) -T $(BL2_LDS) -o $(BL2_ELF) -Map $(BL2_ROOT_DIR)/ $(OBJS_BL2)
       $(OBJCOPY) -O binary $(BL2_ELF) $(BL2_BIN)
       gcc $(CFLAGS_HOST) -o $(IMGMAKE) $(HOST_TOOLS_DIR)/imageMaker.c
fuse: $(BL1_BIN).boot $(BL2_BIN)
       . $(HOST_SCRIPTS_DIR)/
%.o: %.c
       $(CC) -c $(CFLAGS) -I . $(INCLUDES_DIR)  $< -o $@
%.o: %.s
       $(AS) -c $(AFLAGS) -I  $(ASM_INCLUDES)  $< -o $@
%elf: $(OBJS_BL1)
       $(CC) $(OBJS_BL1) $(LDFLAGS) $(LIBS) -o $@
%hex: %elf
       $(HEX) $< $@
%bin: %elf
       $(BIN)  $< $@
       gcc $(CFLAGS_HOST) -o $(HOST_BIN_DIR)/readBytes $(HOST_TOOLS_DIR)/readFileBytes.c
       rm -rf $(OBJS)  $(HOST_BIN_DIR)/*  $(TARGET_BIN_DIR)/*.bin $(TARGET_BIN_DIR)/*.elf $(TARGET_BIN_DIR)/*.boot $(TARGET_BIN_DIR)/*.o *.map *.o $(ASM_SRC_DIR)/*.o SRC_DIR/*.o $(BL2_ASM_DIR)/*.o $(BL2_ROOT_DIR)/*.map

There are a few things to note about this makefile, it maintains host tools that are compiled to run on the host machine, in addition to the compilation of binaries to run on the target. It also runs a script that is used to fuse binaries into the MMC card.
All intermediate objects that are produced through the compilation process reside in the same directory as their sources, but the final result of target binaries are produced in the target_bin directory for target binaries, and host_bin for host tools. Some Makefile targets are for testing purposes, and are not used in the build process.
Operating system build rules are missing, and will be completed when we need them, some BL2 rules are also missing, and will be completed as we progress through the next couple of posts.
Last but not least, a great deal of attention should be paid to the $(HOST_BIN_DIR)/* bit under clean: target, if you misspell the variable name, or provide a variable name that holds no value, then the simple command make clean will recursively and forcefully remove your root directory and its contents, this is basically death.
Finally, some files related to BL2 are added for testing purposes, they will be modified soon, we are now focusing on building BL1.
Next, we'll go back to programming. We'll initialize the UART subsystem to assist us in finishing the low level initialization of the system.