一、引言
在嵌入式開發領域,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工程目錄構建
在項目存儲路徑下創建以下目錄結構,已將代碼遞交到gitee倉庫 https://gitee.com/myliujiuri/gd32f303_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.SARM_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 = app
:定義了最終生成的可執行文件的名稱為app
。在后續的構建規則中,所有的中間文件和最終輸出文件都圍繞這個名稱展開,如$(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 $@
命令創建目錄,如果目錄已經存在,該命令不會產生錯誤。這個目錄在構建過程中用于存放中間文件和最終輸出文件,確保項目目錄結構的清晰。
-