VSCode编辑+GCC for ARM交叉编译工具链+Makefile构建+openocd调试(基于STM32的标准库)

对于嵌入式开发人员,想必最熟悉MDK5或者IAR的IDE开发了。MDK5的调试和仿真功能是其他IDE所不能比拟的,IAR我没用过,本文章只是兴趣指导,旨在搭建一套VSCode的嵌入式开发环境,了解从编译到调试的过程。当然,项目的合作开发还是MDK5的兼容性好。
用到的工具有:
1)VSCode:微软旗下的开源编辑器,需要搭配插件使用。
2)arm-none-eabi-gcc开源的交叉编译工具链,PC平台和嵌入式平台交叉编译,将编译到调试的一整套过程链接起来。
3)Make工具:识别并执行Makefile文件。Make工具的安装可以参考这个链接:https://developer.aliyun.com/article/1113601
在这里插入图片描述

4)Makefile文件:自动化构建脚本,负责文件的编译顺序和编译规则等等功能(这里不细说,大家可自行探索)。
5)openocd工具:作为PC平台到嵌入式平台的软件接口调试工具,与JLINK/STlink/CMSIS-DAPlink硬件接口对应。
上述的下载方式和环境变量设置自行搜索,网上的教程都很细。这里不作过多介绍,我们进入正题:
本文以STM32F401CCU6单片机作为示例,来进行演示。
前提:需要安装好对应的VSCode插件
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、工程架构
在这里插入图片描述
二、VSCode各个脚本文件
task.json,主要作用是使用XX名称,执行XX命令和XX参数。

//任务脚本
{
    //ctrl+shift+B
    "tasks": [
        //编译
        {
            "type": "shell",
            "label": "build",
            "command": "make -j 8",
            "args": [],
            "problemMatcher": [
                "$gcc"
            ],
            "group": "build"
        },
        //清除
        {
            "type": "shell",
            "label": "clean",
            "command": "make clean",
            "args": [],
            "problemMatcher": [
                "$gcc"
            ],
            "group": "build"
        },
        //重编译
        {
            "type": "shell",
            "label": "rebuild",
            "command": "make -j 8",
            "args": [],
            "problemMatcher": [
                "$gcc"
            ],
            "group": "build",
            "dependsOn": [ //每次执行这个任务,会先执行clean任务,再执行build任务,这便是所谓的依赖。
                "clean"
            ]
        },
        //下载
        {
            "type": "shell",
            "label": "flash",
            "command": "openocd",
            "args": [
                "-f",
                // "interface/stlink.cfg",
                "interface/cmsis-dap.cfg",
                "-f",
                "target/stm32f4x.cfg",
                "-c",
                "program build/${workspaceRootFolderName}.elf verify reset exit" //将工程根目录名称作为可执行文件名称
            ], /*command+args相当于主命令+子命令,也就是openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c "program Project.hex verify reset exit"的效果*/
            "problemMatcher": [
                "$gcc"
            ],
            "group": "build",
            "dependsOn": [ //每次执行这个任务,会先执行clean任务,再执行build任务,这便是所谓的依赖。
                "build"
            ]
        },
        {
            "type": "cppbuild",
            "label": "C/C++: arm-none-eabi-gcc.exe 生成活动文件",
            "command": "D:/Software/arm_riscv_develop_tools/arm-none-eabi-gcc/10 2021.10/bin/arm-none-eabi-gcc.exe",
            "args": [
                "-fdiagnostics-color=always",
                "-g",
                "${file}",
                "-o",
                "${fileDirname}\\${fileBasenameNoExtension}.exe",
                "-mcpu=cortex-m4",
                "-mthumb",
                "-mfpu=fpv4-sp-d16",
                "-mfloat-abi=hard"
            ],
            "options": {
                "cwd": "D:/Software/arm_riscv_develop_tools/arm-none-eabi-gcc/10 2021.10/bin"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": "build",
            "detail": "编译器: D:/Software/arm_riscv_develop_tools/arm-none-eabi-gcc/10 2021.10/bin/arm-none-eabi-gcc.exe"
        }
    ],
    "version": "2.0.0"
}

launch.json,主要作用是MCU调试,以及选择什么样的下载调试器。

//调试脚本
{
    "configurations": [
        {
            "name": "Debug with OpenOCD",
            "cwd": "${workspaceRoot}",
            "executable": "./build/${workspaceRootFolderName}.elf", //将工程根目录名称作为可执行文件名称
            "request": "launch",
            "type": "cortex-debug",
            "servertype": "openocd",
            "device": "STM32F401CCU6",
            "configFiles": [
                // "interface/stlink.cfg", 
                "interface/cmsis-dap.cfg",
                "target/stm32f4x.cfg"
            ],
            "svdFile": "./STM32F401.svd", //选择寄存器文件
            "liveWatch": {
                "enabled": true,
                "samplesPerSecond": 4
            },
            "searchDir": [],
            "runToEntryPoint": "main",
            "showDevDebugOutput": "none",
            "preLaunchTask": "flash" //每次调试之前会先调用下载任务再调试
        }
    ],
    "version": "2.0.0"
}

settings.json,主要作用是设置一些快捷功能,以及配合插件Task Buttons来显示编译、重编译、下载、调试等UI按钮。

//UI和功能设置脚本
{
    // 1秒后自动保存
    "files.autoSave": "afterDelay",
    // 开启 material icons
    "workbench.iconTheme": "vscode-icons",
    // theme主题设置
    "workbench.colorTheme": "Dracula Theme",
    // 粘贴的时候格式化
    "editor.formatOnPaste": true,
    // 保存的时候格式化
    "editor.formatOnSave": true,
    // 字体样式
    // "editor.fontFamily": "'Fira Code', monospace",
    // 字体大小
    "editor.fontSize": 16,
    // 字体宽度
    // "editor.fontWeight": "bold",
    "security.workspace.trust.enabled": false,
    "VsCodeTaskButtons.showCounter": true,
    "VsCodeTaskButtons.tasks": [
        {
            "label": "$(tools) Build",
            "task": "build",
            "tooltip": "🛠️ build"
        },
        {
            "label": "$(notebook-delete-cell) Clean",
            "task": "clean",
            "tooltip": "🧹 clean"
        },
        {
            "label": "$(notebook-delete-cell) $(tools) Re-bulid", //"$(notebook-delete-cell) & $(tools)",
            "task": "rebuild",
            "tooltip": "🛠️ rebuild" // "🧹 & 🛠️ rebuild"
        },
        {
            "label": "$(zap) Flash",
            "task": "flash",
            "tooltip": "⚡ flash"
        }
    ],
    // "terminal.integrated.defaultProfile.windows": "Git Bash",
    "C_Cpp.default.cppStandard": "c++17",
    "C_Cpp.default.intelliSenseMode": "gcc-arm",
    "C_Cpp.default.cStandard": "c99",
    "C_Cpp.default.compilerPath": "D:/Software/arm_riscv_develop_tools/arm-none-eabi-gcc/10 2021.10/bin/arm-none-eabi-gcc.exe"
}

c_cpp_properties.json,主要作用是选择对应MCU的宏定义展开,以及MCU的编译选项参数(或者说MCU硬件参数)

{
    "configurations": [
        {
            "name": "STM32F401CCU6",
            "includePath": [
                "${workspaceFolder}/**"
            ],
            "defines": [
                "_DEBUG",
                "UNICODE",
                "_UNICODE",
                "USE_STDPERIPH_DRIVER",
                "STM32F401xx"
            ],
            "compilerPath": "D:/Software/arm_riscv_develop_tools/arm-none-eabi-gcc/10 2021.10/bin/arm-none-eabi-gcc.exe",
            "compilerArgs": [
                "-mcpu=cortex-m4",
                "-mthumb",
                "-mfpu=fpv4-sp-d16",
                "-mfloat-abi=hard"
            ],
            "cStandard": "c99",
            "cppStandard": "c++17",
            "intelliSenseMode": "gcc-arm"
        }
    ],
    "version": 4
}

三、MCU链接脚本.ld
该文件的主要功能是划分SRAM和Flash的起始地址和地址空间。和keil这里的设置原理基本一致。
在这里插入图片描述

获取方法,
第1种方法:使用STM32CubeMX生成一个简单的STM32F401CCU6工程,对应文件夹下含有。
第2种方法:也可以根据不同的MCU内核自定义修改,这种需要耐心一点。
我这里按照第1种方法直接拷贝STM32CubeMX生成的.ld文件。
在这里插入图片描述
四、MCU汇编启动文件.s
该文件的功能是包含了中断向量表信息。需要注意的是,ARMcc/ARMclang与GCC for ARM两者交叉编译工具链下的启动文件是不通用的,这里我们需要选择GCC for ARM工具链对应的汇编启动文件。
获取方法,
第1种方法:同样是使用STM32CubeMX生成一个简单的STM32F401CCU6工程,对应文件夹下含有。
第2种方法:去STM官网==https://www.st.com.cn/content/st_com/zh.html==,按照以下步骤找到STM32F4系列的标准库
我个人是按照第2种方法添加的启动文件。
在这里插入图片描述

在这里插入图片描述
由于阿美莉卡国的封禁,ST官网目前不给予中国地区用户下载权限,我们可以选择访客的方式登录,然后进行邮箱验证,即可下载,具体自行操作。
在这里插入图片描述
在下载的标准库文件中,按照以下路径:en.stsw-stm32065_v1-9-0\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\CMSIS\Device\ST\STM32F4xx\Source\Templates\gcc_ride7,即可寻找到GCC对应的汇编启动文件。如下图所示,
在这里插入图片描述
将该启动文件复制到我们的工程目录下,
在这里插入图片描述
五、Makefile脚本

##########################################################################################################################
# File automatically-generated by tool: [projectgenerator] version: [4.2.0-B44] date: [Fri Jul 05 19:24:39 CST 2024]
##########################################################################################################################

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

######################################
# target-------编译生成目标烧写软件的名称
######################################
TARGET = F401CCU6_demo


######################################
# building variables
######################################
# debug build
# /*
# 编译选项:是否debug模式,如果DEBUG=1,则可以后续使用调试软件gdb等工具进行在线调试
# 如果DEBUG=0,则不能支持在线调试,
# 且DEBUG=1,生成的文件比DEBUG=0大,因为里面包含了调试信息。
# */
DEBUG = 1

# optimization
# /*
# 编译选项:优化等级
# -O0:无任何优化,
# -O1:1级优化,
# -O2: 2级优化,
# -Os: 2.5级优化,
# -O3: 最高级优化。
# -Og:优化调试体验。 -Og启用不会干扰调试的优化。 它是标准编辑 - 编译 - 调试周期可以选择的优化级别,提供合理的优化级别,同时保持快速编译和良好的调试体验。
# */
OPT = -Og


#######################################
# paths
#######################################
# Build path
# /*
# 编译路径: 生成的编译文件保存在build文件夹中,
# 这样做的好处是工程框架比较清晰,且清除编译文件比较简单。
# */
BUILD_DIR = build

######################################
# source-------工程所有需要编译的C文件: 指定需要编译的C文件名称相对路径
######################################
# C sources
C_SOURCES =  \
$(wildcard *.c ./CMSIS/Source/*.c) \
$(wildcard *.c ./Core/Src/*.c) \
$(wildcard *.c ./STM32F4xx_StdPeriph_Driver/src/*.c) \
$(wildcard *.c ./User/Src/*.c) \
# STM32F4xx_StdPeriph_Driver/src/stm32f4xx_gpio.c
# STM32F4xx_StdPeriph_Driver/src/stm32f4xx_syscfg.c
# STM32F4xx_StdPeriph_Driver/src/stm32f4xx_usart.c
# STM32F4xx_StdPeriph_Driver/src/stm32f4xx_exti.c
# STM32F4xx_StdPeriph_Driver/src/stm32f4xx_pwr.c



# ASM sources
# 工程所有需要编译的汇编文件: 指定需要编译的汇编文件名称相对路径
# ASM_SOURCES =  \
# startup_stm32f103xe.s
ASM_SOURCES =
ASM_SOURCES += $(wildcard *.s ./startup_s/*.s)

# ASM sources
ASMM_SOURCES =


#######################################
# binaries
#######################################
# /*
# 工程使用编译的类型: arm-none-eabi-是基于arm芯片开发的编译器,
# none表示无操作系统,eabi表示交叉编译器,即在linux上编译嵌入式arm芯片的代码
# 生成可烧写文件。
# */
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.
# /*
# 编译器路径宏:表示arm-none-eabi-gcc在linux中调用是否需要带路径,
# 一般情况安装好arm-none-eabi-gcc后,系统将安装的可执行程序路径放在了系统的环境变量中,
# 无需要路径即可执行,所以GCC_PATH不用宏定义。
# */
ifdef GCC_PATH#如果定义了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类型,STM32芯片的内核是cortex-m4,指定该内核对应的寄存器库
CPU = -mcpu=cortex-m4

# fpu
# 编译选型:FPU浮点计算器,STM32支持浮点运算
FPU = -mfpu=fpv4-sp-d16

# float-abi
# 编译选型:浮点计算类型,设定硬件浮点运算,还可选型纯软件浮点计算,或者结合形式
FLOAT-ABI = -mfloat-abi=hard


# mcu
# 编译MCU总选项:M4内核,支持浮点运算,浮点计算采用硬件浮点计算器,指定生成thumb精简指令集,【默认不开启:允许编译器进行ARM指令和Thumb指令的相互调用】
# MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)
MCU = $(CPU) $(FPU) $(FLOAT-ABI) -mthumb -mthumb-interwork

# macros for gcc
# AS defines
# 汇编编译宏定义:该Makefile宏定义将在汇编代码中有效
AS_DEFS =

# C defines
# C文件编译宏定义:该Makefile宏定义将在C代码中有效
C_DEFS =  \
-DUSE_STDPERIPH_DRIVER \
-DSTM32F401xx

# AS includes----------汇编头文件路径:编译过程中文件内的头文件搜索路径
AS_INCLUDES =

# C includes--------C文件的头文件路径:编译过程中文件内的头文件搜索路径
C_INCLUDES =  \
-ICore/Inc \
-ICMSIS/Include \
-ISTM32F4xx_StdPeriph_Driver/inc \
-IUser/Inc
# compile gcc flags

# 汇编编译选型:s汇编文件编译成Obj文件需要的设置选项
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

# C编译选型:C文件编译成Obj文件需要的设置选项
CFLAGS += $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

# Debug模式下的C编译选型:仅仅在开启DEBUG模式下有效
ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif


# Generate dependency information
# /*
# C文件自动依赖关系 :-MMD -MP -MF"$(@:%.o=%.d)"
# 自动生成.d文件,里面保存了对应的源文件C代码中包含的非标准库的头文件路径和名称,
# 生成.d文件的目的是产生C文件生成obj的依赖文件,
# 当关联的头文件发生变化时,触发make重新生成obj文件。
# -MMD等同于-MM -MF,-MM表示依赖的头文件(不包括标准头文件夹,-M则是所有头文件),
# -MF生成依赖文件。
# */
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"


#######################################
# LDFLAGS
#######################################
# link script
# /*
# 可执行文件链接脚本:  STM32F401CCUx_FLASH.ld
# 文件中详细给出了芯片的RAM和ROM片区分类区间与大小,
# 代码、全局变量、常数、堆栈等的分配区间。
# */
# LDSCRIPT = STM32F103VETx_FLASH.ld
LDSCRIPT = ./link_script/STM32F401CCUx_FLASH.ld

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

# default action: build all
# Makefile总目标: 这是伪目标,在第一依赖关系位置,输入make指令时必定执行该目标
# 可执行文件elf , hex 和 bin 是arm的常用烧写文件
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
# build/LED_Toggle.elf
# build/LED_Toggle.hex
# build/LED_Toggle.bin


#######################################
# build the application
#######################################
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
# /*
# 指定C文件的搜索路径: $(sort $(dir $(C_SOURCES)))
# $(dir $(C_SOURCES)):所有源文件只保留文件路径,
# sort:对所有路径排序 ,‘d g a’ -> 'a d g'
# */
vpath %.c $(sort $(dir $(C_SOURCES)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASMM_SOURCES:.S=.o)))
vpath %.S $(sort $(dir $(ASMM_SOURCES)))

# /*
# 通配符(%)指定所有c文件编译成OBJ文件:
# $(BUILD_DIR)/%.o: 生成OBJ文件的路径固定不变,在BUILD_DIR文件夹,
# %.c:依赖源文件C文件,地址未指定,Makefile将在本地目录和vpath %c目录下搜索源文件,
# Makefile :Makefile文件自己也是生成obj文件的依赖文件,Makefile文件变化时会重新编译,
# | $(BUILD_DIR): 竖线左边的依赖文件是正常依赖文件,竖线右边的依赖文件是命令提前的依赖文件,即BUILD_DIR会自动执行,编译生成OBJ前生成build文件夹。
# -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)):生成临时中间文件
# -c:仅编译不链接 $<:第一个依赖文件即C文件   $@ 目标文件
# */
$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)
	$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@

# /*
# 通配符(%)指定所有汇编文件编译成OBJ文件:
# $(BUILD_DIR)/%.o: 生成OBJ文件的路径固定不变,在BUILD_DIR文件夹,
# %.c:依赖源文件C文件,地址未指定,Makefile将在本地目录和vpath %c目录下搜索源文件,
# Makefile :Makefile文件自己也是生成obj文件的依赖文件,Makefile文件变化时会重新编译,
# | $(BUILD_DIR): 竖线坐标的依赖文件是正常依赖文件,竖线右边的依赖文件是命令提前的依赖文件,
# -c:仅编译不链接 $<:第一个依赖文件即C文件   $@ 目标文件
# */
$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
	$(AS) -c $(CFLAGS) $< -o $@
$(BUILD_DIR)/%.o: %.S Makefile | $(BUILD_DIR)
	$(AS) -c $(CFLAGS) $< -o $@

# 生成可执行文件ELF文件:依赖于所有OBJ文件
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
	$(CC) $(OBJECTS) $(LDFLAGS) -o $@
	$(SZ) $@

# 生成HEX文件:依赖于elf文件和build文件夹
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(HEX) $< $@

# 生成BIN文件:依赖于elf文件和build文件夹
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(BIN) $< $@

# 生成build文件夹:伪指令
# 通过上面的优先依赖关系生成
$(BUILD_DIR):
	mkdir $@


#######################################
# clean up
#######################################
# /* 清除编译结果:将build文件中所有文件和子文件夹删除。*/
clean:
	-rm -fR $(BUILD_DIR)

#######################################
# dependencies
#######################################
# 添加所有.d依赖文件
-include $(wildcard $(BUILD_DIR)/*.d)

# *** EOF ***

如果顺利的话,到这里就能进行我们的编译、重编译、下载、调试操作了。
在这里插入图片描述
由于篇幅过长,后续我会各出一篇文章讲解Makefile脚本需要修改的地方各个VSCode脚本需要修改的地方.svd寄存器文件如何根据不同厂商的MCU修改.ld链接文件如何根据不同厂商的MCU修改
--------------------------------------------------------------------欢迎各位+QQ交流:1986317910----------------------------------------------------------------------
如果需要工程文件和细节指导可作交流,不作任何收费用途。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值