这篇文章为本人结合网上教程,自行探索,如有错误敬请谅解

Linux STM32初体验一文中,我们已经成功准备好了GCC的编译环境、StLink的烧录环境,已经可以来搭建一个工程了。
而为了实现自动化构建项目,我们也应该学会使用 MakeMakefile ,理解它的语法和用法,并亲手进行实践。囿于篇幅,推荐通过陈皓跟我一起写Makefile来学习Makefile。

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
├── Algorithm(算法源码)
│ ├── PID
│ │ ├── pid.c
│ │ └── pid.h
│ ├── .....
│ └── IMU
│ ├── imu.c
│ └── imu.h
├── Drivers(底层驱动源码)
│ ├── ADC
│ ├── .....
│ └── WDG
├── Libraries(官方开发库)
│ ├── CMSIS
│ │ ├── startup
│ │ │ └── gcc
│ │ │ └── startup_stm32f10x_hd
│ │ ├── cmsis_gcc.h
│ │ ├── core_cm3.c
│ │ ├── .....
│ │ └── mpu_ramv8.h
│ ├── CORE
│ │ ├── stm32f10x_conf.h
│ │ ├── stm32f10x_it.c
│ │ ├── stm32f10x_it.h
│ │ ├── stm32f10x.h
│ │ ├── system_stm32f10x.c
│ │ └── system_stm32f10x.h
│ └── STM32F10x_FWLib
│ ├── inc
│ │ └── ...
│ └── src
│ └── ...
├── Modules(外设模块源码)
│ ├── HCSR04
│ ├── .....
│ └── SG90
├── Project(构建成功文件)
│ ├── Build
│ │ ├── template.bin
│ │ └── template.hex
│ └── stm32f1xx_flash.ld
├── src(工程核心源码)
│ └── main.c
└── Makefile
  • Algorithm
    该目录下存放有嵌入式算法的源码,其中包含IMU融合算法、PID控制算法等等顶层代码。
  • Drivers
    该目录下存放的是硬件驱动层源码,为STM32的底层驱动的源码。
  • Libraries
    该目录下存放的是STM32官方开发库的相关内容,其中CMSIS子目录下包含有STM32内核源码、硬件寄存器和中断定义源码以及启动汇编源码等,CMSIS子目录下包含有STM32工程头文件引用、中断函数定义和中断函数实现等源码,FWLib子目录下包含有STM32提供的官方固件库源码。
  • Modules
    该目录下存放的是所有外设模块层中的源码。
  • Project
    该目录下主要存放根据Makefile中所定义的规则,工程在被成功编译之后还会在本目录下生成.hex和.bin等可供烧写的文件。以及Obj文件夹存放着编译生成的.o中间文件。
  • src
    该目录下存放的是工程的main.c 工程主源码文件。

Makefile详解

根据工程目录结构,我们可以编写工程的Makefile文件。

Makefile 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
PROJECT := template

MCU := cortex-m3

BUILD_DIR = Project/Build
OBJ_DIR = Project/Build/Obj

DDEFS += -DSTM32F10X_HD
DDEFS += -DUSE_STDPERIPH_DRIVER

DIR_DRIVERS += ./Drivers/ADC/
DIR_DRIVERS += ./Drivers/EXTI/
DIR_DRIVERS += ./Drivers/DAC/
DIR_DRIVERS += ./Drivers/ITEMP/
DIR_DRIVERS += ./Drivers/KEY/
DIR_DRIVERS += ./Drivers/LED/
DIR_DRIVERS += ./Drivers/PWM/
DIR_DRIVERS += ./Drivers/RTC/
DIR_DRIVERS += ./Drivers/TIMER/
DIR_DRIVERS += ./Drivers/WDG/
DIR_DRIVERS += ./Drivers/SYSTEM/delay/
DIR_DRIVERS += ./Drivers/SYSTEM/sys/
DIR_DRIVERS += ./Drivers/SYSTEM/usart/

DIR_MODULES += ./Modules/HCSR04/
DIR_MODULES += ./Modules/SG90/
DIR_MODULES += ./Modules/ENCODER/

DIR_ALGORITHM +=

LINK_SCRIPT := ./Project/stm32f1xx_flash.ld

SRC_ASM := ./Libraries/CMSIS/startup/gcc/startup_stm32f10x_hd.s

CC_PREFIX := arm-none-eabi-
st-link := st-flash
###############################################################################

SRC_C += $(wildcard ./Libraries/CMSIS/*.c)
SRC_C += $(wildcard ./Libraries/CORE/*.c)
SRC_C += $(wildcard ./Libraries/STM32F10x_FWLib/src/*.c)
SRC_C += $(wildcard $(addsuffix *.c, $(DIR_DRIVERS)))
SRC_C += $(wildcard $(addsuffix *.c, $(DIR_MODULES)))
SRC_C += $(wildcard $(addsuffix *.c, $(DIR_ALGORITHM)))
SRC_C += $(wildcard ./src/*.c)

INCDIR += -I./Libraries/CMSIS/ \
-I./Libraries/CORE/ \
-I./Libraries/STM32F10x_FWLib/inc/ \
$(addprefix -I, $(DIR_DRIVERS)) \
$(addprefix -I, $(DIR_MODULES)) \
$(addprefix -I, $(DIR_ALGORITHM)) \
-I./src/ \


CC := $(CC_PREFIX)gcc
CXX := $(CC_PREFIX)g++
CP := $(CC_PREFIX)objcopy
GDB := $(CC_PREFIX)gdb
SIZE := $(CC_PREFIX)size
AS := $(CC) -x assembler-with-cpp
HEX := $(CP) -O ihex
BIN := $(CP) -O binary -S

OPT += -Os
OPT += -fsingle-precision-constant
OPT += -fno-common
OPT += -ffunction-sections
OPT += -fdata-sections


DEFS := $(DDEFS) -DRUN_FROM_FLASH=1

FLAGS_MCU := -mcpu=$(MCU)
FLAGS_AS := $(FLAGS_MCU) $(OPT) -g -gdwarf-2 -mthumb
FLAGS_C := -std=c11 -xc $(FLAGS_MCU) $(OPT) -mthumb -g -Wl,-u_printf_float \
-Wall -fverbose-asm -fomit-frame-pointer $(DEFS)
FLAGS_CXX := -std=c++11 -xc++ $(FLAGS_MCU) $(OPT) \
-gdwarf-2 -mthumb -Wl,-u_printf_float -ffunction-sections -fdata-sections \
-fomit-frame-pointer -Wall -g -fverbose-asm -fno-exceptions \
-fno-rtti -fno-threadsafe-statics -fvisibility=hidden $(DEFS)

FLAGS_LD := $(FLAGS_MCU) $(OPT) -lm -g -gdwarf-2 -mthumb \
--specs=nosys.specs --specs=nano.specs -Wl,-u_printf_float -Wl,--print-memory-usage \
-Wl,--gc-sections -T$(LINK_SCRIPT) \
-Wl,-Map=$(OBJ_DIR)/$(PROJECT).map,--cref,--no-warn-mismatch \

OBJS = $(addprefix $(OBJ_DIR)/,$(notdir $(SRC_C:.c=.o))) $(addprefix $(OBJ_DIR)/,$(notdir $(SRC_ASM:.s=.o)))


vpath %.c $(sort $(dir $(SRC_C)))
vpath %.s $(sort $(dir $(SRC_ASM)))



TYPE_BURN := stlink_swd_flash
TYPE_DEBUG := openocd_stlink_debug
TYPE_ERASE := stlink_swd_erase

###############################################################################

.PHONY: all flash debug erase clean distclean

all: $(OBJS) $(BUILD_DIR)/$(PROJECT).elf $(BUILD_DIR)/$(PROJECT).hex $(BUILD_DIR)/$(PROJECT).bin
@$(SIZE) $(BUILD_DIR)/$(PROJECT).elf


$(OBJ_DIR)/%.d: %.c Makefile │$(OBJ_DIR)
@set -e; rm -f $@; \
$(CC) -M $(FLAGS_C) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@;
# rm -f $@.$$$$


$(OBJ_DIR)/%.o: %.c Makefile │$(OBJ_DIR)
@$(CC) -c $(FLAGS_C) $(INCDIR) $< -o $@
@echo "Compile $<"


$(OBJ_DIR)/%.o: %.s Makefile │$(OBJ_DIR)
@$(AS) -c $(FLAGS_AS) $< -o $@


$(BUILD_DIR)/$(PROJECT).elf: $(OBJS) Makefile
@$(CC) $(OBJS) $(FLAGS_LD) -o $@


$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf │$(BUILD_DIR)
@$(HEX) $< $@

$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf │$(BUILD_DIR)
@$(BIN) $< $@

$(OBJ_DIR) $(BUILD_DIR):
@-mkdir $(BUILD_DIR)
@-mkdir $(OBJ_DIR)

###############################################################################

# 调试器定义
flash: $(TYPE_BURN)
# debug: $(TYPE_DEBUG)
erase: $(TYPE_ERASE)


stlink_swd_flash: $(BUILD_DIR)/$(PROJECT).hex
$(st-link) --format ihex write $(BUILD_DIR)/$(PROJECT).hex

# # 调试命令
openocd_stlink_debug: $(BUILD_DIR)/$(PROJECT).elf
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c init \
-c halt -c "flash write_image erase $(BUILD_DIR)/$(PROJECT).elf" -c reset & \
$(GDB) --eval-command="target extended-remote localhost:3333" $(BUILD_DIR)/$(PROJECT).elf


stlink_swd_erase:
$(st-link) --reset erase

###############################################################################

clean:
@echo "删除中间文件...."
@-rm -fR $(OBJ_DIR)

distclean:
@echo "删除全部文件...."
@-rm -fR $(BUILD_DIR)
@-rm -fR $(OBJ_DIR)

详解

工程配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 工程名
PROJECT := template
# CPU类型
MCU := cortex-m3
# 生成目录
BUILD_DIR = Project/Build
OBJ_DIR = Project/Build/Obj
# 编译宏定义
DDEFS += -DSTM32F10X_HD
DDEFS += -DUSE_STDPERIPH_DRIVER
# 链接脚本
LINK_SCRIPT := ./Project/stm32f1xx_flash.ld
# 启动文件
SRC_ASM := ./Libraries/CMSIS/startup/gcc/startup_stm32f10x_hd.s
# 编译工具 烧录工具
CC_PREFIX := arm-none-eabi-
st-link := st-flash

根据注释进行配置即可

目录引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 工程所需目录
DIR_DRIVERS += ./Drivers/ADC/
....
DIR_DRIVERS += ./Drivers/SYSTEM/usart/

DIR_MODULES += ./Modules/HCSR04/

DIR_ALGORITHM +=
INCDIR += -I./Libraries/CMSIS/ \
-I./Libraries/CORE/ \
-I./Libraries/STM32F10x_FWLib/inc/ \
$(addprefix -I, $(DIR_DRIVERS)) \
$(addprefix -I, $(DIR_MODULES)) \
$(addprefix -I, $(DIR_ALGORITHM)) \
-I./src/ \

DIR_DRIVERSDIR_MODULESDIR_ALGORITHM 三个变量是工程中需要使用的底层驱动、外设及算法。
INCDIR 的作用是为了获取工程目录中的.h头文件.

源文件搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
SRC_C   += $(wildcard ./Libraries/CMSIS/*.c)
SRC_C += $(wildcard ./Libraries/CORE/*.c)
SRC_C += $(wildcard ./Libraries/STM32F10x_FWLib/src/*.c)
SRC_C += $(wildcard $(addsuffix *.c, $(DIR_DRIVERS)))
SRC_C += $(wildcard $(addsuffix *.c, $(DIR_MODULES)))
SRC_C += $(wildcard $(addsuffix *.c, $(DIR_ALGORITHM)))
SRC_C += $(wildcard ./src/*.c)

OBJS = $(addprefix $(OBJ_DIR)/,$(notdir $(SRC_C:.c=.o))) $(addprefix $(OBJ_DIR)/,$(notdir $(SRC_ASM:.s=.o)))


vpath %.c $(sort $(dir $(SRC_C)))
vpath %.s $(sort $(dir $(SRC_ASM)))

$(wildcard $(addsuffix *.c, $(DIR_DRIVERS)))函数的含义是首先在DIR_DRIVERS变量的后边添加.c后缀,然后再调用wildcard函数获取其中的所有.c源文件。
因此,SRC_CSCR_ASM 这两个变量用于分别存储C源码和汇编文件。

$(notdir $(C_SOURCES:.c=.o))函数是将源文件集中所有c文件的后缀替换成o文件,并去除所有路径信息.
addprefix函数则将将无路径的o文件集(字符串)添加制定路径前缀信息,生成最终的目标obj文件的路径和名称集合。
因此,OBJS变量用于存储所有通过.c和.s源文件生成的中间目标文件(.o)。

在接着,通过$(sort $(dir $(C_SOURCES)))函数指定C文件和汇编文件的搜索路径.

编译器

1
2
3
4
5
6
7
8
9
10
CC_PREFIX := arm-none-eabi-

CC := $(CC_PREFIX)gcc
CXX := $(CC_PREFIX)g++
CP := $(CC_PREFIX)objcopy
GDB := $(CC_PREFIX)gdb
SIZE := $(CC_PREFIX)size
AS := $(CC) -x assembler-with-cpp
HEX := $(CP) -O ihex
BIN := $(CP) -O binary -S

这些变量为ARM-GCC编译器相关可执行程序。

编译选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
MCU     := cortex-m3
# 编译宏定义
DDEFS += -DSTM32F10X_HD
DDEFS += -DUSE_STDPERIPH_DRIVER
# OPT(Optimization)变量用于表示 编译优化 方面的选项
# 只优化程序代码,不优化符号
OPT += -Os
# 单精度常量
OPT += -fsingle-precision-constant
# 禁止使用共同体
OPT += -fno-common
# 将函数放在独立的段中
OPT += -ffunction-sections
OPT += -fdata-sections
# 指定程序从硬件的FLASH中开始运行
DEFS := $(DDEFS) -DRUN_FROM_FLASH=1

DDEFSDEFS 变量是ARM-GCC编译器的预处理宏定义,用于将指定的功能编译到可执行程序中。
MCU 变量表示当前工程所用芯片的架构。
OPT(Optimization)变量用于表示 编译优化 方面的选项。

编译标签

1
2
3
4
5
6
7
8
9
10
11
12
13
FLAGS_MCU := -mcpu=$(MCU)
FLAGS_AS := $(FLAGS_MCU) $(OPT) -g -gdwarf-2 -mthumb
FLAGS_C := -std=c11 -xc $(FLAGS_MCU) $(OPT) -mthumb -g -Wl,-u_printf_float \
-Wall -fverbose-asm $(DEFS)
FLAGS_CXX := -std=c++11 -xc++ $(FLAGS_MCU) $(OPT) \
-gdwarf-2 -mthumb -Wl,-u_printf_float -ffunction-sections -fdata-sections \
-fomit-frame-pointer -Wall -g -fverbose-asm -fno-exceptions \
-fno-rtti -fno-threadsafe-statics -fvisibility=hidden $(DEFS)

FLAGS_LD := $(FLAGS_MCU) $(OPT) -lm -g -gdwarf-2 -mthumb \
--specs=nosys.specs --specs=nano.specs -Wl,-u_printf_float -Wl,--print-memory-usage \
-Wl,--gc-sections -T$(LINK_SCRIPT) \
-Wl,-Map=$(OBJ_DIR)/$(PROJECT).map,--cref,--no-warn-mismatch \

这些变量都是编译标签,具体含义可参考GCC文档
列出一些常用的标签含义:

  • -fomit-frame-pointer
    减少了栈帧的切换和栈地址的保存,可提高程序性能
  • -fverbose-asm
    在生成的汇编代码中加入额外的注释信息来使汇编代码更具可读性
  • -specs=nano.specs
    使用精简版C库 ,
  • -T$(LINK_SCRIPT)
    依赖的可执行文件链接脚本,即链接脚本
  • -Wl,-Map=(OBJDIR)/(OBJ_DIR)/(PROJECT).map,–cref
    生成map文件
  • -Wl,–gc-sections
    链接使用的分段方式,需要配合C文件/汇编生成obj的时候同样选型分段方式。
  • -Wl,-u_printf_float -Wl,–print-memory-usage
    串口打印时打印出浮点数

总结

至此,一个完整的Makefile函数便完成了。
你可以在终端输入make来编译生成hex文件,
输入make flash来烧写固件;
输入make distclean来清除全部文件…

尾声

总是想记录些什么,却又不知道要怎么写。在实验室的这些天,我不知道要做些什么,要学些什么,反而将大把时间放在了研究GCC编译STM32之上。
我并不是很确认,这是否有什么意义。但是在这个过程中,我已然初步掌握了其编译原理,学会了调试编译参数来生成不同固件。

就像上海交通大学生存手册中所说的那样“国内绝大部分大学的本科教学,不是濒临崩溃,而是早已崩溃”,这种闭源编译器垄断的学习模式是否正确,我无法评判,也没能力评判。
因stm32的gcc编译教程实在是少,便写两篇文章来记录下,也希望能让看到这些文章的人少走些歪路。