LV012-Makefile文件分析
这篇笔记主要是STM32CubeMX软件生成的Makefile文件的学习笔记。这篇笔记按照顺序从上到下分析Makefile文件。
一、完整的Makefile文件
##########################################################################################################################
# File automatically-generated by tool: [projectgenerator] version: [3.19.2] date: [Thu Apr 27 16:55:10 CST 2023]
##########################################################################################################################
# ------------------------------------------------
# Generic Makefile (based on gcc)
#
# ChangeLog :
# 2017-02-10 - Several enhancements + project update mode
# 2015-07-22 - first version
# ------------------------------------------------
######################################
# target
######################################
TARGET = makefile_project
######################################
# building variables
######################################
# debug build?
DEBUG = 1
# optimization
OPT = -Og
#######################################
# paths
#######################################
# Build path
BUILD_DIR = build
######################################
# source
######################################
# C sources
C_SOURCES = \
Core/Src/main.c \
Core/Src/stm32f1xx_it.c \
Core/Src/stm32f1xx_hal_msp.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_gpio_ex.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_tim.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_tim_ex.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_rcc.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_rcc_ex.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_gpio.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_dma.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_cortex.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_pwr.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_flash.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_flash_ex.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_exti.c \
Core/Src/system_stm32f1xx.c \
Core/Src/gpio.c
# ASM sources
ASM_SOURCES = \
startup_stm32f103xe.s
#######################################
# binaries
#######################################
PREFIX = arm-none-eabi-
# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)
# either it can be added to the PATH environment variable.
ifdef GCC_PATH
CC = $(GCC_PATH)/$(PREFIX)gcc
AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp
CP = $(GCC_PATH)/$(PREFIX)objcopy
SZ = $(GCC_PATH)/$(PREFIX)size
else
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S
#######################################
# CFLAGS
#######################################
# cpu
CPU = -mcpu=cortex-m3
# fpu
# NONE for Cortex-M0/M0+/M3
# float-abi
# mcu
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)
# macros for gcc
# AS defines
AS_DEFS =
# C defines
C_DEFS = \
-DUSE_HAL_DRIVER \
-DSTM32F103xE
# AS includes
AS_INCLUDES =
# C includes
C_INCLUDES = \
-ICore/Inc \
-IDrivers/STM32F1xx_HAL_Driver/Inc \
-IDrivers/STM32F1xx_HAL_Driver/Inc/Legacy \
-IDrivers/CMSIS/Device/ST/STM32F1xx/Include \
-IDrivers/CMSIS/Include
# compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections
CFLAGS += $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections
ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif
# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"
#######################################
# LDFLAGS
#######################################
# link script
LDSCRIPT = STM32F103ZETx_FLASH.ld
# libraries
LIBS = -lc -lm -lnosys
LIBDIR =
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections
# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
#######################################
# build the application
#######################################
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))
$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)
$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@
$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
$(AS) -c $(CFLAGS) $< -o $@
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
$(SZ) $@
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(HEX) $< $@
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(BIN) $< $@
$(BUILD_DIR):
mkdir $@
#######################################
# clean up
#######################################
clean:
-rm -fR $(BUILD_DIR)
#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)
# *** EOF ***二、各部分说明
1. target
######################################
# target
######################################
TARGET = makefile_project设置目标文件名,实际上就是做了个变量,并且把Makefile这个作为参数传入。平时当成工程名即可,如果要换工程,就自己随便重新取个名字。
2. building variables
######################################
# building variables
######################################
# debug build?
DEBUG = 1
# optimization
OPT = -Og(1)DEBUG:是否进行调试编译,这里表示是,release版关闭调试时将1改成其它数即可,具体原因后面会提到。
(2)优化等级,这里使用Og的目的是因为开启了调试,所以优化时需要产生合理的优化而不和调试选项冲突。如果是release版本,可以使用 -Os (提高速度并优化体积)或 -O3 (大幅提高速度但会增大体积)等命令,关于这部分的介绍可以参考知乎回答。
3. paths
#######################################
# paths
#######################################
# Build path
BUILD_DIR = build编译目标地址,这也是前面提到为什么我会生成一个build文件夹,实际上就是这个变量进行的命名,如果有需要,可以把它修改成任何名字,然后编译出来的所有文件就都会放在这个新命名的文件夹里面了。不过一般不建议修改,因为大家通用的都是build。
4. source
######################################
# source
######################################
# C sources
C_SOURCES = \
Core/Src/main.c \
#中间的省略......
# ASM sources
ASM_SOURCES = \
startup_stm32f103xe.s(1)C_SOURCES:这里放的就是所有用到的 c 文件。如果我们需要自己写个什么库自己写了什么新的 c 文件,而且最后会被编译到程序里面的话,就都需要自己在这里添加一行 c 文件地址。这些地址可以是相对路径,相对的就是makefile文件的地址,也可以设置为绝对路径,例如这里面所有Drivers的文件,如果我们没有配置为复制到工程里面,那么这些文件就会被写为绝对路径并导航到STM32CubeMX的sdk存储地址里面。
(2)ASM_SOURCES:所有汇编文件的地址,添加方法可以继续参考前面C文件的添加方法,也是绝对地址和相对地址添加即可。而这里的start文件,一般不需要用户自己写,这些厂商在开发好芯片后都至少会提供一个startup的c或s文件的,我们需要做的就只是把他添加进来即可(有的可能不是汇编s文件,如果是c文件,正常添加到前面C文件的路径下面即可,这种情况下汇编这里就不需要添加startup了,不过此外自己写的其他汇编文件倒是仍然可以正常这样写进去)。
**【注意】**当我们采用此种方式生成工程时,若是添加自己的源文件,更新工程时我们自己添加的会保留,不会被清空,可以放心添加。
5. binaries
#######################################
# binaries
#######################################
PREFIX = arm-none-eabi-
# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)
# either it can be added to the PATH environment variable.
ifdef GCC_PATH
CC = $(GCC_PATH)/$(PREFIX)gcc
AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp
CP = $(GCC_PATH)/$(PREFIX)objcopy
SZ = $(GCC_PATH)/$(PREFIX)size
else
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S(1)PREFIX :工具链起始名,就是使用的开源工具链,这里变量的内容是arm-none-eabi-,其实这个是一个gcc工具链的名字,这个工具链全程一般叫arm-none-eabi-gcc,arm表示架构平台,none表示无系统,eabi表示嵌入式应用二进制接口,gcc表示其遵守的GNU GCC的设计规范。
(2)GCC_PATH :这个就是我们gcc工具链的地址。这就涉及到一个问题,也就是环境变量,如果我们将编译工具链的工具目录地址添加到环境变量中,那么我们就可以在命令行中直接通过工具的名称调用这个工具,否则我们就需要输入绝对路径来调用工具。
比如,假设arm-none-eabi-gcc存储在D盘根目录下,那么很可能需要在环境变量中添加 D:\arm-none-eabi-gcc\bin\ 文件夹地址,此时我们就可以在命令行中直接使用 arm-none-eabi-gcc 来运行 D:\arm-none-eabi-gcc\bin\arm-none-eabi-gcc.exe 程序,否则,我们就需要输入完整的绝对路径来调用该程序。
而GCC_PATH就是用来帮助我们添加这个绝对路径的,如果没有添加环境变量,那么就可以在makefile前面定义GCC_PATH并添加上地址,如D:\arm-none-eabi-gcc\bin,那么之后我们会运行ifdef中的程序(如果没定义就直接使用后面else的程序),makefile所有变量的本质其实就是字符替换,此时例如CC,就会被编译为D:\arm-none-eabi-gcc\bin\arm-none-eabi-gcc.exe,其余同理。当然,如果我们不想在makefile中添加这个变量,也可以选择在输入make指令的时候添加 GCC_PATH=D:\arm-none-eabi-gcc\bin 。
(3)CC :直接使用gcc,其实代表了其默认参数编译C语言。
(4)AS :代表了汇编器,后边的两个参数没看懂,暂时不关心。
(5)CP :表示objcopy,这是个复制文件内容的工具。
(6)SZ :是一个记录生成程序内部各分段的大小的工具,这个在编译后查看编译出的文件大小进行项目优化的时候会比较有用,makefile中也用了这个工具,在编译到最后会输出这样的内容:
arm-none-eabi-size build/stm32f103-prj.elf
text data bss dec hex filename
9484 112 2552 12148 2f74 build/stm32f103-prj.elf值得注意的是这个size工具只能对elf文件进行解析,而hex和bin文件中是不包含代码段数据段这些东西的。这里的大概意思是我的代码段有9484字节,数据段有112字节,bss段有2552字节,总共加起来有12148字节。
(7)HEX 和 BIN :这里其实就是使用CP工具,生成十六进制文件和二进制文件,他们都是可以被烧录到stm32中的代码文件格式,只是包含的信息有所不同。
6. CFLAGS
#######################################
# CFLAGS
#######################################
# cpu
CPU = -mcpu=cortex-m3
# fpu
# NONE for Cortex-M0/M0+/M3
# float-abi
# mcu
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)
# macros for gcc
# AS defines
AS_DEFS =
# C defines
C_DEFS = \
-DUSE_HAL_DRIVER \
-DSTM32F103xE
# AS includes
AS_INCLUDES =
# C includes
C_INCLUDES = \
-ICore/Inc \
-IDrivers/STM32F1xx_HAL_Driver/Inc \
-IDrivers/STM32F1xx_HAL_Driver/Inc/Legacy \
-IDrivers/CMSIS/Device/ST/STM32F1xx/Include \
-IDrivers/CMSIS/Include
# compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections
CFLAGS += $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections
ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif
# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"(1)CPU :选择cpu的架构,因为我使用的是stm32f1的芯片,故这里的架构是cortex-m3,如果是其它架构需要进行修改。
(2)FPU :选择fpu的架构,Cortex-M0/M0+/M3没有,那么就直接注释掉就可以了,关闭后无法使用fpu对浮点数计算进行加速。
(3)float-abi :硬浮点或软浮点,如果有FPU就可以开启硬浮点,这里没有就直接注释了。
(4)MCU :此处代表了所有针对MCU硬件的配置,这个 -mthumb 代表的是T32指令集,如果使用了另外一种指令集的架构就需要换这里的参数。
(5)AS_DEFS :汇编中使用的宏定义,这里没有,后边就是空啦。
(6)C_DEFS :而C文件宏定义,真正的宏需要去掉每个宏定义开头的-D,例如此处的-DUSE_HAL_DRIVER ,实际上是宏定义了USE_HAL_DRIVER,这些宏定义会影响到c文件里面相关宏的内容,例如这个HAL,其实就代表了HAL库,那么C文件里面引用的东西就会选择HAL库而非LL库的内容。如果有需要的话,也可以自己添加一些宏定义在c文件中作为开关,然后在这里添加东西表示打开。
(7)AS_INCLUDES :汇编引用的头文件所在的目录其中因为汇编没有引用外部文件,所以什么也没有,如果有需要自己添加。
(8)C_INCLUDES :对于C来说,如果在自己的代码#include中使用了相对地址或绝对地址,则可以不添加,如果是只写了引用文件名或非本文件的文件名,则需要添加,以辅助编译器找到对应文件,编译器会主动从上述路径作为相对路径寻找include的文件。这里 -I(大写的i) 也全部是前缀。**【注意】**当我们采用此种方式生成工程时,若是添加自己的头文件目录,更新工程时我们自己添加的会保留,不会被清空,可以放心添加。
例如我们需要在Core\Src\main.c中引用Core\Test\test.h,那么我们需要在main.c中写#include "test.h"并在C_INCLUDES中添加-ICore/Test,而如果我们不在C_INCLUDES中进行添加,那么main.c中就必须写相对或绝对地址,例如#include "../Test/test.h",这样才能保证我们的编译器能够找准位置。
(9)ASFLAGS、CFLAGS :汇编和C源文件的编译参数,-Wall的功能主要是开启一些警告检测,如果我们的代码出现了不规范的地方,那么通过这个参数,编译器就会检查到这些地方并告知我们。-fdata-sections表示对程序中每个数据创建一个section,而-ffunction-sections则是针对函数创建section,GCC的链接操作是以section作为最小的处理单元,只要一个section中的某个符号被引用,该section就会被加到可执行程序中。
(10)关于这个部分的内容是针对CFLAGS:
ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif如果DEBUG=1,就执行,如果不是1,就不执行。很显然,这段话就是检测目前是否是调试模式,而注意这里CFLAGS前面其实已经出现过,而这里再次出现时使用的就不再是等号=了,而是+=符号,这个符号在makefile中表示在已有变量后面增加新的符号。而增加的其实就是两个新的参数,我们应该不难猜测这两个参数就是针对的调试生成的。
-g表示的意思是生成调试信息,如果不生成的话我们在使用gdb调试的时候就很难做到代码定位,当然如果不使用gdb,还可以使用-gformat然后加上你想要的格式。而-gdwarf-2的意思是生成DWARF version2 的格式的调试信息,常用于IRIXX6上的DBX调试器。GCC会使用DWARF version3的一些特性。
(11)这一小部分还是针对CFLAGS:
# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"这一串东西的主要功能是生成关联信息,关联信息会对链接产生影响,其中-MMD表示会把关联信息输出到.d文件中,-MF是指定用于存放关联信息的文件,-MP生成的依赖文件里面,依赖规则中的所有 .h 依赖项都会在该文件中生成一个伪目标,其不依赖任何其他依赖项。该伪规则将避免删除了对应的头文件而没有更新 “Makefile” 去匹配新的依赖关系而导致 make 出错的情况出现。后边双引号里边的,说实话,没看懂,问题不大,后边看懂了再补充。
7. LDFLAGS
#######################################
# LDFLAGS
#######################################
# link script
LDSCRIPT = STM32F103ZETx_FLASH.ld
# libraries
LIBS = -lc -lm -lnosys
LIBDIR =
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections(1)LDSCRIPT :链接脚本,里面包括了链接时各程序的组织架构,以及包括堆栈地址大小等信息,这个东西是CubeMX生成的,所以里面其实很多东西我们在CubeMX中都可以修改。这里的内容不只是修改芯片,只要修改了了中断向量、堆栈大小等信息,均需要修改此部分,它其实就代表了程序在芯片的ROM中是怎么存储的。
(2)LIBS :代表的链接库,其实就是C语言的标准库,比如说这个-lm就代表了链接了标准数学库,不过具体这几个库链接的都是哪几个,具体可能要参考GCC的手册。
(3)LIBDIR :如果使用的是非标准库需要从外面引用的话,就需要在这里添加东西了,添加的方法和前面添加c和汇编库的方法类似。
(4)LDFLAGS :这个就是链接的参数。
-specs=nano.specs替换精简c库以缩小代码大小。
-T使用脚本文件xxx作为链接器脚本,此脚本替换ld的默认链接脚本(而非加进去),因此命令文件必须给出所有输出文件所需的东西,这里的链接脚本我们在前面已经说过了。
-Wl如果链接器不是直接被使用的,而是被编译器使用(如gcc),则所有的链接命令前面必须加上-Wl参数(或其他特殊编译器对应的参数)。
-MAP打印一个链接表到map文件中。
--cref输出一个交叉引用表。如果一个链接映射文件正在被生成,交叉参照表将会被输出到映射文件中。否则,将会被输出到标准输出。
--gc-sections允许对未使用的段使用垃圾收集。在不支持这个选项的目标上将被自动忽略。默认的行为可用’–no-gc-sections’恢复。
8. 目标
# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin默认执行全部编译,生成elf、hex和bin文件。
9. 文件列表
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))这里主要是将所有的源文件替换成.o,方便后边生成中间文件。vpath用于设置源文件的路径,这样makefile就知道去哪里找源文件啦。
10. 编译链接
$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)
$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@
$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
$(AS) -c $(CFLAGS) $< -o $@
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
$(SZ) $@
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(HEX) $< $@
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(BIN) $< $@
$(BUILD_DIR):
mkdir $@这一部分就是最核心的东西啦,将所有的源文件编译成.o文件,然后再链接到一起,最终生成我们的目标文件。
11. clean up
#######################################
# clean up
#######################################
clean:
-rm -fR $(BUILD_DIR)这一部分主要是清理生成的中间文件以及目标文件。
12. dependencies
#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)依赖引用,这里引用就是前面我们生成的关联文件,这些东西有什么用呢,其实目的就是为了生成我们的.o文件。如果看不懂这个东西什么意思,那么我们随便打开一个d文件其实就应该就懂了:
build/gpio.o: Core/Src/gpio.c Core/Inc/gpio.h Core/Inc/main.h \
Drivers/STM32F1xx_HAL_Driver/Inc/stm32f1xx_hal.h \
Core/Inc/stm32f1xx_hal_conf.h \
Drivers/STM32F1xx_HAL_Driver/Inc/stm32f1xx_hal_rcc.h \
Drivers/STM32F1xx_HAL_Driver/Inc/stm32f1xx_hal_def.h \
后边的部分省略......这个是gpio.d,看着是不是很熟悉,感觉和前面那个《(GCC)STM32CubeMX生成的Makefile详解》说的把生成指令展开后C_INCLUDES、C_SOURCES生成的那一套很像?像就对了,这东西就是为了生成.d文件最顶部那个目标文件而存在的,而这里的gpio.d文件中所指的其实就是gpio.o,其余同理。