不想动了/(ㄒoㄒ)/~~,感觉被掏空,昨天写完第六节以后,开始把机械硬盘下新装的虚拟机系统转移到SSD里,把原来用的Ubuntu14的虚拟机迁回机械。万万没想到啊!!!转移的时候不知道抽哪个风,把原来14的文件居然损坏了!!啊!,我还没把里面的源代码和资料提取出来呢。。昨天折腾到凌晨,今天一起来又折腾到下午。。终于我放弃了。。那个资料实在是修复不了了。哭,我的代码啊!
哎折腾到了晚上6点才把新系统的一部分功能恢复到14的状态,还好前两天为了教程把里面一部分重要的工程托管GitHub了,要不真的是要哭死了。算了不多说了,我们继续吧。
上一篇中我们为大家介绍了makefile的最简单的用法和关于它的学习资料,本篇建立在大家已经稍许看过makefile的教程上,结合我们的 arm 工具链来具体分析上一篇中makefile的配置和作用。
样例工程的Makefile
首先还是先放上我们样例工程中的makefile文件。
#工程的名称及最后生成文件的名字
TARGET = LED_project
#设定临时性环境变量
export CC = arm-none-eabi-gcc
export AS = arm-none-eabi-as
export LD = arm-none-eabi-ld
export OBJCOPY = arm-none-eabi-objcopy
#读取当前工作目录
TOP=$(shell pwd)
#设定包含文件目录
INC_FLAGS= -I $(TOP)/CORE \
-I $(TOP)/HARDWARE \
-I $(TOP)/STM32F10x_FWLib/inc \
-I $(TOP)/SYSTEM \
-I $(TOP)/USER
CFLAGS = -W -Wall -g -mcpu=cortex-m3 -mthumb -D STM32F10X_HD -D USE_STDPERIPH_DRIVER $(INC_FLAGS) -O0 -std=gnu11
C_SRC=$(shell find ./ -name '*.c')
C_OBJ=$(C_SRC:%.c=%.o)
.PHONY: all clean update
all:$(C_OBJ)
$(CC) $(C_OBJ) -T stm32_f103ze_gcc.ld -o $(TARGET).elf -mthumb -mcpu=cortex-m3 -Wl,--start-group -lc -lm -Wl,--end-group -specs=nano.specs -specs=nosys.specs -static -Wl,-cref,-u,Reset_Handler -Wl,-Map=Project.map -Wl,--gc-sections -Wl,--defsym=malloc_getpagesize_P=0x80
$(OBJCOPY) $(TARGET).elf $(TARGET).bin -Obinary
$(OBJCOPY) $(TARGET).elf $(TARGET).hex -Oihex
$(C_OBJ):%.o:%.c
$(CC) -c $(CFLAGS) -o $@ $<
clean:
rm -f $(shell find ./ -name '*.o')
rm -f $(shell find ./ -name '*.d')
rm -f $(shell find ./ -name '*.map')
rm -f $(shell find ./ -name '*.elf')
rm -f $(shell find ./ -name '*.bin')
rm -f $(shell find ./ -name '*.hex')
update:
openocd -f /usr/share/openocd/scripts/interface/stlink-v2.cfg -f /usr/share/openocd/scripts/target/stm32f1x_stlink.cfg -c init -c halt -c "flash write_image erase $(TOP)/LED_project.hex" -c reset -c shutdown
读一个makefile的方式我们大多不是顺序阅读的,而是从它的目标看起,在这个文件中我们可以很明显的看到其中,伪目标.PHONY的关键字,在它的后面是 all clean update 三个目标对象。其中 all 是默认的执行对象。所以我们先来看看 all 后面的依赖文件和执行的指令。
all:$(C_OBJ)
$(CC) $(C_OBJ) -T stm32_f103ze_gcc.ld -o $(TARGET).elf -mthumb -mcpu=cortex-m3 -Wl,--start-group -lc -lm -Wl,--end-group -specs=nano.specs -specs=nosys.specs -static -Wl,-cref,-u,Reset_Handler -Wl,-Map=Project.map -Wl,--gc-sections -Wl,--defsym=malloc_getpagesize_P=0x80
$(OBJCOPY) $(TARGET).elf $(TARGET).bin -Obinary
$(OBJCOPY) $(TARGET).elf $(TARGET).hex -Oihex
可以看到 all 的依赖对象是$(C_OBJ),一个变量的取值,那么这个变量是什么呢?
C_SRC=$(shell find ./ -name '*.c')
C_OBJ=$(C_SRC:%.c=%.o)
从上面一些的地方我们看到,C_OBJ 为 C_SRC变量中 .c 文件 替换为 .o文件的集合,而C_SRC变量则是调用了一个shell指令find 配合后面 ./ -name ‘*.c’ 的参数,其功能是搜索并返回当前路径下的所有 “.c” 文件,那么就很显然了,C_SRC变量是当前路径下所有 .c 文件的集合,而 C_OBJ 变量是当前路径下所有 .o 文件的集合。同时这两个变量为了保证其可读性是使用了一种不成文的命名标准的,即 SRC 代表.c ,OBJ 代表.o。
那么上面语句的作用就是使all的更新由路径下所有 .o文件是否做过改变决定。而接下来的C_OBJ变量的依赖关系又告诉我们,.o文件依赖于所有路径下.c的文件。这样在我们执行make指令时,一种依赖的关系链就产生了,如果 .c 文件做过修改 其 .o 文件就会被更新,而.o 文件更新后 all 的指令也会被随之执行,从而生成新的工程执行文件hex与bin。而这样一种依赖的关系就是我们想要的。
$(C_OBJ):%.o:%.c
$(CC) -c $(CFLAGS) -o $@ $<
我们继续。在上面这条语句中,当 .c 文件新于 .o 文件时,下面的指令就会被调用。其中变量 CC 在文件一开始的位置就已经被定义,当然我们可以不使用变量代替 arm-none-eabi-gcc 等编译指令,但是一个稍大些工程中我们会输入很多次这样的指令,一来不方便,二来在修改时也很麻烦。这样的变量的使用,就像我们在C编程中将常数定义为宏一样。
export CC = arm-none-eabi-gcc
export AS = arm-none-eabi-as
export LD = arm-none-eabi-ld
export OBJCOPY = arm-none-eabi-objcopy
在执行的过程中我们来将 C_OBJ 后执行指令中的变量展开,看看我们最后实际,执行时候的指令的样子。在Makefile中 :
$@:目标文件,$^:所有的依赖文件,$<:第一个依赖文件
所以上面指令展开为(在变量CFLAGS 中储存了所有编译时的指令,由于他太长,所以我们在此不进行展开,其中xxx代表每次执行指令时的.c文件名称)
arm-none-eabi-gcc -c $(CFLAGS) -o xxx.o xxx.c
我们来看看 CFLAGS。
#设定包含文件目录
INC_FLAGS= -I $(TOP)/CORE \
-I $(TOP)/HARDWARE \
-I $(TOP)/STM32F10x_FWLib/inc \
-I $(TOP)/SYSTEM \
-I $(TOP)/USER
CFLAGS = -W -Wall -g -mcpu=cortex-m3 -mthumb -D STM32F10X_HD -D USE_STDPERIPH_DRIVER $(INC_FLAGS) -O0 -std=gnu11
首先大家要明白在使用命令行工具时向工具送入参数的方式,就是像这样在指令的后方添加类似于 “-I xxx” 、”-R “诸如此类格式的参数。所以不难理解CFLAGS 中的参数也是以这样的形式出现的。如 -W -Wall,这些参数的意义大家可以去自行百度关键词 GCC选项 来学习使用,而实际上这些参数说明在 我们之前安装的工具链 文件下 share/doc 的目录下是有完整的说明文档的,只不过全都在几百页左右,而且是全英文的,如果大家在网络上搜不到不妨在 这些说明书中检索一下。
在这里我们只分析一下CFLAGS中的参数:
-W -Wall“两条参数配合用于开启所有警告
-g “产生调试信息,用于生产.elf调试文件
-mcpu=cortex-m3 -mthumb “显然用于设定内核参数
-D STM32F10X_HD ” -D xxx用于设置宏定义,此处即宏定义STM32F10X_HD
-O0 “优化等级为0,还有 O1,O2等多个等级的选择
-std=gnu11 “设定语言标准为 GNU11 ,还有C98等选项
-I “ -I xx 为添加xx目录到include包含中,即搜索 .h 文件的目录,此处我们在INC_FLAGS变量中设置。
当所有的 .c 文件被编译完成后,all 的指令就要开始执行了。
all:$(C_OBJ)
$(CC) $(C_OBJ) -T stm32_f103ze_gcc.ld -o $(TARGET).elf -mthumb -mcpu=cortex-m3 -Wl,--start-group -lc -lm -Wl,--end-group -specs=nano.specs -specs=nosys.specs -static -Wl,-cref,-u,Reset_Handler -Wl,-Map=Project.map -Wl,--gc-sections -Wl,--defsym=malloc_getpagesize_P=0x80
$(OBJCOPY) $(TARGET).elf $(TARGET).bin -Obinary
$(OBJCOPY) $(TARGET).elf $(TARGET).hex -Oihex
一共是3条指令,第一条 为链接指令,使用CC 代替LD 执行以兼顾兼容性,-T 代表为链接操作,后面跟随茫茫多的链接参数。本篇中我们不具体讲解链接器的工作原理和参数,这些内容将会在下一篇中介绍。将指令后的参数缩减,实际上它是这样的:
all:$(C_OBJ)
$(CC) $(C_OBJ) -T stm32_f103ze_gcc.ld -o $(TARGET).elf
该条指令会生成.elf文件,即包含调试信息的执行文件。是由GDB调试器使用的。
而后的两条指令会使用OBJCOPY工具,将.elf文件转换为.hex和.bin文件,用于最后实际工程项目中。
$(OBJCOPY) $(TARGET).elf $(TARGET).bin -Obinary
$(OBJCOPY) $(TARGET).elf $(TARGET).hex -Oihex
我们再来看看之后的clean目标。
clean:
rm -f $(shell find ./ -name '*.o')
rm -f $(shell find ./ -name '*.d')
rm -f $(shell find ./ -name '*.map')
rm -f $(shell find ./ -name '*.elf')
rm -f $(shell find ./ -name '*.bin')
rm -f $(shell find ./ -name '*.hex')
很好理解clean 调用了find 和rm 两条shell指令,会将工程中所有的 中间生成文件和最后的 执行文件删除。
最后我们来看看updata目标
update:
openocd -f /usr/share/openocd/scripts/interface/stlink-v2.cfg -f /usr/share/openocd/scripts/target/stm32f1x_stlink.cfg -c init -c halt -c "flash write_image erase $(TOP)/LED_project.hex" -c reset -c shutdown
它则是开启了openocd程序加载了目标系统的配置,并下载了程序。
其中:
-f 代表输入配置文件
-c 代表输入参数
init 初始化
halt 暂停MCU内核
flash write_image erase 烧写文件
reset 复位MCU
exit 退出OpenOCD
这样启动OpenOCD并烧写程序的任务就完成了。
总结
至此我们已经为大家实际分析了一个makefile的构成,实际上我这个makefile的结构确实在链接的部分显得有些杂乱了,该使用变量来代替。
但我想就目前来说大家应该还是能够理解它的意思的。同时我们也看到了在使用make 指令时我们可以通过自己编写makefile,来达到不同的目的。在很多工程中甚至使用一个make指令来生成工程中多块CPU的执行文件,或是直接使用update通过DFU下载更新程序,这样的方法会极大的方便我们的实际开发操作。可以说make的功能只有我们想不到没有做不到,在我们慢慢学习的过程中相信大家也会逐渐的认识到这一点。
在下一篇中我们将会为大家讲解 ld 链接器的工作原理和参数,同时会为大家分析工程中启动文件的作用。敬请期待下一篇STM32高级开发(8)-链接器与启动文件。