使用VScode开发STM32:基于CMake

13k words

使用VScode开发STM32:基于CMake

本教程使用VScode作为代码编辑工具、使用Cmake作为构建系统生成器、Make进行构建系统、使用arm-none-eabi-gcc进行交叉编译、使用OpenOCD作为代码下载与调试工具,最终搭建出适用于ARM架构系列芯片的开发环境。此教程以STM32F103ZET6芯片为例,演示LED灯闪烁的项目。

经验证,可满足基本基本项目需求。同时由于arm-none-eabi-gcc编译器相比于keil的AC5、AC6编译器,所编译的hex文件比较大,Flash占用较高,为了兼顾keil开发项目,也为了能够与其他人的项目兼容,这里的keil与VScode的项目文件互不干扰,满足兼容性需求。

本文涉及的软件安装包、工程模板已放在我的百度网盘中,需要自取。

1
2
3
链接:https://pan.baidu.com/s/1pO-5R3Li5NIfctx3UzaPig?pwd=ts91 
提取码:ts91
--来自百度网盘超级会员V4的分享

一、软件安装

已默认电脑上存在VScode,这里不讲述Vscode的安装。

涉及软件的安装配置:

  • 安装Cmake
  • 安装arm-none-eabi-gcc
  • 安装OpenOCD
  • MinGW
  • 安装VScode插件C/C++、CMake、Cortex-Debug
1.1 安装CMake
1.1.1 安装

下载地址:

1
https://cmake.org/download/

选择适合自己电脑的最新版本进行下载并安装,我这里选择cmake-3.29.2-windows-x86_64.msi,如下图:

1.1.2 添加环境变量

我们需要将cmake的可执行文件的文件夹路径添加到环境变量,方便使用命令调用cmake,我的路径为:

1
D:\RJ\CMake\bin

将以上目录添加到系统环境变量中去。

1.1.3 验证

在终端输入以下命令,验证是否安装成功。

1
cmake

成功则将显示以下内容:

1.2 安装arm-none-eabi-gcc
1.2.1 安装

下载地址:

1
https://developer.arm.com/downloads/-/gnu-rm

选择适合自己电脑的最新版本进行下载并安装,我这里选择gcc-arm-none-eabi-10.3-2021.10-win32.exe,如下图:

1.2.2 添加环境变量

我们需要将arm-gcc的可执行文件的文件夹路径添加到环境变量,方便使用命令调用arm-gcc,我的路径为:

1
D:\RJ\ARM-GCC\10 2021.10\bin

将以上目录添加到系统环境变量中去。

1.2.3 验证

在终端输入以下命令,验证是否安装成功。

1
arm-none-eabi-gcc

成功则将显示以下内容:

1.3 安装OpenOCD
1.3.1 安装

下载地址:

1
https://gnutoolchains.com/arm-eabi/openocd/

选择适合自己电脑的最新版本进行下载,直接下载的是压缩包文件,解压后可直接使用,我这里选择openocd-20231002.7z,如下图:

1.3.2 添加环境变量

我们需要将OpenOCD的可执行文件的文件夹路径添加到环境变量,方便使用命令调用OpenOCD,我的路径为:

1
D:\RJ\OpenOCD-20231002-0.12.0\bin

将以上目录添加到系统环境变量中去。

1.3.3 验证

在终端输入以下命令,验证是否安装成功。

1
openOCD

成功则将显示以下内容:

1.4 安装MinGW
1.4.1 安装

下载地址:

1
https://sourceforge.net/projects/mingw-w64/files/

选择适合自己电脑的最新版本进行下载,直接下载的是压缩包文件,解压后的mingw64可直接使用,我这里选择MinGW-W64GCC-8.1.0下的x86_64-posix-sjlj,如下图:

1.4.2 添加环境变量

我们需要将make的可执行文件的文件夹路径添加到环境变量,方便使用命令调用make,我的路径为:

1
D:\RJ\mingw64\bin

将以上目录添加到系统环境变量中去。

1.3.3 验证

在终端输入以下命令,验证是否安装成功(由于Window下make执行程序为mingw32-make.exe,我这里将其复制保存同目录下为副本,并改名为make.exe)。

1
make

成功则将显示以下内容:

1.5 在Vscode中安装插件

要安装的插件如下:

二、工程搭建

以下是我的工程框架

与ARM-MDK工程不同,我们配置工程还需要格外的文件,分别是CMakeLists.txt、startup_stm32f10x_hd.s、STM32F103ZETx_FLASH.ld。

2.1 配置CMakeLists.txt文件

CMake根据CMakeLists.txt进行构建,从而创建出Makefile,再由make根据 Makefile 定义的规则调用 GCC 执行编译工作,最终生成可执行的.elf或者.hex文件。以下是CMakeLists.txt的模板,需要更改的部分我已经标明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#THIS FILE IS AUTO GENERATED FROM THE TEMPLATE! DO NOT CHANGE!
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_VERSION 1)
cmake_minimum_required(VERSION 3.20)

# 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)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)

# project settings
project(Project C CXX ASM)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11)

#Uncomment for hardware floating point
#add_compile_definitions(ARM_MATH_CM4;ARM_MATH_MATRIX_CHECK;ARM_MATH_ROUNDING)
#add_compile_options(-mfloat-abi=hard -mfpu=fpv4-sp-d16)
#add_link_options(-mfloat-abi=hard -mfpu=fpv4-sp-d16)

#Uncomment for software floating point
#add_compile_options(-mfloat-abi=soft)

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")
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
message(VERBOSE "Maximum optimization for speed")
add_compile_options(-Ofast)
elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
message(VERBOSE "Maximum optimization for speed, debug info included")
add_compile_options(-Ofast -g)
elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "MinSizeRel")
message(VERBOSE "Maximum optimization for size")
add_compile_options(-Os)
else ()
message(VERBOSE "Minimal optimization, debug info included")
add_compile_options(-Og -g)
endif ()

#添加宏定义
add_definitions(-DUSE_HAL_DRIVER -DSTM32F103xB -DUSE_STDPERIPH_DRIVER -DSTM32F10X_HD)

#添加头文件路径,即.h文件
include_directories(./STM32F10x_FWLib/inc ./User ./Project/Code-Cmake)
#添加源文件路径,即.c或者.s文件
file(GLOB_RECURSE SOURCES ./STM32F10x_FWLib/src/*.c ./User/*.c ./Project/Code-Cmake/*.*)

#添加你的STM32F103ZETx_FLASH.ld的连接脚本路径
set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/Project/Code-Cmake/STM32F103ZETx_FLASH.ld)

add_link_options(-Wl,-gc-sections,--print-memory-usage,-Map=${PROJECT_BINARY_DIR}/${PROJECT_NAME}.map)
#选择cortex-m3内核
add_link_options(-mcpu=cortex-m3 -mthumb -mthumb-interwork)
add_link_options(-T ${LINKER_SCRIPT})

add_link_options(-specs=nano.specs -specs=nosys.specs -u _printf_float)

add_executable(${PROJECT_NAME}.elf ${SOURCES} ${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 $<TARGET_FILE:${PROJECT_NAME}.elf> ${HEX_FILE}
COMMAND ${CMAKE_OBJCOPY} -Obinary $<TARGET_FILE:${PROJECT_NAME}.elf> ${BIN_FILE}
COMMENT "Building ${HEX_FILE}
Building ${BIN_FILE}")
2.2 选择startup_stm32f10x_hd.s

在我们创建ARM-MDK工程时,我们从官方的固件包中选择的是arm版本的启动文件,在这里我们要选择gcc版本的启动文件,即下图中的gcc_ride7。同时为了与ARM-MDK有所区分,我将该文件放在了/Project/Code-Cmake文件夹下。

2.3 STM32F103ZETx_FLASH.ld

STM32F103ZETx_FLASH.ld是一个链接脚本文件,它告诉编译器相关的编译后的可执行代码,内存变量,中断向量,链接在哪个存储区。获取方式主要有三种(请根据自己单片机型号选择):

  • 使用CudeMax编译过程可以生成该链接脚本
  • 搜索已有的工程,你可以直接在浏览器搜索STM32F103ZETx_FLASH.ld,一般都有。
  • 如果你对该型号芯片足够了解,可以自行编写。

以下是我的STM32F103ZETx_FLASH.ld:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/*
******************************************************************************
**
** @file : LinkerScript.ld
**
** @author : Auto-generated by STM32CubeIDE
**
** @brief : Linker script for STM32F103ZETx Device from STM32F1 series
** 512Kbytes FLASH
** 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) 2021 STMicroelectronics.
** All rights reserved.</center></h2>
**
** This software component is licensed by ST under BSD 3-Clause license,
** the "License"; You may not use this file except in compliance with the
** License. You may obtain a copy of the License at:
** opensource.org/licenses/BSD-3-Clause
**
******************************************************************************
*/

/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */

_Min_Heap_Size = 0x200 ; /* required amount of heap */
_Min_Stack_Size = 0x400 ; /* required amount of stack */

/* Memories definition */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 512K
}

/* Sections */
SECTIONS
{
/* The startup code into "FLASH" Rom type memory */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH

/* The program code and other data into "FLASH" Rom type memory */
.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 into "FLASH" Rom type memory */
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH

.ARM.extab : {
. = ALIGN(4);
*(.ARM.extab* .gnu.linkonce.armextab.*)
. = ALIGN(4);
} >FLASH

.ARM : {
. = ALIGN(4);
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
. = ALIGN(4);
} >FLASH

.preinit_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
} >FLASH

.init_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
} >FLASH

.fini_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
. = ALIGN(4);
} >FLASH

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

/* Initialized data sections into "RAM" Ram type memory */
.data :
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
*(.RamFunc) /* .RamFunc sections */
*(.RamFunc*) /* .RamFunc* sections */

. = ALIGN(4);
_edata = .; /* define a global symbol at data end */

} >RAM AT> FLASH

/* Uninitialized data section into "RAM" Ram type memory */
. = ALIGN(4);
.bss :
{
/* This is used by the startup in order to initialize the .bss section */
_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" Ram type memory left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM

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

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

2.4 关于core_cm3.c文件

由于gcc编译的问题,如果不更改core_cm3.c,可能出现以下报错:

我对此做出以下两处更改,并放在了/Project/Code-Cmake文件夹下,与MDK-ARM分开:

2.5 配置.vscode文件夹

这是VScode配置文件的位置

2.4.1 添加并配置c_cpp_properties.json

将其C/C++模式更改为gcc-arm,注意将gcc路径替换为自己的路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"compilerPath": "D:\\RJ\\mingw64\\bin\\gcc.exe",
"cStandard": "gnu17",
"cppStandard": "gnu++14",
"intelliSenseMode": "gcc-arm",
"configurationProvider": "ms-vscode.cmake-tools"
}
],
"version": 4
}

2.4.1 添加并配置launch.json

这个文件是关于烧录与调试相关的,在此目录下你可以选择你的下载器型号、芯片型号。其中的stm32f103.svd可以在调试时查看看寄存器的值,请将以下路径改为自己工程的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"cwd": "${workspaceRoot}",
"executable": "D:/GC/STM32F1/build/Project.elf",
"name": "Debug with OpenOCD",
"request": "launch",
"type": "cortex-debug",
"servertype": "openocd",
"configFiles": [
"D:/RJ/OpenOCD-20231002-0.12.0/share/openocd/scripts/interface/stlink-v2.cfg", //在OpenOCD选择下载器
"D:/RJ/OpenOCD-20231002-0.12.0/share/openocd/scripts/target/stm32f1x.cfg" //在OpenOCD选择芯片
],
"svdFile": "D:/GC/STM32F1/stm32f103.svd", //选择寄存器文件
}
]
}

三、编译、下载与调试

如果我们配置完成后,用VScode打开CMakeLists.txt所在文件夹工程过后,Cmake tool会自动提示配置Cmake,点击配置后,会生成build文件夹,产生的Makefile及其他中间文件会存放在该目录。

3.1 选择编译器

点击VScode下方的配置按钮,选择gcc-arm

3.2 编译

点击VScode下方的进行编译,生成目标文件

编译过程

在build文件夹下会生成目标文件

3.3 烧录

进入build文件夹下执行以下命令,其中将Project.hex替换为自己的目标文件,stlink-v2.cfg是选择下载器类型,stm32f1x.cfg是芯片型号

1
openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c "program Project.hex verify reset exit"

烧录成功

3.3 调试

打开左侧的运行和调试,选择Debug with OpenOCD

点击运行,可进行断点调试,变量监测,寄存器查看等操作。

3.4 关于变量定义

使用gcc编译时,我们一般需要告诉编译器这个变量是可变的,不然会造成内存覆盖,程序无法运行的情况,即voatile关键词

四、参考链接

此文章参考以下文章,若描述不清,可查看下方文章

Vscode搭建开发调试STM32/RISC-V环境IDE(最全面)

VSCode 和 CMake 搭建嵌入式开发环境