65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Visual Studio Code 中创建和调试 Arduino 程序 - 第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (9投票s)

2019年6月26日

CPOL

13分钟阅读

viewsIcon

54377

downloadIcon

805

本文介绍了如何设置 VS Code 来调试你的 Arduino 程序。

引言

这是关于在 Visual Studio Code 中创建和调试程序的三个系列文章的第二部分。关于第一部分,涉及设置 VS Code 来构建 Arduino 程序,请参见此处。关于第三部分,涉及通过使用自定义引导加载程序来改进调试器,请参见此处

首先我应该明确一点——本文档是关于调试 Arduino Uno、Nano、Mega(以及可能基于 AVR 微控制器的其他板子)的。VS Code Arduino 扩展的文档中有一个“调试 Arduino 代码”的章节,这可能会有点误导。他们所说的调试仅对少数板子可用(例如 Arduino M0 Pro),这些板子在板子上包含硬件调试器接口。对于 Uno、Nano 或 Mega 等流行的基于 AVR 的板子,Arduino 扩展中没有支持。这些板子没有调试支持,一般来说,在不购买外部调试探针的情况下,是不可能调试这些板子上的代码的。实际上,有一个方法,我想在本文中进行描述。有关 Arduino 调试选项的更多信息,请参阅我在 Code Project 上较早的文章

背景

不久前,我写了一篇关于适用于 Arduino Uno 的软件调试器的文章,该调试器与 GNU 调试器 GDB 一起工作。这篇文章是关于在 Eclipse IDE 中使用调试器。它运行良好,但需要大量工作来设置环境。最近,我发现 Visual Studio Code IDE 可以替代 eclipse,从而使事情变得更容易。

因此,在本文中,我将介绍一个简单的教程,以便在 VS Code 中启用调试。我们将使用一个名为 avr-debugger 的 Arduino 库,这是我创建的,以便更容易使用调试器。

步骤 1 – 安装 avr-debugger 库

该库已附加到本文。只需将 avr-debugger 文件夹解压到你的 Documents/Arduino/libraries 文件夹。你现在应该有一个 Documents/Arduino/libraries/avr-debugger 文件夹。

或者,你可以从 github 下载(或克隆)存储库 – https://github.com/jdolinay/avr_debug。在此存储库中,找到 avr_debug/arduino/library 中的文件夹并将其复制到 Documents/Arduino/libraries

如果 VS Code 当前正在运行,请重新启动它,以便加载新库。

步骤 2 – 将库添加到你的程序

在 VS Code 中,打开命令面板(F1)并输入 Arduino,然后选择 Arduino: Library manager。

在 Filter your search… 框中键入 avr-debugger

avr-debugger 库应出现在列表中。

点击 Include Library 按钮。

这将向你的程序添加 2 行 include。

#include <app_api.h>
#include <avr8-stub.h>

在你的 setup 函数中添加对函数 debug_init 的调用。请参阅第 3 步下方的图片。

验证(构建)程序并记下大小。在我的例子中,它是 4852 字节。

步骤 3:关闭优化

程序现在已启用优化功能进行构建,这意味着将你的代码翻译成微控制器原生语言的编译器可以更改代码的组织方式,使其更小。当然,编译器会确保程序按照你编写的方式运行;它只是使其更有效率。因此,优化通常是好的;它们可以让你将更多的代码放入微控制器的小内存中。但它们对调试并不那么好,因为我们希望能够按照我们编写的方式逐行执行代码并在编辑器中查看它。所以,在调试程序时关闭优化是一个好主意。

关闭优化

  • 在您喜欢的文件夹管理器中,转到 avr-debugger 文件夹并找到 platform.local.txt 文件。
  • 将此文件复制到 Arduino IDE 安装的文件夹中,放在子文件夹 hardware/arduino/avr 中。

例如,在我的系统中,platform.local.txt 文件应放在 c:\Program Files.(x86)\Arduino\hardware\arduino\avr\。你可能需要管理员权限才能将文件复制到 Program Files。

  • 重新启动 VS Code。
  • 在文件管理器中,还要删除程序文件夹中的 build 子文件夹的内容 – Documents/Arduino/vscode/test/build
  • 再次验证程序并记下大小。它应该比之前大得多。在我的例子中,现在关闭优化后是 7896 字节。如果程序大小不在此范围内,则说明有问题。尝试再次删除 build 文件夹并重新启动 VS Code,并仔细检查 platform.local.txt 文件的位置是否正确。

关于此的两个说明

  1. 此步骤是可选的——如果你不使用 platform.local.txt 文件,你仍然可以调试你的程序;它只是有时可能会做一些奇怪的事情,比如跳转到与预期不同的行。
  2. platform.local.txt 文件是我找到的定义 Arduino 编译器选项的唯一方法。它会影响你所有的 Arduino 程序,包括在 Arduino IDE 中构建的程序。调试完成后,你可以删除或重命名此文件以恢复正常、优化的程序。我希望可以为每个项目设置构建选项,但不行;platform.local.txt 文件必须位于上述文件夹中才能工作。

步骤 4:创建 launch.json 文件 – Launch 配置

要在 VS Code 中调试程序,你需要创建一个 launch 配置,它告诉 VS Code 如何启动你的程序。

点击左侧操作栏中的调试按钮 – 它是带有虫子图标的图标。

在左上方,你会看到一个绿色的“运行”按钮,旁边有一个齿轮图标,鼠标悬停时会显示“Configure or fix launch.json”工具提示。

点击此齿轮按钮

顶部会出现一个选项列表。

从列表中,选择 C++ (GDB/LLDB)

重要提示:不要选择 Arduino;这对我们不起作用。它仅适用于具有集成调试接口的板子。

一个 launch.json 文件将出现在你文件夹的 .vscode 子文件夹中,并且它也会在编辑器中打开。

launch.json 文件中更改以下选项(有关最终结果,请参见下方的图片)
program - 设置为 "${workspaceFolder}/build/app.ino.elf"。

miDebuggerPath – 设置到 Arduino IDE 安装目录下的 avr-gdb.exe 的路径,例如:“c:\\Program Files (x86)\\Arduino\\hardware\\tools\\avr\\bin\\avr-gdb.exe”。

miDebuggerPath 行下方添加此行 – 但请将 COM 端口号更改为你的端口 – 有关与 Arduino 通信的当前 COM 端口,请参阅状态栏右下角。

     "miDebuggerServerAddress": "\\\\.\\COM3",

setupCommands 部分,用以下块替换“Enable pretty printing”的默认块

"description": "Remote debug enable",
"text": "-gdb-set debug remote 1",
"ignoreFailures": false

这是生成的 launch.json

现在我们准备开始调试了。

开始调试

将你的程序上传到你的 Arduino。你可能需要切换到 app.ino 文件,以便能够使用窗口右上角的上传按钮。

点击 loop() 函数中 digitalWrite(13, HIGH); 行左侧的边距。这将在该行设置一个断点,程序应该会在此停止。你会看到一个红点。

点击左上角的开始调试(绿色播放)按钮。

如果一切顺利,几秒钟后,你应该会看到顶部用于控制程序的按钮,状态栏应变为橙色,断点所在的行应高亮显示。请参见图片。

点击上方的工具栏中的单步执行按钮,或按 F10 执行程序的一行。Arduino 板上的 LED 现在应该会亮起。

通过反复点击单步执行按钮逐步执行程序,并观察 LED 闪烁。

在单步执行完最后一个 delay() 后,你将进入 main.cpp 文件。要返回到你的 loop(),请在 loop() 行上点击单步进入F11)。

你也可以点击继续按钮或按 F5 来运行程序。它会再次在断点处停止。

尝试运行程序,删除和插入断点,并逐步执行代码。

准备就绪后,按停止红色方块按钮停止调试程序。

使用变量

让我们在程序中添加一些变量,看看在调试器中可以对它们做什么。

进行如下代码更改

int globalVar;

void setup()
{
    debug_init();
    pinMode(13, OUTPUT);
}

void loop()
{
    int localVar = 5;
    globalVar++;
    localVar++;    
    digitalWrite(13, HIGH);
    delay(100);
    digitalWrite(13, LOW);
    delay(1000);
    localVar++;
}

我们创建了两个变量——一个是全局变量,在整个程序中可见;一个是局部变量,定义在 loop 中。局部变量仅在 loop 函数中可见,并且仅在 loop 函数执行期间存在。当 loop 结束时,变量将被删除,然后在 loop 再次执行时重新创建。

再次验证并上传程序

重要提示:每次更改代码后,都需要重新上传程序。点击 Start debugging 按钮时,它不会自动上传。

点击开始调试按钮。

程序在 loop 中的断点处停止后(我希望你仍然在那里),查看左侧的 Variables 视图。有一个局部变量 localVar,值为 6

全局变量 globalVar 不会自动显示。你需要将其添加到 Watch 视图。只需将鼠标悬停在 Watch 窗口上,然后点击出现的加号 (+) 按钮。然后键入 globalVar 到出现的“Expression to watch”框中。

下面是所有内容的显示

逐步执行程序,观察值的变化。globalVar 的值应该持续增长,而 localVar 在每次进入 loop 函数时会重置为 5

你还可以设置 localVar 的值——只需单击值,输入新数字,然后按 Enter 键。

不幸的是,无法在 Watch 视图中更改 globalVar 的值。但你仍然可以通过执行 gbd 命令来更改值。

更改全局变量的值:
点击程序代码下方的 Debug console。底部应该有一行命令提示符。

在此处输入以下命令

-exec set var globalVar = 10

在程序进行单步执行以强制刷新视图之前,你不会在 Watch 窗口中看到更改,但变量值已设置为 10

这不是很方便,但你可以使用键盘上的向上/向下箭头快速选择以前的命令——无需一遍又一遍地键入。

好了,教程到此结束。享受你的调试吧!

关注点

请注意,调试器有一些有趣的选项可以设置。我们使用了基本配置,我称之为“RAM 断点”。此配置的优点是无需对 Arduino 板进行任何操作,只需将库添加到你的程序即可。缺点是程序在调试时运行速度要慢得多。

为了克服这个缺点,你可以切换到“Flash 断点”。但是使用 Flash 断点需要你的 Arduino 中有特殊的引导加载程序。我现在有一个适用于 Arduino Uno (ATmega328) 的引导加载程序。你可以在库的 bootloader 子文件夹中找到它并将其烧录到你的 Arduino 中。然后打开 avr8-stub.h 文件并将 AVR8_BREAKPOINT_MODE1 改为 0。有关详细说明,请参阅本文第 3 部分

你还可以查看调试器的文档,该文档可在 github 上的存储库中找到 – https://github.com/jdolinay/avr_debug。查看 doc 子文件夹。

此调试器解决方案的局限性

使用此调试器有 4 个重要限制

  1. 串行通信(硬件串行)不能与调试器一起使用 – 程序中不能有 Serial.something 函数。

    如果你习惯使用 Serial.print 进行调试,那么当你有了调试器时就不需要这样做了——只需在你想检查程序中发生的情况的地方设置断点即可。如果你的程序需要通过串行线进行正常通信,则调试器可能无法实现。你可以尝试使用软件串行进行通信(不是为了调试器,它需要硬件串行端口),但可能会出现计时问题——请参阅下面的第 3 点。或者使用条件编译来调试程序而不使用串行线,并在不再需要调试器时启用串行线。

  2. 必须保留一个引脚供调试器使用 – 默认情况下是 Uno 上的引脚 2,可以在 avr8-stub.h 文件中更改为引脚 3。你不能在程序中使用此引脚。
  3. 当任何断点启用时(并且 VS Code 中始终至少有一个断点启用,请参阅下方的常见问题),程序运行速度都会变慢。一些对时间敏感的操作在调试程序时可能无法正常工作。
  4. 无法在中断服务例程中停止程序,或者总的来说,在禁用中断时无法逐步执行程序 – 例如在 Arduino micros() 函数中发生的那样。如果你发现自己正在单步执行代码中的 cli(); 命令,那么可能会发生意外情况。

这些限制在完整调试器项目(Arduino 库是其中的一部分)的文档中有详细解释,位于 github 上的github: https://github.com/jdolinay/avr_debug。查看 doc 子文件夹。

常见问题

VS Code 调试器始终将断点设置在 main() 函数中,到目前为止,我还没有找到任何方法可以阻止它这样做。程序永远不会在此断点处停止,因为我们实际上连接的是一个正在运行的程序,该程序已经在 main 函数中运行,但当此断点存在时,程序运行的速度会比正常速度慢——请参阅上方“限制”部分中的第 3 点。程序在调试器停止后,你可以在 Debug Console 中执行以下命令来删除断点

-exec clear main

此处描述的调试解决方案已在 Windows 10 上使用 Arduino Uno、Nano 和 Mega 以及在 Windows 7 上使用 Nano 进行了测试。它也应该可以在 Linux 和 Mac 上运行。在 Win 7 及更早版本上,你可能需要使用“代理”程序而不是直接串行连接。请参阅 Code Project 上的这篇文章或 github 上 avr_debug 项目的文档以获取更多信息。

如果连接调试器时出现问题,你可以通过在 launch.jsonsetupCommands 部分添加以下块来启用通信日志记录。

{
    "description": "Log file enable",
    "text": "-gdb-set remotelogfile gdb_logfile.txt",
    "ignoreFailures": false
}

然后你将在程序文件夹中找到 gdb_logfile.txt 文件,你可以检查此文件以了解出了什么问题。

如果在逐步执行程序时出现奇怪的行为,例如跳过几行,这很可能是因为编译器生成的代码与你编写的代码不同。请参阅上面步骤 3 部分中关于优化的讨论。即使禁用了优化,这种情况有时也会发生。为了检查问题,查看编译器生成的代码很有用。

为此,请在项目 build 子文件夹的命令行中运行以下命令

"c:\Program Files (x86)\Arduino\hardware\tools\avr\bin\avr-objdump.exe" -S app.ino.elf > list.txt

如果 Arduino IDE 的路径不同,请替换你的路径。

此命令将生成 list.txt 文件,显示你的代码以及编译器生成的相应汇编代码。你可以搜索此文件中的 <main><loop> 符号,以查看你的程序如何转换为处理器的实际操作。

下一步

本文的第 3 部分中,你可以了解如何替换 Arduino 中的引导加载程序,以便在不减慢速度的情况下调试程序。

历史

  • 2019-09-11:更新了包含已修复引导加载程序的 zip 文件。
  • 2019-07-19:添加了指向第 3 部分文章的链接。
  • 2019-06-26:初版
© . All rights reserved.