一、前言
入门STM32开发时,用的是keil 这个IDE。后面因为要提高开发效率和keil 版权问题,选择开源的arm-none-eabi-gcc
,通过命令行调用make工具进行编译、链接、烧录,打包。
二、要达到的效果
2.1 编译STM32 工程
make all
2.2 烧录单片机
make install
三、几个重要的问题点
3.1 如何高效地编译每一个源文件
3.1.1 源文件编译的原理
我们知道,编译器是对每一个源文件进行编译,生成对象文件,然后再把所有的对象文件链接起来,生成最终的elf文件。一个工程那么多源文件,怎么高效地组织源文件进行编译?
3.1.2 常见的一些组织源文件的方式
我之前在网上看到的做法,都是在makefile 文件中定义一个C_SOURCES
变量 , 作为源文件容器, 将工程中的源文件,都一一写到这个变量中去,然后在后面的处理中,利用这个C_SOURCES
变量 进行处理。
3.1.3 存在的问题
一个工程那么大,每次添加或减少一个源文件,或者代码重构时,移动源文件的路径,都要修改一次makefile ,太麻烦。按照一般的处理,makefile 文件更新时,都会重新编译整个工程,效率太低。
3.1.4 自动生成源文件列表
C_SOURCES =$(shell find . -name "*.c")
这里利用了make的shell 函数,调用外部bash 的find
命令,找到所有的当前目录下的.c
文件,保存到列表中。我们也可以利用这个思路,解决源文件并不完全在当前目录的情况。
3.2 如何找到所有的头文件
与前面描述源文件的情况类似,我们需要自动生成一个所有头文件的路径的列表。C_INCLUDES=$(addprefix -I, sort $(dir $(shell find . -name "*.h")))
- 利用make 的shell函数,调用外部bash 的
find
命令,找到当前工程的所有.h
文件(包含路径)。 - 我们需要的其实是路径。所以调用make 的
dir
命令,截取路径。 - 如果同一个路径下,有多个
.h
文件,我们上述处理后,会有重复的路径。所以调用make 的sort
命令,主要是为了利用它过滤掉重复字符串的特性。 - 上述处理,已经得到工程中所有头文件的路径。由于gcc 寻找头文件时,需要在每个路径前面加 ‘-I’ 前缀,所以,调用make的
addprefix
函数,给每个路径加上-I
前缀。
3.3 怎么解决头文件依赖关系
每个源文件都可能包含一个或者多个头文件,它包含的头文件还可能包含其他头文件。我们要解决的问题有两个:
3.3.1 怎样高效找到源文件依赖的所有头文件
gcc 的-M
选项自动生成目标文件和源文件和依赖关系。-M
选项会把系统头文件都包含进来,如果不需要输出系统头文件的依赖关系,可以-MM
选项。一般情况下,依赖文件是后缀为.d
的文件。例如main.c 的依赖文件是main.d 。
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"
$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)
$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@
3.3.2 依赖的头文件修改了,怎么自动编译源文件
上面针对所有的.c
文件生成依赖文件(.d
)文件存放在BUILD_DIR 目录下。依赖文件,以规则的语法,列出了对象文件的依赖头文件
既然依赖文件是一条条规则,我们将所有的规则包含进makefile,make就可以自动推导了-include $(wildcard $(BUILD_DIR)/*.d)
3.4 编译链接优化问题
3.4.1 设置编译优化等级
$(CC) -O2 main.c -o main.o
3.4.2 没使用的函数不链接
- 在编译源文件时,在gcc 编译选项中增加
-ffunction-sections、-fdata-sections
, 在编译生成的目标文件中,会将每个函数或者数据段,放在单独的section中。 - 在前面的基础上,链接对象文件时,加上
-Wl,--gc-sections
参数,链接器不会链接未使用的函数,从而达到压缩hex文件的目的。
四、 用到的make 相关知识整理
4.1 shell 函数的用法
-
shell 函数的参数是操作系统的shell 命令,shell函数把执行操作系统命令后的输出作为函数返回。
函数使用格式如下: -
$(shell <shell cmd> <shell cmd argmuments>)
-
示例:
contents := $(shell cat foo)
4.2 排序函数sort
$(sort <list>)
- 函数给列表中的子字符串排序(升序),并且会去掉相同的子串
- 示例:
$(sort foo bar lose)
返回bar foo lose
4.3 取目录函数dir
$(dir <names...> )
- 从文件名系列中取出目录部分,目录部分是指最后一个反斜杠(/)之前的部分。如果没有反斜杠,那么返回
./
- 示例:
$(dir src/foo.c hacks)
返回src/ ./
4.4 加前缀函数
$(addprefix <prefix>, <names...> )
- 把前缀 加到 中的每个字符串前面。返回加过前缀的字符串系列。
- 示例:
$(addprefix src/, foo bar)
返回src/foo src/bar