Background
Before we start the excursion into the world of debugging, some background on the debugWIRE interface is provided, sketching the physical properties of the debugWIRE protocol. Then we survey other open-source approaches to debugging classic AVR MCUs
With that out of the way, we have a look at what we need on the hardware side. You need an Arduino UNO (or something equivalent) as the hardware debugger and a chip or board that understands debugWIRE, i.e., a classic ATtiny or an ATmegaX8. Then, you only have to install the debugger firmware on the UNO and set up the hardware for a debugging session. Finally, you need to install a debugging environment and then you can start debugging.
If you have performed all the above steps, then the setup should look like as in the following picture.
Your development machine, the host, is connected to the UNO acting as a hardware debugger over the usual USB connection. The two devices use the GDB remote serial protocol to communicate with each other. The hardware debugger in turn is connected to the target system, whereby the debugWIRE protocol is used for communication.
The physical connection between the hardware debugger and the target, as described in the section about the hardware setup, is something that might need some enhancements. Instead of six jumper wires, you may want to have a more durable connection. This is covered in the part about a better hardware debugger. Finally, possible problems and troubleshooting is covered.
And what do you with your hardware debugger once you have debugged all your programs and they work flawlessly? Since version 2.2.0, you can use dw-link also as an STK500 v1 ISP programmer. If you connect to dw-link with 19200 bps and start avrdude, then dw-link becomes an ISP programmer.
The debugWIRE interface
The basic idea of debugWIRE is that the RESET line is used as a communication line between the target system (the system you want to debug) and the hardware debugger, which in turn can then communicate with the development machine or host, which runs a debug program such as GDB. 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.
With respect to the debugWIRE protocol there are basically three states your 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, 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 the transitional state, from which one can reach the normal state using ordinary ISP programming by disabling the DWEN fuse.
The hardware debugger will take care of bringing you from normal state to debugWIRE state when you type the command monitor debugwire enable
. In fact, when using the Arduino IDE 2, this will be done in the background for you. After the hardware debugger has enabled the DWEN fuse, the system LED will flash in a particular pattern, which signals that you should power-cycle the target. Further, in the GDB debugger, a message will be shown that asks you to power-cycle the target system. If the hardware debugger powers the target, it will power-cycle automatically. This transition is only necessary once. The next time, when you start a debugging session, the target system is already in the debugWIRE state and nothing needs to be done.
When you are done with debugging and you want to get the target system back into the normal state, you must type the command monitor debugwire disable
just before exiting the debugger.
Other debugging approaches for classic ATtinys and ATmegaX8s
While dw-link is (unsurprisingly) one of my preferred open source solution for debugging classic tiny AVRs and ATmegaX8s, there are a number of other possible solutions.
Bloom is not a hardware debugger, but it is a pretty extensive implementation of a gdbserver for almost all AVR MCUs using the Microchip hardware debuggers. The only drawback is that it runs only under Linux. Similarly, avarice is another such gdbserver. Recently, I added another gdbserver to the mix, which is written in Python: dw-gdbserver.
There exists a software simulator called SIMAVR and there is a GDB remote stub for some ATmegas, called avr_debug. Both are integrated into PlatformIO as debuggers. However, both tools come with a lot of restrictions and using them is not the same as debugging on the hardware where your firmware should finally run.
Based on RikusW's work on reverse engineering the debugWIRE protocol, you can find a few attempts at building debuggers using debugWIRE. First, there is an implementation called dwire-debug for host systems that uses only the serial line interface to talk with a target using the debugWIRE interface. This program implements GDB's remote serial protocol. Unfortunately, the particular way of turning a serial interface into a one-wire interface did not work for me on a Mac. This approach has been further developed, resulting in an interesting solution for debugging Arduino UNOs using a CH552 board.
Then there is also an Arduino UNO based hardware debugger called DebugWireDebuggerProgrammer. However, it does not provide an interface for GDB's remote serial protocol. On top of that, all these solutions allow only one breakpoint (the hardware breakpoint of debugWIRE).
There exists an implementation similar to dwire-debug in Pascal called debugwire-gdb-bridge that appears to be more complete. In particular, it handles multiple breakpoints. However, I was not able to install it. That is probably based on the fact that my knowledge of Pascal is rusty and I have no experience with the Lazarus IDE.
I took all of the above ideas (and some of the code) and put it together in order to come up with a cheap debugWIRE hardware debugger supporting GDB's remote serial protocol. Actually, it was a bit more than just throwing things together. I developed a new library for single wire serial communication that is much more reliable and robust than the usually employed SoftwareSerial library. Further, I fixed a few loose ends in the existing implementations, sped up communication and flash programming, supported slow MCU clocks, implemented an interrupt-safe way of single-stepping, and spent a few nights debugging the debugger. And I tested the debugger on almost all MCUs supported by ATTinyCore, MicroCore, and MiniCore. Along the way, I also made a number of interesting discoveries.