做嵌入式的朋友们都应该有过想要有个功能强大的IDE或者编辑器,Keil MDK5、IAR EWARM是用的比较普遍的平台了,但是两者在编辑器方面都比较弱势,当然可以设置 或者使用外部编辑器,像Sourceinsight、notePad++这样,但毕竟需要切换回来进行Build、Debug,非常麻烦。
VScode从发布至今口碑一直很好,而且里面有非常多的插件,比如彩虹括号、Code Runner、Git、各种语言的支持包等,都非常好用,并且,启动速度快,界面和使用方式和VS很像,各种好看的主题配色。像Keil、IAR不支持深色主题,看久了真的是眼睛痛(如果只把editor背景设为深色会觉得很不协调)。
前段时间刚好研究了一下linux下用makefile进行编译链接,对编译、链接有了一定的了解。这两天正好在玩STM32的CubeMX,发现里面可以自动生成makefile,就产生了在windows平台下,使用VScode和makefile编译链接,GDB调试的想法。
调试好的工程已经上传到GitHub上:https://github.com/ruomenganran/VScode_Debug_STM32
##1. 需要安装的环境:
C:\Program Files (x86)\CodeBlocks\MinGW\bin;D:\LLVM\bin;
C:\Program Files (x86)\GNU Tools ARM Embedded\8 2019-q3-update\bin;
C:\Program Files (x86)\SEGGER\JLink_V512f;
jlink的目录可以不加,加入是为了做后面的自动开启Jlink GDB Server用。
##3. 准备工程模板
这里只做一个最简要的工程,使用CubeMX创建工程,在Project Manager中配置Toolchain为makefile。然后配置项目名称、位置之类。
配置时钟、引脚功能。
点击GENERATE CODE进行生成代码。
生成完成后使用VScode打开工程目录,在main.c中添加一些代码,我写的是两个LED闪烁的程序。注意要在CubeMX规定的用户代码区域中添加代码,否则重新生成工程会被覆盖。写好后,保存。
##3. 配置默认终端
在终端里,选择默认终端:
选择Git Bash。这里选择这个终端的原因是用makefile来编译的指令“make”是minGW的指令,cmd是无法识别的。另外,CubeMX生成的makefile里也会有一些linux指令,使用minGW终端可以解决这个问题。不过,在后面的tasks.json、launch.json中的command要注意使用shell命令。
##4. 配置debug、make
之前完成的工程应该是这样的:
如果没有.vscode文件夹没关系,自己新建这个文件夹,然后在里面新建这两个文件:
launch是用来载入debug的配置文件,tasks是配置的任务,可以单独执行(ctrl+shift+B)。
先在tasks中创建一个Build任务,让他通过makefile进行编译、链接,生成烧录文件。然后创建一个Clean任务,可以清空build文件。在tasks.json中输入如下代码:
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Build",
"type": "shell",
"options": {
"cwd": "${workspaceRoot}/STM32F429IGT"
},
"command": "mingw32-make",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "Clean",
"type": "shell",
"options": {
"cwd": "${workspaceRoot}/STM32F429IGT"
},
"command": "mingw32-make -f makefile clean",
"group": {
"kind": "build",
"isDefault": true
}
},
]
}
这里我们可以先测试一下这个build task。Ctrl+Shift+B,选择Bulid,终端窗口会打印当前的状态,然后完成编译,如下图:
CubeMX的makefile默认是生成elf、hex、bin文件的,足够使用。
如果已经build,再次build,会出现如下提示:
如果想要重新生成,可以先运行Clean,然后Build。下图是运行Clean任务:
可以看到makefile中clean其实是将build文件夹删除。
然后在launch中创建一个debug配置,这个配置中要调用刚刚的build任务,然后在进行debug。
因为使用的是Jlink,所以这里采用的方法是使用Jlink的GDB server方式。原理是VScode调用GNU的gdb调试器,将gdb远程调试链接到Jlink GDB server的端口,Jlink GDB server再链接目标Device。Jlink GDB server的默认端口是 2331。
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Debug with Jlink",
"type": "cppdbg",
"request": "launch",
"targetArchitecture": "arm",//虽然官方说弃用了,但实际上必须指明
"program": "${workspaceFolder}/STM32F429IGT/a.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/STM32F429IGT",
"environment": [],
"externalConsole": true,
"preLaunchTask": "Build",
"MIMode": "gdb",
"miDebuggerPath": "arm-none-eabi-gdb.exe",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"miDebuggerServerAddress": "localhost:2331",
"customLaunchSetupCommands": [
{
"text": "target remote :2331",
"description": "connect to server",
"ignoreFailures": false
},
{
"text": "file E:/Git/STM32/STM32F429/STM32F429IGT/build/STM32F429IGT.elf",
"description": "load file to gdb",
"ignoreFailures": false
},
{
"text": "load",
"description": "download file to MCU",
"ignoreFailures": false
},
{
"text": "monitor reset",
"description": "reset MCU",
"ignoreFailures": false
},
{
"text": "b main",
"description": "set breakpoints at main",
"ignoreFailures": false
},
]
}
]
}
##5. 开始调试
因为用到了Jlink GDB server,所以在调试之前要先打开Jlink GDB server,选择对应单片机,这个时候Jlink要连接到电脑上。
点击OK,server就打开了,如下图:
GDB的状态是Waiting for connection。
现在就可以开始debug了。切换到VScode的debug页面,选择上面配置的(gdb) Debug with Jlink,点击开始按钮,就开始调试了。
局部变量、监视都可以正常使用,但有个问题不知道是为什么,在程序运行时打断点,会出现下面的情况:
点击继续运行就可以运行到断点处。
另外一个问题是在launch中有一个command是让gdb在main处打一个断点,但是开始debug后程序会直接运行。这两个问题需要解决之外,这样调试的缺点还有不能查看寄存器、memory。不过基本的编程、烧录功能使用起来都比较简单,可以替代MDK5、EWARM。
##6. 更新、优化
在研究之后,我将打开Jlink GDB server也加入到debug的前置任务中,如果已经打开,则会先关闭当前打开Jlink GDB server然后重启,并且Jlink GDB server的单片机型号可以在task中直接设置。
另外,增加rebuild all的task,原理是先clean然后build。
在做了这些工作后,发现开始debug后,可以在main函数出自动断点,等待运行了。
修改后的tasks.json:
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Build",
"type": "shell",
"options": {
"cwd": "${workspaceRoot}/STM32F429IGT"
},
"command": "mingw32-make",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
},
{
"label": "Clean",
"type": "shell",
"options": {
"cwd": "${workspaceRoot}/STM32F429IGT"
},
"command": "mingw32-make -f makefile clean",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
},
{
"label": "RebuildAll",
"type": "shell",
"options": {
"cwd": "${workspaceRoot}/STM32F429IGT"
},
"command": "E:/Git/STM32/STM32F429/.vscode/RebuildAll.cmd",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
},
{
"label": "Jlink GDB Server",
"type": "process",
"options": {
"cwd": "${workspaceRoot}/STM32F429IGT"
},
"command": "E:/Git/STM32/STM32F429/.vscode/MakeAndStartJlink.cmd",
"args": ["STM32F429IG"],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
}
]
}
其中可以看到RebuildAll、Jlink GDB Server的命令使用了cmd,因为不止一条命令,最好单独写一个文件。另外,由于cmd终端和minGW终端不太兼容,Jlink GDB Server的type为process,即外部进程。
"args"为传入cmd的参数,填写MCU型号,一定要能在Jlink devices中找到的,比如这里写了STM32F429IG,写STM32F429就是不可以的。
下面附上两个cmd的代码:
MakeAndStartJlink.cmd:
echo off
cd %ProgramFiles(x86)%/SEGGER/JLink_V512f
tasklist /fi "Imagename eq JLinkGDBServer.exe"|find "JLinkGDBServer.exe"&&taskkill /f /im "JLinkGDBServer.exe"
tasklist /fi "Imagename eq JLinkGDBServer.exe"|find "JLinkGDBServer.exe"&&taskkill /f /im "JLinkGDBServer.exe"
start JLinkGDBServer.exe -select USB -device %1 -if JTAG -speed 1000 -noir
mingw32-make
RebuildAll.cmd:
mingw32-make -f makefile clean
mingw32-make
exit
然后就是launch.json,只是将前置任务改为新添加的Jlink GDB Server。
{
//-select USB -device STM32F429IG -if JTAG -speed 1000 -noir
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Debug with Jlink",
"type": "cppdbg",
"request": "launch",
"targetArchitecture": "arm",//虽然官方说弃用了,但实际上必须指明
"program": "${workspaceFolder}/STM32F429IGT/a.exe",
"args": [],
"stopAtEntry": true,
"cwd": "${workspaceFolder}/STM32F429IGT",
"environment": [],
"externalConsole": true,
"preLaunchTask": "Jlink GDB Server",
"MIMode": "gdb",
"miDebuggerPath": "arm-none-eabi-gdb.exe",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"miDebuggerServerAddress": "localhost:2331",
"customLaunchSetupCommands": [
{
"text": "target remote :2331",
"description": "connect to server",
"ignoreFailures": false
},
{
"text": "file E:/Git/STM32/STM32F429/STM32F429IGT/build/STM32F429IGT.elf",
"description": "load file to gdb",
"ignoreFailures": false
},
{
"text": "load",
"description": "download file to MCU",
"ignoreFailures": false
},
{
"text": "monitor reset",
"description": "reset MCU",
"ignoreFailures": false
},
{
"text": "b main",
"description": "set breakpoints at main",
"ignoreFailures": false
},
]
}
]
}
这些工作都完成后,.vscode文件目录结构应该是这样的:
这样,改好了,debug运行一下,Jlink GDB Server自动打开了,debug也正常,惊奇的是,现在debug会停在main函数入口等待开始了。
综上,开发环境基本配置完毕,也满足基础的需求,在这个过程中,主要遇到的问题是cmd、shell命令。我不知道在launch.json和tasks.json中能不能指定使用的终端,所以只能都采用shell(因为make指令、makefiles里是shell命令),因此,也花了一些时间去学习、测试这些指令。