1. VSCode + Keil
在 Keil 里面编码不太方便,如果直接用 vscode 打开源码目录,会有很多告警(使用 C/C++ 扩展),这些告警会对跳转、自动补全产生影响。告警的原因是 vscode 自动生成的 C/C++ 扩展配置是自动检测电脑编译链之后的结果,例如,windows-msvc-x64
,这样的话,vscode 找不到正确的头文件,进而,也找不到 ARMCC 相关的宏定义,于是导致上面的问题发生。
可以借用 Keil Assistant 扩展生成 C/C++ 扩展配置文件,解决告警的问题。
进行如下配置:
安装 Keil Assistant 扩展。
打开 Keil Assistant 配置页面,设置好
UV4.exe
的路径。安装好 Keil Assistant 插件后,vscode 资源管理器的下方会出现
KEIL UVISION PROJECT
,光标移动到上面上,旁边会出现加号图标,点击该图标选择对应的 Keil 工程。- 根据提示,切换工作空间(打开的目录为 Keil 工程文件所在的目录)。
- 使用 Keil 工程文件所在目录下自动生成的
.vscode/c_cpp_properties.json
文件替换原工作空间(代码所在目录)中的.vscode/c_cpp_properties.json
文件。 - 重新打开源代码所在目录。
尽管使用 Keil Assistant 打开工程文件后,可以直接在 vscode 中进行编译、烧录,但是由于此时的工作空间目录是 Keil 工程文件所在目录,快捷键 Ctrl+P
、Ctrl+Shift+P
、Ctrl+Shift+O
等无法搜索不在该目录内的源码文件,不方便。
2. VSCode + GNU Arm Embedded Toolchain + OpenOCD
在这套方案下,编码、编译、调试都可以在 VSCode 中完成。
2.1 安装开发环境
(1) 安装 Windows Terminal 和 PowerShell
在 Windows 上进行命令行操作时,强烈推荐使用 Windows Terminal 和 PowerShell 的组合,例如使用 wsl、Rust 或者手动调用 gcc 的时候,比系统自带的 CMD 和 Windows PowerShell 工具好用很多。
可以快速浏览官网介绍,了解 Windows Terminal 的特性:什么是 Windows 终端?
Windows Terminal 在 Microsoft Store 中安装,PowerShell 要从 GitHub 仓库下载:
1
https://github.com/PowerShell/PowerShell/releases
安装完成后,在 Windows Terminal 中新建一个 PowerShell 的配置文件,并将其设置为 Windows Terminal 的默认配置文件,这样每次启动 Windows Terminal 时,默认打开的就是 PowerShell 了。
(2) 安装 arm-none-eabi 工具链
从 ARM 官网下载工具链,例如 gcc-arm-none-eabi-10.3-2021.10-win32.exe,下载完成后安装,安装完成后将工具链路径添加到环境变量中,例如:
1
D:\Program Files (x86)\GNU Arm Embedded Toolchain\10 2021.10\bin
之后在 PowerShell
中测试一下工具链是否能被调用:
1
2
3
4
5
gcm arm-none-eabi-gcc
gcm arm-none-eabi-gdb
arm-none-eabi-gcc.exe -v
arm-none-eabi-gdb.exe -v
(3) 安装 make
借助 MSYS2 安装 make 工具(按照官网首页的教程安装 mingw-w64 GCC
即可),安装完成之后,将 make
所在目录添加到环境变量中,例如:
1
C:\msys64\usr\bin
之后在 PowerShell
中测试一下 make
是否能被调用:
1
2
3
gcm make
make -v
(4) VSCode 安装 Cortex-Debug 插件
搜索安装即可。
(5) 安装 OpenOCD
打开刚才安装好的 MSYS2 命令行,安装 OpenOCD:
1
pacman -S --needed mingw-w64-x86_64-openocd
如果遇到安装包签名问题,可以尝试执行如下命令解决:
1
2
3
4
5
rm -rf /etc/packman.d/gnupg
pacman-key --init
pacman -S msys2-keyring
pacman-key --populate msys2
pacman -Syu
OpenCDC
会被安装到 MSYS2 的 usr\bin
目录下(和 make
目录相同)。同样,在命令行中测试一下是否可用:
1
openocd -v
2.2 生成示例工程并在命令行中编译调试
使用 STM32CubeMX
来生成 make
工程,配置好外设、时钟之后,在 Project Manger
-> Project
-> Toolchain/IDE
中选择 Makefile
,生成 make
工程,例如为 NUCLEO-L452RE
生成的名为 base
的工程:
1
2
3
4
5
6
7
8
9
10
11
├─build
├─Core
│ ├─Inc
│ └─Src
├─Drivers
│ ├─CMSIS
│ └─STM32L4xx_HAL_Driver
├─Makefile
├─startup_stm32l452xx.s
├─STM32L452RETx_FLASH.ld
└─base.ioc
然后打开 Windows Terminal,进入工程目录,执行 make
编译工程:
1
make
编译完成后,连接 NUCLEO-L452RE
开发板,在 Windows Terminal 中启动 OpenOCD
进行调试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
base> openocd -f interface/stlink-V2.cfg -f target/stm32l4x.cfg
Open On-Chip Debugger 0.12.0
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
WARNING: interface/stlink-v2.cfg is deprecated, please switch to interface/stlink.cfg
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 500 kHz
Info : STLINK V2J38M27 (API v2) VID:PID 0483:374B
Info : Target voltage: 3.263559
Info : [stm32l4x.cpu] Cortex-M4 r0p1 processor detected
Info : [stm32l4x.cpu] target has 6 breakpoints, 4 watchpoints
Info : starting gdb server for stm32l4x.cpu on 3333
Info : Listening on port 3333 for gdb connections
[stm32l4x.cpu] halted due to breakpoint, current mode: Thread
xPSR: 0x01000000 pc: 0x08000874 msp: 0x20027ff8
再打开一个中断,启动 gdb:
1
2
3
4
base> arm-none-eabi-gdb.exe -q .\build\base.elf
D:\Program Files (x86)\GNU Arm Embedded Toolchain\10 2021.10\bin\arm-none-eabi-gdb.exe: warning: Couldn't determine a path for the index cache directory.
Reading symbols from .\build\base.elf...
(gdb)
连接 OpenOCD
:
1
2
3
4
5
(gdb) target remote: 3333
Remote debugging using : 3333
main () at Core/Src/main.c:100
100 while (1)
(gdb)
OpenOCD
会有如下输出:
1
2
3
4
5
6
7
8
9
Info : accepting 'gdb' connection on tcp/3333
Info : device idcode = 0x20016462 (STM32L45/L46xx - Rev Y : 0x2001)
Info : RDP level 0 (0xAA)
Info : flash size = 512 KiB
Info : flash mode : single-bank
Info : device idcode = 0x20016462 (STM32L45/L46xx - Rev Y : 0x2001)
Info : RDP level 0 (0xAA)
Info : OTP size is 1024 bytes, base address is 0x1fff7000
Warn : Prefer GDB command "target extended-remote :3333" instead of "target remote :3333"
在 gdb 中增加断点。首先,使用 list main
命令查看 main
函数源码:
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
(gdb) list main
63 /**
64 * @brief The application entry point.
65 * @retval int
66 */
67 int main(void)
68 {
69 /* USER CODE BEGIN 1 */
70
71 /* USER CODE END 1 */
72
(gdb)
73 /* MCU Configuration--------------------------------------------------------*/
74
75 /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
76 HAL_Init();
77
78 /* USER CODE BEGIN Init */
79
80 /* USER CODE END Init */
81
82 /* Configure the system clock */
(gdb)
83 SystemClock_Config();
84
85 /* USER CODE BEGIN SysInit */
86
87 /* USER CODE END SysInit */
88
89 /* Initialize all configured peripherals */
90 MX_GPIO_Init();
91 MX_USART2_UART_Init();
然后在添加断点,比如在 main
函数入口以及 MX_GPIO_Init()
所在的第 90 行添加断点:
1
2
3
4
5
6
(gdb) break main
Breakpoint 1 at 0x800085a: file Core/Src/main.c, line 76.
Note: automatically using hardware breakpoints for read-only addresses.
(gdb) break 90
Breakpoint 2 at 0x8000862: file Core/Src/main.c, line 90.
(gdb)
查看已经添加的断点:
1
2
3
4
5
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0800085a in main at Core/Src/main.c:76
2 breakpoint keep y 0x08000862 in main at Core/Src/main.c:90
(gdb)
装载:
1
2
3
4
5
6
7
8
9
10
11
(gdb) load
Loading section .isr_vector, size 0x198 lma 0x8000000
Loading section .text, size 0x3e68 lma 0x80001c0
Loading section .rodata, size 0x60 lma 0x8004028
Loading section .ARM, size 0x8 lma 0x8004088
Loading section .init_array, size 0x8 lma 0x8004090
Loading section .fini_array, size 0x8 lma 0x8004098
Loading section .data, size 0x850 lma 0x80040a0
Start address 0x08002204, load size 18632
Transfer rate: 22 KB/sec, 2661 bytes/write.
(gdb)
运行:
1
2
3
4
5
6
(gdb) c
Continuing.
Breakpoint 1, main () at Core/Src/main.c:76
76 HAL_Init();
(gdb)
遇到了第一个断点,停住了。继续执行:
1
2
3
4
5
6
7
(gdb) c
Continuing.
halted: PC: 0x08000fc0
Breakpoint 2, main () at Core/Src/main.c:90
90 MX_GPIO_Init();
(gdb)
遇到了第二个断点,停住了。
截止到现在,已经可以在终端中编译、调试 STM32 程序了。接下来看一下如何在 VS Code 中编译、调试程序。
2.3 配置 VS Code 并在 VS Code 中开发调试
使用 VSCode 打开刚才生成的工程目录,新增 make
任务(.vscode/tasks.json
):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"version": "2.0.0",
"tasks": [
{
"label": "make",
"type": "shell",
"command": "make",
"group": "build",
"problemMatcher": [],
"detail": "target build task"
}
]
}
配置调试参数(.vscode/launch.json
):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"version": "0.2.0",
"configurations": [
{
/* Configuration for the NUCLEO-L452RE board */
"type": "cortex-debug",
"request": "launch",
"name": "Debug (OpenOCD)",
"servertype": "openocd",
"cwd": "${workspaceRoot}",
"preLaunchTask": "make",
"runToEntryPoint": "main",
"executable": "./build/base.elf",
"device": "STM32L452RE",
"configFiles": [
"interface/stlink.cfg",
"target/stm32l4x.cfg"
]
}
]
}
完整目录如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
├─.vscode
│ ├─launch.json
│ └─tasks.json
├─build
├─Core
│ ├─Inc
│ └─Src
├─Drivers
│ ├─CMSIS
│ └─STM32L4xx_HAL_Driver
├─Makefile
├─startup_stm32l452xx.s
├─STM32L452RETx_FLASH.ld
└─base.ioc
关闭之前打开的 OpenOCD
和 gdb
终端(否则 VS Code 中运行调试会失败),在 VS Code 中点击 运行和调试
,即可编译、调试程序。
2.4 使用“半托管”特性
借助 semihosting
,可以将一些调试信息输出到 OpenOCD
的控制台上。需要修改 Makefile,连接 rdimon
库,并在 main
函数中调用 initialise_monitor_handles()
:
1
2
3
4
5
# LIBS = -lc -lm -lnosys
LIBS = -lc -lm -lrdimon
# LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections
LDFLAGS = $(MCU) --specs=rdimon.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections
代码:
1
2
3
4
5
6
7
8
9
10
11
/* ... */
#include <stdio.h>
/* ... */
int main(void)
{
/* ... */
initialise_monitor_handles();
/* ... */
printf("Hello World.\n");
/* ... */
}
清除编译工程,重新编译并调试:可以在 OpenOCD
的控制台输出中看到打印信息:
1
2
make clean
make
在 gdb
的控制台使能 semihosting
(输入 monitor arm semihosting enable
):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(gdb) target remote: 3333
(gdb) monitor arm semihosting enable
(gdb) list main
(gdb)
93
94 printf("Hello World.\n");
95
(gdb) break 94
(gdb) load
Loading section .isr_vector, size 0x198 lma 0x8000000
Loading section .text, size 0x41d8 lma 0x80001c0
Loading section .rodata, size 0x80 lma 0x8004398
Loading section .ARM, size 0x8 lma 0x8004418
Loading section .init_array, size 0x8 lma 0x8004420
Loading section .fini_array, size 0x8 lma 0x8004428
Loading section .data, size 0x858 lma 0x8004430
Start address 0x08002208, load size 19552
Transfer rate: 23 KB/sec, 2444 bytes/write.
(gdb) c
Continuing.
Breakpoint 1, main () at Core/Src/main.c:94
94 printf("Hello World.\n");
(gdb)c
OpenOCD
控制台有如下输出:
1
2
3
4
[stm32l4x.cpu] halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08002208 msp: 0x20028000
Info : halted: PC: 0x08000870
Hello World.