由于 KEIL,IAR 这些 IDE 对很多配置和功能都做了封装,大大减少了上手难度,同时也隐藏了很多必要的操作,而开源的方案(或者说本应如此)则需要你每一步都亲自操刀,这就导致很多人在进行开源方案的探索时走上了从入门到放弃的道路。这一放弃,在某种意义上并不仅仅是放弃了一个开发方案的学习,更是给自己关上了进阶的大门。

今天这篇文章会帮助大家重新开启这扇大门,这扇门背后一定会有很多你没见过的景色或不理解的道路,甚至我本人对门后的世界也理解的微乎其微,但不要有压力,今天我们只 “开门”,先保证迈出第一步,往后我们再逐一领略门后的广阔世界。

首先我们捋一下进行一个 MCU 开发所需要的资源:

  1. 我们需要有一个写代码的工具
  2. 我们需要有一个能将代码转换为 MCU 可运行固件的工具
  3. 我们需要能够将固件下载到 MCU 中以及进行调试的工具

这三点可以说是 MCU 开发的三个基本要素,而本文的题目已经揭示了这三个要素的真面目:
写代码 —— VSCode。当然如果你是大神,那么你完全可以使用 Vim,或者直接用文本编辑器写,这完全没有任何问题,VSCode 对于萌新来说,体验感应该是最好的,并且其插件还能支持可视化的调试,这一点可以说是萌新跨出进入大门第一步的精神支柱。官网及下载地址:

https://code.visualstudio.com/代码转固件 —— GCC。这里其实需要加点字,应该是 arm-none-eabi-gcc 交叉编译工具链,其是专门针对于 ARM 内核的交叉编译器(如果你的 MCU 不是 ARM 内核,那此处需要选择对应的工具链),而且既然称之为 “链”,就说明其中包含了一系列的编译工具,如连接器、汇编器、调试工具等等,这也进一步说明了从代码到固件,不仅仅是 KEIL 软件上的一个编译按键这么简单。当然,对其中的原理本文不详细说明,后续会有针对性的文章带大家一起理解。官网及下载地址:

https://developer.arm.com/downloads/-/gnu-rm固件下载及调试 —— OpenOCD。OpenOCD 全称是 Open On Chip Debugger,也就是开源片上调试器。其与硬件调试器结合(如 JLink、CMSIS-DAP 等)可以实现对 MCU 的烧录与调试功能。其支持 SWD、JTAG 等常用的调试接口,以及 ARM、RISC-V 等多种处理器架构,非常强大。官网及下载地址:

https://github.com/openocd-org/openocd/releases

以上三个工具的介绍末尾都附上了对应的官网链接,大家可以根据自己的开发环境自行选择对应的版本进行下载及安装。注意,确保 arm-none-eabi-gcc 和 openocd 的执行路径(各自的 bin 文件夹路径)被添加到了系统的环境变量中,后续才能更方便地使用。验证方法为在系统终端中分别输入 “arm-none-eabi-gcc -v” 和 “openocd -v” 这两个指令,看是否有对应的版本信息输出,如果有,则说明添加完成了:

理论上来说,有了以上工具,就能够完成 MCU 的全部开发了,至少,对于大佬来说是这样的,但,我们是萌新啊!有了这些工具应该怎么用,代码怎么编译,又怎么能下载到板子里运行呢?

相信很多朋友已经在想,有没有一个已经搭好整体框架,可以直接上手的的工程来参考一下呢?

当然是有的!这时候就要请出我们的老朋友 —— STM32 CubeMX 了。不得不说,ST 的生态确实做得相当好,基本属于各种开发环境的配套资源都能提供,真的很方便。我们接下来要借助于 CubeMX 生成一个 Makefile 工程。这里解释下,我们虽然可以手动调用 gcc 的各项指令完成源码的构建,但当工程复杂性增大时一个个手动敲指令必然会变得很麻烦,而 Makefile 则可以根据预先写好的构建规则自动帮我们调用相应的工具进行构建,类似于 KEIL 的编译按钮。但这需要我们额外安装一个 make 工具(如果你的系统中没有的话),后面会详细介绍,我们先创建示例工程。

这里基于比较常用的 STM32F103 为例,使用 CubeMX 建立工程的具体步骤就不详细介绍了,不熟悉的朋友可以先去看下本文开头提到的文章。其中需要特别注意的就是在工程管理(Project Manager)页面的 Toolchain/IDE 需要设置为 Makefile :

点击右上角 GENERATE CODE 生成我们的示例工程:

示例工程目录结构如下:

工程有了,接下来怎么编译呢?还记得之前提到的吗,原本我们是可以一个个文件手动编译的,但这听起来就很麻烦,这里我们创建的是一个 Makefile 例程,而 Makefile 中可以定义一系列的编译规则,我们所需要做的就是在命令行中敲一个 “make” 调用 make 工具解析 Makefile 中的规则并运行,即可在 Makefile 编写正确的情况下编译出最终的固件!

不会写 Makefile 怎么办?

当然是要学啊!

学肯定是要学的,不过不是现在(资料都帮你们找好了,公众号后台回复 makefile 获取),我们现在要做的是把这一套流程跑通,如果你现在不会写 Makefile 也无所谓,很显然例程中已经帮我们写好了一个 Makefile:

整个文件近 200 行,并不是很大,现在看不懂也没事,后面会有文章带大家逐一理解。但现在仍然不能完成编译,因为我们还缺少一个重要的工具:make 工具(如果系统已经有 make 工具的小伙伴可以忽略本节)。由于大多数小伙伴是 Windows 系统开发,因此这里着重讲一下 Windows 下 make 工具的安装(其他系统可以自行搜索安装)。

首先我们要下载 w64devkit 这个工具,官网下载地址如下:https://github.com/skeeto/w64devkit/releases

下载安装完后需要将安装目录下的 bin 文件夹添加到环境变量中。然后打开一个终端输入 make -v 查看是否安装成功,如果显示出类似如下信息,则说明安装成功了:

此时我们回到刚才新建的工程目录下,在该目录下打开 VSCode:

此时先打开一个终端,然后输入 make 尝试编译一下:

可以看到编译非常顺利,最终输出了我们熟悉的固件大小统计数据,并且生成了 hex 和 bin 文件。当然,为了后面更直观地验证我们的程序是能够正常运行的,我们在主函数中加入点灯逻辑:

这里我板子上的 LED 为 PC13,电平每隔 1 秒进行翻转。修改好后再次输入 make 进行编译,编译成功后我们在 build 文件夹下就能找到最终的固件:

如果你用过脱机烧录工具,那么实际上这个固件就已经能够放到脱机烧录工具中烧录运行了。不过,开发阶段我们肯定还是希望能够通过调试器来进行烧录,那要怎么做呢?

还记得我们之前下载安装的 OpenOCD 吗,我们的调试以及在线下载就全靠它!当然,要让它运行起来我们还需要一些准备工作,首先我们直接在命令行中输入 openocd 看看会有什么效果:

果不其然报错了, 根据框选的信息我们大概能了解到是缺少了配置文件。这也十分合理, OpenOCD 本身支持各种各样的调试器以及芯片,而各个调试器或者芯片间的处理逻辑和流程肯定是不同的,那自然是需要什么东西来告诉 OpenOCD 应该以什么规则来进行调试,而这个规则就通过配置文件来描述。

此时我们需要两个配置文件,一个是接口配置文件,一个是目标芯片配置文件,幸运的是,对于常用的接口和芯片,OpenOCD 本身已经写好了,我们只需要调用其安装目录下提供的现成文件即可。

其中接口配置文件在安装目录的 share/openocd/scripts/interface 目录下:

目标芯片配置文件在 share/openocd/scripts/target 目录下:

你可以根据自己的调试器和目标芯片选用对应的配置文件,这里我使用的是 CMSIS-DAP 以及 STM32F103,因此选用 cmsis-dap.cfg 和 stm32f1x.cfg。你可以将这两个文件复制粘贴到 STM32 工程目录下直接使用,也可以在工程目录下新建一个配置文件,然后在其中引用这两个文件

可以看到我这里是在工程目录下新建了一个 debug.cfg,然后使用两行代码指定了我所选用的两个配置文件。此时在终端中输入 openocd -f debug.cfg 即可开启 openocd:

可以看到此时 openocd 已经连上并识别到了 F103 芯片,如果没连接成功,首先检查一下芯片是不是已经连上调试器,调试器是不是已经连上电脑,以及芯片是否正常供电。

与此同时我们可以看到 openocd 打开了两个端口供我们连接:

我们就需要通过这两种方式(tcl 或 telnet)连接 openocd 服务器并操作我们的调试器。这里我使用 telnet 来进行演示。

新建一个终端,输入 telnet localhost 4444 进行连接。注意,由于默认 Windows 的 telnet 功能是关闭的,所以需要提前打开,打开的方式可以参考以下文章:

如何开启 Windows 自带的 Telnet 功能?

如果看到如下界面,则说明连接成功了:

OpenOCD 本身支持很多指令,这里我们不一一描述,感兴趣的可以查阅网上的资料或官网文档。我们现在的目的是烧录固件,在烧录之前必须要让芯片停止运行,输入如下指令:

reset halt

此时输出如下:

Open On-Chip Debugger> reset halt[stm32f1x.cpu] halted due to debug-request, current mode: Thread xPSR: 0x01000000 pc: 0x08000c6c msp: 0x20005000

然后我们输入如下指令进行固件烧录:

flash write_image erase ./build/stm32f103_makefile.bin 0x08000000

这段指令的意思是调用 flash 进行 write_image 写固件操作,写之前先擦除(erase),固件为 ./build/stm32f103_makefile.bin,写入的起始地址为 0x08000000 (STM32 的 FLASH 起始地址):

> flash write_image erase ./build/stm32f103_makefile.bin 0x08000000device id = 0x20036410flash size = 64 KiBAdding extra erase range, 0x08000d60 .. 0x08000fffauto erase enabledwrote 3424 bytes from file ./build/stm32f103_makefile.bin in 0.395209s (8.461 KiB/s)

看到上述信息则说明固件已经写入成功了,此时芯片还处于停止状态不会运行,小灯也不会闪烁,我们输入如下命令让芯片运行:

reset run

这时候我们的小灯就开始闪烁了:

到目前为止,我们已经实现了程序的编译和固件的烧录,此时还差一项,也是非常重要的一项:调试!其实刚才在启动 OpenOCD 时已经启动了一个 GDB 调试服务:

我们通过 arm-none-eabi-gdb 已经可以直接连上芯片就开始调试了,但直接使用 gdb 调试是需要我们手动输命令进行调试的,这对于萌新来说显然难度有点大了,即使对于大佬来说,要一步步输命令来调试肯定也不是非常方便。有没有什么方法能像 KEIL 一样基于可视化的界面来进行调试呢?
当然是有的!此时我们安装一个 VSCode 插件 —— Cortex-Debug:

安装好后点击左侧 Run and Debug 按钮,进入后点击 Create a launch.json 创建一个启动配置文件:

在上方的弹框中选择 Cortex Debug:

按照如下格式修改 launch.json:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Cortex Debug",
            "cwd": "${workspaceFolder}",
            "executable": "./build/stm32f103_makefile.elf",
            "request": "launch",
            "type": "cortex-debug",
            "runToEntryPoint": "main",
            "servertype": "openocd",
            "interface": "swd",
            "configFiles": [
                "debug.cfg"
            ],
            "device": "STM32F103C8",
        }
    ]
}

其中几个关键修改点如下:

  • executable:编译输出的 elf 文件路径。
  • servertype:gdb server 类型,这里显然是 openocd。
  • interface:调试接口,我们这里用的是 swd。
  • configFiles:openocd 配置文件。
  • device:目标芯片。

原本我们要还对 Cortex Debug 插件本身进行配置,主要是设置 Arm Toolchain Path 和 Openocd Path 供插件使用,但由于之前我们已经将这两套工具的路径加入到了系统环境变量,所以这里就无须再配置了,如果之前没有设置环境变量,那么这里需要在 Cortex Debug 插件的设置项中将两者的路径添加进配置文件:

至此,我们的调试插件也配置好了,进入 main.c ,在左侧打上断点:

点击 F5 就可以进入调试了,值得注意的是,这里进入调试默认会烧录最新的程序,因此调试前完全没必要调用 openocd 指令手动烧录,非常方便!

到这里,VSCode + GCC +OpenOCD 这套方案可以说是完全走通了,虽然其中有很多知识点并没有去深究,但至少我们已经成功迈出了第一步,成功用这一套流程完成了一个 MCU 程序的开发,也似乎接触到了一些原来从来根本不会关注到的底层原理。

当然,这只是起始。

Avatar photo

作者 skyate

发表回复