对于嵌入式开发人员,想必最熟悉在Keil MDK的程序开发了。Keil MDK的调试和仿真功能是其他IDE所不能比拟的,在公司内部的项目合作开发方面,兼容性也是非常优秀。
嵌入式领域的编码IDE/编辑器多如牛毛,笔者在这里列举一些:
通用IDE:
Keil(ARM背书,历史悠久,老牌稳定可靠)
IAR(IAR背书,与Keil在嵌入式领域旗鼓相当)
VSCode(微软背书,需要配置各种插件)
Clion(JetBrains背书,需要配置各种插件)
Eclipse(Eclipse开源基金会背书,需要配置各种插件)
Embedded Studio(SEEGER背书,打开速度超级快,比Keil还快,支持ARM和RISC-V芯片类型众多)
专用IDE:
STM32CubeMX(针对STM32芯片类的HAL库开发)
Arduino(基于芯片开发板SDK包+外设库,主要针对Arduino类)
ESPRESSIF(针对国产乐鑫无线通信MCU类)
Eclipse魔改IDE:
RT-Thread Studio(RT-Thread国产研发,基于Eclipse魔改,支持的芯片类型众多)
STM32CubeIDE(ST研发,基于Eclipse魔改,针对旗下的STM32芯片)
TrueStudio(ST研发,基于Eclipse魔改,已停止维护,官方转去维护STM32CubeIDE)
CCS(TI背书)
MounRiver Studio(沁恒国产研发,基于Eclipse魔改,针对旗下的CH芯片)
MounRiver Studio Ⅱ(沁恒国产研发V2.0版本,基于VSCode,针对旗下的CH芯片)
Nuclei Studio(芯来科技国产研发,基于Eclipse魔改,针对旗下的RISC-V芯片)
MLXIDE(迈来芯研发,基于Eclipse魔改)
e² studio(瑞萨研发,基于Eclipse魔改,针对旗下的瑞萨芯片)
S32DS、MCUXpresso IDE(NXP研发,基于Eclipse魔改,针对旗下的恩智浦芯片)
Embeetle IDE(这个有些冷门,貌似正和国产兆易创新GD打造完善该IDE)
本篇文章旨在搭建一套基于VSCode的开源嵌入式开发环境,探索从编译到调试的过程。
📚NOTE:
下述的GitHub链接皆参考于xPack项目,xPack开源项目是一个针对于C/C++与嵌入式跨平台第三方工具的可重现构建框架,一位名为 Liviu Ionescu (ilg) 的大佬及其团队一直在维护迭代,感谢他们的付出,致敬拥抱开源。该框架存在的目的是让人们不再重复造轮子!
诸如aarch64-none-elf-gcc
、arm-none-eabi-gcc
、riscv-none-elf-gcc
、riscv-none-embed-gcc
、clang
、cmake
、mingw-w64-gcc
、Ninja Build
、OpenOCD
、windows-build-tools
、GNU sed
、QEMU Arm
、QEMU RISC-V
等第三方工具资源都整合在xPack项目,
至此形成了一个优秀的GitHub开源项目,我们需要时可以使用Git拉取🔗 xpack-dev-tools里的子项目;
或者想在GitHub直接下载的话,可以点击 链接1 或者 链接2 镜像加速下载所需文件(进入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
arm-none-eabi-gcc:GCC的ARM版本,应用于PC平台和嵌入式平台的开源交叉编译工具链。
⭐⭐⭐INFO:
交叉编译器的职责是将在一台机器下(PC)编写的代码翻译成二进制文件后,使得二进制文件可在另外一台机器上(arm/risc-v)运行。
编译器和交叉编译器最大的区别是:
交叉编译器生成的二进制文件只能在目标机器上(arm/risc-v)运行,而不能在本机(执行翻译交叉编译器指令的PC)上运行!
其负责从编译到调试的整套过程,包含以下但不限于
- 下载GitHub的最新xPack-arm-none-eabi-gcc二进制发行版本🔗https://github.com/xpack-dev-tools/arm-none-eabi-gcc-xpack/releases/tag/v13.3.1-1.1
- 或者进ARM官网的最新下载页面🔗https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads(与GitHub的xPack-arm-none-eabi-gcc同步更新,已迭代到Version 13.3.Rel1)
- 或者进ARM官网的Arm GNU Toolchain下载页面🔗https://developer.arm.com/downloads/-/gnu-rm(已停止更新deprecated,截至Version 10.3-2021.10)
📚NOTE:xPack GNU Arm Embedded GCC二进制发行版(即①)向下完全兼容Arm的GNU Arm Embedded Toolchain二进制发行版(即②和③),笔者推荐下载①。
解压缩后,放在上述推荐的embedded_dev_tools
路径即可,记得设置系统环境变量,打开cmd输入arm-none-eabi-gcc -v
验证。
(3)MinGW(GCC的win平台版本)
GNU:GNU’s Not Unix!
GCC:GNU Compiler Collection,中文译为GNU编译器集合。
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
解压离线免安装包,将其安装在上述推荐的embedded_dev_tools
路径即可,设置mingw64\bin
目录为系统环境变量。
安装完成后,记得设置系统环境变量,打开cmd输入gcc -v
验证。
📚NOTE:安装MinGW-64的意义是作一份make.exe的备用,make构建工具的话,笔者推荐选择下面xpack-windows-build-tools中安装的make.exe和rm.exe,笔者不建议使用MinGW/bin下由mingw32-make.exe重命名的make.exe,至于具体的原因,请参考笔者写的另外一篇博客,请点击这里移步→基于VSCode搭建开源嵌入式环境的make构建工具选择。
(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.exe
,选择安装在上述推荐的embedded_dev_tools
路径即可,sed安装后会生成GnuWin32
文件夹(在embedded_dev_tools
路径下,笔者将GnuWin32
文件夹修改安装在自定义的make
文件夹),但是此时的make
文件夹并不包含make.exe、rm.exe等
Linux可执行工具。因此下面我们继续移植make.exe、rm.exe等
工具到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
下的所有可执行文件,复制粘贴到已安装的make\bin
文件夹下。
安装完成后,记得将make\bin
路径设置为系统环境变量,打开cmd输入make -v
验证。
(5)OpenOCD工具
OpenOCD:Open On-Chip Debugger。
GDB:GNU symbolic debugger。
OpenOCD作为PC平台和嵌入式平台之间的软件烧录/调试工具,与 J-Link / ST-Link / CMSIS-DAP-Link 等硬件接口、GDB调试器相辅使用。
三者之间的联系为:ARM-GDB调试器<——>OpenOCD工具<——>下载调试器件J-Link/ST-Link/CMSIS-DAP-Link<——>目标芯片MCU
下载GitHub的最新xPack-OpenOCD二进制发行版本🔗https://github.com/xpack-dev-tools/openocd-xpack/releases/tag/v0.12.0-4
解压缩放在上述推荐的embedded_dev_tools
路径即可,记得设置系统环境变量,打开cmd输入openocd -v
验证。
(6)ST-Link/CMSIS-DAP-Link/J-Link驱动下载
- ST-Link驱动:请移步ST官网-ST-Link驱动下载页面。
- CMSIS-DAP-Link驱动:CMSIS-DAP是一个ARM开源的调试器项目,支持所有Cortex-A/R/M的元器件,采用了HID连接,可实现
无驱动连接电脑
并进行调试。- J-Link驱动:请移步SEGGER官网的J-link驱动下载页面。
📚NOTE:因OpenOCD的J-Link下载方式和Keil MDK 5的J-Link下载方式不一样,故OpenOCD的J-Link下载要修改些许设置,对此笔者另写了一篇笔记:OpenOCD之J-Link下载。
安装在上述推荐的embedded_dev_tools
路径即可。
上述工具的系统环境变量设置自行搜索,其他博客有诸多保姆级教程,这里不作过多介绍,我们进入正题:
本文以【STM32F401CCU6】单片机作为示例来进行演示。
一、VSCode需安装的插件列表
二、工程架构
工程文件目录树:
F401CCU6_demo
└─ .vscode
│ ├─ c_cpp_properties.json
│ ├─ launch.json
│ ├─ settings.json
│ └─ tasks.json
├─ build
│ ├─ F401CCU6_demo.bin
│ ├─ F401CCU6_demo.elf
│ ├─ F401CCU6_demo.hex
│ ├─ F401CCU6_demo.map
│ ├─ gpio.d
│ └─ usart.o
├─ CMSIS
│ ├─ Source
│ │ └─ system_stm32f4xx.c
│ └─ Include
│ ├─ stm32f4xx.h
│ └─ system_stm32f4xx.h
├─ Core
│ ├─ Src
│ │ └─ stm32f4xx_it.c
│ └─ Inc
│ ├─ core_cm4.h
│ ├─ core_cmFunc.h
│ ├─ core_cmInstr.h
│ ├─ core_cmSimd.h
│ ├─ core_sc000.h
│ ├─ core_sc300.h
│ ├─ stm32f4xx_conf.h
│ └─ stm32f4xx_it.h
├─ link_script
│ └─ STM32F401CCUx_FLASH.ld
├─ startup_s
│ └─ startup_stm32f401xx.s
├─ STM32F4xx_StdPeriph_Driver
│ ├─ src
│ │ ├─ misc.c
│ │ ├─ stm32f4xx_adc.c
│ │ ├─ stm32f4xx_wwdg.c
│ │ └─ stm32f4xx_……….c
│ └─ inc
│ ├─ misc.h
│ ├─ stm32f4xx_adc.h
│ ├─ stm32f4xx_wwdg.h
│ └─ stm32f4xx_……….h
├─ User
│ ├─ Src
│ │ ├─ gpio.c
│ │ ├─ main.c
│ │ ├─ syscalls.c
│ │ ├─ tim.c
│ │ └─ usart.c
│ └─ Inc
│ ├─ gpio.h
│ ├─ main.h
│ ├─ tim.h
│ └─ usart.h
├─ Makefile
└─ STM32F401.svd
📚gitee工程链接: https://gitee.com/Reality_Light/vscode-gcc-arm.git
三、VSCode各个配置脚本文件
⭐⭐⭐NOTE:下述4个json脚本的修改要点请移步该篇文章 → .vscode文件中各个脚本需要修改的地方。
tasks.json
📚INFO:主要作用是使用XX名称,执行XX命令和XX参数。
//任务脚本
{
//快捷键ctrl+shift+B调出各个task命令
"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",
// "shutdown",
"-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",
// "shutdown",
"-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-swd.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",
// "shutdown",
"-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 ST-Link" //每次调试之前会先下载再调试
},
{
"name": "Debug with J-Link",
"cwd": "${workspaceRoot}",
"executable": "./build/${workspaceRootFolderName}.elf",
"request": "launch",
"type": "cortex-debug",
"servertype": "openocd", //要选择的GDB server
"device": "STM32F401CCU6",
"configFiles": [
"interface/jlink-swd.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
],
"interface": "swd",
"svdFile": "./STM32F401.svd",
"liveWatch": {
"enabled": true,
"samplesPerSecond": 4
},
"runToEntryPoint": "main",
"showDevDebugTimestamps": true,
// "preLaunchTask": "build",
"showDevDebugOutput": "none",
"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,
//设置字体的大小,最小值能设置为6
"editor.fontSize": 16,
//设置字体的粗细
"editor.fontWeight": "500",
//设置字体的样式
// "terminal.integrated.fontFamily":"Courier New",
//使用Ctrl+滚轮缩放编辑区的字体大小
"editor.mouseWheelZoom": true,
//使用Ctrl+滚轮缩放终端Terminal的字体大小
"terminal.integrated.mouseWheelZoom": true,
//设置为false,这样打开新的文件时,不会自动关闭旧的文件
"workbench.editor.enablePreview": false,
"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": "⚡ Download"
}
],
}
c_cpp_properties.json
📚INFO:主要作用是选择代码中的宏定义展开(当定义了_DEBUG时,assert()断言函数会被编译,而定义NDEBUG时不被编译)以及MCU的编译选项参数(或者说MCU硬件参数)
{
"configurations": [
{
"name": "Linux-STM32F401CCU6",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE",
"__CC_ARM", // 解决编辑器中提示无法识别uint32_t, uint16_t, uint8_t的问题
"USE_STDPERIPH_DRIVER",
"STM32F401xx"
],
"compilerArgs": [
"-mcpu=cortex-m4",
"-mthumb",
"-mfpu=fpv4-sp-d16",
"-mfloat-abi=hard"
],
"compilerPath": "D:/Software/embedded_dev_tools/xpack-arm-none-eabi-gcc-13.3.1-1.1/bin/arm-none-eabi-gcc.exe",
"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>© 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/ARMclang与arm-none-eabi-gcc两者交叉编译工具链对应的启动文件是不通用的,这里我们需要选择arm-none-eabi-gcc工具链对应的汇编启动文件。
获取方法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芯片支持包(具体下载方法请于必应搜索)
以本文的F401CCU6芯片为例:下载好 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脚本
⭐⭐⭐NOTE:下述Makefile脚本需要修改的地方请参考该篇文章 → 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 ./CMSIS/Source/*.c) \
$(wildcard ./Core/Src/*.c) \
$(wildcard ./STM32F4xx_StdPeriph_Driver/src/*.c) \
$(wildcard ./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 =
# ASM_SOURCES += $(wildcard ./startup_s/*.s)
ASM_SOURCES = $(wildcard ./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浮点计算器,STM32F4xx支持浮点运算来做DSP
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模式下有效
# -g -gdwarf-2是调试选项,其中-g表示在生成的文件中添加调试信息,-gdwarf-2表示调试信息的格式为DWARF,版本号为2。调试信息将在对应的GDB(比如arm-none-eabi-gdb)中使用。
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
# 编译选型: 依赖的标准库
# -lc:链接libc库(ANSI C标准);-lm:链接libm库(数学函数库)
LIBS = -lc -lm -lnosys
# 编译选型: 依赖的指定路径库,.a库文件(window中的lib文件需要转换成.a文件才能识别)
LIBDIR =
# 链接工具的总选项:
# MCU 芯片类型,
# -u_printf_float显式启用浮点数打印。-u_scanf_float显式启用浮点数输入
# specs:Specifying Subprocesses缩写,子进程。
# -specs=nano.specs链接精简版C库,nan0.specs将-lc替换成-lc_nano,用精简C库newlib-nano替代标准C库glibc/newlib,减少.elf体量。
# -T$(LDSCRIPT)依赖的可执行文件链接脚本,
# $(LIBDIR) 标准库文件 , $(LIBS) 指定库文件 ,
# -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref 生成map文件 ,
# -Wl,--gc-sections 链接使用的分段方式,需要配合C文件/汇编生成obj的时候同样选型分段方式,好处是链接的时候源文件中的未使用变量和未调用# 函数将不会被链接到elf文件中,最终可执行文件elf会很精简。
# --no-warn-rwx-segments:消除 LOAD segment with RWX permissions 警告
LDFLAGS = $(MCU) -flto -u_printf_float -u_scanf_float -specs=nano.specs -specs=nosys.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections,--print-memory-usage,--no-warn-rwx-segments
# default action: build all
# Makefile总目标: 这是伪目标,在第一依赖关系位置,输入make指令时必定执行该目标
# 可执行文件elf , hex 和 bin 是arm的常用烧写文件
all: version $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
# 在正式编译前输出 gcc 版本信息。
version:
$(CC) --version
# 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)))
vpath %.c $(sort $(dir $(C_SOURCES)))
# /*
# 指定C文件的搜索路径: $(sort $(dir $(C_SOURCES)))
# $(dir $(C_SOURCES)):所有源文件只保留文件路径,
# sort:对所有路径排序 ,‘d g a’ -> 'a d g'
# */
# 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)))
OBJ_DIR = obj
# /*
# 通配符(%)指定所有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 $@
@echo $(notdir $(<:.c=.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 $@
@echo $(notdir $(<:.s=.o))
$(BUILD_DIR)/%.o: %.S Makefile | $(BUILD_DIR)
@$(AS) -c $(CFLAGS) $< -o $@
@echo $(notdir $(<:.S=.o))
# 生成可执行文件ELF文件:依赖于所有OBJECTS文件
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
@$(CC) $(OBJECTS) $(LDFLAGS) -o $@
@echo linking...
$(SZ) $@
rm -fR $(BUILD_DIR)/$(OBJ_DIR)
mkdir $(BUILD_DIR)/$(OBJ_DIR)
# 生成HEX文件:依赖于elf文件和build文件夹
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(HEX) $< $@
# 生成BIN文件:依赖于elf文件和build文件夹
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(BIN) $< $@
mv -f $(BUILD_DIR)/*.o $(BUILD_DIR)/$(OBJ_DIR)/
mv -f $(BUILD_DIR)/*.d $(BUILD_DIR)/$(OBJ_DIR)/
mv -f $(BUILD_DIR)/*.lst $(BUILD_DIR)/$(OBJ_DIR)/
# 生成build文件夹:伪指令。通过上面的优先依赖关系生成
$(BUILD_DIR):
mkdir $@
#######################################
# clean up
#######################################
# /* 清除编译结果:将build文件中所有文件和子文件夹删除。*/
clean:
# Linux下的makefile语法(请使用xpack-windows-build-tools二进制发行工具包中的make.exe)
-rm -fR $(BUILD_DIR)
# Window CMD终端的语法(请使用MinGW二进制发行工具包中的mingw32-make.exe或其重命名的make.exe)
# -del /q $(BUILD_DIR)
#######################################
# dependencies
#######################################
# 添加所有.d依赖文件
-include $(wildcard $(BUILD_DIR)/*.d)
# *** EOF ***
八、下载
拿出各位手上的ST-Link或CMSIS-DAP-Link或J-Link,连接stm32f401ccu6的SWD接口,如果一切顺利的话,到这里就能执行编译、重编译、下载、调试操作了。
①点击Build编译
②选择SWD下载方式
③下载完成
九、待定拓展
由于篇幅过长,基于上述所列内容,笔者后续抽空各出一篇文章讲解。
🌵【.vscode文件中各个脚本需要修改的地方】
🌵【.svd寄存器文件如何根据不同厂商的MCU修改】
🌵【.s启动文件如何根据不同厂商的MCU修改】
🌵【.ld链接文件如何根据不同厂商的MCU修改】
🌵【makefile脚本需要修改的地方】