在嵌入式领域能够选择的集成开发环境(IDE)很多,有通用型的,例如 Keil
,IAR
,给他们安装一个相应芯片的描述包即可开发相应芯片的驱动程序。
也有专用型的,例如 德州仪器 TI 的 CCS
,意法半导体 ST 的 STM32CubeIDE
,国产 RTOS 操作系统的 RT-Thread Studio
,以及开源 Arduino 的 Arduino IDE
等等。它们各自的使用方式也是五花八门,一般情况下芯片厂商的芯片在不受通用集成开发环境(IDE)的支持下都会选择向开发者提供一个自己特有的集成开发环境比如 TI 的 CCS
集成开发环境。
如果使用的芯片能够被通用集成开发环境例如 Keil
支持倒还是不错的,至少是统一的,否则你需要为每一款不同的芯片安装不同的集成开发环境,同时还需要去熟悉以使用它们,当然一般情况下通用集成开发环境(例如 Keil)是收费的,并且不支持跨平台使用。
所以为了开发环境的统一性,我们可以基于构建工具 Make
与编译器 GCC
自己动手在 Windows 上搭建一个通用并且跨平台的嵌入式开发环境。可以这么做的原因是很多芯片厂商提供的专用 IDE 也是基于 GCC
以及相关开源的工具链来打造的。
前面说到我们使用 Make
和 GCC
来搭建我们通用的开发环境,估计听到这里大多数人都已经望而却步了,因为要使用 Make
就需要能够编写 Makefile
文件来制定编译规则(即 Make
读取并解析 Makefile
,根据 Makefile
定义的规则调用 GCC
执行编译工作)。
好了不用担心,先不讨论你是否具备编写 Makefile
的能力,即使具备编写 Makefile
的能力实际开发中也不建议大家自己动手编写 Makefile
,其中的原因有以下两点:编写 Makefile
需要消耗大量时间,效率较低,并且不支持跨平台。
既然使用 Make
则 Makefile
是必须的,不自己编写那怎么得到 Makefile
呢,这就是文章标题 CMake
要做的事情,为了解决跨平台问题开源的构建工具 CMake 问世了。目前大型的开源项目大多是使用 CMake 来构建的,而非直接编写 Makefile
,例如开源图像处理框架 openCV 就是使用 CMake 来生成 Makefile 的。
本篇文章以 Windows 10 作为开发平台,主要原因是 Windows 端具有丰富的开发工具链供我们选择,同时相比使用虚拟机虚拟 Linux 开发是方便许多的,不需要安装虚拟机可以免去虚拟机硬盘空间占用问题,数据备份,快照备份,虚拟机崩溃的麻烦。
本片文章所用的工具链以及方法适用于 Windows 和 Linux。在 Linux 中搭建使用的工具也是相同的以及过程也是类似的。
STM32F103RCT6,芯片这里以 STM32
为例。
DAP-Link,下载与仿真这里以 CMSIS-DAP
仿真器为例。
CMake(英文 Cross platform Make 的缩写)它不属于构建系统,而是构建系统生成器,属于一个开源跨平台构建工具,在 Linux 平台生成构建系统 Make 的 Makefile
文件,在 Windows 平台生成 Visual Studio 或 MSVC 的 工程
等。所以具体的构建工作还是需要交给例如 Make,Ninja,MSVC 等这些构建系统去执行。在这里我们主要应用它来管理 C/C++
源码以及生成 Makefile
文件。
下载: https://cmake.org/download/
选择:cmake-3.24.1-windows-x86_64.msi(这是目前最新版,选择其他版本也可以)
安装后需要将 bin
文件夹添加到 Windows 系统的用户环境变量中,以便可以使用命令调用它。
添加好环境变量之后在 Windows 终端中执行 cmake
命令能够输出以下信息表明安装成功
Microsoft Windows [版本 10.0.19044.1889]
(c) Microsoft Corporation。保留所有权利。
C:\Users\20220615>cmake
Usage
cmake [options]
cmake [options]
cmake [options] -S -B
Specify a source directory to (re-)generate a build system for it in the
current working directory. Specify an existing build directory to
re-generate its build system.
Run 'cmake --help' for more information.
C:\Users\20220615>
Windows 端的 CMake 是含有图形界面的,所以在 Windows 上既可以使用命令操作,也可以使用图形化来操作,图形界面如下图。
该软件具体如何使用这里不讲解,可以到各大博客平台搜索相关教程。
MinGW(即 Minimalist GNU for Windows 的缩写),它是一些头文件和端口库的集合,该集合允许人们在没有第三方动态链接库的情况下使用 GCC(GNU Compiler C)产生 Windows32 程序。
MinGW 收集了一系列免费的 Windows 平台的头文件和库文件,同时整合了GNU 的工具集,特别是 GNU 程序开发工具 gcc
,g++
,make
等等。MinGW 是完全免费的自由软件,它在 Windows 平台上模拟了 Linux 下 GCC 的开发环境。为了那些不喜欢工作在 Linux(FreeBSD) 操作系统而留在 Windows 的人提供一套符合 GNU 的 GNU 工作环境。
下载: https://sourceforge.net/projects/mingw-w64/files/
选择: mingw-w64
安装后需要将 bin
文件夹添加到 Windows 系统的用户环境变量中,以便可以使用命令调用它。
在 Windows 终端中执行 make -v
命令,能够输出以下信息表明安装成功。
C:\Users\20220615>make -v
GNU Make 4.2.1
Built for x86_64-w64-mingw32
Copyright (C) 1988-2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
C:\Users\20220615>
一般情况下执行 make
命令 Windows 会提示 make
为无法识别的命令,这是因为在 MiniGW 安装目录的 bin
文件夹下 make.exe
的全称为 mingw32-make.exe
为了简化命令长度将其复制一份并命名为 make.exe
,同时可以保留原件作为备份。
gcc-arm-none-eabi 是一个开源的 ARM 开发工具链,它包括了 GNU 编译器 GCC,以及 GDB 调试器,所以不要认为它只是个 GCC。可用于 Windows,Linux,MacOS 系统上的交叉编译。
下载: https://developer.arm.com/downloads/-/gnu-rm
选择:gcc-arm-none-eabi-10.3-2021.10-win32.exe 或 gcc-arm-none-eabi-10.3-2021.10-win32.zip 压缩包形式的(这个是目前最新版,选择其他版本也可以)。
安装后需要将 bin
文件夹添加到 Windows 系统的用户环境变量中,以便可以通过命令调用它。
在在 Windows 终端中执行 arm-none-eabi-gcc
命令,能够输出以下信息则表明安装成功。
C:\Users\20220615>arm-none-eabi-gcc
arm-none-eabi-gcc: fatal error: no input files
compilation terminated.
C:\Users\20220615>
openOCD(即 Open On-Chip Debugger 缩写)是一个开源的片上调试器,openOCD 旨在提供针对嵌入式设备的调试,系统编程和边界扫描功能,openOCD 需要搭配调试器使用,支持的调试器有 ST-Link,DAP-Link,J-Link 等。
下载: https://gnutoolchains.com/arm-eabi/openocd/
选择:openocd-20211118.7z(这个是目前最新版,选择其他版本也可以)
安装后需要将 bin
文件夹添加到 Windows 系统的用户环境变量中,以便可以通过命令来调用它。
在在 Windows 终端中执行 openOCD
命令,能够输出以下信息则表明安装成功。
C:\Users\20220615>openOCD
Open On-Chip Debugger 0.11.0 (2021-11-18) [https://github.com/sysprogs/openocd]
Licensed under GNU GPL v2
libusb1 09e75e98b4d9ea7909e8837b7a3f00dda4589dc3
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
embedded:startup.tcl:26: Error: Can't find openocd.cfg
in procedure 'script'
at file "embedded:startup.tcl", line 26
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Error: Debug Adapter has to be specified, see "adapter driver" command
embedded:startup.tcl:26: Error:
in procedure 'script'
at file "embedded:startup.tcl", line 26
C:\Users\20220615>
VSCode 是一个开源的代码编辑器,由 MicroSoft 发行,有意思的是 VSCode 自带终端,支持安装嵌入式开发插件,例如 ARM 仿真调试插件,以及支持运行自定义的任务来调用其他的软件。
下载: https://code.visualstudio.com/
选择:Windows 版本,建议使用最新版本。
一般情况下添加环境变量添加到用户环境变量即可,当然添加到系统环境变量也是可以的(区别在于添加到系统环境变量则该变量对所有用户有效)。
必须添加环境变量的工具最终添加环境变量后的效果,如下图
前面的属于基础工具,安装好并添加好环境变量后就没有更多需要配置的东西了,下面我们来配置 VSCode,这才是整个开发环境搭建过程中需要重点讲解的环节。
VSCode 中需要安装两个插件,分别为 C/C++ 插件,Cortex-Debug 插件。下面我们来看看这两个插件的作用和安装以及配置方法。
C/C++ 插件是微软官方提供的,主要用于支持 VSCode 的 C/C++ 代码跳转,代码自动补全以及 C/C++ 的语法高亮,如果用于开发桌面应用的话该插件还可以实现代码编译,但是我们用于开发嵌入式软件我们就不需要用它来编译代码了,而是用我们前面自己安装的 arm-none-eabi-gcc。
启动 VSCode 在侧边的 扩展 选项卡,在搜索框中输入 C/C++ 即可搜索到该插件,如下图。
搜索到后直接点击安装,安装完成后的效果如下图。
安装好插件后我们可以简单的配置一下 C/C++
插件,按下键盘的 F1
按键,按下后弹出如下面板,在输入框中输入 C/C++ 搜索,得到 C/C++ 编辑配置,支持两种配置方式,UI 方式或修改 JSON 文件方式,我们点击选择 UI 方式,点击这时会在文件夹中自动生成一个 .vscode
文件夹,并生成一个 c_cpp_properties.json
文件。
选择后会弹出 C/C++ 插件的配置页面,如下图。
在页面中找到 IntelliSense 模式,将模式修改为 gcc-arm,如下图。
在页面中找到 C 标准,C++ 标准,修改为你希望的版本,这里我选择 C 语言标准使用 C17,C++ 标准使用 C++14,如下图所示。
Cortex-Debug 插件以用来调试 ARM Cortex 系列芯片,所以不只可以用来调试 STM32
哦。前面我们安装了 openOCD
并添加了环境变量,那么在使用调试功能时 Cortex-Debug
插件就可以调用 openOCD
执行下载和调试任务。
搜索到后直接点击安装,安装完成后的效果如下图。
编译程序可以在 VSCode 自带的终端中输入并执行 make 命令进行编译,执行该命令时需要进入 Makefile
文件所在的目录。
使用 openOCD 下载程序需要使用命令操作,通过阅读 openOCD 的帮助信息可知下载程序需要使用 openocd -f
命令。
在命令中需要指定调试的接口的 .cfg
文件,比如 ST-Link
调试接口对应 stlink-v2.cfg
文件,DAP-Link
调试接口对应 cmsis-dap.cfg
文件。这些文件可以在 openOCD 的安装目录下的 interface
文件夹下找到。
在命令中还需要指定目标芯片的 .cfg
文件,比如 STM32F103
对应 stm32f1x.cfg
文件,STM32F4
对应 stm32f4x.cfg
文件。这些文件可以在 openOCD 安装目录下的 target
文件夹下找到。
在命令中还需要指定待下载的固件,固件文件支持 .elf
,.hex
格式,将编译好的固件所在路径及名称填写到命令中即可。
将上述命令组合起来,最后我们可以得到以下这样一条命令,在终端中执行该命令即可将固件下载到单片机中。
openocd -f interface/cmsis-dap.cfg -f target/stm32f1x.cfg -c "program build/Hello.elf verify reset exit"
但是每次下载需要输入上述命令,使用起来总是不够方便优雅,所以我们可以将上述的命令自定义成 VSCode 中的一个任务,由 VSCode 去执行,这样点击一下图形化菜单即可执行上述命令,将程序下载到单片机中。
可以在 VSCode 菜单栏中点击 终端,在下拉菜单中选择 配置任务,点击后会在 .vscode
文件夹下自动生成一个 task.json
文件和模板。如果你熟悉内容格式的前提下也可以直接一步到位在项目根目录 .vscode
文件夹下创建 task.json
文件。
task.json
文件内容及格式如下,为了示例直观,使用到的文件路径直接使用绝对路径,其中用到的的 .cfg
路径,待下载固件路径,以你自己电脑的路径为准,可以使用相对路径的。
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "Download",
"command": "openocd",
"args": [
"-f",
"D:/Others/openocd-0.10.0/scripts/interface/cmsis-dap.cfg",
"-f",
"D:/Others/openocd-0.10.0/scripts/target/stm32f1x.cfg",
"-c",
"program C:/Users/20220615/Desktop/Ctrl-FOC-Lite-main/2.Firmware/STM32_HAL_version/Ctrl-FOC-Lite-fw/build/Ctrl-FOC-Lite-fw.elf verify reset exit"
],
"problemMatcher": [
"$gcc"
],
"group": "build",
}
]
}
在菜单栏中点击终端,在下拉菜单中点击 运行任务。
点击运行任务菜单后会弹出已有的任务列表,可以看到我们自定义的 Dowload
任务已经显示在任务列表了,点击 Download
即可下载固件到单片机。
在线调试就需要用到我们安装的 Cortex-Debug
插件了,所以我们需要将 Cortex-Debug
插件和 openOCD
对接起来,让 Cortex-Debug
插件能够调用 openOCD
。
如果我们之前没有给 VSCode 配置过调试功能,那么点击 VSCode 侧边的 调试 按钮会提示我们需要创建 launch.json
文件,点击 提示 后会自动在 .vscode
文件夹下创建 launch.json
文件和模板。
你也可以直接在在项目根目录 .vscode
文件夹下创建 lanuch.json
文件,内容及格式如下,其中用到的的 svdFile
, .cfg
路径,待下载固件的路径,以你自己电脑的路径为准,这里为了直观我直接使用绝对路径,自己使用时可以改为相对路径。
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"cwd": "${workspaceRoot}",
"executable": "C:/Users/20220615/Desktop/Ctrl-FOC-Lite-main/2.Firmware/STM32_HAL_version/Ctrl-FOC-Lite-fw/build/Ctrl-FOC-Lite-fw.elf",
"name": "Debug with OpenOCD",
"request": "launch",
"type": "cortex-debug",
"servertype": "openocd",
"configFiles": [
"D:/Others/OpenOCD-20211118-0.11.0/share/openocd/scripts/interface/cmsis-dap.cfg",
"D:/Others/OpenOCD-20211118-0.11.0/share/openocd/scripts/target/stm32f1x.cfg",
],
"svdFile": "C:/Users/20220615/Desktop/MiniBot/build/STM32F103xx.svd",
}
]
}
编写好 launch.json
文件后可以在这里看到 VSCode 出现了一个运行按钮并且已经识别出了 openOCD,并且还显示了下拉菜单用于选择不同的 GDB 调试服务(注意 openOCD 实际上就是一个 GDB 调试服务,GDB 在线调试是需要基于它的),如下图。
点击上图的按钮后开始进入在线调试模式,进入调试模式过程中输出信息,如下。
进入在线调试模式后代码中出现了运行位置指针,以及顶部存在一个用于执行和控制调试的菜单栏,如下图。
svd 文件指的是仿真寄存器描述文件,根据实际测试 svdFile
仿真寄存器描述文件对于调试功能不是必须的,但是如果提供该文件则可以在调试的过程中查看每个外设所包含的所有寄存器的值。例如使用了 STM32 的 SVD
文件后调试的过程中我们可以看到 STM32 的 GPIO 外设 BSRR
寄存器的参数值,如下图。
SVD 文件下载: https://github.com/posborne/cmsis-svd
最终到这里你可能会有一个疑问,使用这样的工具链后,那该怎么搭建工程?比如说如何搭建大家都熟悉的 STM32 的工程。
搭建工程实际上和使用 Keil
是类似的方法,首先就是将用到的 STM32 固件库中的文件以及我们编写的源码文件按照我们喜欢的规则组织好,然后编写 CMake 的 CMakeLists.txt
文件去描述这个文件组织规则,比如说要包含哪些头文件,要编译哪些文件,指定编译后的固件名称,等等。
所以对于使用了 CMake 之后 CMakeLists.txt
就是所谓的工程,工程的本质就是管理一些编译的参数,项目编译的选项,包含的文件等,这些 CMakeLists.txt
都能够做到。
所以使用 CMake 之后不要求具备编写 Makefile
文件的能力,但是需要会编写
CMakeLists.txt
文件,具体如何编写 CMakeLists.txt
这里不讲解,可以到各大博客平台搜索相关教程。
注意:由于我们现在使用 ARM 版本
gcc
来编译工程了,所以在搭建工程(比如 STM32 的工程)时.s
启动文件的选择上因该选择gcc
版本,而不能再继续选择Keil
版本。
使用 gcc 编译需要使用链接文件,所以需要准备好链接文件,链接文件可以从官方的固件库中找到,例如 STM32F103RC 的链接文件可以选择
STM32F103XB_FLASH.ld
根据你的型号以及 Flash 的容量选择对应的链接文件,否则会出现系统时钟初始化不了的问题。
(1) CMake 构建过程中 ABI 编译器状态检测无法通过问题,这是因为在编写 CMakeLists.txt
文件时没有指定 ARM 的交叉编译链,导致 CMake 调用了默认的标准 gcc 进行编译。
(2) 程序固件无法下载问题,这可能是 openOCD
版本太低,或与下载器不兼容导致的问题,可以安装最新版本的 openOCD
以解决问题。
(3) 提示 gdb
版本太低无法进入调试问题,这是 arm-none-eabi-gcc
版本太低导致的问题,安装一个新的版本即可。
在此附上一份完整的基于 STM32F103 HAL 库的 CMake 模板,注意 CMakeLists 文件用到的编译选项,以及 HAL 库或标准库的宏定义,例如 USE_HAL_DRIVER
,__MICROLIB
,STM32F1
,STM32F1xx
,STM32F103xB
。
# No operating system
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_VERSION 1)
cmake_minimum_required(VERSION 3.21)
# specify cross compilers and tools
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_AR arm-none-eabi-ar)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
set(CMAKE_OBJDUMP arm-none-eabi-objdump)
set(SIZE arm-none-eabi-size)
# skip compiler checks
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
project(MiniBot C CXX ASM)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11)
add_compile_options(-mcpu=cortex-m3 -mthumb -mthumb-interwork)
add_compile_options(-ffunction-sections -fdata-sections -fno-common -fmessage-length=0)
# uncomment to mitigate c++17 absolute addresses warnings
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-register")
# Enable assembler files preprocessing
add_compile_options($<$:-x$assembler-with-cpp>)
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
message(STATUS "Maximum optimization for speed")
add_compile_options(-Ofast)
elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
message(STATUS "Maximum optimization for speed, debug info included")
add_compile_options(-Ofast -g)
elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "MinSizeRel")
message(STATUS "Maximum optimization for size")
add_compile_options(-Os)
else ()
message(STATUS "Minimal optimization, debug info included")
add_compile_options(-Og -g)
endif ()
include_directories(
./
${CMAKE_SOURCE_DIR}/drivers/CMSIS/Include
${CMAKE_SOURCE_DIR}/drivers/CMSIS/Device/ST/STM32F1xx/Include
${CMAKE_SOURCE_DIR}/drivers/STM32F1xx_HAL_Driver/Inc
${CMAKE_SOURCE_DIR}/drivers/STM32F1xx_HAL_Driver/Inc/Legacy
${CMAKE_SOURCE_DIR}/main
)
add_definitions(-DUSE_HAL_DRIVER -D__MICROLIB -DSTM32F1 -DSTM32F1xx -DSTM32F103xB)
aux_source_directory(${CMAKE_SOURCE_DIR}/drivers/STM32F1xx_HAL_Driver/Src HAL_DRIVER)
aux_source_directory(${CMAKE_SOURCE_DIR}/drivers/CMSIS/Device/ST/STM32F1xx/Source/Templates SYSTEM)
aux_source_directory(${CMAKE_SOURCE_DIR}/main MAIN)
set(STARTUP ${CMAKE_SOURCE_DIR}/drivers/CMSIS/Device/ST/STM32F1xx/Source/Templates/gcc/startup_stm32f103xb.s)
set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/drivers/CMSIS/Device/ST/STM32F1xx/Source/Templates/gcc/linker/STM32F103XB_FLASH.ld)
add_link_options(-Wl,-gc-sections,--print-memory-usage,-Map=${PROJECT_BINARY_DIR}/${PROJECT_NAME}.map)
add_link_options(-mcpu=cortex-m3 -mthumb -mthumb-interwork)
add_link_options(-T ${LINKER_SCRIPT})
add_executable(${PROJECT_NAME}.elf ${HAL_DRIVER} ${SYSTEM} ${MAIN} ${STARTUP} ${LINKER_SCRIPT})
set(HEX_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.hex)
set(BIN_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.bin)
add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -Oihex $ ${HEX_FILE}
COMMAND ${CMAKE_OBJCOPY} -Obinary $ ${BIN_FILE}
COMMENT "Building ${HEX_FILE}
Building ${BIN_FILE}")