Click here to Skip to main content
15,313,293 members
Articles / Internet of Things / Arduino
Article
Posted 19 Jan 2022

Stats

25.4K views
263 downloads
28 bookmarked

Debugging an Arduino project with GDB on Classic ATtiny and Small ATmega MCUs

Rate me:
Please Sign up or sign in to vote.
5.00/5 (15 votes)
12 Mar 2022GPL324 min read
A tutorial for people who finally want to debug their Arduino projects that run on AVR MCUs using the GNU project debugger GDB
The current Arduino IDE does unfortunately not support debugging. Even the new beta version supports debugging only for ARM MCUs. With a few easy steps, it is nevertheless possible to use avr-gdb in order to debug your Arduino project on many AVR chips. In this article, I will focus on MCUs that support debugWIRE, i.e., the classic ATtinys and the ATmegaX8 family.

Introduction

The Arduino IDE is very simple and makes it easy to get started. After a while, however, one notes that a lot of important features are missing. In particular, the current IDE does not support any kind of debugging. So what can you do when you want to debug your Arduino project on small ATmegas (such as the popular ATmega328) or classic ATtinys? The usual way is to insert print statements and see whether the program does the things it is supposed to do. However, there should be more sophisticated ways. The chips mentioned support the debugWIRE protocol, which one can use to access the on-chip debugging hardware on these MCUs.

There is support from Microchip in the form of hardware debuggers and proprietary IDEs (Microchip Studio and MPLAB) on a professional level. The hardware debuggers are not exactly for free, while the IDEs are. However, you would be forced to a quite different software eco system and some people are not thrilled by these IDEs. So what are the alternatives?

The most popular open-source debugger is GDB, The GNU Project Debugger. In fact, this is the debugger you find in many IDEs for embedded development, e.g., PlatformIO and Arduino IDE 2.0. So the question is in how far it is possible to interface the mentioned AVR MCUs to GDB.

In this article, I will address the above question. In addition, I will show you how to make use of it by setting up a debugging environment, connecting your target system to a host, and finally debugging your Arduino project on the target system.

Background

A source-code debugger such as GDB enables you to look at the inner workings of a program while it executes. You can start and stop execution, you can place breakpoints at which execution stops, you can make single steps, you can inspect variables, and you may also change the value of a variable. This is all usually done while the program that is to be debugged runs on the same machine as the debugger.

Debugging Embedded Systems

When one wants to debug embedded systems, the above scenario does not work any longer. Now we need to run the debugger on the development machine, the host, while the program executes on the target. Such a scenario is supported by GDB in the form of remote debugging. The debugger communicates via the GDB remote serial protocol (RSP) with the target in order to control execution and for gathering information, which means that we need some software on the target that receives and interprets this protocol.

If the target runs its own operating system, then a separate process could be used for it. For many different architectures, a program called gdbserver is available, which will do that. On a bare metal system without an operating system, this is not a possibility.

For bare metal systems, you have two options. First, you can link a library, a so-called gdb-stub, into the program that is to be debugged. This library is then responsible for what gdbserver would do, i.e., controlling execution and communicating with the host using RSP. In the context of Arduino software and more general AVR MCUs, Jan Dolinay has designed such a gdb-stub and reported about it on CodeProject. This stub works for ATmega328(P), ATmega1284(P), ATMega1280, and ATMega2560. It is a great step forward concerning debugging Arduino projects, but it has, of course, inherent limitation. One of them is that the serial line is used by the remote serial protocol for communicating with the host and therefore cannot be used by the user program. Another limitation is that you cannot use it to debug ATtinys.

The second option for bare metal systems is to use a hardware debugger, which plays the role of a gdbserver. Almost all modern MCUs contain on-chip debugging (OCD) hardware. This hardware gives access to the MCU internals such as registers and memory, and it can control execution. The access is provided by another communication protocol, often JTAG, an industry standard. The hardware debugger than translates between JTAG on the target side and GDB RSP on the host side. Often, another program plays the role of a mediator, e.g., openOCD.

The debugWIRE Protocol

The MCUs, we are concerned about here, namely, the classic ATtinys and the ATmegaX8 family, do not provide a JTAG interface. They have instead a proprietary interface to their OCD features called debugWIRE. The basic idea of debugWIRE is that one uses the RESET line as a communication line between the target and the hardware debugger. The idea of using only a single line that is not used otherwise is very cool because it does not waste any of the other pins for debugging purposes (as does e.g. the JTAG interface). However, using the RESET line as a communication channel means, of course, that one cannot use the RESET line to reset the MCU anymore. Furthermore, one cannot any longer use ISP programming to upload new firmware to the MCU or change the fuses of the MCU. Firmware uploads are possible over the debugWIRE interface, they are a bit slower, however.

When you intend to use the debugWIRE interface, you have to have a basic understanding of what state the MCU could be in with respect to the debugWIRE protocol. Depending on the state, the RESET line and ISP programming may work or not. There are basically three states the MCU could be in:

  1. The normal state in which the DWEN (debugWIRE enable) fuse is disabled. In this state, you can use ISP programming to change fuses and to upload programs. By enabling the DWEN fuse, one reaches the transitional state.
  2. The transitional state is the state in which the DWEN fuse is enabled. In this state, you could use ISP programming to disable the DWEN fuse again, in order to reach the normal state. By power-cycling (switching the target system off and on again), one reaches the debugWIRE state.
  3. The debugWIRE state is the state in which you can use the debugger to control the target system. If you want to return to the normal state, a particular debugWIRE command leads to a transition to the transitional state, from which one can reach the normal state using ordinary ISP programming.

Existing debugWIRE Interfaces

There exist a few open-source systems that implement parts of the debugWIRE protocol based on RikusW work on reverse engineering the protocol, but most of them have some limitations. DebugWireDebuggerProgrammer is an Arduino-based solution that provides an ISP programmer and a debugWIRE debugger. Unfortunately, it does not provide a GDB RSP interface. dwire-debug is a C-program that can be used under Linux, Windows and macOS. It accesses targets over the serial line, whereby the TX and RX lines are joined using a diode. The program provides an RSP interface, but allows only one breakpoint. Furthermore, under macOS, I was not able to communicate reliably over the joined lines. And then, there is a Pascal implementation similar to dwire-debug called debugwire-gdb-bridge, which I did not try out.

Recently, I have developed an Arduino sketch called dw-link that turns an Arduino Uno, Nano, or Pro Mini into a hardware debugger. It does not suffer from OS dependencies that could make serial communication difficult. Instead, it just relies on using RSP over the serial line and it is easy to install. The setup will then look as in the following picture, where the role of the hardware debugger is played by an Arduino Uno with dw-link running on it.

Image 1

The steps necessary to debug a program on the target are then:

  1. installing dw-link,
  2. setting up a debugging environment, e.g., PlatformIO or Arduino IDE & avr-gdb,
  3. setting up the hardware, and
  4. start debugging.

Installing dw-link

Use the source-code archive of the article or download the code from the GitHub repository. If you want to use the Arduino IDE to upload the firmware to your hardware debugger, extract the archive at a place, where the Arduino IDE expects sketches. You can then use the IDE to upload the sketch dw-link to the board you want to use as a hardware debugger, i.e., an Uno, Nano, or Pro Mini board.

If you use PlatformIO, you can extract it, e.g., in your PlatformIO project folder, and then open the project. It already contains a platformio.ini file that supports you in compiling and uploading the firmware to an Arduino Uno, Nano, or Pro Mini.

There is also extensive documentation in the repository that supports you in setting up everything.

The example folder contains some example sketches and configuration files. As a running example, we will use the sketch varblink.ino:

C++
#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);
}

Put this sketch into a folder where your Arduino IDE can find and compile it. The sketch is meant to run on an ATtiny85, but can, of course be compiled for any MCU that is covered by the Arduino IDE.

Setting Up the Debugging Environment

There are (at least) two possible ways to set up a debugging environment. The first one, installing PlatformIO, is straightforward and requires in the end a bit of configuration. The main advantage is that the entire toolchain including the debugger will be installed in the background. Further, it makes debugging very easy because one gets a very accessible GUI that gives access to almost all important details. In contrast, using the Arduino IDE and avr-gdb takes a bit of work to get everything right. However, you can stay in the Arduino universe and only have to learn something about using avr-gdb. This approach is, however, very console oriented.

Installing and configuring PlatformIO

PlatformIO is an IDE aimed at embedded systems and is based on Visual Studio Code. It supports many MCUs, in particular almost all AVR MCUs. And it is possible to import Arduino projects, which are then turned into PlatformIO projects. Projects are highly configurable using the configuration file platformio.ini, that is, a lot of parameters can be set for different purposes. However, that makes things in the beginning a bit more challenging.

Installing PlatformIO is straightforward. Download and install Visual Studio Code. Then start it and click on the extension icon (four squares) on the left, search for the PlatformIO extension and install it, as is described here. Check out the quick start guide. Now we are all set.

Next time, when you start VSCode again, you can import the varblink.ino sketch. Click on the Home symbol in the lower navigation bar, which brings up the home screen of PlatformIO. Choose then to import an Arduino project. You need to choose a board and then the directory from which you want to import the Arduino project.

After the import, you will have a folder with entries such as include, lib, src, etc. Your Arduino sketch can be found in the sub-folder src. In addition, there is the important configuration file platform.ini. It is set up for using it with the board you have specified. If you want to use it with the hardware debugger, you need to add a few things. The best way is probably to replace the existing file with the platformio.ini file that you find in the folder example/pio-files. You need also to specify the serial port you are using to access the hardware debugger and you need to specify the type of your target board in the platform.ini file. The folder example/pio-files contains also a file extra_script.py, which you need to copy to the project folder of the varblink.ino sketch as well.

Installing avr-gdb and Changing the Arduino IDE Configuration Files

Assuming that you are working with the Arduino IDE and/or Arduino CLI, the simplest way of starting to debug your code is to install and use the GNU debugger. You only have to download the debugger and make a few changes to some of the configuration files. Please take notes on what you change because these changes will vanish when you upgrade to a new version of the Arduino package.

If you want to debug anything other than the standard Arduino boards, then you also have to download the respective core. For instance, if you want to debug classic ATtinys, you need to install ATTinyCore. If you want to debug MCUs from the ATmegaX8 family, you need to install MiniCore.

The first important change for making debugging possible is that you enable the IDE/CLI to export ELF files from your projects. These are machine code files that contain machine-readable symbols and line number information. You can enforce the export of ELF files by adding a file platform.local.txt to one of the platform specification folders, which needs to contains the following lines:

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}"
These three lines make sure that you receive an ELF file in your sketch directory when you select Export compiled Binary under the menu Sketch. When you are working with the CLI, then the -e option will do it.

The file with the three lines (which you find in the example/configuration-files repository folder) needs to be copied to the following Arduino folder (NAME being the name of the core, VERSIONNUMBER being the version of the core):

  • 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

Note that for ATtinyCore starting with version 2.0.0, you do not need to do this since this core already exports the ELF files.

Because of the compiler optimization level that is used by the Arduino IDE, the machine code produced by the compiler does not follow straightforwardly your source code. For this reason, it is advisable to use the optimization flag -Og (compile in a debugging friendly way) instead of the default optimization flag -Os (optimize to minimize space). If you are using the console line interface arduino-cli, then this is easily achieved by adding the following option to the arduino-cli compile command:

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

I noticed recently, that another optimization setting can significantly impact your "debugging experience." Usually, link-time optimization is enabled in the Arduino IDE, which has the effect that most information about class inheritance and object attributes vanishes. So, if you want to debug object-oriented code, then it makes sense to disable this kind optimization and add the flag -fno-lto.

So, what can you do, if you do not want to use the CLI interface, but the IDE? You can modify the boards.txt file (residing in the same directory as the platform.local.txt file) and introduce for each type of MCU a new menu entry debug that when enabled adds the build option -Og.

In order to simplify life for you, I have provided a Python script (in the folder examples/configuration-files) called debugadd.py. Copy this script into the same folder as where you copied platform.local.txt, change into this folder, and execute the script by calling python3 debugadd.py. This will add a menu entry for each listed MCU/board in the boards.txt file. Again, for ATtinyCore starting with version 2.0.0, this is not necessary because you already can choose the optimization options.

Now you have to restart the Arduino IDE. If you select an menu entry in the boards menu, then you will notice that there is a new menu option Debug.

Finally, you need to install avr-gdb, the AVR version of the GDB debugger. It used to be part of the Arduino IDE toolchain. However, it is not any longer. Depending on your OS, you can get the executable as follows:

  • macOS: Use homebrew to install it.
  • Linux: Install avr-gdb with your favorite packet manager.
  • Windows: You can download a complete AVR-GCC toolchain including the debugger avr-gdb from Microchip or from Zak's Electronic Blog~*.

Setting Up the Hardware

The software is now in place. What remains to be done is to connect the hardware debugger to the host and to the target. The connection to the host is straight-forward, because we just need to connect the two using a USB cable. When you want to start the debugging session, you will need the name of the serial port. However, I assume you are able to figure that out for yourself. The connection to the target is a bit more involved.

Requirements on the RESET Line

Since the RESET line of the target system is used as an open-drain, asynchronous, half-duplex serial communication line, one has to make sure that there is no capacitive load on the line when it is used in debugWIRE mode. Further, there should be a pull-up resistor of around 10 kΩ. According to reports of other people, 4.7 kΩ might also work. And the RESET line should, of course, not be directly connected to Vcc and there should not be any external reset sources on the RESET line.

If your target system is an Arduino Uno, you have to be aware that there is a capacitor between the RESET pin of the ATmega328 and the DTR pin of the serial chip, which implements the auto-reset feature. This is used by the Arduino IDE to issue a reset pulse in order to start the bootloader. One can disconnect the capacitor and auto-reset-line by cutting a solder bridge labeled *RESET EN* on the board (see picture), but then you cannot use the automatic reset feature of the Arduino IDE any longer.

Image 2

A recovery method may be to either put a bit of soldering on the bridge or better to solder two pins on the board and use a jumper. Alternatively, you could always manually reset the Uno before the Arduino IDE attempts to upload a sketch. The trick is to release the reset button just when the compilation process has finished.

Other Arduino boards, such as the Nano, are a bit harder to modify, while a Pro Mini, for example, can be used without a problem, provided the DTR line of the FTDI connector is not connected. In general, it is a good idea to get hold of a schematic of the board you are going to debug. Then it is easy to find out what is connected to the RESET line, and what needs to be removed. It is probably also a good idea to check the value of the pull-up resistor, if present.

So, what is the worst-case scenario when using debugWIRE? It could happen that you successfully bring your target chip into debugWIRE mode using ISP programming (see below), but then you cannot communicate over the RESET line. In particular, you may not be able to put the target back in a state, in which ISP programming is possible. Your MCU is bricked. It still works with the firmware programmed last time. However, the only way to reset the MCU is now to power-cycle it. Further, it is impossible to reprogram it using ISP programming.

There are two ways out. First you can try to make the RESET line compliant with the debugWIRE requirements. Then you should be able to connect to the target using the hardware debugger. Second, you can use high-voltage programming, where 12 volt have to be applied to the RESET pin. So you either remove the chip from the board and do the programming offline or you remove any connection from the RESET line to the Vcc rail and other components on the board. Then you can use either an existing high-voltage programmer or you build one on a breadboard.

Connecting the Debugger to the Target

I use an ATtiny85 on a breadboard as the example target system and an Uno as the example debugger. However, any MCU that supports debugWIRE would do as a target. And one could even use an Arduino Uno as a target, provided the modifications described in the previous subsection are done.

Image 3

First of all, notice the capacitor of 10 µF or more between RESET and GND on the Uno board. This will disable auto-reset of the Uno board. This is optional and is helpful to speed up the connection process to the host.

Second, note the LED and resistor plugged in to pin 7 and 6. This is the system LED which is used to visualise the internal state of the debugger. Again, this is optional, but very helpful. There are four states the debugger can be in, and each one is signaled by a different blink pattern of the system LED:

  • not connected (LED is off),
  • waiting for power-cycling the target (LED flashes every second for 0.1 sec),
  • target is connected (LED is on),
  • error state, i.e., not possible to connect to target or internal error (LED blinks furiously every 0.1 sec).

Third, as you can see, the Vcc rail of the breadboard is connected to pin D9 of the Arduino Uno so that it will be able to power-cycle the target chip. Note that powering the target system through a GPIO of the debugger works only as long as the target system needs 20 mA or less. Otherwise, you need to power the target externally and then power-cycle the system manually, when that becomes necessary.

Furthermore, pin D8 of the Arduino Uno is connected to the RESET pin of the ATtiny (pin 1). Note the presence of the pull-up resistor of 10kΩ on the ATtiny RESET pin. The remaining connections between Arduino Uno and ATtiny are MOSI (Arduino Uno D11), MISO (Arduino Uno D12) and SCK (Arduino Uno D13), which you need for ISP programming. In addition, there is a LED connected to pin 3 of the ATtiny chip (which is PB4 or pin D4 in Arduino terminology). The pinout of the ATtiny85 is given in the next figure (with the usual "counter-clockwise" numbering of Arduino pins).

Image 4

Here is a table of all the connections so that you can check that you have made all the connections.

ATtiny pin# Arduino Uno pin Component
1 (Reset) D8 10k resistor to Vcc
2 (D3)    
3 (D4)   220 Ω resistor to LED (+)
4 (GND) GND LED (-), decoupling cap 100 nF, blocking cap of 10µF (-),
5 (D0, MOSI) D11  
6 (D1, MISO) D12  
7 (D2, SCK) D13  
8 (Vcc) D9 10k resistor, decoupling cap 100 nF
  RESET blocking cap of 10 µF (+)
  D7 system LED (+)
  D6 200 Ω to system LED (-)

If instead of an ATtiny85, you want to debug an Uno board, everything said above applies here as well. A Fritzing sketch showing you the connections is below. Remember to cut the `RESET EN` solder bridge on the target board as described above.

 

Image 5

Note that sourcing the target board from an ATmega pin might stretches the limits a bit. In fact the voltage goes down to 4V. You can also connect the supply pin of the target board to the 5V on the debbugger board and do the power-cycling manually when the systen LED signals that.

Finally, I should mention that you may run into problems when you try to connect to the Uno target for the first time. In this case, most probably, some of the lock bits of the target MCU are set and it is not possible to debug the target. You need to erase the entire chip in order to clear the lock bits. This can be done by using an ISP programmer,  by issuing the GDB command monitor erase, or, when using PlatformIO, by selecting the custom task Erase Chip.

Debugging the Sketch

After having installed the software and prepared the hardware, we are now ready to start a debugging session.

Debugging under PlatformIO

Debugging with PlatformIO is very straight-forward. After making the project you want to debug the current project, you simply have to select the Debug icon on the left.

Image 6

This will reconfigure the GUI to a debugging configuration, showing a number of different information panes. When you now click on the green triangle labelled PIO Debug, a debug session will be started. This will compile the project and start the debugger. The target will be switched into debugWIRE mode, either by power-cycling automatically or by requesting (by the system LED blink pattern and a message in the Debug Console) you to power-cycle. If the MCU was already in debugWIRE mode, perhaps from a previous debug session, no power-cycling will be necessary.

Image 7

If the debug session is not started, then you should see an error message in the DEBUG CONSOLE. One possible error message is Cannot Connect: Lock bits are set. This happens in particular, when you try to debug a chip that contains a boot loader for the first time. You can clear the lock bits by erasing the entire chip. Select Erase Chip under the Custom PROJECT TASKS in the debug environment. After that, you can try to start the debugger again (after resetting the hardware debugger).

Image 8

If all went well with starting the debugger, the program will be uploaded and started. The initial stop is in the main routine (signaled by the yellow triangle to the left of the line numbers). You can now use the execution control panel in the upper right corner to start (and stop) execution. Or you can single-step, either not stepping into function calls (step-over) or stepping into the function (step-in). The upward arrow initiates a step-out operation, i.e, execution is continued until the current function is finished. The green circle initiates a restart, and the red square terminates the debug session.

Image 9

You can now single-step into the loop function, or you may also reload the varblink.ino file into the editor window. There you may want to set breakpoints, i.e., points where execution shall be stopped. This can be done by clicking in the empty column to the left of the line numbers. That will place a read dot, at which execution will be stopped.

There are now a lot of things you could do to inspect the inner workings of your programs and you may even change values of your variables by typing the respective GDB command into the input line of the displayed Debug Console. A good introduction to debugging with PlatformIO has been written up Valerii Koval: Debugging with PlatformIO, Part I and Part II.

Image 10

If you want to bring back the MCU into normal state, you can type monitor dwoff into the input line of the Debug Console during a debugging session. Alternatively, when the debugging session is not active any longer, you can first click on the PlatformIO symbol on the left (the ant), and then select under Program Tasks the Debug/Custom category, where you will find the entry DebugWIRE Disable. Clicking on that will disable the debugWIRE mode.

Image 11

Debugging with avr-gdb

Let us now start a debug session using only avr-gdb. So compile the example varblink.ino with debugging enabled and require the binary files to be exported, which gives you the file varblink.ino.elf in the sketch directory. Then connect your Uno to the host and start avr-gdb. All the lines starting with either the > or the (gdb) prompt contain user input and everything after # is a comment. <serial port> is the serial port you use to communicate with the Uno.

Shell
> 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
>

If something did not work out, you may want to consult the troubleshooting guide in the dw-link manual.

Note that the MCU is still in debugWIRE mode and the RESET pin cannot be used to reset the chip. If you want to bring the MCU back to the normal state, you need to execute avr-gdb again.

Shell
> 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
>

Of course, you could have done that before leaving the debug session above.

You have seen a number of different GDB commands already in the above example, but there are tons more. A good reference card can be found online at the GDB documentation website, which contains also an extensive manual. I also recommend the tips on using GDB for embedded debugging written up by Jay Carlson.

Summary

So far, embedded debugging of classic ATtiny and small ATmega MCUs was possible only with proprietary soft- and hardware or it had a lot of limitations. With the recently developed open-source firmware dw-link, it is possible to turn an Arduino Uno (or similar board) into a hardware debugger, which translates between debugWIRE and GDB RSP. In other words, now one can use avr-gdb in order to debug MCUs that support debugWIRE. In this article, it is outlined how to install dw-link on an Arduino board, to set up a debugging environment, to connect the hardware debugger to the target and the host, and to perform a debugging session. Hopefully, this will make debugging Arduino sketches for these MCUs much more enjoyable.

History

  • 19th January, 2022: Provided updated source code (1.3.2) and corrected description of blinking patterns in order to fix a problem caused by a timeout when reading a reply from the target
  • 2nd February, 2022: Added Fritzing sketch for connecting an Uno as the target, updated description of how to modify Arduino configuration files, and updated source code archive
  • 12th March, 2022: Added explanation concerning lock bits and how to deal with them, and updated source code

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

Share

About the Author

Bernhard Nebel
Retired
Germany Germany
Arduino addict

Comments and Discussions

 
QuestionMessage Closed Pin
10-Feb-22 12:32
MemberMember 1553115110-Feb-22 12:32 
GeneralMessage Closed Pin
10-Feb-22 12:31
MemberMember 1553115110-Feb-22 12:31 
QuestionMessage Closed Pin
10-Feb-22 12:30
MemberMember 1553115110-Feb-22 12:30 
QuestionMessage Closed Pin
10-Feb-22 12:30
MemberMember 1553115110-Feb-22 12:30 
QuestionGreat. Thank you very much! Pin
martin@pischky.de4-Feb-22 0:41
Membermartin@pischky.de4-Feb-22 0:41 
Questionexcellent! Pin
Southmountain3-Feb-22 16:09
MemberSouthmountain3-Feb-22 16:09 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA23-Jan-22 20:26
professionalȘtefan-Mihai MOGA23-Jan-22 20:26 
Questionvery interesting and nice - what about UPDI for avr2 chips? Pin
7alken23-Jan-22 4:32
Member7alken23-Jan-22 4:32 
AnswerRe: very interesting and nice - what about UPDI for avr2 chips? Pin
Bernhard Nebel23-Jan-22 8:23
MemberBernhard Nebel23-Jan-22 8:23 
GeneralRe: very interesting and nice - what about UPDI for avr2 chips? Pin
7alken23-Jan-22 16:01
Member7alken23-Jan-22 16:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.