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

使用 GDB 调试经典 ATtiny 和小型 ATmega MCU 上的 Arduino 项目

starIconstarIconstarIconstarIconstarIcon

5.00/5 (15投票s)

2022 年 1 月 19 日

GPL3

24分钟阅读

viewsIcon

51523

downloadIcon

389

为那些最终想使用 GNU 调试器 GDB 来调试运行在 AVR MCU 上的 Arduino 项目的人提供的教程

引言

Arduino IDE 非常简单,易于上手。然而,一段时间后,人们会发现它缺少许多重要功能。特别是,当前的 IDE 不支持任何形式的调试。那么,当您想在小型 ATmega(例如流行的 ATmega328)或经典 ATtiny 上调试您的 Arduino 项目时,该怎么办呢?通常的方法是插入 print 语句,看看程序是否按预期执行。然而,应该有更复杂的方法。提到的芯片支持 debugWIRE 协议,您可以使用该协议访问这些 MCU 上的片上调试硬件。

Microchip 以专业水平提供硬件调试器和专有 IDE(Microchip Studio 和 MPLAB)形式的支持。硬件调试器并非免费,但 IDE 是免费的。然而,您将被迫使用一个完全不同的软件生态系统,有些人对这些 IDE 并不感冒。那么,有什么替代方案呢?

最流行的开源调试器是 GDB,即GNU 项目调试器。事实上,这是您在许多嵌入式开发 IDE 中都能找到的调试器,例如 PlatformIOArduino IDE 2.0。所以问题是,如何将提到的 AVR MCU 连接到 GDB。

在本文中,我将解决上述问题。此外,我将向您展示如何通过设置调试环境,将您的目标系统连接到主机,最后在目标系统上调试您的 Arduino 项目来利用它。

背景

GDB 等源代码调试器使您能够在程序执行时查看其内部工作原理。您可以启动和停止执行,可以设置断点以停止执行,可以单步执行,可以检查变量,还可以更改变量的值。所有这些通常都是在要调试的程序与调试器在同一台机器上运行时完成的。

调试嵌入式系统

当需要调试嵌入式系统时,上述场景不再适用。现在我们需要在开发机器(即主机)上运行调试器,而程序在目标上执行。GDB 以远程调试的形式支持这种场景。调试器通过 GDB 远程串行协议 (RSP) 与目标通信,以控制执行并收集信息,这意味着我们需要目标上有一些软件来接收和解释此协议。

如果目标运行自己的操作系统,那么可以使用一个单独的进程来完成此操作。对于许多不同的架构,都有一个名为 gdbserver 的程序可用,它将完成这项工作。在没有操作系统的裸机系统上,这是不可能的。

对于裸机系统,您有两种选择。首先,您可以将一个库(即所谓的 gdb-stub)链接到要调试的程序中。然后,该库负责 gdbserver 的工作,即控制执行并使用 RSP 与主机通信。在 Arduino 软件和更通用的 AVR MCU 的上下文中,Jan Dolinay 设计了这样一个 gdb-stub,并在 CodeProject 上进行了报道。这个 stub 适用于 ATmega328(P)、ATmega1284(P)、ATMega1280 和 ATMega2560。这是调试 Arduino 项目的一个巨大进步,但它当然有固有的局限性。其中之一是串行线路被远程串行协议用于与主机通信,因此用户程序无法使用。另一个局限性是您无法用它来调试 ATtiny。

裸机系统的第二个选项是使用扮演 gdbserver 角色的硬件调试器。几乎所有现代 MCU 都包含片上调试 (OCD) 硬件。此硬件提供对 MCU 内部(例如寄存器和内存)的访问,并且可以控制执行。访问通过另一种通信协议提供,通常是行业标准 JTAG。然后,硬件调试器在目标侧的 JTAG 和主机侧的 GDB RSP 之间进行转换。通常,另一个程序扮演调解器的角色,例如 openOCD

debugWIRE 协议

我们这里关注的 MCU,即经典的 ATtiny 和 ATmegaX8 系列,不提供 JTAG 接口。它们反而提供了一个专有的接口,用于其 OCD 功能,称为 debugWIRE。debugWIRE 的基本思想是使用 RESET 线作为目标和硬件调试器之间的通信线。只使用一条不用于其他目的的单线这个想法非常酷,因为它不会浪费任何其他引脚用于调试目的(例如 JTAG 接口)。然而,使用 RESET 线作为通信通道当然意味着不能再使用 RESET 线来复位 MCU。此外,也不能再使用 ISP 编程来上传新的固件到 MCU 或更改 MCU 的熔丝。固件上传可以通过 debugWIRE 接口进行,但速度会慢一些。

当您打算使用 debugWIRE 接口时,您必须对 MCU 相对于 debugWIRE 协议可能处于何种状态有一个基本的了解。根据状态,RESET 线和 ISP 编程可能工作也可能不工作。MCU 基本可以处于三种状态:

  1. 正常状态,其中 DWEN (debugWIRE enable) 熔丝被禁用。在此状态下,您可以使用 ISP 编程来更改熔丝并上传程序。通过启用 DWEN 熔丝,可以达到过渡状态
  2. 过渡状态是 DWEN 熔丝已启用的状态。在此状态下,您可以使用 ISP 编程再次禁用 DWEN 熔丝,以达到正常状态。通过断电重启(关闭并重新打开目标系统),可以达到debugWIRE 状态
  3. debugWIRE 状态是您可以使用调试器控制目标系统的状态。如果您想返回正常状态,一个特定的 debugWIRE 命令会导致过渡到过渡状态,从该状态可以使用普通的 ISP 编程达到正常状态

现有的 debugWIRE 接口

存在一些开源系统,它们基于 RikusW 对协议逆向工程的工作实现了 debugWIRE 协议的一部分,但其中大多数都存在一些限制。DebugWireDebuggerProgrammer 是一个基于 Arduino 的解决方案,它提供了一个 ISP 编程器和 debugWIRE 调试器。不幸的是,它不提供 GDB RSP 接口。dwire-debug 是一个 C 程序,可以在 Linux、Windows 和 macOS 下使用。它通过串行线访问目标,其中 TX 和 RX 线使用二极管连接。该程序提供 RSP 接口,但只允许一个断点。此外,在 macOS 下,我无法通过连接的线路可靠地通信。然后,还有一个类似于 dwire-debug 的 Pascal 实现,称为 debugwire-gdb-bridge,我没有尝试过。

最近,我开发了一个名为 dw-link 的 Arduino 草图,它将 Arduino Uno、Nano 或 Pro Mini 变成一个硬件调试器。它不受可能使串行通信困难的操作系统依赖关系的影响。相反,它仅依赖于通过串行线使用 RSP,并且易于安装。然后设置将如下图所示,其中硬件调试器的角色由运行 dw-link 的 Arduino Uno 扮演。

在目标上调试程序的必要步骤如下:

  1. 安装 dw-link,
  2. 设置调试环境,例如 PlatformIO 或 Arduino IDE & avr-gdb,
  3. 设置硬件,以及
  4. 开始调试。

安装 dw-link

使用本文的源代码存档或从 GitHub 仓库下载代码。如果您想使用 Arduino IDE 将固件上传到硬件调试器,请将存档解压到 Arduino IDE 期望草图的位置。然后您可以使用 IDE 将草图 dw-link 上传到您想用作硬件调试器的板,即 Uno、Nano 或 Pro Mini 板。

如果您使用 PlatformIO,您可以将其解压,例如,到您的 PlatformIO 项目文件夹中,然后打开项目。它已经包含一个 platformio.ini 文件,支持您编译固件并上传到 Arduino Uno、Nano 或 Pro Mini。

仓库中还有详细的文档,可以帮助您设置一切。

example 文件夹包含一些示例草图和配置文件。作为一个运行示例,我们将使用草图 varblink.ino

#include <Arduino.h>

#ifndef LED_BUILTIN
 #define LED 4
#else
 #define LED LED_BUILTIN
#endif
byte thisByte = 0;
void setup() {
  pinMode(LED, OUTPUT);
}

void loop() {
  int i=random(100);
  digitalWrite(LED, HIGH);  
  delay(1000);              
  digitalWrite(LED, LOW);              
  thisByte++;
  thisByte = thisByte + i;
  delay(100+thisByte);
}

将此草图放入您的 Arduino IDE 可以找到并编译的文件夹中。该草图旨在运行在 ATtiny85 上,但当然可以为任何 Arduino IDE 支持的 MCU 进行编译。

设置调试环境

有(至少)两种可能的方法来设置调试环境。第一种,安装 PlatformIO,直截了当,最后需要一些配置。主要优点是整个工具链,包括调试器,将在后台安装。此外,它使调试非常容易,因为您会获得一个非常易于访问的 GUI,可以访问几乎所有重要细节。相比之下,使用 Arduino IDE 和 avr-gdb 需要一些工作才能使一切正常。但是,您可以留在 Arduino 世界中,并且只需要学习如何使用 avr-gdb。但是,这种方法非常面向控制台。

安装和配置 PlatformIO

PlatformIO 是一个面向嵌入式系统的 IDE,基于 Visual Studio Code。它支持许多 MCU,特别是几乎所有 AVR MCU。并且可以导入 Arduino 项目,然后将其转换为 PlatformIO 项目。项目可以通过配置文件 platformio.ini 进行高度配置,也就是说,可以为不同目的设置许多参数。然而,这使得一开始的事情更具挑战性。

安装 PlatformIO 很简单。下载并安装 Visual Studio Code。然后启动它,点击左侧的扩展图标(四个方块),搜索 PlatformIO 扩展并安装它,如此处所述。查看快速入门指南。现在我们都准备好了。

下次再次启动 VSCode 时,您可以导入 varblink.ino 草图。单击下方导航栏中的主页符号,这将显示 PlatformIO 的主屏幕。然后选择导入 Arduino 项目。您需要选择一个开发板,然后选择要从中导入 Arduino 项目的目录。

导入后,您将看到一个包含 includelibsrc 等条目的文件夹。您的 Arduino 草图可以在 src 子文件夹中找到。此外,还有重要的配置文件 platform.ini。它已设置为与您指定的开发板一起使用。如果您想将其与硬件调试器一起使用,您需要添加一些内容。最好的方法可能是用您在 example/pio-files 文件夹中找到的 platformio.ini 文件替换现有文件。您还需要指定用于访问硬件调试器的串行端口,并且需要在 platform.ini 文件中指定目标板的类型。 example/pio-files 文件夹中还包含一个 extra_script.py 文件,您也需要将其复制到 varblink.ino 草图的项目文件夹中。

安装 avr-gdb 并更改 Arduino IDE 配置文件

假设您正在使用 Arduino IDE 和/或 Arduino CLI,最简单的调试代码方法是安装和使用 GNU 调试器。您只需下载调试器并对一些配置文件进行少量更改。请记下您所做的更改,因为当您升级到新版本的 Arduino 包时,这些更改将消失。

如果您想调试标准 Arduino 板以外的任何东西,那么您还需要下载相应的核心。例如,如果您想调试经典的 ATtiny,您需要安装 ATTinyCore。如果您想调试 ATmegaX8 系列的 MCU,您需要安装 MiniCore

为了使调试成为可能,第一个重要的更改是启用 IDE/CLI 从您的项目中导出 ELF 文件。这些是包含机器可读符号和行号信息的机器代码文件。您可以通过在平台规范文件夹之一中添加一个名为 platform.local.txt 的文件来强制导出 ELF 文件,该文件需要包含以下行:

recipe.hooks.savehex.postsavehex.1.pattern.macosx=cp 
    "{build.path}/{build.project_name}.elf" "{sketch_path}"
recipe.hooks.savehex.postsavehex.1.pattern.linux=cp 
    "{build.path}/{build.project_name}.elf" "{sketch_path}"
recipe.hooks.savehex.postsavehex.1.pattern.windows=cmd 
    /C copy "{build.path}\{build.project_name}.elf" "{sketch_path}"
这三行确保当您在 Sketch 菜单下选择 Export compiled Binary 时,您的草图目录中会收到一个 ELF 文件。当您使用 CLI 时,-e 选项会完成此操作。

包含这三行的文件(您可以在 example/configuration-files 仓库文件夹中找到)需要复制到以下 Arduino 文件夹(NAME 是核心名称,VERSIONNUMBER 是核心版本)

  • macOS: ~/Library/Arduino15/packages/CORENAME/hardware/avr/VERSIONNUMBER

  • Linux: ~/.arduino15/packages/CORENAME/hardware/avr/VERSIONNUMBER

  • Windows: C:\Users\USERNAME\AppData\Local\Arduino15\packages\ACORENAME\hardware\avr\VERSIONNUMBER

请注意,对于 2.0.0 版本及以上的 ATtinyCore,您不需要这样做,因为该核心已经导出了 ELF 文件。

由于 Arduino IDE 使用的编译器优化级别,编译器生成的机器代码不会直接遵循您的源代码。因此,建议使用优化标志 -Og(以调试友好的方式编译)而不是默认优化标志 -Os(优化以最小化空间)。如果您使用的是命令行界面 arduino-cli,则可以通过在 arduino-cli compile 命令中添加以下选项轻松实现:

--build-property build.extra_flags="-Og"

我最近注意到,另一个优化设置可能会显著影响您的“调试体验”。通常,Arduino IDE 中会启用链接时优化,这会导致大多数关于类继承和对象属性的信息消失。因此,如果您想调试面向对象的代码,那么禁用这种优化并添加标志 -fno-lto 是有意义的。

那么,如果您不想使用 CLI 界面,而是想使用 IDE,该怎么办?您可以修改 boards.txt 文件(与 platform.local.txt 文件位于同一目录中),并为每种 MCU 类型引入一个新的菜单项 debug,启用后会添加构建选项 -Og

为了简化您的操作,我提供了一个 Python 脚本(在 examples/configuration-files 文件夹中)名为 debugadd.py。将此脚本复制到您复制 platform.local.txt 的同一文件夹中,进入该文件夹,然后通过调用 python3 debugadd.py 执行该脚本。这将为 boards.txt 文件中列出的每个 MCU/开发板添加一个菜单项。同样,对于 2.0.0 版本及以上的 ATtinyCore,这没有必要,因为您已经可以选择优化选项。

现在您必须重新启动 Arduino IDE。如果您在开发板菜单中选择一个菜单项,您会注意到有一个新的菜单选项 Debug

最后,您需要安装 avr-gdb,这是 GDB 调试器的 AVR 版本。它曾经是 Arduino IDE 工具链的一部分。然而,现在不再是了。根据您的操作系统,您可以通过以下方式获取可执行文件:

  • macOS:使用 homebrew 安装。
  • Linux:使用您喜欢的包管理器安装 avr-gdb。
  • Windows:您可以从 MicrochipZak's Electronic Blog~* 下载包含调试器 avr-gdb 的完整 AVR-GCC 工具链。

设置硬件

软件现已就位。剩下的就是将硬件调试器连接到主机和目标。与主机的连接非常简单,因为我们只需要使用 USB 电缆连接两者。当您要开始调试会话时,您将需要串行端口的名称。但是,我假设您能够自行解决。与目标的连接稍微复杂一些。

RESET 线的要求

由于目标系统的 RESET 线用作开漏异步半双工串行通信线,因此必须确保在 debugWIRE 模式下使用时,线上没有电容负载。此外,应该有一个大约 10 kΩ 的上拉电阻。根据其他人的报告,4.7 kΩ 也可能有效。当然,RESET 线不应直接连接到 Vcc,并且 RESET 线上不应有任何外部复位源。

如果您的目标系统是 Arduino Uno,您必须注意 ATmega328 的 RESET 引脚和串行芯片的 DTR 引脚之间有一个电容器,该电容器实现了自动复位功能。Arduino IDE 使用它来发出复位脉冲以启动引导加载程序。您可以通过切断板上标有 *RESET EN* 的焊桥来断开电容器和自动复位线(见图),但这样您就不能再使用 Arduino IDE 的自动复位功能了。

一种恢复方法是焊接一些焊料在焊桥上,或者更好地在板上焊接两个引脚并使用跳线。或者,您可以在 Arduino IDE 尝试上传草图之前,始终手动复位 Uno。技巧是在编译过程完成后立即释放复位按钮。

其他 Arduino 板,例如 Nano,修改起来更困难,而 Pro Mini,例如,只要 FTDI 连接器的 DTR 线未连接,就可以毫无问题地使用。一般来说,最好获取您要调试的开发板的原理图。这样就可以很容易地找出哪些东西连接到 RESET 线,以及需要移除哪些东西。检查上拉电阻(如果存在)的值可能也是一个好主意。

那么,使用 debugWIRE 时最坏的情况是什么?可能会发生您成功地通过 ISP 编程(见下文)将您的目标芯片置于 debugWIRE 模式,但随后您无法通过 RESET 线进行通信。特别是,您可能无法将目标恢复到 ISP 编程可用的状态。您的 MCU 变砖了。它仍然可以使用上次编程的固件工作。然而,现在重置 MCU 的唯一方法是断电重启。此外,无法使用 ISP 编程重新编程它。

有两种方法可以解决。首先,您可以尝试使 RESET 线符合 debugWIRE 的要求。然后您应该能够使用硬件调试器连接到目标。其次,您可以使用高压编程,其中 12 伏电压必须施加到 RESET 引脚。因此,您可以将芯片从板上取下并离线编程,或者断开 RESET 线与 Vcc 电源轨和板上其他组件的所有连接。然后您可以使用现有高压编程器,或者在面包板上构建一个

将调试器连接到目标

我使用面包板上的 ATtiny85 作为示例目标系统,使用 Uno 作为示例调试器。但是,任何支持 debugWIRE 的 MCU 都可以作为目标。甚至可以将 Arduino Uno 作为目标,前提是完成了上一小节中描述的修改。

首先,请注意 Uno 板上 RESET 和 GND 之间有一个 10 µF 或更大的电容器。这将禁用 Uno 板的自动复位。这是可选的,有助于加快与主机的连接过程。

其次,请注意连接到引脚 7 和 6 的 LED 和电阻器。这是系统 LED,用于可视化调试器的内部状态。同样,这是可选的,但非常有用。调试器可以处于四种状态,每种状态都由系统 LED 的不同闪烁模式表示:

  • 未连接(LED 熄灭),
  • 等待目标断电重启(LED 每秒闪烁 0.1 秒),
  • 目标已连接(LED 亮起),
  • 错误状态,即无法连接到目标或内部错误(LED 每 0.1 秒剧烈闪烁)。

第三,如您所见,面包板的 Vcc 总线连接到 Arduino Uno 的 D9 引脚,以便能够为目标芯片断电重启。请注意,通过调试器的 GPIO 为目标系统供电仅在目标系统需要 20 mA 或更少电流时有效。否则,您需要外部为目标供电,然后在必要时手动断电重启系统。

此外,Arduino Uno 的 D8 引脚连接到 ATtiny 的 RESET 引脚(引脚 1)。请注意 ATtiny RESET 引脚上存在 10kΩ 的上拉电阻。Arduino Uno 和 ATtiny 之间的其余连接是 MOSI(Arduino Uno D11)、MISO(Arduino Uno D12)和 SCK(Arduino Uno D13),您需要这些连接进行 ISP 编程。此外,还有一个 LED 连接到 ATtiny 芯片的引脚 3(在 Arduino 术语中是 PB4 或引脚 D4)。ATtiny85 的引脚排列如下图所示(采用 Arduino 引脚通常的“逆时针”编号)。

以下是所有连接的表格,您可以检查是否已完成所有连接。

ATtiny 引脚# Arduino Uno 引脚 组件 (Component)
1(复位) D8 10k 电阻至 Vcc
2(D3)    
3(D4)   220 Ω 电阻至 LED (+)
4(GND) GND LED (-),去耦电容 100 nF,旁路电容 10µF (-),
5(D0,MOSI) D11  
6(D1,MISO) D12  
7(D2,SCK) D13  
8(Vcc) D9 10k 电阻,去耦电容 100 nF
  RESET 旁路电容 10 µF (+)
  D7 系统 LED (+)
  D6 200 Ω 至系统 LED (-)

如果您想调试 Uno 板而不是 ATtiny85,上面所说的也同样适用。下面是显示连接的 Fritzing 草图。请记住按照上述说明切断目标板上的 `RESET EN` 焊桥。

 

请注意,从 ATmega 引脚为目标板供电可能会有些超限。事实上,电压会降至 4V。您也可以将目标板的供电引脚连接到调试器板上的 5V,并在系统 LED 指示时手动进行断电重启。

最后,我应该提一下,当您第一次尝试连接 Uno 目标时,您可能会遇到问题。在这种情况下,很可能是目标 MCU 的某些锁定位已设置,并且无法调试目标。您需要擦除整个芯片以清除锁定位。这可以通过使用 ISP 编程器、发出 GDB 命令 monitor erase,或者在使用 PlatformIO 时,选择自定义任务 Erase Chip 来完成。

调试草图

安装软件并准备好硬件后,我们现在可以开始调试会话了。

在 PlatformIO 下调试

使用 PlatformIO 进行调试非常简单。将您要调试的项目设置为当前项目后,您只需点击左侧的调试图标即可。

这将重新配置 GUI 为调试配置,显示多个不同的信息窗格。现在,当您点击标有 PIO Debug 的绿色三角形时,将启动一个调试会话。这将编译项目并启动调试器。目标将被切换到 debugWIRE 模式,要么通过自动断电重启,要么通过请求您进行断电重启(通过系统 LED 闪烁模式和 Debug Console 中的消息)。如果 MCU 已经处于 debugWIRE 模式,可能是来自之前的调试会话,则不需要断电重启。

如果调试会话未启动,您应该在 DEBUG CONSOLE 中看到错误消息。一个可能的错误消息是 Cannot Connect: Lock bits are set。这尤其发生在您第一次尝试调试包含引导加载程序的芯片时。您可以通过擦除整个芯片来清除锁定位。在 debug 环境中,在 Custom PROJECT TASKS 下选择 Erase Chip。之后,您可以尝试再次启动调试器(在重置硬件调试器之后)。

如果启动调试器一切顺利,程序将被上传并启动。初始停止点在 main 例程中(由行号左侧的黄色三角形表示)。您现在可以使用右上角的执行控制面板来启动(和停止)执行。或者您可以单步执行,要么不进入函数调用(跳过),要么进入函数(步入)。向上箭头启动步出操作,即执行继续直到当前函数完成。绿色圆圈启动重新启动,红色方块终止调试会话。

现在您可以单步进入循环函数,或者您也可以将 varblink.ino 文件重新加载到编辑器窗口中。在那里,您可能希望设置断点,即执行将停止的点。这可以通过单击行号左侧的空列来完成。这将在执行将停止的位置放置一个红色圆点。

现在您可以做很多事情来检查程序的内部工作原理,甚至可以通过在显示的 Debug Console 的输入行中键入相应的 GDB 命令来更改变量的值。 Valerii Koval 撰写了一篇关于使用 PlatformIO 调试的优秀介绍:使用 PlatformIO 调试,第一部分第二部分

如果您想将 MCU 恢复到正常状态,您可以在调试会话期间在 Debug Console 的输入行中键入 monitor dwoff。或者,当调试会话不再活动时,您可以首先单击左侧的 PlatformIO 符号(蚂蚁),然后在 Program Tasks 下选择 Debug/Custom 类别,在那里您会找到 DebugWIRE Disable 条目。单击它将禁用 debugWIRE 模式。

使用 avr-gdb 调试

现在让我们只使用 avr-gdb 启动一个调试会话。因此,编译示例 varblink.ino,启用调试并要求导出二进制文件,这将在 sketch 目录中为您提供文件 varblink.ino.elf。然后将您的 Uno 连接到主机并启动 avr-gdb。所有以 >(gdb) 提示符开头的行都包含用户输入,# 之后的所有内容都是注释。 <serial port> 是您用于与 Uno 通信的串行端口。

> avr-gdb -b 115200 varblink.ino.elf
GNU gdb (GDB) 10.1
Copyright (C) 2020 Free Software Foundation, Inc.
...
Reading symbols from varblink.ino.elf...
(gdb) target remote <serial port>              # connect to the serial port of debugger  
Remote debugging using <serial port>           # connection made
0x00000000 in __vectors ()                     # we always start at location 0x0000
(gdb) monitor dwconnect                        # show propertied of the debugWIRE connection
Connected to ATtiny85
debugWIRE is now enabled, bps: 125736
(gdb) load                                     # load binary file
Loading section .text, size 0x714 lma 0x0
Loading section .data, size 0x4 lma 0x714
Start address 0x00000000, load size 1816
Transfer rate: 618 bytes/sec, 113 bytes/write.
(gdb) list loop                                # list part of loop and shift focus
6       byte thisByte = 0;
7       void setup() {
8         pinMode(LED, OUTPUT);
9       }
10      
11      void loop() {
12        int i=random(100);
13        digitalWrite(LED, HIGH);  
14        delay(1000);              
15        digitalWrite(LED, LOW);              
(gdb) break loop                               # set breakpoint at start of loop function
Breakpoint 1 at 0x494: file ..., line 12.
(gdb) br 15                                    # set breakpoint at line 15
Breakpoint 2 at 0x4bc: file ..., line 15.
(gdb) c                                        # start execution at PC=0
Continuing.

Breakpoint 1, loop () at /.../varblink.ino:12
12        int i=random(100);
(gdb) next                                     # single-step over function
13        digitalWrite(LED, HIGH);  
(gdb) n                                        # again
14        delay(1000);              
(gdb) print i                                  # print value of 'i'
$1 = 7
(gdb)  print thisByte                          # print value of 'thisByte'
$2 = 0 '\000'
(gdb) set var thisByte = 20                    # set variable thisByte
(gdb) p thisByte                               # print value of 'thisByte' again
$3 = 20 '\024'
(gdb) step                                     # single-step into function
delay (ms=1000) at /.../wiring.c:108
108             uint32_t start = micros();
(gdb) finish                                    # execute until function returns
Run till exit from #0  delay (ms=1000)
    at /.../wiring.c:108

Breakpoint 2, loop () at /.../varblink.ino:15
15        digitalWrite(LED, LOW);              
(gdb) info br                                   # give information about breakpoints
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x00000494 in loop()
                                           at /.../varblink.ino:12
        breakpoint already hit 1 time
2       breakpoint     keep y   0x000004bc in loop()
                                           at /.../varblink.ino:15
        breakpoint already hit 1 time
(gdb) delete 1                                  # delete breakpoint 1
(gdb) detach                                    # detach from remote target
Detaching from program: /.../varblink.ino.elf, Remote target
Ending remote debugging.
[Inferior 1 (Remote target) detached]
(gdb) quit                                      # quit debugger
>

如果有什么不顺利,您可能需要查阅 dw-link 手册中的故障排除指南。

请注意,MCU 仍处于 debugWIRE 模式,RESET 引脚不能用于复位芯片。如果您想将 MCU 恢复到正常状态,需要再次执行 avr-gdb。

> avr-gdb
GNU gdb (GDB) 10.1
...

(gdb) set serial baud 115200            # set baud rate
(gdb) target remote <serial port>       # connect to serial port of debugger    
Remote debugging using <serial port>
0x00000000 in __vectors ()
(gdb) monitor dwoff                     # terminate debugWIRE mode
Connected to ATtiny85
debugWIRE is now disabled
(gdb) quit
>

当然,您可以在退出上述调试会话之前完成此操作。

您在上面的示例中已经看到了许多不同的 GDB 命令,但还有更多。您可以在 GDB 文档网站上找到一份很好的参考卡,其中还包含一份详尽的手册。我还推荐 Jay Carlson 撰写的关于使用 GDB 进行嵌入式调试的技巧

摘要

到目前为止,经典 ATtiny 和小型 ATmega MCU 的嵌入式调试只能通过专有软硬件进行,或者存在许多限制。随着最近开发的开源固件 dw-link,可以将 Arduino Uno(或类似板)变成硬件调试器,在 debugWIRE 和 GDB RSP 之间进行转换。换句话说,现在可以使用 avr-gdb 调试支持 debugWIRE 的 MCU。本文概述了如何在 Arduino 板上安装 dw-link,设置调试环境,将硬件调试器连接到目标和主机,并执行调试会话。希望这能让这些 MCU 的 Arduino 草图调试变得更加愉快。

历史

  • 2022 年 1 月 19 日:提供了更新的源代码 (1.3.2) 并修正了闪烁模式的描述,以修复由于读取目标回复时超时导致的问题
  • 2022 年 2 月 2 日:添加了连接 Uno 作为目标的 Fritzing 草图,更新了修改 Arduino 配置文件的描述,并更新了源代码存档
  • 2022 年 3 月 12 日:添加了关于锁定位及其处理方法的解释,并更新了源代码
© . All rights reserved.