一、引言
在嵌入式开发领域,GD32F303 微控制器以其出色的性能和丰富的功能被广泛应用。为了充分发挥其潜力,搭建一个高效的开发环境并深入理解项目构建过程至关重要。本文将详细介绍如何基于 GCC 工具链搭建 GD32F303 的开发环境,重点聚焦于 Makefile 文件的编写与解析,助力开发者快速上手项目开发。
二、工具链安装
以下是在 Linux 系统上搭建 GCC 工具链的详细步骤:
下载工具链
- 使用
wget
命令从上述下载链接下载工具链(以下是一个示例,根据你选择的版本更新 URL):
wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/10.3-2021.10/gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2
解压工具链
将工具链解压到 /opt
目录下,这是一个常见的系统级软件安装目录。使用以下命令进行解压:
sudo tar -xvf gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 -C /opt
配置环境变量
- 为了能够在命令行中方便地使用工具链,需要将工具链的
bin
目录添加到系统的PATH
环境变量中。你可以将以下命令添加到用户的.bashrc
或系统的/etc/profile
文件中:
export PATH=$PATH:/opt/gcc-arm-none-eabi-10.3-2021.10-x86_64-linux/bin
- 为了使环境变量的修改立即生效,执行以下命令(如果你将上述命令添加到
.bashrc
):
source ~/.bashrc
三、EmbeddedBuilder 获取相关文件
EmbeddedBuilder 是一款基于 Eclipse 和 Java 平台的软件,用于开发 GD32 系列单片机。它具有图形化界面,方便用户进行引脚和外设的配置,并能自动生成代码。通过EmbeddedBuilder 工具生成我们在gcc环境下所需要的文件和代码。
官网地址及安装步骤
EmbeddedBuilder 的官网地址为:https://gd32mcu.com/cn/download/7 。
安装步骤如下:
- 安装 JAVA 环境:在 Oracle 官网下载相应的 Java 安装包(如 jdk-8u152-windows-x64.exe),以管理员身份运行并安装,记住安装路径(如 “D:\Program Files\Java\jdk1.8.0_351”)。然后编辑系统变量,添加 JAVA_HOME 变量,并在 Path 变量中添加相关路径,在系统变量中新建 CLASSPATH 变量。最后在 Windows+R 键打开的 dos 窗口中分别输入 java 和 javac,若能正常输出提示信息则说明配置成功。
- 下载 EmbeddedBuilder:从官网下载 EmbeddedBuilder 压缩包。
- 解压并运行:解压后双击 “EmbeddedBuilder.exe” 打开 IDE,选择一个路径作为 workspace 的存放位置,确认后即可进入 IDE 页面。|
创建GD32F303工程
-
创建新工程:在导航栏依次单击 “File->New->Project…”,选择 C Project,并在可执行文件 “Executable” 选项卡下选择 “GigaDevice ARM C Project”,填写项目名字后进行芯片选择和其他配置。
-
导入工程:在导航栏处依次选择 “File->Import”,在导入页面的 General 选项卡下选择 “Existing Projects into Workspace”,选择原有工程的路径,IDE 会自动检索并列出存在的 Embedded Builder 项目,勾选需要导入的项目后单击 Finish 即可。
获取相关文件
-
官方库:GD32F303 的官方库提供了丰富的驱动函数和底层支持,能够大大简化开发过程。通过 EmbeddedBuilder 可以方便地获取官方库,并将其集成到项目中
-
LD文件:LD 文件(链接脚本)在项目构建中起着关键作用,它描述了如何将各个目标文件组合成最终的可执行文件,并确定内存布局。
-
启动文件:.s 文件通常包含汇编启动代码,是系统启动过程的关键部分。
GCC工程目录构建
在项目存储路径下创建以下目录结构
四、Makefile
makefile需要我们自己来实现,以下是一个标准的 Makefile 模板,用户可以根据实际项目情况进行修改。
# target
TARGET = app
# building variables
DEBUG = 1
OPT = -Os
# paths
BUILD_DIR = build
# source
C_SOURCES = \
Firmware/CMSIS/GD/GD32F30x/Source/system_gd32f30x.c \
# 其他 C 源文件路径省略
C_INCLUDES = \
-IFirmware/CMSIS \
# 其他头文件路径省略
ASM_SOURCES = \
Firmware/CMSIS/GD/GD32F30x/Source/GCC/startup_gd32f30x_hd.S
ARM_TOOCHAIN?=./Toolchain/arm-gnu-toolchain/bin
# binaries
PREFIX = $(ARM_TOOCHAIN)/arm-none-eabi-
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 = -mcpu=cortex-m4
FPU =
FLOAT-ABI =
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)
AS_DEFS =
C_DEFS = \
-DUSE_STDPERIPH_DRIVER \
-DGD32F30X_HD
AS_INCLUDES =
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections
CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections -std=c99
ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"
# LDFLAGS
LDSCRIPT = Firmware/Ld/gd32f30x_flash.ld
LIBS = -lc -lm -lnosys
LIBDIR =
LDFLAGS = $(MCU) -u_printf_float -specs=nosys.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections -Wl,--print-memory-usage
# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
# build the application
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
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 $@
$(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 $@
# program
program:
openocd -f /usr/share/openocd/scripts/interface/cmsis-dap.cfg -f /usr/share/openocd/scripts/target/stm32f1x.cfg -c "program build/$(TARGET).elf verify reset exit"
# clean up
clean:
-rm -fR $(BUILD_DIR)
# dependencies
-include $(wildcard $(BUILD_DIR)/*.d)
五、Makefile 详解
目标与变量定义
- 目标(Target)
TARGET = subBoard
:定义了最终生成的可执行文件的名称为subBoard
。在后续的构建规则中,所有的中间文件和最终输出文件都围绕这个名称展开,如$(BUILD_DIR)/$(TARGET).elf
、$(BUILD_DIR)/$(TARGET).hex
和$(BUILD_DIR)/$(TARGET).bin
分别表示生成的 ELF 格式可执行文件、十六进制文件和二进制文件。
- 构建变量
DEBUG = 1
:用于控制是否开启调试信息。当DEBUG
为 1 时,在CFLAGS
中会添加调试相关的编译选项-g -gdwarf-2
,这些选项使得生成的可执行文件包含调试符号,方便在调试器中进行源代码级别的调试。OPT = -Os
:指定了优化级别为Os
,这是一种针对代码大小的优化选项。GCC 提供了多种优化级别,如-O0
(不优化)、-O1
(基本优化)、-O2
(更高级别的优化)、-O3
(激进的优化)等,-Os
会在保证一定性能的前提下尽量减小代码体积,适用于资源受限的嵌入式系统。
- 路径变量
BUILD_DIR = build
:定义了构建过程中生成的中间文件和最终输出文件的存放目录为build
。在后续的规则中,所有的目标文件(.o
文件)、可执行文件等都会存放在这个目录下,通过这种方式可以保持项目目录的整洁,便于管理和清理构建产物。
源文件与头文件路径
- C 源文件(C_SOURCES)
- 这里列出了项目中所有的 C 语言源文件路径,包括来自固件库(如
Firmware/CMSIS/GD/GD32F30x/Source/system_gd32f30x.c
)和用户自定义的源文件(如User/main.c
等)。这些源文件是项目的核心代码部分,在构建过程中会被编译成目标文件(.o
文件),然后链接成最终的可执行文件。在实际项目中,随着功能的增加,可能会不断添加新的源文件到这个列表中。
- 这里列出了项目中所有的 C 语言源文件路径,包括来自固件库(如
- C 头文件路径(C_INCLUDES)
- 定义了 C 语言源文件在编译时所需的头文件搜索路径。例如
-IFirmware/CMSIS
表示编译器会在Firmware/CMSIS
目录下搜索头文件。这些头文件包含了函数声明、宏定义和类型定义等信息,对于源文件的正确编译至关重要。如果头文件路径设置不正确,编译器将无法找到相应的头文件,导致编译错误。
- 定义了 C 语言源文件在编译时所需的头文件搜索路径。例如
工具链相关定义
- 工具链路径(ARM_TOOCHAIN)
ARM_TOOCHAIN?=./Toolchain/arm-gnu-toolchain/bin
:指定了 ARM GNU 工具链的安装路径。这里使用了条件赋值?=
,如果在外部没有定义ARM_TOOCHAIN
变量,就会使用这个默认值。工具链包含了编译器(gcc
)、汇编器(as
)、链接器(ld
)等工具,是将源代码转换为可执行文件的关键。
- 工具前缀(PREFIX)
PREFIX = $(ARM_TOOCHAIN)/arm-none-eabi-
:定义了工具链中各个工具的前缀。例如,$(PREFIX)gcc
就是实际调用的 ARM 架构的 GCC 编译器。这个前缀确保了在系统中安装了多个工具链或存在不同版本工具链时,能够准确地调用所需的工具。
- 编译器及相关工具定义
- 根据是否定义了
GCC_PATH
变量,分别设置了编译器(CC
)、汇编器(AS
)、目标文件复制工具(CP
)和文件大小查看工具(SZ
)的具体路径。如果定义了GCC_PATH
,则会在该路径下查找工具,否则使用默认的PREFIX
路径下的工具。例如,CC = $(PREFIX)gcc
表示使用默认路径下的 GCC 编译器进行 C 语言源文件的编译。
- 根据是否定义了
编译参数(CFLAGS 和 ASFLAGS)
- 通用编译参数
CPU = -mcpu=cortex-m4
:指定了目标处理器为 Cortex-M4 内核。这是因为 GD32F303 基于 Cortex-M4 内核,编译器需要针对这个内核进行特定的代码生成和优化。MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)
:综合了处理器设置、Thumb 指令集启用以及浮点运算相关设置(这里FPU
和FLOAT-ABI
根据实际情况可能为空或有特定设置)。-mthumb
表示启用 Thumb 指令集,这是一种在 ARM 架构中常用的指令集模式,能够减小代码体积。CFLAGS
和ASFLAGS
都包含了$(MCU)
、$(OPT)
、-Wall
、-fdata-sections
、-ffunction-sections
等参数。-Wall
启用了所有常见的警告信息,有助于在编译过程中发现潜在的问题。-fdata-sections
和-ffunction-sections
分别将数据和函数放入独立的节区,这在链接阶段可以实现更精细的内存管理和优化,例如可以只链接实际使用到的节区,减小最终可执行文件的大小。
- 调试相关参数
- 当
DEBUG = 1
时,CFLAGS += -g -gdwarf-2
。-g
选项开启调试信息生成,-gdwarf-2
指定了调试信息的格式为 DWARF-2,这是一种广泛支持的调试信息格式,使得调试器能够正确解析源代码和变量信息,方便开发者进行调试。
- 当
- 依赖信息生成参数
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"
:这些参数用于自动生成源文件的依赖关系信息。-MMD
表示生成依赖文件(.d
文件),-MP
会为每个依赖添加一个虚拟的目标,避免在头文件更新时出现不必要的错误,-MF
则指定了依赖文件的名称格式,其中$(@:%.o=%.d)
表示将目标文件(.o
文件)的扩展名替换为.d
作为依赖文件的名称。这些依赖文件在后续的构建过程中会被 Makefile 自动包含,确保在源文件或头文件发生变化时,能够正确地重新编译相关的文件。
链接参数(LDFLAGS)
- 链接脚本(LDSCRIPT)
LDSCRIPT = Firmware/Ld/gd32f30x_flash.ld
:指定了链接脚本文件的路径。链接脚本详细描述了可执行文件的内存布局,包括代码段、数据段在 Flash 和 RAM 中的位置和大小等信息。对于 GD32F303 项目,这个链接脚本需要根据芯片的内存映射进行定制,确保程序能够正确地加载和运行。
- 库文件与链接选项
LIBS = -lc -lm -lnosys
:列出了链接过程中需要链接的库文件。-lc
是 C 标准库,-lm
是数学库,-lnosys
通常用于提供一些系统调用的空实现,在嵌入式系统中可能不需要完整的操作系统级别的系统调用,这个库可以提供一些基本的替代实现。LDFLAGS
还包含了-u_printf_float
、-specs=nosys.specs
、-Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections -Wl,--print-memory-usage
等参数。-u_printf_float
确保链接器在链接时包含浮点格式的printf
函数。-specs=nosys.specs
指定了链接器的规范文件,用于调整链接行为。-Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref
会生成一个链接映射文件,该文件详细展示了可执行文件中各个节区的内存地址分配、符号引用等信息,方便开发者分析程序的内存布局。-Wl,--gc-sections
启用了链接器的垃圾回收功能,会删除未使用的节区,进一步减小可执行文件的大小。-Wl,--print-memory-usage
则会在链接过程中输出内存使用情况的统计信息,帮助开发者了解程序的内存占用情况。
构建规则
-
目标文件生成规则
$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)
和$(BUILD_DIR)/%.o: %.S Makefile | $(BUILD_DIR)
:这两条规则分别定义了如何从 C 源文件和汇编源文件生成目标文件(.o
文件)。对于 C 源文件,使用$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@
命令进行编译。其中$(CC)
是前面定义的 C 编译器,-c
表示只进行编译不进行链接,$(CFLAGS)
是编译选项,-Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst))
是传递给汇编器的选项,用于生成汇编列表文件(.lst
文件),$<
表示第一个依赖文件(即源文件),$@
表示目标文件。对于汇编源文件,使用$(AS) -c $(CFLAGS) $< -o $@
进行编译,其中$(AS)
是汇编器,过程类似。
-
可执行文件生成规则
-
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
:这条规则定义了如何从所有的目标文件生成最终的 ELF 格式可执行文件。使用$(CC) $(OBJECTS) $(LDFLAGS) -o 命令进行链接,其中(CC)
是编译器,是前面生成的所有目标文件列表,(LDFLAGS)
是链接选项,表示目标文件(即(BUILD_DIR)/)。链接完成后,还使用(SZ) 命令查看生成的可执行文件的大小信息,这里(SZ)
是前面定义的文件大小查看工具。
\3. 十六进制和二进制文件生成规则$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
和$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
:这两条规则分别定义了如何从 ELF 格式可执行文件生成十六进制文件和二进制文件。对于十六进制文件,使用$(HEX) $< $@
命令,其中$(HEX)
是前面定义的目标文件转换工具,$<
表示输入文件(即 ELF 文件),$@
表示目标文件(即十六进制文件)。对于二进制文件,使用$(BIN) $< $@
命令,过程类似。
- 目录创建规则
$(BUILD_DIR):
:这条规则定义了如何创建构建目录$(BUILD_DIR)
。使用mkdir $@
命令创建目录,如果目录已经存在,该命令不会产生错误。这个目录在构建过程中用于存放中间文件和最终输出文件,确保项目目录结构的清晰。
-