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






4.14/5 (4投票s)
本文介绍如何用支持调试的特殊引导加载程序替换标准的 Arduino 引导加载程序,以使调试更有用。
引言
这是关于在 Visual Studio Code 中创建和调试 Arduino 程序的系列文章的第三部分。
- 第一部分介绍了如何设置 VS Code 来构建 Arduino 程序。
- 第二部分展示了如何设置 VS Code 来调试你的 Arduino 程序。.
- 第三部分介绍如何用支持调试的特殊引导加载程序替换标准的 Arduino 引导加载程序,以使调试更有用。
我为什么要这样做?
在使用第二部分中描述的默认调试器配置时,调试的程序运行速度比正常情况慢得多。这是因为调试器必须将当前执行程序的当前位置与断点位置表进行比较,以确定何时停止程序。在许多情况下,这种减速不成问题,但有时也会。
如果你需要在程序停止在断点之前以正常速度运行(例如,生成某些信号),则需要配置调试器库以使用闪存断点。使用闪存断点,程序中插入断点的位置将被跳转到调试器替换,因此程序会以正常速度运行,直到它命中断点并停止。但是,使用闪存断点需要能够写入闪存,只有在使用特殊引导加载程序时才可能。
这就是为什么你需要替换 Arduino 中的引导加载程序。目前,特殊引导加载程序仅适用于 Atmega328,因此闪存断点只能与基于 Atmega328 的 Arduino 板一起使用——即 Uno 和 Nano(均已测试并正常工作)以及其他一些变体(我没有测试过)。抱歉,它不适用于 Arduino Mega,因此你只能使用 RAM 断点调试 Mega。
背景
下面的教程适用于没有 AVR ISP 编程器的人。它展示了如何使用另一块 Arduino Uno 作为编程器来替换 Arduino Uno 中的引导加载程序。
如果你有 ISP 编程器并且想在 Arduino IDE 中使用该编程器,你仍然可以使用本教程。如果你使用其他软件(如 Atmel Studio)与你的编程器一起使用,你可能不需要本教程;你可以直接使用此 ISP 编程器将新的引导加载程序烧录到你的 Arduino 中。你可以在 avr-debugger 项目的 arduino/library/avr-debugger/bootloader 文件夹中找到引导加载程序,并在 readme.txt 中找到设置熔断器的说明。
所需工具
- Arduino Uno – 2 个(或可作为 ISP 编程器使用的其他 Arduino)
- Arduino IDE
- Avr-debugger 库 - 可从本文第二部分下载。
- Arduino 的硬件平台 - 可从本文下载。
步骤 1:将新的硬件配置添加到你的 Arduino 安装中
原因?硬件配置是能够从 Arduino IDE 将自定义引导加载程序(我们的启用调试器引导加载程序)烧录到你的 Arduino 板所必需的。
- 下载附加到本文的 zip 文件。
- 将 zip 存档中的 hardware 文件夹复制到你的 Arduino sketch 文件夹。
默认情况下,它是 [your user folder]/Documents/Arduino。你可以在 **Arduino IDE** > **Preferences** 中找到该位置。如果你在那里已经有 hardware 文件夹,只需将 avr-debugger 子文件夹复制到此文件夹。 - 如果 Arduino IDE 正在运行,请在此时间重启 Arduino IDE。
- 检查 **Tools** > **Board** 菜单。应该有一个名为 **avr-debugger Atmega328 bootloader** 的新项目。
步骤 2:准备将 Arduino 用作 ISP 编程器
原因?你需要使用编程器来替换 Arduino 中的引导加载程序。你可以购买一个外部编程器来做到这一点,但你也可以使用另一个 Arduino 板作为编程器。本教程假设你使用 Arduino Uno 作为编程器,但也可以使用其他板。只需在网上搜索有关如何连接它的信息。
- 将要用作编程器的 Arduino Uno 连接到你的计算机。
- 启动 Arduino IDE 并通过转到 **File** > **Examples** > **Arduino ISP** 来打开 **Arduino ISP 示例程序**。
- 在 Arduino IDE 的 **Tools** 菜单中,选择主板 **Arduino/Genuino Uno**。
- 选择 Arduino 连接到的正确端口,例如,COM6。
- 单击 **Upload** 按钮并将 Arduino ISP 程序上传到 Arduino。
此程序会将 Arduino 变成一个 ISP 编程器。
步骤 3:连接目标 Arduino 和编程器 Arduino
- 断开编程器 Arduino 与计算机的连接——拔下 USB 线。
- 使用下面显示的连接图连接目标 Arduino。该图像来自 Arduino.cc 上的这篇文章,你也可以在那里找到更多关于将 Arduino 用作 ISP 编程器的信息。
图片来源:https://www.arduino.cc/en/Tutorial/ArduinoISP
步骤 4:将新的引导加载程序烧录到目标 Arduino
- 将编程器 Arduino 连接到你的计算机。不要将目标 Arduino 连接到你的计算机;它将由编程器 Arduino 供电。
- 如果尚未运行,请启动 Arduino IDE。
- 在 **Tools** 菜单中,选择主板 **avr-debugger Atmega328 bootloader**。
- 在 **Tools** > **Port** 中,选择编程器 Arduino 的端口,例如 COM6。
- 在 **Tools** > **Programmer** 中,选择 **Arduino as ISP**。
- 单击 **Tools** > **Burn bootloader**。
IDE 应该会在状态栏中显示“Burning bootloader”消息,并在一段时间后显示“done”。
步骤 5:验证引导加载程序是否正常工作
- 断开编程器 Arduino 的 USB 线。
- 断开目标 Arduino(现在具有新引导加载程序的那个)的电线。
- 使用 USB 线将目标 Arduino 连接到你的计算机。
- 在 Arduino IDE 中,在 **Tools** > **Port** 中选择端口。这将是与前几个步骤不同的端口,例如 COM4。
- 打开 Blink 示例并将其上传到开发板。
如果一切正常,Arduino 上的 LED 现在应该会闪烁。从 Arduino IDE 上传程序应该像往常一样工作——新引导加载程序和旧(原始)引导加载程序在此情况下行为相同。
注意:使用新引导加载程序时,程序的最大大小为 30720 字节,比使用原始引导加载程序少约 1.5 KB。这是因为新引导加载程序比原始引导加载程序大。你可能不会达到此限制,但如果你希望 Arduino IDE 为你监控程序大小,你可以将主板保留为 **Tools** > **Board** 菜单中的“avr-debugger Atmega328 bootloader”,而不是标准的 Arduino/Genuino Uno。
步骤 6:启用闪存断点调试你的程序
在此步骤中,我假设你已经在 Visual Studio Code 中配置了一个项目(文件夹)来开发和调试 Arduino 程序。如果不是,请参阅第二步的文章。
- 在某个文件管理器(如 Total Commander 或 Windows Explorer)中,找到
avr-debugger
库中的 avr8-stub.h 文件——它位于 [your user folder]\Documents\Arduino\libraries\avr-debugger\src。
在任何文本编辑器中打开此文件——你可以使用 VS Code 或记事本。
找到这一行
#define AVR8_BREAKPOINT_MODE (1)
并将 1 更改为 0(零),使该行变为
#define AVR8_BREAKPOINT_MODE (0)
这告诉 avr-debugger
库使用闪存断点而不是 RAM 断点。
- 在 VS Code 中生成(验证)你的程序。我建议使用简单的闪烁程序。与 RAM 断点相比,启用闪存断点后,程序的实际大小现在大约大 700 字节。在我的情况下,程序使用了大约 8600 字节的程序空间。
- 将程序上传到你的 Arduino。除非你在代码中放置了断点,否则 LED 现在应该会闪烁。
- 单击 VS Code 左侧的调试按钮,然后单击调试视图顶部(看起来像一个绿色的播放按钮)的 **Start debugging** 按钮。
过一段时间后,调试器应该会连接到程序——VS Code 底部的状态栏会变为橙色。
- 单击顶部出现的调试器工具栏中的 **Pause** 按钮。你应该会发现自己身处 Arduino 核心代码中,可能在
delay()
函数中。你可以使用 **Step Out** 按钮回到循环函数中的你的代码。
现在你可以像往常一样逐行执行代码等操作。
我如何知道我正在使用闪存断点?
如果你让程序全速运行——单击 **Continue** (**F5**)按钮(并且没有断点),LED 应该会以正确的速度闪烁。例如,如果你在 digitalWrite
与 HIGH
和 LOW
之间使用 delay(1000),LED 应该亮起 1 秒。如果是这样,你正在使用闪存断点。另一方面,如果 LED 亮起的时间更长(可能约 5 秒),则表示你正在使用 RAM 断点。
关于闪存损耗的重要提示
Arduino 中使用的 AVR 微控制器的程序(闪存)内存可以承受约 10,000 次重写。通常,在上传新程序到 Arduino 时,它会被重写一次。所以理论上,你可以上传 10,000 次,然后你的 Arduino 可能会出现故障。实际上,可能的重写次数要高得多,但仍然不是无限的。
使用带有闪存断点的调试器时,内存会被更频繁地重写。插入或删除断点时,需要重写部分内存。断点不仅在你实际在 IDE 中插入/删除断点时插入和删除,而且在你从断点继续执行时(原始指令需要恢复和执行)也会自动插入和删除,有时在单步调试代码时,调试器会插入临时断点,例如在单步执行函数后停止程序。
总的来说,你可以期望在 IDE 中的每次单步/继续单击操作都会写入一次闪存。这看起来很吓人,但并非如此糟糕,你的 Arduino 应该能正常使用很长时间。了解这一点并尝试遵循这些建议以最大程度地减少内存损耗是很有好处的。
- 仅使用真正需要的断点数量。通常,你一次只需要一个断点。程序运行时,调试器必须始终更新所有断点,而不仅仅是下一个将命中的断点——调试器不知道哪个是下一个将命中的。
- 当不再需要断点时,将其删除。这并不是说每次程序停在断点上时都应该删除和重新插入断点,而是当你处理程序的其他部分并且在一段时间内不需要该断点时,将其删除。在你发出命令删除断点时,这只需要一次写入,而这个相同的断点会在你每次让程序运行时不必要地自动删除和插入。
- 删除 VS Code 自动插入到 main 函数的断点。这是 VS Code 的一个奇怪功能,它总是将断点插入到 main 函数,而且我找不到禁用它的方法。因此,在你开始调试后,暂停程序,然后在 VS Code 底部的 Debug 控制台视图中,输入此命令:
-exec clear main
关注点
要点 1
在 boards.txt 文件中,该文件定义了用于从 Arduino IDE 烧录调试器的硬件平台,锁位设置为 0x2F。此值允许从应用程序(你的)程序读取引导加载程序内存。这对闪存断点调试工作至关重要。Arduino Uno 的默认值是 0xCF,这意味着禁止在引导加载程序部分进行读写。
要点 2
如果你想知道在你的调试会话期间闪存被重写了多少次,请执行此操作。
在 avr-debugger
库的 src 子文件夹中打开 app_api.c 文件。
取消注释此行
#define AVR8_API_DEBUG
重新构建并上传你的程序。
在 VS Code 中调试时,在 Expressions 窗口中添加以下表达式:g_boot_write_cnt
此变量显示闪存被写入的次数。它计算内存中所有位置的总写入次数,而不是内存特定页面的写入次数,因此实际内存损耗可能较低。
要点 3
在上面描述的某些步骤中,我们通过编辑 libraries/avr-debugger/src 中的头文件来修改了 avr-debugger
库选项。问题是,每次这样的更改都会影响所有使用该库的程序。例如,如果你启用了闪存断点,那么对于所有使用 avr-debugger
库的程序,你都启用了它。
这是 Arduino 环境的一个特性,它不支持将任何配置值(预处理器定义)传递给库。一种克服这个问题的方法是将库文件复制到你的 VS Code 文件夹。这样,你将为每个程序拥有一个单独的 avr-debug
库副本,任何设置仅对该项目有效。要做到这一点,不要将库插入到你的程序中,只需将 avr-debugger/src 中的所有文件复制到你的程序文件夹。但请注意,我还没有测试过此选项。
历史
- 2019-09-11:更新了包含新引导加载程序的 zip 文件。
- 2019-07-16:发布了第一个版本。