如何使用C++优雅地编写STM32工程

前言

为什么要引入C++
在单片机开发领域,C语言无疑是占大头的,由于C语言执行迅速,编译体积小巧且逻辑清晰,相比其他语言有了十足的优势

但是…没错但是,当一个工程稍微复杂起来的时候,你是否经历过因为名称被占用而不得不绞尽脑汁想一个合法名称的窘境?或是在一堆相似的函数里迷失自我?亦或是苦恼于自己不想被他人使用的工具函数暴露在外?对于C来说,想要解决这些问题就很麻烦了,于是乎,我引入C++的理由也就来了

对于一段有效的C程序,它同时可以几乎不加修改便能在C++下运行,且C++引入了名称空间,可以很好的解决命名冲突、重复的问题,且最重要的,C++有类的特性,这在大型项目开发中可以节约大量的时间,且结构清晰明了,也许写一个类很烦躁,但使用一个类却非常容易,而且,基于类的编写方式让程序在各个平台上的移植变得非常容易,此外,我还能列举出引入C++许多别的理由 (主要是比纯C逼格更高)

也许你担心引入C++后会占用更多原本就宝贵的单片机资源,但经过我的测试,这个”多”占用的部分实际上很少很少,在编译器的加持下,与C源码编译后的结果相差无几

好了,吹了半天C++,现在让我们来进入正题

准备工作

在开始之前你需要的是什么?当然是一个STM32的开发环境,没有的话赶紧去用CubeMX生成一个,这里我准备了一个F103C8T6的HAL库工程

对于MDK工程方式

对于MDK开发方式来说引入C++是很简单的,只需要在工程设置里关闭使用MicroLIB,然后再将你的源文件从*.c重命名为*.cpp就行了,别忘了对原有的库包含使用extern "C"关键字
关闭MicroLIB
所需要做的一切就是这些,非常的简单,如果你的主要开发方式是MDK,那么,你可以退出了

对于MakeFile工程方式

厌倦了MDK古早的界面和奇奇怪怪的附属包?我就喜欢轻量级的MakeFile工程,配合armgcc工具链,在VsCode编辑,使用openOCD等工具下载,优雅~

但是,将它更改为C++编译方式就比较麻烦了,不过完成后用得很爽也是真的….

总的来说,有下面四个步骤

  1. 处理工程目录
  2. 处理源文件
  3. 处理MakeFile

让我们一步一步来

1.处理工程目录

原本工程目录下的文件都是.c文件,我们需要将Core目录下的C源文件与我们之后的C++源文件分开放置便于管理,在这里我选择新建了一个文件夹C,并将原本的IncSrc文件夹放到新建的C文件夹内

与此同时,在C同级目录下新建CPP文件夹,里面与C文件夹内相同,需要新建一个IncSrc文件夹

再然后,于CPP/Src内添加real_main.cpp,于CPP/Inc内添加real_main.hpp,为什么要新建这两个文件?既然都打算使用C++编写了,当然要将main函数写在C++源文件下嘛,至于为什么起这个名…..你也可以改,那是你的自由

处理完后成后的工程目录长这样:
工程目录

2.处理源文件

首先是对Core/C/Inc/main.h的处理,它是main.c的重要依赖文件,也是与HAL库连接的入口,我们需要把原本放置在extern"C"内的内容释放出来,为了简洁,我删掉了CubeMX生成的注释,真正有效的内容只有这么一点点
处理main.h
然后,我们在Core/CPP/Inc/real_main.hpp内添原本位于main.c内的需要用到的函数原型并包含main.h,当然,不要忘记extern "C"

处理好的real_main.hpp长这样:
处理real_main.hpp

顺便,一定要去确认下位于main.c下的MX_GPIO_Init()函数定义是否包含了准确的GPIO区,在生成空GPIO定义的工程者某些特殊情况下,CubeMX可能会漏掉开启某些GPIO区域的操作,需要自己手动补上

最后,当然是把main函数整个从main.c里删掉,搬到real_main.cpp啦~~
看起来非常简洁,比起原来的main.c一堆注释舒服多了

处理好的real_main.cpp长这样:
处理real_main.cpp

3.处理MakeFile

这里是需要处理改动最多的地方,有整整7处需要修改,不过基本也就照猫画虎复制来复制去就行了,我在这里贴一份我改好的MakeFile,
借助搜索功能搜索1./2./3./~~~~7.来快速预览对应的修改处吧

[点击展开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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247

##########################################################################################################################
# File automatically-generated by tool: [projectgenerator] version: [3.13.0-B3] date: [Sat Aug 21 14:57:16 CST 2021]
##########################################################################################################################

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

######################################
# target
######################################

#=====================================可选修改,设置目标命名=========================================

TARGET = target

#====================================================================================================


######################################
# building variables
######################################
# debug build?
DEBUG = 1
# optimization
OPT = -Og


#######################################
# paths
#######################################
# Build path
BUILD_DIR = build

######################################
# source
######################################

#=================================1.修改C源文件路径并添加CPP源文件路径=================================
# C源文件
C_SOURCES = \
Core/C/Src/main.c \
Core/C/Src/stm32f1xx_it.c \
Core/C/Src/stm32f1xx_hal_msp.c \
Core/C/Src/system_stm32f1xx.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_gpio_ex.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_tim.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_tim_ex.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_rcc.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_rcc_ex.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_gpio.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_dma.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_cortex.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_pwr.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_flash.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_flash_ex.c \
Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_exti.c

#添加C++文件
CXX_SOURCES = \
Core/CPP/Src/real_main.cpp

#==================================================================================================
# ASM sources
ASM_SOURCES = \
startup_stm32f103xb.s


#######################################
# binaries
#######################################
#=========================================2. 添加"BINPATH ="字段===================================
BINPATH =
#==================================================================================================

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.


#=========================================3. 增加C++文件编译方式====================================
CXX = $(BINPATH)$(PREFIX)g++
#===================================================================================================


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
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S

#######################################
# CFLAGS
#######################################
# cpu
CPU = -mcpu=cortex-m3

# fpu
# NONE for Cortex-M0/M0+/M3

# float-abi


# mcu
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)

# macros for gcc
# AS defines
AS_DEFS =

# C defines
C_DEFS = \
-DUSE_HAL_DRIVER \
-DSTM32F103xB

# AS includes
AS_INCLUDES =

#=================================4.修改C头文件路径并添加CPP头文件路径==================================


# C 头文件目录
C_INCLUDES = \
-ICore/C/Inc \
-IDrivers/STM32F1xx_HAL_Driver/Inc \
-IDrivers/STM32F1xx_HAL_Driver/Inc/Legacy \
-IDrivers/CMSIS/Device/ST/STM32F1xx/Include \
-IDrivers/CMSIS/Include

# C++ 头文件目录
CXX_INCLUDES = \
-ICore/CPP/Inc

#=============================================================================================
# compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections


#================================5.增加编译器参数设置===============================================


CXXFLAGS = -lstdc++ $(CFLAGS) $(CXX_DEFS) $(CXX_INCLUDES) -g -ggdb3 -fno-rtti -fno-exceptions \
-fverbose-asm -fdata-sections -ffunction-sections -fpermissive -Wa,-ahlms=$(BUILD_DIR)/$(notdir $(<:.cpp=.lst))


#====================================================================================================


ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif


# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"


#######################################
# LDFLAGS
#######################################
# link script
LDSCRIPT = STM32F103C8Tx_FLASH.ld

# libraries
LIBS = -lc -lm -lnosys
LIBDIR =
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections

# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin


#######################################
# build the application
#######################################
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))

#================================6.添加C++目标文件================================================

OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(CXX_SOURCES:.cpp=.o)))
vpath %.cpp $(sort $(dir $(CXX_SOURCES)))

#=================================================================================================


# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))

$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)
$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@


#=================================7.增加C++目标文件连接设置==========================================
$(BUILD_DIR)/%.o: %.cpp Makefile | $(BUILD_DIR)
$(CXX) -c $(CXXFLAGS) $< -o $@

#=====================================================================================================


$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
$(AS) -c $(CFLAGS) $< -o $@

$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
$(SZ) $@

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

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

$(BUILD_DIR):
mkdir $@

#######################################
# clean up
#######################################
clean:
-rm -fR $(BUILD_DIR)

#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)

# *** EOF ***

然后,只需要简单的make指令即可完成整个工程的构建
如果需要添加新的源文件,记得向MakeFile添加对应的文件索引,配合bat脚本食用更佳哦

至此,一个优雅的C++与C混合编译的STM32工程就构建完成了,enjoy!!!

相关参考资料

MakeFile修改参考
工程修改参考


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!