breeze微型四轴系列(三):stm32嵌入式开发-makefile详解

前言

在上一篇文章中,我详细介绍了STM32嵌入式开发工具的相关内容,也通过具体的操作向大家展现了STM32嵌入式开发环境的整个搭建过程,但是如果你想看懂本篇文章或者想尝试自己在GNU/Linux下编写STM32嵌入式程序,那以上的知识是远远不够的,你还需要自学MakeMakefile,理解它的语法和用法,并亲手进行实践。

本篇文章因为篇幅有限,并不对Make和Makefile进行详细介绍,请大家先自行上网查找教程资料进行学习,这里我个人强烈推荐大神陈皓曾在CSDN上发表过的《跟我一起写Makefile》系列文章(当初我就是通过这份教程学会Makefile的),里面对Makefile的基本规则、命令、变量、条件判断、函数、运行和隐含规则等内容进行了详细的介绍,讲的非常棒,网上也有PDF版本的,可以下载到本地,方便阅读。

内容

系统架构

breeze_embedded_architecture

如上图所示,Breeze微型四轴飞行器的嵌入式系统架构自底向上共由五个部分组成,它们分别为硬件设备层官方开发库层底层驱动层外设模块层飞控算法层(其中官方开发库层比较特殊,因为它为底层驱动层和外设模块层代码的编写均提供STM32底层函数实现,因此当初我在构思嵌入式架构图的时候,为简化设计,并且能更好体现官方开发库层在整个架构图中的层级关系,就将其放置在了驱动层和模块层的右侧,只表示逻辑上的调用关系,不存在层级上的高低之分)。

目录结构

根据上一节所讲的嵌入式系统架构图,我设计了如下图所示的工程根目录结构,它共由八个子功能目录组成,分别为Algorithm(飞控算法源码)、Documents(工程开发文档)、Drivers(底层驱动源码)、Libraries(官方开发库)、Modules(外设模块源码)、Project(Makefile文件)、Tools(功能脚本)和User(工程核心源码)。

breeze_dir_make

Makefile详解

根据工程目录结构、ARM-GCC和OpenOCD等工具的使用手册以及HandsFree项目所提供的STM32学习资料,我编写了工程的Makefile文件。在开始详细讲解相关内容之前,还是先放上工程的Makefile文件,好让大家对其能有一个整体的印象。

因为我在把Makefile中的内容拷过来的时候,为了能够让其中的命令以Soft Wrap(Atom等现代编辑器所提供的功能,可以自动让一行文字在某个特定列换行显示)的形式进行显示,我把原本Makefile中的TAB全部替换成空格,并且在80列的边界处进行了换行处理,所以如果你想自己编写Makefile,请复制并编辑工程目录里的Makefile,而不要直接复制下面的内容到你自己的Makefile文件中,否则运行make肯定会报错!

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
PROJECT := breeze_firmware_none

DIR_DRIVERS += ../Drivers/Driver_Clock/
DIR_DRIVERS += ../Drivers/Driver_Delay/
DIR_DRIVERS += ../Drivers/Driver_EEPROM/
DIR_DRIVERS += ../Drivers/Driver_Flash/
DIR_DRIVERS += ../Drivers/Driver_IIC/
DIR_DRIVERS += ../Drivers/Driver_IO/
DIR_DRIVERS += ../Drivers/Driver_NVIC/
DIR_DRIVERS += ../Drivers/Driver_SPI/
DIR_DRIVERS += ../Drivers/Driver_Timer/
DIR_DRIVERS += ../Drivers/Driver_USART/

DIR_MODULES += ../Modules/Module_Battery/
DIR_MODULES += ../Modules/Module_CommLink/
DIR_MODULES += ../Modules/Module_LED/
DIR_MODULES += ../Modules/Module_Motor/
DIR_MODULES += ../Modules/Module_MPU6050/
DIR_MODULES += ../Modules/Module_MS5611/
DIR_MODULES += ../Modules/Module_NRF24L01/

DIR_ALGORITHM += ../Algorithm/Algorithm_Altitude/
DIR_ALGORITHM += ../Algorithm/Algorithm_Control/
DIR_ALGORITHM += ../Algorithm/Algorithm_Filter/
DIR_ALGORITHM += ../Algorithm/Algorithm_Flight/
DIR_ALGORITHM += ../Algorithm/Algorithm_IMU/

DIR_INCLUDE += -I../Libraries/CMSIS/
-I../Libraries/FWLib/inc/
$(addprefix -I, $(DIR_DRIVERS))
$(addprefix -I, $(DIR_MODULES))
$(addprefix -I, $(DIR_ALGORITHM))
-I../User/

SRC_C += $(wildcard ../Libraries/CMSIS/*.c)
SRC_C += $(wildcard ../Libraries/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 ../User/*.c)

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

OBJS := $(filter %.o, $(SRC_ASM:.s=.o)) $(filter %.o, $(SRC_C:.c=.o))

LINK_SCRIPT := ../Libraries/LinkScript/stm32f10x_flash.lds

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

DDEFS += -DSTM32F10X_MD
DDEFS += -DHSE_VALUE=8000000 -DUSE_STDPERIPH_DRIVER

DEFS := $(DDEFS) -DRUN_FROM_FLASH=1

MCU := cortex-m3

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

SPECS := --specs=rdimon.specs -u _printf_float

FLAGS_MCU := -mcpu=$(MCU)
FLAGS_AS := $(SPECS) $(FLAGS_MCU) $(OPT) -c -g -gdwarf-2 -mthumb
FLAGS_C := $(SPECS) $(FLAGS_MCU) $(OPT) -c -g -gdwarf-2 -mthumb
-fomit-frame-pointer -Wall -fverbose-asm $(DEFS)
FLAGS_CXX := $(SPECS) $(FLAGS_MCU) $(OPT) -c -g -gdwarf-2 -mthumb
-fomit-frame-pointer -Wall -fverbose-asm -fno-exceptions
-fno-rtti -fno-threadsafe-statics -fvisibility=hidden -std=c++11
$(DEFS)
FLAGS_LD := $(SPECS) $(FLAGS_MCU) $(OPT) -lm -g -gdwarf-2 -mthumb
-nostartfiles -Xlinker --gc-sections -T$(LINK_SCRIPT)
-Wl,-Map=$(PROJECT).map,--cref,--no-warn-mismatch

TYPE_BURN := openocd_swd_flash
TYPE_DEBUG := openocd_swd_debug
TYPE_ERASE := openocd_swd_erase





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

%.o: %.c
$(CC) $(FLAGS_C) $(DIR_INCLUDE) $< -o [email protected]

%.o: %.s
$(AS) $(FLAGS_AS) $< -o [email protected]

%.elf: $(OBJS)
$(CC) $(OBJS) $(FLAGS_LD) -o [email protected]

%.hex: %.elf
$(HEX) $< [email protected]

%.bin: %.elf
$(BIN) $< [email protected]



burn: $(TYPE_BURN)
debug: $(TYPE_DEBUG)
erase: $(TYPE_ERASE)

openocd_swd_flash: $(PROJECT).bin
openocd -f interface/jlink.cfg -c "transport select swd" -f
target/stm32f1x.cfg -c "init" -c "reset halt" -c "sleep 100" -c "wait_halt
2" -c "flash write_image erase $(PROJECT).bin 0x08000000" -c "sleep 100" -c
"verify_image $(PROJECT).bin 0x08000000" -c "sleep 100" -c "reset run" -c
shutdown

openocd_swd_debug: $(PROJECT).bin
xterm -e openocd -f interface/jlink.cfg -c "transport select swd" -f
target/stm32f1x.cfg -c "init" -c "halt" -c "reset halt" &
$(GDB) --eval-command="target extended-remote localhost:3333" $(PROJECT).elf

openocd_swd_erase:
openocd -f interface/jlink.cfg -c "transport select swd" -f
target/stm32f1x.cfg -c "init" -c "reset halt" -c "sleep 100" -c "stm32f1x
mass_erase 0" -c "sleep 100" -c shutdown



clean:
-rm -rf $(OBJS)
-rm -rf $(PROJECT).elf
-rm -rf $(PROJECT).map
-rm -rf $(PROJECT).hex
-rm -rf $(PROJECT).bin

总结

读到这里,我相信大家对Breeze微型四轴飞行器的嵌入式架构以及工程本身的Makefile有了一个比较清楚的认识。这里,我想再强调一下项目整体架构以及目录组织结构的重要性,一个开源项目能否成功在很大程度上取决于系统架构设计的好坏,优秀的系统架构可以降低各模块之间的耦合性,提高底层代码的封装性,并向上提供较为丰富的API接口,除此之外,最重要的是它统一了接口标准,降低程序开发的复杂程度,从而提高系统整体的鲁棒性。当然,拥有设计出色的系统架构之后,我们还需要根据其来组织整个项目工程的目录结构,把不同功能的代码、Makefile以及开发文档等放到不同的目录当中去,这样有利于后期项目代码的编写和维护等工作。

在接下来的文章中,我将会为大家讲解STM32链接脚本的工作原理和相关配置,并分析其在程序链接和运行阶段的作用。由于下一篇的链接脚本部分涉及很多非常深奥的软件底层知识,所以我希望那些想深入理解STM32嵌入式程序是如何在硬件上运行的同学可以提前先看看《C专家编程》这本书中的“第6章 运动的诗章:运行时数据结构”,里面对程序的堆、栈和段进行了介绍,对你理解链接脚本中的相关内容有着很好的促进作用。最后,我还是希望能有更多的爱好者可以从本系列教程中受益匪浅。

本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0许可协议。获得许可后,要求转载时注明文章出处和网站链接,谢谢!