• 《嵌入式 - 嵌入式大杂烩》基于VS Code开发嵌入式


    很多朋友在开发嵌入式的时候,一般常用Keil MDK、IAR等IDE工具,不过这些都是收费的,而且非常笨重,跨平台开发不方便,依次笔者将推荐使用VS Code开发嵌入式。

    如果想要使用VS Code开发嵌入式,则需要以下工具:

    (1) GNU Arm Embedded ToolchainARM用的GNU工具链,包括编译器(gcc),调试器(gdb),链接器(ld)和其它工具,支持Windows、Linux、Mac。GCC(GNU Compiler Collection)。

    (2) Git/make:代码管理工具,但是我们这里要使用的是下载git附带的git bash。这个bash是基于mingw的,非常轻量,甚至于make都没有,但是可以安装相应的插件来支持make,wget等工具。当然啦,如果是在Linux平台直接使用make工具就行。

    (3) OpenOCD:一个开源的片上调试器(Open On-Chip Debugger)。OpenOCD负责把GDB的高级别命令转换成JTAG命令,并通过特定下载器的要求进行打包,准备调用OS提供的USB驱动由USB发送出去。GDB和OpenOCD之间使用TCP协议进行连接,说的简单点,OpenOCD就是一个协议转换工具。

    本文将分为两个部分:工具安装,固件开发。

    1软件安装

    1.1 GCC编译工具

    编译代码需要编译器,Linux系统的编译器是GCC,而Windows的C/C++编译器是Microsoft Visual C++,那么要想在Windows也能GCC等一系列编译工具,就需要安装MinGW。

    MinGW 是用于进行 Windows 应用开发的 GNU 工具链(开发环境),它的编译产物一般是原生 Windows 应用,虽然它本身不一定非要运行在 Windows 系统下(是的 MinGW 工具链也存在于 Linux、BSD 甚至 Cygwin 下)。说的通俗点,MinGW就是你在Windows下使用GNU工具链的一个编译工具。

    MinGW编译的程序只能在X86上运行,不能运行在嵌入式的硬件平台,因为嵌入式平台大都是ARM体系结构,因此这就需要一个在Windows环境下能使用GNU编译ARM体系结构的编译工具,这也就是交叉编译工具。

    所谓交叉编译工具就是在一种平台上编译出能运行在体系结构不同的另一种平台上的程序,比如在PC平台(X86 CPU)上编译出能运行在以ARM为内核的CPU平台上的程序,编译得到的程序在X86 CPU平台上是不能运行的,必须放到ARM CPU平台上才能运行。

    做过嵌入式开发的朋友都知道,在嵌入式开发过程中有宿主机和目标机的角色之分:宿主机是执行编译、链接嵌入式软件的计算机;目标机是运行嵌入式软件的硬件平台。

    在这里插入图片描述

    嵌入式开发流程大致就是在宿主机完成目标的开发工具,使用功能交叉编译工具生成固件,将固件烧写到目标机,在开发初期,还需要在线调试等工作,这就需要诸如J-link等调试工具。
    gcc-arm-none-eabi就是一个基于ARM的交叉编译工具链,而且还是开源的,适用于Arm Cortex-M和Coretex-A系列处理器,包括GNU编译器(GCC),以及GDB,不仅适用于Windows,还适用于Linux,MacOS上的交叉编译。

    好了,直接看下载地址吧。

    下载地址

    在这里插入图片描述

    【注】这里要选择GUN-RM下的工具,GUN-A是Cortex-A系列的交叉编译工具。

    下载后解压,并把安装目录下的bin文件夹添加到环境变量:

    在这里插入图片描述

    然后在命令窗口中输入下面的命令验证安装是否成功:

    #arm-none-eabi-gcc -v
    
    • 1

    如果有信息输出,那就是装好了。

    在这里插入图片描述

    值得注意的是,如果不是ARM体系,就需要更换交叉编译工具,比如RISC-V,就需要安装RISC-V的交叉编译工具链。

    1.2 make工具安装

    如果使用gcc来编译工程,一般需要使用功能Makefile来管理工程,那么就需要一个工具来识别Makefile文件,也就是make工具,在Linux中已经自带make了,在Windows就需要安装make工具。

    在安装make工具之前,先来看看什么是makefile?Makefile 可以简单的认为是一个工程文件的编译规则,描述了整个工程的编译和链接等规则。其中包含了那些文件需要编译,那些文件不需要编译,那些文件需要先编译,那些文件需要后编译,那些文件需要重建等等。编译整个工程需要涉及到的,在 Makefile 中都可以进行描述。换句话说,Makefile 可以使得我们的项目工程的编译变得自动化,不需要每次都手动输入一堆源文件和参数。

    本文的make工具是依赖Git工具的,我相信很多朋友都用过Git,但是很少使用Git的make等功能。
    Git的bash实际上也就是一个mingw,是可以支持部分Linux指令的,但是只有少部分。在编译代码的时候经常会使用make命令反而在bash下默认是不支持的。

    Make工具下载地址

    在这里插入图片描述

    下载make-4.1-2-without-guile-w32-bin.zip 文件。

    把该文件进行解压,把解压出来的文件全部拷贝的git的安装目录下:
    C:\Program Files\Git\mingw64

    把文件夹进行合并,如果跳出来需要替换的文件要选择不替换。

    在这里插入图片描述

    这样在git bash窗口下就可以执行make了。

    在这里插入图片描述

    没有安装Git先安装Git工具。

    Git下载地址

    1.3 OpenOCD安装

    OpenOCD是用于对MCU进行下载仿真的工具,是一个开源软件包。

    下载地址

    下载后解压即可。

    在这里插入图片描述

    打开share/openocd/scripts,里面有很多提前写好的配置文件:

    在这里插入图片描述

    其中interface目录下都是接口相关配置文件,例如jlink.cfg,stlink.cfg;target目录下都是芯片相关的配置文件,例如stm32f1x.cfg。

    然后配置下环境变量:

    在这里插入图片描述

    然后在命令窗口中输入下面的命令验证安装是否成功:

    在这里插入图片描述

    表示安装成功。

    值得注意的是,想要在线下载调试就需要调试工具的驱动安装。笔者首推J-link,几乎所有的MCU都是可以用,当然如果是ST的MCU,也可以使用ST-Link。

    2固件开发

    在开始之前,需要准备一个基于GCC的工程,笔者这里准备的是STM32的工程。

    2.1 Visual Studio Code导入工程

    选择打开文件夹,选择STM32CubeMX新建的工程.

    在这里插入图片描述

    最后创建的工程如下:

    在这里插入图片描述

    2.2 Visual Studio Code工程配置

    接下来就是配置工程。

    1. settings.json

    点击“在settings.json中编辑",修改为自己安装git bash的路径:

    {
        "terminal.integrated.profiles.windows": {
            "my-bash": {
              "source": "Git Bash",
              "args": []
            }
          },
          "terminal.integrated.defaultProfile.windows": "my-bash"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    打开终端,就可以使用Bash了。当然了,这不是非必须的。

    2. c_cpp_properties.json
    VS Code只是一个编辑器,它检查代码的时候并不会去读makefile,因此有些宏定义需要自行配置。c_cpp_properties.json的作用就是配置工程的头文件、工具链、宏定义等参数。

    打开c_cpp_properties.json配置文件,输入以下内容:

    {
        "configurations": [
            {
                "name": "Win32",
                "includePath": [
                    "D:/gcc/gcc-arm-none-eabi-10.3-2021.07/arm-none-eabi/include",
                    "${workspaceFolder}/Core/Inc",
                    "${workspaceFolder}/Drivers/STM32F1xx_HAL_Driver/Inc",
                    "${workspaceFolder}/Drivers/STM32F1xx_HAL_Driver/Inc/Legacy",
                    "${workspaceFolder}/Drivers/CMSIS/Device/ST/STM32F1xx/Include",
                    "${workspaceFolder}/Drivers/CMSIS/Include"
                ],
                "defines": [
                    "USE_HAL_DRIVER",
                    "STM32F103xE"
                ],
                "compilerPath": "D:/gcc/gcc-arm-none-eabi-10.3-2021.07/bin/arm-none-eabi-gcc.exe",
                "intelliSenseMode": "gcc-x64",
                "browse": {
                    "limitSymbolsToIncludedHeaders": true,
                    "databaseFilename": "",
                    "path": [
                        "${workspaceFolder}"
                    ]
                }
            }
        ],
        "version": 4
    }
    
    • 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

    name:这是用于标记使用的平台的标签。除了win32还可以选Linux或Mac。也就是说,这个json里“configuration“下可以写三组配置,只要每组配置前面写上不同的平台,即可在不同的操作系统上使用就会自动适配不同的配置。

    includePath:头文件路径。第一个目录是C语言标准库的目录, 剩下的几个目录直接从Makefile里复制然后稍微修改下即可。"${workspaceFolder}"表示项目文件夹;

    defines:全局宏定义。

    compilerPath:编译器的路径。

    intelliSenseMode:因为我用的是gcc所以选gcc-x64。

    browse:源文件搜索路径。用来做代码补全和查找定义的。这个路径和includePath不同,browse.path是自动递归所有子目录的。而include.path默认只看本目录。

    3.tasks.json
    tasks.json的作用就是配置工程的编译、下载等工作。如果没有则需要创建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",
                    "command": "make",
                    "args": [
                        "-j4"
                    ] 
                },
                {
                    "label": "download",
                    "type": "shell",
                    "command": "openocd",
                    "args": [
                        "-f",
                        "./interface/jlink.cfg",
                        "-f",
                        "./target/stm32f1x.cfg",
                        "-c",
                        "program build/STM32F103ZE.elf verify exit"
                    ] 
                },
                {
                    "label": "clean",
                    "type": "shell",
                    "command": "make",
                    "args": [
                        "clean"
                    ] 
                }
            ]
    }
    
    • 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

    这个文件创建了三个任务,分别叫做build、download和clean,build任务就是在bash里执行了make -j4,也就是使用多线程编译,download也就是用于在线下载固件,clean任务就是在bash里执行了make clean。

    值得注意的是,这里需要拷贝OpenOCD中的interface和target中的相关配置,笔者是整个文件夹都拷贝过来了。

    在这里插入图片描述

    2.3编译

    点击‘终端->运行任务’,然后虚着呢‘build’。

    在这里插入图片描述

    等待编译完成即可。

    在这里插入图片描述

    这里还可以直接使用终端编译。

    在这里插入图片描述

    最后我们使用make编译下前文新建的工程,编译通过显示如下:

    在这里插入图片描述

    2.4固件下载

    笔者这里使用功能OpenOCD下载。选择“终端->运行任务…”

    在这里插入图片描述

    选择task中配置的命令download。

    在这里插入图片描述

    复位设备,即可下载成功。

    在这里插入图片描述

    当然也可使用命令下载。

    # openocd -f ./interface/stlink.cfg -f ./target/stm32f1x.cfg -c 'program build/STM32F103ZE.elf verify exit‘
    
    • 1

    效果和使用界面是一样的。

    当然换成J-Link下载也是一样的操作,只是需要修改命令。

    # openocd -f ./interface/jlink.cfg -f ./target/stm32f1x.cfg -c 'program build/STM32F103ZE.elf verify exit‘
    
    • 1

    另外,还需要注意的是如果J-Link的接口是swd,则需要在interface/jlink.cfg中添加如下语句。

    transport select swd
    
    • 1

    当然还可以连接到OpenOCD守护程序烧写。

    $ openocd
    
    • 1

    在这里插入图片描述

    值得注意的是,如果不加参数,需要事先配置openocd.cfg,
    在这里插入图片描述

    如果不想配置,也可使用以下命令:

    #openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg
    
    • 1

    在这里插入图片描述

    效果都是一样的。
    打开另一个终端,输入以下命令连接到OpenOCD守护程序。后面的所有命令都是在这个终端运行的。

    # telnet localhost 4444
    
    • 1

    成功通过ST-Link连接到STM32上之后,OpenOCD会监听本机的4444端口。通过telnet登录上去,之后就可以控制OpenOCD干些什么了。

    在这里插入图片描述

    接下来就可以烧写固件了。

    > program build/STM32F103ZE.elf verify reset exit
    
    • 1

    在这里插入图片描述

    当然也可使用功能J-Link的J-Flash下载固件。

    如果是ST的,还可使用ST-Link下载固件。

    【注】OpenOCD使用J-link使用须知
    在使用J-link之前,需要将J-link的驱动改为libusb,否则openocd无法连接到J-link。这里使用的工具是UsbDriverTool。

    UsbDriverTool下载地址:https://visualgdb.com/UsbDriverTool/
    使用功能方法如下:

    在这里插入图片描述

    如果使用J-link调试,则不修改修改。

    2.5 ARM-GDB调试

    直接使用GDB调试,

    首先在终端输入一下命令:

    #openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg
    
    • 1

    在这里插入图片描述

    【注】也可将命令配置成task任务。

    【注】如果不带参数启动,openocd就会自动查找当前目录下有没有名为openocd.cfg的文件,并把它作为配置文件来启动。

    【注】openocd默认TCP/IP的3333端口作为gdb端口,4444作为telnet端口。在连接gdb之前,我们可以先用telnet连上试用一下。

    如果你的电脑没有开启telnet功能,需要打开“启用或关闭Windows功能”,然后在里面找到“telnet客户端”,启动即可。

    在这里插入图片描述

    openocd运行时,这个终端就被占用了因此还需要再开一个终端。
    GDB也属于GNU项目的一部分,跟在Linux上调试是一样的,只是这里使用的是交叉编译工具中的GDB。

    # arm-none-eabi-gdb build/STM32F103ZE.elf
    
    • 1

    在这里插入图片描述

    关于GDB的使用请看笔者文章:
    GDB

    接下来需要连接openocd服务,openocd给GDB的TCP/IP端口是3333。

    # target remote localhost:3333
    
    • 1

    在这里插入图片描述

    接下来和在Linux中调试一样。

    在 gdb 中键入"l"(list)就可以查看所载入的文件,如下所示。

    在这里插入图片描述

    自行参考笔者关于GDB的博文去调试吧。

    如果觉得使用命令不方便,也可安装VS Code的插件,但是这是基于Cortex-M系列的MCU,如果是其他系列的,还是使用GDB吧。

    2.6 VS Code插件Cortex-Debug调试

    如果没有安装Cortex-Debug调试插件,需要先安装。

    2.6.1 Cortex-Debug调试配置

    1.GDB server路径设置
    设置J-Link和OpenOCD的路径。
    在这里插入图片描述
    在这里插入图片描述

    根据实际情况设置。
    在这里插入图片描述

    2.launch.json文件
    在vscode文件夹中新建一个launch.json,该文件是调试的入口文件。内容如下:

    {
        "version": "0.2.0",
        "configurations": [
            {
                "cwd": "${workspaceRoot}",
                "type": "cortex-debug",
                "request": "launch",
                "name": "OpenOCD",
                "interface": "swd",
                "servertype": "openocd",
                "executable": "./build/STM32F103ZE.elf",
                //"runToMain": true,
                "device": "STM32F103ZE",
                "svdFile": "./STM32F103xx.svd",
                "configFiles": [
                    "${workspaceRoot}/openocd.cfg"
                ],
                //"preLaunchTask": "build",
                "armToolchainPath": "D:/gcc/gcc-arm-none-eabi-10.3-2021.07/bin/"
            },
            {
                "cwd": "${workspaceRoot}",
                "type": "cortex-debug",
                "request": "launch",
                "name": "J-Link",
                "interface": "swd",
                "servertype": "jlink",
                "executable": "./build/STM32F103ZE.elf",
                //"runToMain": true,
                "device": "STM32F103ZE",
                "svdFile": "./STM32F103xx.svd",
                //"preLaunchTask": "build",
                "armToolchainPath": "D:/gcc/gcc-arm-none-eabi-10.3-2021.07/bin/"
            },
            {
                "cwd": "${workspaceRoot}",
                "type": "cortex-debug",
                "request": "launch",
                "name": "ST-Link",
                "interface": "swd",
                "servertype": "stlink",
                "executable": "./build/STM32F103ZE.elf",
                //"runToMain": true,
                "device": "STM32F103ZE",
                "svdFile": "./STM32F103xx.svd",
                //"preLaunchTask": "build",
                "armToolchainPath": "D:/gcc/gcc-arm-none-eabi-10.3-2021.07/bin/"
            }
        ]
    }
    
    • 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

    executable:编译出的二进制文件,也就是最终烧录到单片机中的,这里是elf文件。根据芯片的不同,可能产生不同的名称和后缀(例如TI的TM4C123芯片编译出来的名称是"main.axf")

    request:可以选launch或attach。launch是指启动调试时同时开始执行程序;attcah是指程序已经在运行了,然后开始调试。我没测试过attach。

    type:调试的类型,选cortex-debug,这是我们装的插件。其实也可以填cppdbg之类的,但是那样我们就得自己配置gdb了,配置起来将会非常麻烦。

    device:目标芯片。如果你使用J-LINK GDB Server时必须要设置这个选项。

    svdFile:svd文件的路径,每个MCU的各不相同。

    servertype:要选择的gdb server。我这里用openocd。

    configFiles:gdb的配置文件路径。openocd会自动读当前目录下的openocd.cfg文件,这个选项不填也行。但是如果你想把openocd.cfg放在别处,就可以用这个选项指定配置文件的路径。

    preLaunchTask:在启动调试前,预先执行的任务。

    armToolchainPath:工具链的路径。

    3.openocd.cfg文件
    在项目文件夹下新建一个openocd.cfg文件,用于配置调具体的调试器。内容如下:

    # 选择调试器为jlink
    #source [find interface/jlink.cfg]
    source [find interface/stlink-v2.cfg]
    
    # 选择接口为SWD
    # transport select swd
    
    # 选择目标芯片
    source [find target/stm32f1x.cfg]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我这里选择使用ST-Link,SWD接口,目标芯片为stm32f1x。

    【注】使用J-link调试也是一样的,只需将openocd.cfg文件配置成J-link调试即可。

    4.svd文件
    用于寻找STM32F1的svd文件。CMSIS-SVD是CMSIS的一个组件,它包含完整微控制器系统(包括外设)的程序员视图的系统视图描述 XML 文件。VS Code可以通过它来知道外设寄存器的地址分布,从而把寄存器内容展示到窗口中。

    svd文件地址

    在这里插入图片描述

    将下载好的STM32F103xx.svd文件放在项目文件夹根目录即可。

    2.6.2 Cortex-Debug调试

    直接按F5,openocd启动时,会自动在当前目录下寻找名为openocd.cfg的文件作为配置文件。调试界面如下:

    在这里插入图片描述

    界面左边可以看到变量窗口、调用堆栈等。窗口中间就是单步调试的各个按钮。这个就没啥好说的了,赶紧去玩起来吧。

    这里可以在launch.json文件添加多个选项,就可使用多种调试手段了。

    在这里插入图片描述

    Cortex-Debug的本质还是使用的OpenOCD。

    本文介绍的开发方式不仅适用于ARM,还适用于RISC-V等锡系列的处理器。



    欢迎访问我的网站

    BruceOu的哔哩哔哩
    BruceOu的主页
    BruceOu的博客
    BruceOu的CSDN博客
    BruceOu的简书
    BruceOu的知乎


    欢迎订阅我的微信公众号

    关注公众号[嵌入式实验楼]获取更多资讯

  • 相关阅读:
    【C++】vector的介绍 | 常见接口的使用
    掌动智能:UI自动化测试工具几点优势
    react context
    梦开始的地方——C语言指针练习题
    SpringBoot SpringBoot 开发实用篇 5 整合第三方技术 5.17 发送多部件邮件
    php常用算法
    C++ Log日志进阶
    K8s技术全景:架构、应用与优化
    idea插件推荐——Bito提高编码效率
    Java 内存模型
  • 原文地址:https://blog.csdn.net/u013162035/article/details/124936266