“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.
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.
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:
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.
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.
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.”