VSCode编译调试STM32的环境搭建

VSCode编译调试STM32的环境搭建

深夜了,我很饿,我只是想起床吃个泡面。

@firestaradmin 2021年5月13日00:49:02

一、软件环境安装

  • VSCode

    主角不用多说。

  • STM32CubeMX

    用于生成带Makefile的HAL库工程。(本文章后面会介绍如何替换为STD标准库)

  • GNU Arm Embedded Toolchain

    arm用的GNU工具链,记得把bin文件夹添加到系统Path环境变量。

  • OpenOCD

    开源的片上调试器(Open On-Chip Debugger),记得把bin文件夹添加到系统Path环境变量。

    linux可以直接 apt install openocd

  • Git

    git版本管理,我们这里要使用的是下载git附带的git bash。这个bash是基于mingw的,非常轻量了,甚至于make都没有。但是问题不大,相信你们电脑里肯定都装有MinGW,或者QT,进入他们的目录,搜索mingw32-make.exe,复制一份重命名为make.exe,然后将存放的目录添加到系统环境变量即可。(如果是Linux则无视这条)

我的环境变量如下:

image-20210513011459480

环境变量修改完毕后,可能需要重启电脑才能生效。然后打开cmd,输入:

arm-none-eabi-gcc --version

and

openocd --version

能看到版本号,说明ok。

二、工程创建

1. 用STM32CubeMX创建工程

新建工程,如下:

image-20210513182206951

选择工程后配置,这里就不详细介绍配置了。主要配置一下时钟和调试接口,SWD就选Serial Wire调试模式。

image-20210513182653731

image-20210513182709266

之后到Project Manager页面 ,选择Toolchain 为 MakeFile,如图:

image-20210513182902639

紧接着,选择拷贝所有的库到工程目录,这样HAL库就全部在工程目录了。当然到时候你可以自己替换为标准库。

image-20210513183115734

最后点击 GENERATE CODE 按钮,生成工程:

image-20210513183206234

于是乎,生成了如下的文件:

image-20210513183322131

2. 配置VSCode

可以直接用VSCode 打开当前目录,或者重新复制一份:

image-20210513183526491

这里有些文件,是后面生成的,亲们目录里没有不要着急哦。

这里 .ioc 文件和 .mxproject 文件是STM32Cube的工程文件,Drivers文件夹里是STM32和ARM CMSIS的库。Core文件夹里的Inc和Src是用户源码和头文件。


接下来装一些插件啦

  • **C/C++**:提供代码补全、智能感知和debug功能;
  • C/C++ Snippets:C/C++ 代码模板片段,自动生成代码段;
  • ARM:提供ARM汇编语言的代码高亮;
  • Cortex-Debug:结合ARM工具链和OpenOCD等工具可以进行图形化调试操作,这样就不用了敲命令来GDB调试了。

以下为可选插件:

  • **Chinese (Simplified)**:中文支持;

  • file-icons:文件图标;

  • LinkerScript:ld链接脚本语法高亮。

  • 其他的插件,你们自己想装就装咯。


下一步(Linux 无视之),Ctrl + Shift + P 快捷键,搜索 settings json,会出现下图,选中打开设置(JSON)

image-20210513212324549

之后在文件末尾添加如下两行设置,用来设置VSCode默认终端为我们上面下载的Git自带的Bash。

"terminal.integrated.shell.windows": "D:\\Program Files\\Git\\bin\\bash.exe",

Ctrl + S 保存,Ctrl + ~(Tab按键上面那个键) 打开终端。

或者如果你只想在当前工程目录下使用bash开发,可以只在当前目录下新建一个.vscode文件夹,然后新建一个单独的settings.json,在里面写上:

{
    "terminal.integrated.shell.windows": "C:\\Program Files\\Git\\bin\\bash.exe"
}

这里如果你是另存为工作区了,可能新建settings.json 文件设置不了终端,需要下图的设置打上勾

image-20210608171112190

然后点击你的工作区文件,进行设置,如下图:

image-20210608171147563


现在其实已经可以打开终端,make 了。但是VSCode 会提示很多波浪线,让人头大,而且会发现也没有语法提示,这怎么能受得了呢。

以前用Keil 开发的时候,我们会在Project Options里设置全局宏定义,如:

image-20210513213420906

而我们用CubeMX生成的Makefile项目中,这两个宏是通过gcc的-D参数在编译时添加的:

image-20210513213535863

但是,VS Code只是一个编辑器,它检查代码的时候并不会去读Makefile,而是只看.h和.c文件,于是在STM32的头文件(如stm32f1xx.h)中就检测不到那个宏,从而认为这个宏没有被定义。

因此我们需要在当前目录的.vscode文件夹下创建c_cpp_properties.json配置文件,用来告诉VS Code我们定义了这些宏。

Ctrl + Shift + P 打开搜索 c/c++,选择编辑配置,如图:

image-20210513213747665

这里是设置好的(我会解释每个设置的作用):

{
    "configurations": [
        {
            "name": "Win32",

            "includePath": [
                "D:/Program Files/gcc-arm-none-eabi-10-2020-q4-major/**",
                "${workspaceFolder}/**"

                // "${workspaceFolder}/Drivers/CMSIS",
                // "${workspaceFolder}/Core/Inc",
                // "${workspaceFolder}/Drivers/STM32F1xx_HAL_Driver/Inc",
                // "${workspaceFolder}/Drivers/CMSIS/Device/ST/STM32F1xx/Include"
                
            ],
            "defines": [
                "USE_HAL_DRIVER",
                "STM32F103xE"
            ],
            "compilerPath": "D:\\Program Files\\gcc-arm-none-eabi-10-2020-q4-major\\bin\\arm-none-eabi-gcc.exe",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "gcc-x64"
        }
    ],
    "version": 4
}
  • name:用于标记当前平台,如win32、Linux或Mac。也就是说,这个json里“configuration“下可以写三组配置,只要每组配置前面写上不同的平台,即可在不同的操作系统上使用就会自动适配不同的配置。

  • includePath:VSCode会从这些目录寻找头文件。

    第一个目录是ARM交叉编译工具链的目录,/** 代表递归寻找目下的文件,可能会降低识别的速度,当文件夹下文件夹很多的时候,尽量用详细点的路径比较好。 目录可以参考Makefile, “${workspaceFolder}”`表示项目文件夹。

  • defines:全局宏定义,告诉VSCode这些宏都被定义了,只是没写在源码中而已。上述多加的两个宏是Makefile里的。

  • compilerPath:指定编译器的路径。因为有些宏是编译器自带的,连Makefile里也没有,例如__GNUC__。有些教程里会让你在defines里面加上__GNUC__,但是这是没必要的。只要你指定了编译器路径,所有的编译器自带的宏都会导入VS Code。

  • intelliSenseMode:gcc选gcc-x64。

  • cStandard: 使用的c标准(一般不影响)

  • cppStandard: 使用的c++标准(一般不影响)

配置好后,Ctrl + S 保存, 神情气爽,没有红色波浪线了,如果有,请慢慢找问题,一般是头文件路径的问题。


这个时候make编译,提示没有make的,去MinGW,或者QT,进入他们的目录,搜索mingw32-make.exe,复制一份重命名为make.exe,然后将存放的目录添加到系统环境变量即可。


(可以无视这里,命令不多的情况下,我喜欢用手敲指令,这里只是介绍一下)为了方便操作,我们在.vscode目录下创建 tasks.json 文件,内容如下:

{
        "version": "2.0.0",
        "tasks": [
            {
                "label": "build",
                "type": "shell",
                "command": "make",
                "args": [
                    "-j"
                ] 
            },
            {
                "label": "clean",
                "type": "shell",
                "command": "make",
                "args": [
                    "clean"
                ] 
            }
        ]
}

这个文件创建了两个任务,分别叫做buildcleanbuild任务就是在bash里执行了make -j,clean任务就是在bash里执行了make clean。

VS Code是可以给任务绑定快捷键的,有兴趣可以自己搜索。

不使用快捷键而执行task的方法:按Ctrl + P,然后输入”task[空格]“,就会出现可用的任务列表。

三、编译调试

1、Openocd 设置

直接在项目文件夹下新建一个openocd.cfg文件,内容如下

# 选择调试器
#source [find interface/jlink.cfg]
source [find interface/cmsis-dap.cfg]

# 选择接口为SWD
transport select swd

# 选择目标芯片
source [find target/stm32f1x.cfg]
#source [find target/stm32f4x.cfg]

openocd启动时,会自动在当前目录下寻找名为openocd.cfg的文件作为配置文件。

本配置文件中引用到的配置文件,都在openocd安装目录下的share/openocd/scripts目录下。其中interface目录下都是接口相关配置文件、target目录下都是芯片相关的配置文件。


2、下载svd文件(可选)

<我是链接>寻找对应STM32的svd文件。

CMSIS-SVD是CMSIS的一个组件,它包含完整微控制器系统(包括外设)的程序员视图的系统视图描述 XML 文件。VS Code可以通过它来知道外设寄存器的地址分布,从而把寄存器内容展示到窗口中。将下载好的STM32FXXX.svd文件放在项目文件夹根目录即可。

3. 配置VS Code的调试功能

在.vscode文件夹中新建一个launch.json,内容如下:

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        
        {
            "name": "Cortex Debug",
            "cwd": "${workspaceRoot}",
            "executable": "${workspaceRoot}/build/${workspaceFolderBasename}.elf",
            "request": "launch",
            "type": "cortex-debug",
            
            "device":"STM32F103ZE",        //使用J-link GDB Server时必须;其他GBD Server时可选(有可能帮助自动选择SVD文件)。支持的设备见 https://www.segger.com/downloads/supported-devices.php
            //"svdFile": "./STM32F407.svd",  //svd文件,有这个文件才能查看寄存器的值,每个单片机都不同。可以在以下地址找到 https://github.com/posborne/cmsis-svd
            "servertype": "openocd",       //使用的GDB Server
            "configFiles": [                  
                "${workspaceRoot}/openocd.cfg"
            ],
            //"preLaunchTask": "build",
            //"armToolchainPath": "C:/Program Files (x86)/GNU Arm Embedded Toolchain/9 2020-q2-update/bin/"
        }
    ]
}

解释几个重要选项:

  • executable:编译出的文件,也就是最终烧录到单片机中的,这里是elf文件。根据芯片的不同,可能产生不同的后缀。
  • request:可以选launch或attach。launch是指启动调试时同时开始执行程序;attcah是指程序已经在运行了,然后开始调试。
  • type:调试的类型,选cortex-debug,这是我们装的插件。默认是cppdbg之类的,但是那样我们得自己配置gdb,配置起来听说很非常麻烦。
  • device:使用J-link GDB Server时必须;其他GBD Server时可选(有可能帮助自动选择SVD文件)。支持的设备见 https://www.segger.com/downloads/supported-devices.php
  • svdFile:svd文件的路径。
  • servertype:要选择的gdb server。我们用openocd。
  • configFiles:gdb server的配置文件路径。其实openocd会自动读当前目录下的openocd.cfg文件,这个选项不填也行。但是如果你想把openocd.cfg放在别处,就可以用这个选项指定配置文件的路径。
  • preLaunchTask:在启动调试前,预先执行的任务。在这里如果配置成前面可选的build任务。这样每次调试前都会先自动编译好。
  • armToolchainPath:工具链的路径。配置了全局环境变量的情况下好像不设置也行

(PS:我注释掉某些的意思就是,大家可以多变通,不必跟着教程一步一步来,按照自己的想法来(当然我自己就是上面那么配置的)。)


4、编译调试

配置完后,如下图:

image-20210513220116017

接下来,按F5 或者点击 左侧:

image-20210513220242332

image-20210513220301119

就开始调试、设置断点、单步执行和暂停了。

左边可以看到变量窗口、调用堆栈、断点、外设寄存器、CPU寄存器。

配置已经全部完成,你的show time 到了。

四、替换为标准库

0、删掉之前的HAL库文件,如图红框中的文件:

image-20210513231131457

1、下载对应芯片的标准库,解压后:

image-20210513230423786


2、进入Library 文件夹,复制目录下的两个文件夹,到我们工程目录下的Drivers。

image-20210513230757976


3、进入解压标准库下的STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\USART\Printf文件夹,也就是随便找个例程

image-20210513231217656

然后全部复制到我们工程目录下的Core文件夹(readme.txt 可以不复制),文件夹的命名当然随意了,如图:

image-20210513231325816


4、打开VSCode,目录结构如下:

image-20210513231650999

5、修改之前的c_cpp_properties.json文件,包含头文件,修改后如下:

{
    "configurations": [
        {
            "name": "Win32",

            "includePath": [
                "${workspaceFolder}/**",
                "D:/Program Files/gcc-arm-none-eabi-10-2020-q4-major/**"

                
            ],
            "defines": [
                "USE_STDPERIPH_DRIVER",
                "STM32F10X_HD"
   
            ],
            "compilerPath": "D:\\Program Files\\gcc-arm-none-eabi-10-2020-q4-major\\bin\\arm-none-eabi-gcc.exe",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "gcc-x64"
        }
    ],
    "version": 4
}

这里主要修改了define 里的定义,因为之前是HAL库,现在是STD库。


6、打开根目录下的MakeFile文件,修改

首先是 C_SOURCES 变量:

image-20210513232951903

C_SOURCES =  \
Core/main.c \
Core/stm32f10x_it.c \
Drivers/CMSIS/CM3/CoreSupport/core_cm3.c \
Drivers/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c \
$(wildcard Drivers/STM32F10x_StdPeriph_Driver/src/*.c)

也就是将用到的.c 添加进 C_SOURCES 变量。$(wildcard Drivers/STM32F10x_StdPeriph_Driver/src/*.c) ,会读取目录下所有.c文件,你也可以不全部添加,一个一个输入。下图中有两个system_stm32f10x.c文件,随便包含一个即可,这是因为刚刚复制的printf demo里有,重复了,可以删掉一个。

image-20210513232706311

下一步,修改ASM_SOURCES 变量:

image-20210513233104725

这里为什么要选择TrueSTUDIO 下的启动文件呢,因为在其中,arm是MDK-kei使用的、gcc_ride7是RIDE7 toolchain使用的,iar是IAR使用的,TrueSTUDIO是Atollic toolchain使用的。我们使用TrueSTUDIO文件夹里面的汇编启动文件即可。


修改C_DEFS 变量:

image-20210513233240976

C_DEFS =  \
-DUSE_STDPERIPH_DRIVER \
-DSTM32F10X_HD              

因为替换为了STD标准库,定义当然不一样了。


修改 C_INCLUDES 变量:

image-20210513233456617

C_INCLUDES =  \
-ICore \
-IDrivers/CMSIS/CM3/CoreSupport/ \
-IDrivers/CMSIS/CM3/DeviceSupport/ST/STM32F10x \
-IDrivers/STM32F10x_StdPeriph_Driver/inc 

也就是包含用到的头文件。


接下来make 编译试试,发现报错:

image-20210513233622854

说main.c 里找不到 stm32_eval.h 因为我们没有复制该文件,我们简单的修改main.c 文件如下:

image-20210513233901498

#include "stm32f10x.h"
#include <stdio.h>

int main(){
    while(1){}
}

然后再次make编译,发现还有错误:

image-20210513233955273

提示我们,core_cm3.c 文件有问题,后来我百度发现,需要修改该文件内容。

将其中原来的这两个函数注释掉,然后替换为以下两个函数。

uint32_t __STREXB(uint8_t value, uint8_t *addr)
{
   uint32_t result=0;

   __ASM volatile ("strexb %0, %2, [%1]" : "=&r" (result) : "r" (addr), "r" (value) );
   return(result);
}

uint32_t __STREXH(uint16_t value, uint16_t *addr)
{
   uint32_t result=0;

   __ASM volatile ("strexh %0, %2, [%1]" : "=&r" (result) : "r" (addr), "r" (value) );
   return(result);
}

函数分别位于core_cm3.c 中的732、749行:

image-20210513234244284

替换后如下:

image-20210513234326508


这个时候我们再次make编译:

image-20210513234405225

成功,神清气爽!

你的show time 又到了。

五、使用CMAKE生成

重点就是在CMakeLists.txt 文件首行添加 set(CMAKE_SYSTEM_NAME Generic)

这里有一份我写好的F103 的CMAKE文件,STD库的。修改头文件路径和源文件就好了。如果是F4的或者其他的,照着修改就好了,主要就是编译参数什么的,可以去默认的Makefile文件里找,慢慢测试,不要心急。

set(CMAKE_SYSTEM_NAME Generic)
cmake_minimum_required(VERSION 3.0.0)
project(mydemo C CXX ASM)

# specify the cross compiler
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_OBJCOPY arm-none-eabi-objcopy)
set(CMAKE_OBJDUMP arm-none-eabi-objdump)
set(CMAKE_SIZE arm-none-eabi-size)

## add project components
set(ELF_TARGET ${PROJECT_NAME}.elf)


# set the build type
set(CMAKE_BUILD_TYPE Debug)

# generate flags from user variables
if(CMAKE_BUILD_TYPE MATCHES Debug)
set(DBG_FLAGS "-O0 -g2 -ggdb")
elseif(CMAKE_BUILD_TYPE MATCHES Release)
set(DBG_FLAGS "-O3")
endif()

# print build type.
if(CMAKE_BUILD_TYPE MATCHES Debug)
  message(STATUS "Build type: Debug")
elseif(CMAKE_BUILD_TYPE MATCHES Release)
  message(STATUS "Build type: Release")
endif()



#######################################
# MCU_FLAGS
#######################################
# cpu
set(CPU "-mcpu=cortex-m3")
# fpu
# NONE for Cortex-M0/M0+/M3
set(FPU "")
# float-abi
set(FLOAT_ABI "")

## auto-set variables from user input
set(MCU_FLAGS "${CPU} -mthumb ${FPU} ${FLOAT_ABI}")

#######################################
# LDFLAGS
#######################################
# link script
set(LDSCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/STM32F103ZETx_FLASH.ld)

# libraries
set(LIBS "-lc -lm -lnosys")
set(LIBDIR "")
set(LDFLAGS "${MCU} -specs=nano.specs -T${LDSCRIPT} ${LIBDIR} ${LIBS} -Wl,-Map=${PROJECT_BINARY_DIR}/${PROJECT_NAME}.map,--cref -Wl,--gc-sections")
set(CMAKE_EXE_LINKER_FLAGS "${LDFLAGS}")

# compiler: language specific flags
set(CMAKE_C_FLAGS "${MCU_FLAGS} -Wall -fdata-sections -ffunction-sections ${DBG_FLAGS} ")
set(CMAKE_CXX_FLAGS "${MCU_FLAGS} -fno-rtti -fno-exceptions -fno-builtin -Wall -fdata-sections -ffunction-sections ${DBG_FLAGS} ")
set(CMAKE_ASM_FLAGS "${MCU_FLAGS} -x assembler-with-cpp ${DBG_FLAGS} ")

# global define.
add_definitions(
-DUSE_STDPERIPH_DRIVER 
-DSTM32F10X_HD              
)

# include path.
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/Core 
${CMAKE_CURRENT_SOURCE_DIR}/Drivers/CMSIS/CM3/CoreSupport 
${CMAKE_CURRENT_SOURCE_DIR}/Drivers/CMSIS/CM3/DeviceSupport/ST/STM32F10x 
${CMAKE_CURRENT_SOURCE_DIR}/Drivers/STM32F10x_StdPeriph_Driver/inc 
)

# C source files
set(C_SOURCES
    ${CMAKE_CURRENT_SOURCE_DIR}/Core/main.c
    ${CMAKE_CURRENT_SOURCE_DIR}/Core/stm32f10x_it.c
    ${CMAKE_CURRENT_SOURCE_DIR}/Drivers/CMSIS/CM3/CoreSupport/core_cm3.c
    ${CMAKE_CURRENT_SOURCE_DIR}/Drivers/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c
    
)
# 添加文件夹里所有的源文件到 C_SOURCES_2
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/Drivers/STM32F10x_StdPeriph_Driver/src/ C_SOURCES_2)

# ASM sources
set(ASM_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/Drivers/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/TrueSTUDIO/startup_stm32f10x_hd.s
)


add_executable(${ELF_TARGET} ${C_SOURCES} ${C_SOURCES_2} ${ASM_SOURCES})


# # name of targets
set(ELF_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.elf)
set(HEX_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.hex)
set(BIN_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.bin)

# # create binary & hex files and show size of resulting firmware image
add_custom_command(TARGET "${PROJECT_NAME}.elf" POST_BUILD
    COMMAND ${CMAKE_OBJCOPY} -Obinary ${ELF_FILE} ${BIN_FILE}
    COMMAND ${CMAKE_OBJCOPY} -Oihex  ${ELF_FILE} ${HEX_FILE}
    COMMENT "Building ${PROJECT_NAME}.bin and ${PROJECT_NAME}.hex"

    COMMAND ${CMAKE_COMMAND} -E copy ${HEX_FILE} "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.hex"
    COMMAND ${CMAKE_COMMAND} -E copy ${BIN_FILE} "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.bin"
    COMMAND ${CMAKE_SIZE} --format=berkeley ${PROJECT_NAME}.elf ${PROJECT_NAME}.hex
    COMMENT "Invoking: Cross ARM GNU Print Size"
)

1、编译生成

  1. CTRL+` 键打开终端, 输入 mkdir build & cd build 新建文件夹并进入

  2. 输入 cmake .. -G "MinGW Makefiles" 生成makefile 文件。

  3. make -j 编译


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!