вторник, 25 октября 2016 г.

Makefile для сборки проектов на STM32, v2

В процессе использования Makefile, описанного в данной заметке, стали видны некоторые неудобства в его использовании. А именно: вынесение некоторых переменных в отдельные файлы создаёт проблемы при переносе и клонировании проекта, необходимость явного указания как файлов исходного кода, так и получаемых из них объектных файлов создаёт обширное поле для ошибок, также явное задание правил компиляции каждого файла добавляет неразберихи и, опять же, возможности для ошибок, в проект.
Хотелось, чтобы после задания произвольного количества файлов исходного кода их компиляция в объектные файлы происходила без явного создания правил для каждого из них. Также желательно не использовать сторонние средства (внешние скриптовые языки, Perl, Python и проч.), а ограничиться возможностями языка Makefile.
В результате получился следующий файл:

# Выбор типа ядра контроллера
MCU = cortex-m4
# Адрес начала внутренней FLASH-памяти контроллера
LOADADDRESS = 0x08000000
# Использовать драйверы периферийных устройств, предоставленные ST.
# Компиляция для контроллеров серии STM32F373x
DEFINES = -DUSE_STDPERIPH_DRIVER -DSTM32F373xC

# Путь к каталогу драйверов периферии контроллера и вспомогательных библиотек
BASEDIRNAME = /c/STM32Cube/Repository/STM32Cube_FW_L1_V1.6.0/
# Скрипт линковщика, специфичный для контроллера
# Возможно, придётся забрать его в каталог проекта,
# и внести изменения (размер стека, объём внутренней
# FLASH- и RAM-памяти)
LDSCRIPT = $(BASEDIRNAME)Drivers/CMSIS/Device/ST/STM32F3xx/Source/Templates/gcc/linker/STM32F373XC_FLASH.ld
# Путь к каталогу кросскомпилятора
GCCDIRNAME = /c/gcc-arm-none-eabi-5_3-2016q1-20160330-win32/bin/
# Префикс кросскомпилятора
PREFIX = arm-none-eabi-

# Файл конфигурации OpenOCD для программирования заданного контроллера
OPENOCDBOARD = stm32f3discovery.cfg
# Путь к каталогу OpenOCD
PROGRAMMERDIR = /c/openocd-0.9.0/
# Путь к исполняемому файлу OpenOCD, для разных платформ у него разные имена
PROGRAMMERBIN = $(PROGRAMMERDIR)bin-x64/openocd.exe
# Если программируем самодельную плату, то используется один файл конфигурации
# Для демонстрационной платы -- другой
ifdef OPENOCDINTERFACE
 OPENOCDCONFIG = -f $(PROGRAMMERDIR)scripts/interface/$(OPENOCDINTERFACE)
else ifdef OPENOCDBOARD
 OPENOCDCONFIG = -f $(PROGRAMMERDIR)scripts/board/$(OPENOCDBOARD)
else
 $(error "Not defined OPENOCDINTERFACE or OPENOCDBOARD")
endif
# Команды OpenOCD для загрузки файла в контроллер
OPENOCDLOAD = "program result.bin $(LOADADDRESS) verify"
OPENOCDPROGRAM =\
$(OPENOCDCONFIG)\
-c "init"\
-c "halt"\
-c $(OPENOCDLOAD)\
-c "reset run"\
-c "shutdown"
## To unlock chip use next commands after "halt":
# -c "flash protect 0 0 last off"\
# -c "shutdown"

# Для компиляции всех типов файлов и линковки используем gcc.exe
# Он сам разберётся, что вызывать для переданного файла
AS = $(GCCDIRNAME)$(PREFIX)gcc
CC = $(GCCDIRNAME)$(PREFIX)gcc
CXX = $(GCCDIRNAME)$(PREFIX)gcc
LD = $(GCCDIRNAME)$(PREFIX)gcc
# Получение листинга из объектного файла
OBJDUMP = $(GCCDIRNAME)$(PREFIX)objdump
# Преобразование между форматами бинарных файлов
OBJCOPY = $(GCCDIRNAME)$(PREFIX)objcopy

OFLAGS = -Os
CXXSTANDARD = -std=c++11
CSTANDARD = -std=c11

# Каталоги, в которых будут искаться заголовочные файлы
INCLUDES = $(shell pwd)/
INCLUDES += $(BASEDIRNAME)Drivers/CMSIS/Include/
INCLUDES += $(BASEDIRNAME)Drivers/CMSIS/Device/ST/STM32F3xx/Include/
INCLUDES += $(BASEDIRNAME)Drivers/STM32F3xx_HAL_Driver/Inc/

# Флаги, общие для почти всех команд сборки проекта
COMMONFLAGS = -mcpu=$(MCU) -mthumb -MD $(INCLUDES:%=-I%) -c

# Флаги, специфичные для компиляции ассемблерных файлов
ASFLAGS += $(COMMONFLAGS)
# Любые файлы, переданные в переменную ASFILES, трактовать, как ассемблерные
ASFLAGS += -x assembler-with-cpp

# Флаги, специфичные для компиляции файлов C
CFLAGS += $(COMMONFLAGS)
CFLAGS += $(CSTANDARD)
CFLAGS += $(OFLAGS)
CFLAGS += $(DEFINES)
CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
CFLAGS += -Wall
CFLAGS += -fno-exceptions -ffunction-sections -fdata-sections -Wextra -Winline -Wpointer-arith -Wredundant-decls -Wshadow -Wcast-qual -Wcast-align -pedantic
CFLAGS += -ggdb3
# Любые файлы, переданные в переменную CFILES, трактовать как файлы C
CFLAGS += -x c

# Флаги, специфичные для файлов C++
CXXFLAGS += $(COMMONFLAGS)
CXXFLAGS += $(CXXSTANDARD)
CXXFLAGS += $(OFLAGS)
CXXFLAGS += $(DEFINES)
CXXFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
CXXFLAGS += -Wall
CXXFLAGS += -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -Wextra -Winline -Wpointer-arith -Wredundant-decls -Wshadow -Wcast-qual -Wcast-align -pedantic
CXXFLAGS += -ggdb3
# Любые файлы, переданные в переменную CXXFILES, трактовать как файлы C++
CXXFLAGS += -x c++

# Флаги, специфичные для линкера
LDFLAGS += -Wl,-Map=$(TARGET).map -Wl,--cref
LDFLAGS += -Wl,--start-group -lc_nano -lrdimon_nano -lm -Wl,--end-group
LDFLAGS += -Wl,--gc-sections
# Из переменной COMMONFLAGS забираем элементы с первого по второй
LDFLAGS += $(wordlist 1, 2, $(COMMONFLAGS))

OBJCOPYFLAGS = -O binary
OBJDUMPFLAGS += -x -S

# Объектные и промежуточные файлы сохраняются в этом каталоге, чтобы не засорять проект
OBJDIR = ./obj/
-include $(shell mkdir $(OBJDIR) 2> /dev/null) $(wildcard $(OBJDIR)\*)

# Из каталога с данным Makefile разбираем ассемблерные файлы,
# файлы C и C++ по соответствующим переменным
ASFILES = $(wildcard *.s *.S *.asm)
CFILES = $(wildcard *.c)
CXXFILES = $(wildcard *.cpp)
# Добавляем файлы в проект
ASFILES += $(BASEDIRNAME)Drivers/CMSIS/Device/ST/STM32F3xx/Source/Templates/gcc/startup_stm32f373xc.s
CFILES += $(BASEDIRNAME)Drivers/CMSIS/Device/ST/STM32F3xx/Source/Templates/system_stm32f3xx.c
CXXFILES +=
# Объектные файлы будут с именем соответствующих файлов исходников,
# с добавлением расширения .o
# Таким образом, на сей момент недопустимо наличие в проекте
# файлов с одинаковыми именами для каждого из типов (asm, C, C++).
# В дальнейшем надеюсь это поправить
ASOBJFILES = $(patsubst %,$(OBJDIR)%,$(notdir $(ASFILES:%=%.o)))
COBJFILES = $(patsubst %,$(OBJDIR)%,$(notdir $(CFILES:%=%.o)))
CXXOBJFILES = $(patsubst %,$(OBJDIR)%,$(notdir $(CXXFILES:%=%.o)))

.PHONY : clean all program

# Из элементов переменной *FILES для компиляции выбирается файл,
# имя и расширение которого совпадают с одним из элементов
# переменной *OBJFILES с отброшенным расширением .o
# Если в переменной *FILES окажутся файлы с одинаковыми именами
# и расширениями, то компиляция пройдёт некорректно.
$(ASOBJFILES) : $(ASFILES)
 @echo "-> compiling $@ as ASM-file"
 $(AS) $(ASFLAGS) $(filter %$(notdir $(@:%.o=%)),$^) -o $@

$(COBJFILES) : $(CFILES)
 @echo "-> compiling $@ as C-file"
 $(CC) $(CFLAGS) $(filter %$(notdir $(@:%.o=%)),$^) -o $@
 $(OBJDUMP) $(OBJDUMPFLAGS) $@ > $(@:%.o=%.lst)

$(CXXOBJFILES) : $(CXXFILES)
 @echo "-> compiling $@ as C++-file"
 $(CXX) $(CXXFLAGS) $(filter %$(notdir $(@:%.o=%)),$^) -o $@
 $(OBJDUMP) $(OBJDUMPFLAGS) $@ > $(@:%.o=%.lst)

# Сборка axf-файла из полученных объектных файлов
result.axf : $(ASOBJFILES) $(COBJFILES) $(CXXOBJFILES)
 @echo "-> linking"
 $(LD) -T$(LDSCRIPT) $^ $(LDFLAGS) -o $@

# Преобразование axf-файла в бинарный, и получение ассемблерного листинга
all : result.axf
 @echo "-> copying"
 $(OBJCOPY) $(OBJCOPYFLAGS) $^ result.bin
 $(OBJDUMP) $(OBJDUMPFLAGS) $^ > result.lst

# Заливка бинарного файла в контроллер
program :
 @echo "-> load firmware to controller"
 $(PROGRAMMERBIN) $(OPENOCDPROGRAM)

# Очистка проекта
clean :
 $(RM) -f *.axf *.map *.lst $(OBJDIR)*.d *.bin $(OBJDIR)*.o $(OBJDIR)*.lst


В дальнейшем планирую избавиться от требования уникальности имён файлов, и добавить возможность переопределения флагов компиляции для каждого файла.