使用GCC编译STM32工程

  • Post author:
  • Post category:其他




一、STM32在linux环境编译

STM32等arm芯片的工程编译环境比较多,类似于keil、IAR等成熟的MDK集成环境被大部分开发者所使用的,但是如果要在linux在开发,或者使用的芯片不是stm32芯片,需要自己搭建编译环境。

本文将基于STM32芯片使用arm-none-eabi-gcc编译器,并使用工具makefile完成。



二、编译文件介绍

STM32工程建立,可以是在keil中使用的工程基础上修改。在gcc编译器下与在keil中的区别主要是3个文件:

1、Makefile文件,gcc独有文件;

2、startup_stm32f407xx.s,有区别文件,主要是gcc和keil编译汇编格式不同;

3、STM32F407IGHX_FLASH.ld,gcc独有文件,keil环境自带无需工程配置;



三、解析Makefile文件

本文将对STM32CUBMX生成的Makefile文件进行详细的解释,如果要直接使用下面的Makefile文件验证,要将所有备注/



/删除。

##########################################################################################################################
# File automatically-generated by tool: [projectgenerator] version: [3.0.0] date: [Wed Jun 10 23:45:01 CST 2020]
##########################################################################################################################

# ------------------------------------------------
# Generic Makefile (based on gcc)
#
# ChangeLog :
#	2017-02-10 - Several enhancements + project update mode
#   2015-07-22 - first version
# ------------------------------------------------

######################################
# target
######################################
/* 编译生成目标烧写软件的名称 */
TARGET = GccProject

######################################
# building variables
######################################
# debug build?
/* 编译选项:是否debug模式,如果DEBUG=1,则可以后续使用调试软件gdb等工具进行在线调试
如果DEBUG=0,则不能支持在线调试,
且DEBUG=1,生成的文件比DEBUG=0大,因为里面包含了调试信息。 */
DEBUG = 1

# optimization
/* 编译选项:优化等级
-O0:无任何优化,
-O1:1级优化,
-O2: 2级优化,
-Os: 2.5级优化,
-O3: 最高级优化。 */
OPT = -O0


#######################################
# paths
#######################################
# Build path
/* 编译路径: 生成的编译文件保存在build文件夹中,
这样做的好处是工程框架比较清晰,且清除编译文件比较简单。*/
BUILD_DIR = build

######################################
# source
######################################
# C sources
/* 工程所有需要编译的C文件: 指定需要编译的C文件名称相对路径。*/
C_SOURCES =  \
Src/main.c \
Src/stm32f4xx_it.c \
Src/stm32f4xx_hal_msp.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim_ex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc_ex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ramfunc.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma_ex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr_ex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c \
Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c \
Src/system_stm32f4xx.c  

# ASM sources
/* 工程所有需要编译的汇编文件: 指定需要编译的汇编文件名称相对路径。*/
ASM_SOURCES =  \
startup_stm32f407xx.s


#######################################
# binaries
#######################################
/* 工程使用编译的类型: arm-none-eabi-是基于arm芯片开发的编译器,
none表示无操作系统,eabi表示交叉编译器,即在linux上编译嵌入式arm芯片的代码
生成可烧写文件。*/
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.
/* 编译器路径宏:表示arm-none-eabi-gcc在linux中调用是否需要带路径,
一般情况安装好arm-none-eabi-gcc后,系统将安装的可执行程序路径放在了系统的环境变量中,
无需要路径即可执行,所以GCC_PATH不用定义。*/
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
/* 编译C语言文件编译器选型 :arm-none-eabi-gcc */
CC = $(PREFIX)gcc
/* 编译汇编语言文件编译器选型 :arm-none-eabi-gcc -x assembler-with-cpp 
-x:表示指定编译语言的选型,包含c cpp ObJECT-C assembler go assemble-with-cpp等不用语言 */
AS = $(PREFIX)gcc -x assembler-with-cpp
/* 编译器将可执行文件elf(linux中可执行文件常见格式)转换成其他格式的工具:arm-none-eabi-objcopy */
CP = $(PREFIX)objcopy
/* 编译器计算可执行文件中存储分配的工具:arm-none-eabi-size */
SZ = $(PREFIX)size
endif
/* elf文件转换成HEX文件:arm-none-eabi-objcopy -O ihex */
HEX = $(CP) -O ihex
/* elf文件转换成bin文件:arm-none-eabi-objcopy -O binary -S */
BIN = $(CP) -O binary -S
 
#######################################
# CFLAGS
#######################################
# cpu
/* 编译选型:CPU类型,只STM32芯片的内核是cortex-n4,指定该内核对应的寄存器库 */
CPU = -mcpu=cortex-m4

# fpu
/* 编译选型:FPU浮点计算器,STM32支持浮点运算 */
FPU = -mfpu=fpv4-sp-d16

# float-abi
/* 编译选型:浮点计算类型,设定硬件浮点运算,还可选型纯软件浮点计算,或者结合形式 */
FLOAT-ABI = -mfloat-abi=hard

# mcu
/* 编译MCU总选项: M4内核,精简指令集mthumb , 支持浮点运算 ,浮点计算采用硬件浮点计算器 */
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)

# macros for gcc
# AS defines
/* 汇编编译宏定义:该Makefile宏定义将在汇编代码中有效 */
AS_DEFS = 

# C defines
/* C文件编译宏定义:该Makefile宏定义将在C代码中有效,\符号表示Makefile同1条指令换行 */
C_DEFS =  \
-DUSE_HAL_DRIVER \
-DSTM32F407xx


# AS includes
/* 汇编头文件路径:编译过程中文件内的头文件搜索路径 */
AS_INCLUDES = 

# C includes
/* C文件的头文件路径:编译过程中文件内的头文件搜索路径 */
C_INCLUDES =  \
-IInc \
-IDrivers/STM32F4xx_HAL_Driver/Inc \
-IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy \
-IDrivers/CMSIS/Device/ST/STM32F4xx/Include \
-IDrivers/CMSIS/Include \
-IDrivers/CMSIS/Include


# compile gcc flags
/* 汇编编译选型:s汇编文件编译成Obj文件需要的设置选项 */
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections
/* C编译选型:C文件编译成Obj文件需要的设置选项 */
CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

/* Debug模式下的C编译选型:仅仅在开启DEBUG模式下有效 */
ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif


# Generate dependency information
/* C文件自动依赖关系 :-MMD -MP -MF"$(@:%.o=%.d)" 
自动生成.d文件,里面保存了改源文件C代码中包含的非标准库的头文件路径和名称,
生成.d文件的目的是产生C文件生成obj的依赖文件,
当关联的头文件发生变化时,触发make重新生成obj文件。
-MMD等同于-MM -MF,-MM表示依赖的头文件(不包括标准头文件夹,-M则是所有头文件),
-MF生成依赖文件。  */
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"


#######################################
# LDFLAGS
#######################################
# link script
/* 可执行文件链接脚本:  STM32F407IGHx_FLASH.ld 
文件中详细给出了芯片的RAM和ROM片区分类区间与大小,
代码、全局变量、常数、堆栈等的分配区间。*/
LDSCRIPT = STM32F407IGHx_FLASH.ld

# libraries
/* 编译选型: 依赖的标准库*/
LIBS = -lc -lm -lnosys 
/* 编译选型: 依赖的指定路径库,.a库文件(window中的lib文件需要转换成.a文件才能识别)*/
LIBDIR = 
/* 链接工具的总选项: 
MCU 芯片类型,
-specs=nano.specs 精简版C库 ,
-T$(LDSCRIPT)依赖的可执行文件链接脚本,
$(LIBDIR) 标准库文件 , $(LIBS) 指定库文件 ,
-Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref 生成map文件 ,
-Wl,--gc-sections 链接使用的分段方式,需要配合C文件/汇编生成obj的时候同样选型分段方式,好处是链接的时候源文件中的未使用变量和未调用函数将不会被链接到elf文件中,最终可执行文件elf会很精简。
*/
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections

# default action: build all
/* Makefile总目标: 这是伪目标,在第一依赖关系位置,输入make指令时必定指令该目标 
可执行文件elf , hex 和 bin 是arm的常用烧写文件 。*/
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin


#######################################
# build the application
#######################################
# list of objects
/* 将所有C文件获取OBJ文件: 将待编译的所有C文件生成OBJ文件集
$(notdir $(C_SOURCES:.c=.o)):将源文件集中所有c文件的后缀替换成o文件,并去除所有路径信息,
addprefix函数 :将无路径的o文件集(字符串)添加制定路径前缀信息,生成最终的目标obj文件的路径和名称集合。*/
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))

/* 指定C文件的搜索路径: $(sort $(dir $(C_SOURCES))) 
$(dir $(C_SOURCES)):所有源文件只保留文件路径,
sort:对所有路径排序 ,‘d g a’ -> 'a d g' */
vpath %.c $(sort $(dir $(C_SOURCES)))

# list of ASM program objects
/* 将所有汇编文件获取OBJ文件: 将待编译的所有汇编文件生成OBJ文件集
$(notdir $(ASM_SOURCES:.s=.o)) :将源文件集中所有编译文件后缀替换成o文件,并去除所有路径信息,
addprefix函数 :将无路径的o文件集(字符串)添加制定路径前缀信息,生成最终的目标obj文件的路径和名称集合。*/
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
/* 指定汇编文件的搜索路径: $(sort $(dir $(ASM_SOURCES)))
$(dir $(C_SOURCES)):所有汇编文件只保留文件路径,
sort:对所有路径排序 ,‘d g a’ -> 'a d g' */
vpath %.s $(sort $(dir $(ASM_SOURCES)))

/* 通配符指定所有c文件编译成OBJ文件: 
$(BUILD_DIR)/%.o: 生成OBJ文件的路径固定不变,在BUILD_DIR文件夹,
%.c:依赖源文件C文件,地址未指定,Makefile将在本地目录和vpath %c目录下搜索源文件,
Makefile :Makefile文件自己也是生成obj文件的依赖文件,Makefile文件变化时会重新编译,
| $(BUILD_DIR): 竖线坐标的依赖文件是正常依赖文件,竖线右边的依赖文件是命令提前的依赖文件,即BUILD_DIR会自动执行,编译生成OBJ前生成build文件夹。 
-Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)):生成临时中间文件
-c:仅编译不链接 $<:第一个依赖文件即C文件   $@ 目标文件 */
$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) 
	$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@

/* 通配符指定所有汇编文件编译成OBJ文件: 
$(BUILD_DIR)/%.o: 生成OBJ文件的路径固定不变,在BUILD_DIR文件夹,
%.c:依赖源文件C文件,地址未指定,Makefile将在本地目录和vpath %c目录下搜索源文件,
Makefile :Makefile文件自己也是生成obj文件的依赖文件,Makefile文件变化时会重新编译,
| $(BUILD_DIR): 竖线坐标的依赖文件是正常依赖文件,竖线右边的依赖文件是命令提前的依赖文件,
-c:仅编译不链接 $<:第一个依赖文件即C文件   $@ 目标文件 */
$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
	$(AS) -c $(CFLAGS) $< -o $@

/* 生成可执行文件ELF文件:依赖于所有OBJ文件 
$(CC) $(OBJECTS) $(LDFLAGS) -o $@:生成elf文件
$(SZ) $@:计算和打印elf文件的存储分配信息 。*/
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
	$(CC) $(OBJECTS) $(LDFLAGS) -o $@
	$(SZ) $@
	
/* 生成HEX文件:依赖于elf文件和build文件夹
build文件:优先于目标,直接执行*/
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(HEX) $< $@

/* 生成BIN文件:依赖于elf文件和build文件夹
build文件:优先于目标,直接执行*/	
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(BIN) $< $@	

/* 生成build文件夹:伪指令
通过上面的优先依赖关系生成。*/		
$(BUILD_DIR):
	mkdir $@		

#######################################
# clean up
#######################################
/* 清除编译结果:将build文件中所有文件和子文件夹删除。*/
clean:
	-rm -fR $(BUILD_DIR)
  
#######################################
# dependencies
#######################################
/* 添加所有.d依赖文件:include 添加其他makefile文件
-include 表示如果无d文件不报错继续执行makefile命令,
wildcard函数:表示在函数中让通配符生效,即在include 选型中让‘*’生效,搜索到build文件中所有d文件。
最终生成所有c文件的依赖关系,
make工具将依赖关系与上面定义的 %.o:%.c makefile 显性目标关系合并,
make工具将合并后的执行。*/
-include $(wildcard $(BUILD_DIR)/*.d)

# *** EOF ***



版权声明:本文为ysu_wangli原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。