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

对于嵌入式开发人员,想必最熟悉Keil MDK或者IAR的IDE开发了。Keil MDK的调试和仿真功能是其他IDE所不能比拟的,在公司项目合作开发方面的兼容性也是非常优秀;至于IAR IDE,笔者没用过。
本文旨在搭建一套基于VSCode的嵌入式开发环境,了解从编译到调试的过程。


⭐⭐⭐NOTE:
下述的GitHub链接皆参考于xPack项目,xPack开源项目是一个针对于C/C++与嵌入式跨平台第三方工具的可重现构建框架,一位名为 Liviu Ionescu (ilg) 的大佬及其团队一直在维护迭代,感谢他们的付出,致敬拥抱开源。该框架存在的目的是让人们不再重复造轮子!
诸如 aarch64-none-elf-gccarm-none-eabi-gccriscv-none-elf-gccriscv-none-embed-gccclangcmakemingw-w64-gccNinja BuildOpenOCDwindows-build-toolsGNU sedQEMU ArmQEMU RISC-V等第三方工具资源都整合在xPack项目,
至此形成了一个优秀的GitHub开源项目,我们需要时可以利用Git直接拉取🔗 xpack-dev-tools(GitHub加载缓慢时的话,请移步下载🔗Watt Toolkit或者🔗FastGithub加速)。


工具列表

(1)VSCode

微软旗下的开源-现代化-轻量级-编辑器,配合插件使用。

下载请进微软官网🔗https://code.visualstudio.com/Download,建议安装System Installer版本

—————————————————————————————————————————————————————————
⭐NOTE:从此处开始,建议在电脑D盘或E盘或F盘,新建文件夹embedded_dev_tools,安装以下的工具,如下图所示:
在这里插入图片描述
—————————————————————————————————————————————————————————

(2)arm-none-eabi-gcc

开源的交叉编译工具链。应用于PC平台和嵌入式平台交叉编译,负责从编译到调试的整套过程,包含编译器gcc、调试器gdb、链接器ld、镜像文件拷贝器objcopy、反汇编器objdump、程序存储计算器size。

⭐⭐⭐INFO:
交叉编译器的职责是将在一台机器下(PC)编写的代码翻译成二进制文件后,使得二进制文件可在另外一台机器上(arm/risc-v)运行.
编译器和交叉编译器最大的区别是: 交叉编译器编译生成的二进制文件只能在目标机器上(arm/risc-v)运行,而不能在本机(执行翻译交叉编译器指令的PC)上运行!

下载请进ARM官网的Arm GNU Toolchain下载页面🔗https://developer.arm.com/downloads/-/gnu-rm(已停止更新deprecated,截至Version 10.3-2021.10)
或者ARM官网的最新下载页面🔗https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads(与xPack同步更新,目前已迭代到Version 13.3.Rel1)
或者下载GitHub的最新xPack-arm-none-eabi-gcc二进制发行版本🔗https://github.com/xpack-dev-tools/arm-none-eabi-gcc-xpack/releases/tag/v13.3.1-1.1

⭐⭐⭐INFO:xPack GNU Arm Embedded GCC向下完全兼容Arm的GNU Arm Embedded Toolchain发行版

安装完成后,记得设置系统环境变量,打开cmd输入arm-none-eabi-gcc -v验证。

(3)MinGW

一套Minimalist GNU for Windows工具集合,由Linux系统下的GCC编译器发展移植而来,可理解为“Windows的迷你版GCC编译器”。

①MinGW官网🔗https://www.mingw-w64.org/,点击官网左侧的Downloads
在这里插入图片描述
②再下拉页面到 MinGW-W64-builds 处,点击GitHub,
在这里插入图片描述
③下载名称为 x86_64-14.2.0-release-win32-seh-ucrt-rt_v12-rev0.7z 的二进制发行版压缩包。
⭐⭐⭐INFO:
x86_64表示64bit-cpu架构,i686是32bit-cpu架构的衍生系列,从属于32bit架构;
release表示为正式发行版;
posix 或 win32指的是线程模型。posix,是 UNIX 系统的 API 设计标准,很多类 UNIX 系统也在支持兼容这个标准,如 Linux 操作系统。如果在 Windows 下开发 Linux 应用程序,则选择 posix;win32,是 Windows 系统的 API 设计标准,如果开发 Windows 平台下的应用程序,就需要选择 win32;
seh版本较新只支持64bit,dwarf版本较新只支持32bit,还有个sjlj版本较旧,同时支持32bit和64bit;
ucrt兼容C99标准,而msvcrt比较古老,不兼容C99标准。
在这里插入图片描述
或者选择下载GitHub的在线安装版:🔗mingwInstaller.exe,安装时的配置选择和上述二进制发行版保持一致即可。
或者下载笔者的CSDN资源请进🔗https://download.csdn.net/download/ZZLLLLLLZ/89709135

下面我们开始配置mingw64,安装方式:解压离线免安装包,将其放在一个软件常安装的地方(后续不改动),设置mingw64\bin目录为系统环境变量。
安装完成后,记得设置系统环境变量,打开cmd输入gcc -v验证。

(4)make工具

识别并执行Makefile脚本文件。

① sed下载、安装

下载sourceforge网站的sed.exe版本🔗 https://sourceforge.net/projects/gnuwin32/
或者去GitHub下载sed-windows.exe版本:🔗https://github.com/chapvic/sed/releases/tag/4.2.1
或者下载笔者的CSDN资源🔗https://download.csdn.net/download/ZZLLLLLLZ/89709256

下载的是sed可执行文件,默认安装路径即可,sed安装后不包含make.exe、rm.exe等可执行工具,会生成GnuWin32文件夹,记录好安装路径,后面移植make等工具会用到。

② make.exe下载、移植

下载GitHub的最新xPack-windows-build-tools二进制发行版本🔗https://github.com/xpack-dev-tools/windows-build-tools-xpack/releases/tag/v4.4.1-2,选择下载 xpack-windows-build-tools-4.4.1-2-win32-x64.zip

在这里插入图片描述
将解压后的xpack-windows-build-tools-4.4.1-2\bin下的所有可执行文件,复制粘贴到已安装的GnuWin32\bin文件夹中。
在这里插入图片描述
在这里插入图片描述
安装完成后,记得将GnuWin32\bin路径设置为系统环境变量,打开cmd输入make -v验证。

(5)OpenOCD工具

作为PC平台到嵌入式平台的软件烧录、调试工具,与J-Link/ST-Link/CMSIS-DAPLink等硬件接口和GDB调试器相辅使用。

三者之间的联系为:GDB调试器<——>OpenOCD工具(作为翻译器)<——>目标芯片MCU。

下载GitHub的最新xPack-OpenOCD二进制发行版本🔗https://github.com/xpack-dev-tools/openocd-xpack/releases/tag/v0.12.0-4
在这里插入图片描述

安装完成后,记得设置系统环境变量,打开cmd输入openocd -v验证。

(6)ST-Link/DAP-Link/J-Link驱动下载

ST-Link:请移步ST官网-ST-Link驱动下载页面
DAP-Link:无需下载驱动。
J-Link:目前笔者手上没有J-Link下载器,有需要驱动的可去其他博客寻找下载和安装方法,J-Link的下载方法后续会更新到该篇博客。

上述工具的系统环境变量设置自行搜索,其他博客有诸多保姆级教程,这里不作过多介绍,我们进入正题:
本文以STM32F401CCU6单片机作为示例来进行演示。

VSCode插件安装

汇编文件解析
(汇编文件解析插件)
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、工程架构

在这里插入图片描述

上述参考工程:F401CCU6_demo.zip
链接: https://pan.baidu.com/s/1atSOfKI_vTEWJiK68_1xrg?pwd=zeca 提取码: zeca
⭐⭐⭐TODO:工程较小,暂时附上百度网盘的链接,后续完善好J-Link下载、CMake构建方式等后,再上传到GitHub仓库。

二、VSCode各个配置脚本文件

tasks.json

⭐⭐⭐INFO:主要作用是使用XX名称,执行XX命令和XX参数。

//任务脚本
{
    //ctrl+shift+B
    "tasks": [
        //编译
        {
            "type": "shell",
            // type:Task的类型,分为shell和process两种
            // shell:作为Shell命令运行
            // process:作为一个进程运行
            "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"
            ]
        },
        //cmsis-dap方式下载
        {
            "type": "shell",
            "label": "flash with cmsis-dap-link",
            "command": "openocd",
            "args": [
                "-f",
                "interface/cmsis-dap.cfg", //D:\Software\embedded_dev_tools\xpack-openocd-0.12.0-3\openocd\scripts\interface
                "-f",
                "target/stm32f4x.cfg", //D:\Software\embedded_dev_tools\xpack-openocd-0.12.0-3\openocd\scripts\target
                "-c",
                // "program build/F401CCU6_demo.elf verify reset exit"
                "program build/${workspaceRootFolderName}.elf verify reset", //将工程根目录名称作为可执行文件名称
                "-c",
                "reset run",
                "-c",
                "exit"
            ], /*command+args相当于主命令+子命令,也就是openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c "program build/F401CCU6_demo.elf verify reset exit"的效果*/
            "problemMatcher": [
                "$gcc"
            ],
            "group": "build",
            "dependsOn": [ //每次执行这个任务,会先build任务,这便是所谓的依赖。
                "build"
            ]
        },
        //stlink方式下载
        {
            "type": "shell",
            "label": "flash with ST-link",
            "command": "openocd",
            "args": [
                "-f",
                "interface/stlink.cfg", //D:\Software\embedded_dev_tools\xpack-openocd-0.12.0-3\openocd\scripts\interface
                "-f",
                "target/stm32f4x.cfg", //D:\Software\embedded_dev_tools\xpack-openocd-0.12.0-3\openocd\scripts\target
                "-c",
                // "program build/F401CCU6_demo.elf verify reset exit"
                "program build/${workspaceRootFolderName}.elf verify reset", //将工程根目录名称作为可执行文件名称
                "-c",
                "reset run",
                "-c",
                "exit"
            ], /*command+args相当于主命令+子命令,也就是openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c "program build/F401CCU6_demo.elf verify reset exit"的效果*/
            "problemMatcher": [
                "$gcc"
            ],
            "group": "build",
            "dependsOn": [ //每次执行这个任务,会先build任务,这便是所谓的依赖。
                "build"
            ]
        },
        //J-link方式下载
        {
            "type": "shell",
            "label": "flash with J-link",
            "command": "openocd",
            "args": [
                "-f",
                "interface/jlink.cfg", //D:\Software\embedded_dev_tools\xpack-openocd-0.12.0-3\openocd\scripts\interface
                "-f",
                "target/stm32f4x.cfg", //D:\Software\embedded_dev_tools\xpack-openocd-0.12.0-3\openocd\scripts\target
                "-c",
                // "program build/F401CCU6_demo.elf verify reset exit"
                "program build/${workspaceRootFolderName}.elf verify reset", //将工程根目录名称作为可执行文件名称
                "-c",
                "reset run",
                "-c",
                "exit"
            ], /*command+args相当于主命令+子命令,也就是openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c "program build/F401CCU6_demo.elf verify reset exit"的效果*/
            "problemMatcher": [
                "$gcc"
            ],
            "group": "build",
            "dependsOn": [ //每次执行这个任务,会先build任务,这便是所谓的依赖。
                "build"
            ]
        }
    ],
    "version": "2.0.0"
}

launch.json

⭐⭐⭐INFO:主要作用是MCU调试,以及选择什么样的下载调试器。

//调试脚本
{
    "configurations": [
        {
            "name": "Debug with CMSIS-DAP-link",
            "cwd": "${workspaceRoot}",
            // "executable": "./build/F401CCU6_demo.elf",
            "executable": "./build/${workspaceRootFolderName}.elf", //将工程根目录名称作为可执行文件名称
            "request": "launch",
            "type": "cortex-debug",
            "servertype": "openocd",
            "device": "STM32F401CCU6",
            "configFiles": [
                "interface/cmsis-dap.cfg", //D:\Software\embedded_dev_tools\xpack-openocd-0.12.0-3\openocd\scripts\interface
                "target/stm32f4x.cfg" //D:\Software\embedded_dev_tools\xpack-openocd-0.12.0-3\openocd\scripts\target
            ],
            "svdFile": "./STM32F401.svd", //选择寄存器文件
            "liveWatch": {
                "enabled": true,
                "samplesPerSecond": 4
            },
            "searchDir": [],
            "runToEntryPoint": "main",
            "showDevDebugOutput": "none",
            "preLaunchTask": "flash with cmsis-dap-link"
            //每次调试之前,会先下载程序再调试
        },
        {
            "name": "Debug with ST-link",
            "cwd": "${workspaceRoot}",
            // "executable": "./build/F401CCU6_demo.elf",
            "executable": "./build/${workspaceRootFolderName}.elf", //将工程根目录名称作为可执行文件名称
            "request": "launch",
            "type": "cortex-debug",
            "servertype": "openocd",
            "device": "STM32F401CCU6",
            "configFiles": [
                "interface/stlink.cfg", //D:\Software\embedded_dev_tools\xpack-openocd-0.12.0-3\openocd\scripts\interface
                "target/stm32f4x.cfg" //D:\Software\embedded_dev_tools\xpack-openocd-0.12.0-3\openocd\scripts\target
            ],
            "svdFile": "./STM32F401.svd", //选择寄存器文件
            "liveWatch": {
                "enabled": true,
                "samplesPerSecond": 4
            },
            "searchDir": [],
            "runToEntryPoint": "main",
            "showDevDebugOutput": "none",
            "preLaunchTask": "flash with stlink" //每次调试之前会先下载再调试
        },
        {
            "name": "Debug with J-link",
            "cwd": "${workspaceRoot}",
            "executable": "./build/${workspaceRootFolderName}.elf",
            "request": "launch",
            "type": "cortex-debug",
            "servertype": "jlink", //要选择的GDB server
            "device": "STM32F401CCU6",
            "interface": "swd",
            "runToEntryPoint": "main",
            "showDevDebugTimestamps": true,
            "svdFile": "./STM32F401.svd",
            // "preLaunchTask": "build",
            "preLaunchTask": "flash with J-link" //每次调试之前会先下载再调试
        }
    ],
    "version": "2.0.0"
}

settings.json

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

//UI和功能设置脚本
{
    // 字符集编码选择
    "files.encoding": "utf8",
    // 自动保存
    "files.autoSave": "afterDelay",
    // 文件图标主题
    "workbench.iconTheme": "office-material-icon-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) Download",
            // "task": "flash",
            "tasks": [
                {
                    "label": "⚓ CMSIS-dap-link", //icon copied from https://emojipedia.org/
                    "task": "flash with cmsis-dap-link"
                },
                {
                    "label": "⤵️ ST-link", //icon copied from https://emojipedia.org/
                    "task": "flash with ST-link"
                },
                {
                    "label": "🚀 J-link", //icon copied from https://emojipedia.org/
                    "task": "flash with J-link"
                }
            ],
            "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

⭐⭐⭐INFO:主要作用是选择代码中的宏定义展开(当定义了_DEBUG时,assert()断言函数会被编译,而定义NDEBUG时不被编译)以及MCU的编译选项参数(或者说MCU硬件参数)

{
    "configurations": [
        {
            "name": "STM32F401CCU6",
            "includePath": [
                "${workspaceFolder}/**"
            ],
            "defines": [
                "_DEBUG",
                "UNICODE",
                "_UNICODE",
                "USE_STDPERIPH_DRIVER",
                "STM32F401xx"
            ],
            "compilerPath": "D:/Software/embedded_dev_tools/xpack-arm-none-eabi-gcc-13.3.1-1.1/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文件。
在这里插入图片描述

链接脚本STM32F401CCUx_FLASH.ld:

/*
******************************************************************************
**

**  File        : LinkerScript.ld
**
**  Author		: STM32CubeMX
**
**  Abstract    : Linker script for STM32F401CCUx series
**                256Kbytes FLASH and 64Kbytes RAM
**
**                Set heap size, stack size and stack location according
**                to application requirements.
**
**                Set memory bank area and size if external memory is used.
**
**  Target      : STMicroelectronics STM32
**
**  Distribution: The file is distributed “as is,” without any warranty
**                of any kind.
**
*****************************************************************************
** @attention
**
** <h2><center>&copy; COPYRIGHT(c) 2019 STMicroelectronics</center></h2>
**
** Redistribution and use in source and binary forms, with or without modification,
** are permitted provided that the following conditions are met:
**   1. Redistributions of source code must retain the above copyright notice,
**      this list of conditions and the following disclaimer.
**   2. Redistributions in binary form must reproduce the above copyright notice,
**      this list of conditions and the following disclaimer in the documentation
**      and/or other materials provided with the distribution.
**   3. Neither the name of STMicroelectronics nor the names of its contributors
**      may be used to endorse or promote products derived from this software
**      without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
*****************************************************************************
*/

/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM);    /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack */

/* Specify the memory areas */
MEMORY
{
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 64K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 256K
}

/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH

  /* The program code and other data goes into FLASH */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH

  /* Constant data goes into FLASH */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >FLASH

  .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
  .ARM : {
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
  } >FLASH

  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >FLASH
  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >FLASH
  .fini_array :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
  } >FLASH

  /* used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections goes into RAM, load LMA copy after code */
  .data : 
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
  } >RAM AT> FLASH

  
  /* Uninitialized data section */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss secion */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM

  /* User_heap_stack section, used to check that there is enough RAM left */
  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >RAM

  

  /* Remove information from the standard libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }

  .ARM.attributes 0 : { *(.ARM.attributes) }
}

四、MCU汇编启动文件.s

该文件包含了MCU的中断向量表信息。需要注意的是,ARMcc/ARMclangGCC for ARM两者交叉编译工具链对应的启动文件是不通用的,这里我们需要选择GCC for ARM工具链对应的汇编启动文件。
获取方法1:同样是使用STM32CubeMX生成一个简单的STM32F401CCU6工程,对应文件夹下含有。
获取方法2:去🔗ST官网,按照以下步骤找到STM32F4系列的标准外设软件库。
笔者是按照方法2添加的.s启动文件,寻找和下载的方法如下图所示。
在这里插入图片描述
在这里插入图片描述
因阿美莉卡的原因,ST官网目前不给予中国地区用户下载权限,我们可以选择访客的方式登录,然后进行邮箱验证(网易邮箱可能收不到ST下载验证码,请选QQ邮箱,在电脑端的网页打开QQ邮箱验证),即可下载,具体自行操作。
在这里插入图片描述
在下载的标准库文件中,按照以下路径:

\en.stsw-stm32065_v1-9-0\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\CMSIS\Device\ST\STM32F4xx\Source\Templates\gcc_ride7

即可寻找到GCC对应的汇编启动文件。如下图所示,
在这里插入图片描述
将该启动文件复制到我们的工程目录下
在这里插入图片描述

五、寄存器文件.svd

获取方法1:
① 进入🔗ST官网,直接搜索svd
在这里插入图片描述
② 找到STM32F4系列的svd压缩文件,下载
在这里插入图片描述
③ 解压缩下载好的文件中,找到对应型号的.svd寄存器文件,笔者选择的是STM32F401.svd,复制粘贴到我们工程目录下即可。
在这里插入图片描述
获取方法2:
ARM Keil官网下载对应MCU型号的.pack芯片支持包(具体下载方法请必应搜索)
例如下载好了 Keil.STM32F4xx_DFP.2.17.1.pack,将其.pack扩展名修改为.zip压缩扩展名,得到Keil.STM32F4xx_DFP.2.17.1.zip,然后解压缩,得到Keil.STM32F4xx_DFP.2.17.1文件夹。
如下图所示,打开路径 \Keil.STM32F4xx_DFP.2.17.1\CMSIS\SVD ,同样可找到MCU型号的对应.svd寄存器文件。
在这里插入图片描述

六、Makefile脚本

自动化构建脚本,负责文件的编译顺序和编译规则等等功能(这里不细说,大家可自行探索)。
构建脚本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 ***

七、下载

拿出各位手上的ST-Link或DAP-Link(J-Link待定),连接STM32F401CCU6的下载接口SWD,如果一切顺利的话,到这里就能进行编译、重编译、下载、调试操作了。
在这里插入图片描述

八、待定拓展

由于篇幅过长,基于上述所列内容,后续笔者会抽空各出一篇文章讲解。

  • 【Makefile脚本需要修改的地方】
  • 【.vscode文件中各个脚本需要修改的地方】
  • 【.svd寄存器文件如何根据不同厂商的MCU修改】
  • 【.s启动文件如何根据不同厂商的MCU修改】
  • 【.ld链接文件如何根据不同厂商的MCU修改】
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值