Manual
Manual
This work is licensed under a Creative Commons Attribution 4.0 International License.
dw-link
An Arduino-based debugWIRE debugger
1. Introduction
1.1 Enter the wonderful world of debugging in a few easy steps
1.2 Other debugging approaches for classic ATtinys and ATmegaX8s
Warning
2. The debugWIRE interface
3. Hardware requirements
3.1 The hardware debugger
3.2 MCUs with debugWIRE interface
3.3 Requirements concerning the RESET line of the target system
3.4 Worst-case scenario
4. Installation of firmware and hardware setup
4.1 Firmware installation
4.2 Setting up the hardware
4.2.1 Debugging an ATtiny85
4.2.2 Debugging an UNO
4.2.3 Debugging an UNO that needs a serial connection to the host
4.3 States of the hardware debugger
5. Arduino IDE and avr-gdb
5.1 Installing avr-gdb
5.2 Installing board manager files
5.3 Compiling the sketch
5.4 Example session with avr-gdb
5.5 Disabling debugWIRE mode explicitly
5.6 GDB commands
5.7 A graphical user interface: Gede
6 PlatformIO
6.1 Installing PlatformIO
6.2 Open an existing project
6.2 Adapting the platformio.ini file
6.3 Debugging with PlatformIO
6.4 Disabling debugWIRE mode
6.5 Configuring platformio.ini
7. A "real" hardware debugger
7.1 The basic solution
7.2 A simple shield
7.3 Adapter with level-shifters and switchable power supply: dw-link probe
8. Problems and shortcomings
8.1 Flash memory wear
8.2 Slow responses when loading or single-stepping
8.3 Program execution is very slow when conditional breakpoints are present
8.4 Single-stepping and interrupt handling clash
8.5 Limited number of breakpoints
8.6 Power saving is not operational
8.7 MCU operations interfering with debugWIRE
8.8 BREAK instructions in your program
8.9 Some MCUs have stuck-at-one bits in the program counter
8.10 The start of the debugger takes two seconds
8.11 Code optimization reorganizes code and makes it impossible to stop at a particular source line or to
inspect or change values of local variables
9 Troubleshooting
Problem: It is impossible to upload the dw-link firmware to the UNO board
Problem: After debugging, the chip is unresponsive, i.e., does not respond anymore to ISP programming or
bootloader upload
Problem: After changing optimization options, the binary is still too large/very small
Problem: When starting the debug session in PlatformIO, you get the message pioinit:XX: Error in sourced
command file
Problem: When connecting to the target using the target remote command, it takes a long time and then
you get the message Remote replied unexpectedly to 'vMustReplyEmpty': timeout
Problem: When connecting to the target using the target remote command, you do not get an error
message, but the system LED is still off and there is apparently no connection
Problem: In response to the monitor dwire + command, you get the error message Cannot connect: ...
Problem: You receive the message Protocol error with Rcmd
Problem: You get the message Connection to target lost, the program receives a SIGHUP signal when you try
to start execution, and/or the system LED is off
Problem: When trying to start execution with the run command, GDB stops with an internal error
Problem: When stopping the program with Ctrl-C (or with the stop button), you get the message Cannot
remove breakpoints because program is no longer writable.
Problem: The debugger responses are very sluggish
Problem: The debugger does not start execution when you request single-stepping or execution and you get
the warning Cannot insert breakpoint ... Command aborted
Problem: When single stepping with next or step , you receive the message Warning: Cannot insert
breakpoint 0 and the program is stopped at a strange location
Problem: While single-stepping, time seems to be frozen, i.e., the timers do not advance and no timer
interrupt is raised
Problem: When single stepping with next or step , the program ends up at the start of flash memory,
e.g., 0x0030
Problem: The debugger does not start execution when you request single-stepping or execution, you get the
message illegal instruction, and the program receives a SIGILL signal
Problem: After requesting to stop at a function, the debugger displays a completely different file, where the
execution will stop
Problem: The debugger does not stop at the line a breakpoint was set
Problem: The debugger does things that appear to be strange
Problem: After a reset (external, power-up, or WDT) of the target, it behaves strangely
Problem: You have set the value of a local variable using the set var <var>=<value> command, but the
value is still unchanged when you inspect the variable using the print command
Problem: The system LED blinks furiously and/or the program receives an ABORT signal when trying to
start execution
Acknowledgements
Revision history
V 1.1
V 1.2
V 1.3
V 1.4
V 1.5
V 1.6
V 1.7
V 1.8
V 1.9
V 1.10
V 1.11
V 2.0
V 3.0
1. Introduction
The Arduino IDE is very simple and makes it easy to get started. After a while, however, one notices that a
lot of important features are missing. In particular, neither the old nor the new IDE supports any kind of
debugging for the classic AVR chips. So what can you do when you want to debug your Arduino project on
small ATmegas (such as the popular ATmega328) or ATtinys? The usual way is to insert print statements and
see whether the program does the things it is supposed to do. However, supposedly one should be able to
do better than that because the above-mentioned MCUs support on-chip debugging via debugWIRE.
When you want hardware debugging support, you could buy expensive hardware-debuggers such as the
Atmel-ICE or the MPLAB Snap and you have to use the propriatery development IDE Microchip Studio (for
Windows) or MPLAB X IDE (for all platforms). There is also AVaRICE, but I was never able to get a version that
worked on my Mac.
The question is, of course, whether there are open-source alternatives. Preferably supporting avr-gdb, the
GNU debugger for AVR MCUs. With dw-link, you have such a solution. It turns an Arduino UNO into a
hardware debugger that acts as a gdbserver by implementing the GDB remote serial protocol. Meanwhile,
you can buy an UNO shield called dw-link probe at Tindie, which simplifies the hardware setup, allows to
debug 5 and 3.3 Volt systems, and is able to provide up to 300 mA supply current.
Finally, you need to install a debugging environment. I will describe two approaches. The first one, covered
in Section 5, is the easiest one. In addition to installing new board definition files, it requires you to
download avr-gdb. Debugging will then take place in a shell window, in which you have to start avr-gdb. For
people unhappy with command line interfaces, Section 5.7 covers how to install and use a graphical user
interface (which works only for macOS and Linux, though). The second method, described in Section 6,
involves downloading the PlatformIO IDE, setting up a project, and starting your first debug session with this
IDE.
There are numerous other possibilities, which you might try out. In the guide to debugging with avr_debug,
there is an extensive description of how to setup Eclipse for debugging with avr_debug, which applies to dw-
link as well. Another option may be Emacs.
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 Section 4.2, is
something that might need some enhancements. Instead of six flying wires, you may want to have a more
durable connection. This is covered in Section 7. Finally, possible problems and troubleshooting are covered
in Section 8 and Section 9, respectively.
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 either with 19200 or 115200 bps and start avrdude, then dw-link turns into an ISP programmer.
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 of all, there is an implementation called dwire-debug for host
systems that use just the serial 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 (most probably, the large latency of my FTDI adapter was
the culprit). This approach has been further developed resulting in an interesting solution for debugging
Arduino UNOs using a CH552 board. Finally, there exists 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 for one breakpoint.
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 the 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 (4 kHz), implemented an interrupt-safe way of single-stepping, and spent a few nights
debugging the debugger. Along the way, I also made a number of interesting discoveries. And I tested the
debugger on almost all MCUs supported by ATTinyCore and MiniCore.
Warning
Read Sections 3.3 & 3.4 about the requirements on the RESET line carefully before trying to debug a target
system. You might very well "brick" your MCU by enabling debugWIRE on a system that does not satisfy
these requirements.
With respect to the debugWIRE protocol there are basically three states your 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, 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.
The hardware debugger will take care of bringing you from normal state to debugWIRE state when you
connect to the target by using the target remote command or when using the monitor dwire +
command. The system LED will flash in a particular pattern, which signals that you should power-cycle the
target. Alternatively, if the target is powered by the hardware debugger, it will power-cycle automatically.
The transition from debugWIRE state to normal state will take place when you terminate GDB. It can also be
achieved by the GDB command monitor dwire - . If things seem to have not worked out, you can simply
reconnect the target to the hardware debugger and issue monitor dwire - again.
Since Version 3.4.0, you can disable the automatic switching to and from debugWIRE state by connection pin
D3 to ground. On the dw-link probe, you can set a jumper. If the automatism is disabled, only the monitor
wire commands change the state.
3. Hardware requirements
There are a few constraints on what kind of board you can use as the base for the hardware debugger and
some requirements on how to connect the debugger to the target system. Furthermore, there is only a
limited set of AVR MCUs that can be debugged using debugWIRE.
Arduino UNO,
Arduino Nano,
If you intend to use dw-link on a board with an MCU different from ATmega328P, you should be aware that
dw-link makes heavy use of the particular hardware features of the ATmega328P and operates close to the
limit. I tried it out on the Leonardo and on the Mega256, but was not successful.
The most basic setup is to use the UNO board and connect the cables as it is shown in the Fritzing sketch
further down. If you want to use the debugger more than once, it may pay off to use a prototype shield and
put an ISP socket on it. The more luxurious solution is a shield for the UNO as described further down in
Section 7.
3.2 MCUs with debugWIRE interface
In general, almost all "classic" ATtiny MCUs and some ATmega MCUs have the debugWIRE interface.
Specifically, the following MCUs that are supported by the Arduino standard core, by ATTinyCore, and/or by
MiniCore can be debugged using this interface:
ATtiny13
ATtiny43U
ATtiny2313(A), ATtiny4313
ATtiny441, ATtiny841
ATtiny87, ATtiny167
ATtiny828
ATtiny48, ATtiny88
ATtiny1634
I have tested the debugger on MCUs marked bold. The untested PB types appear to be very very difficult to
get. I excluded the ATtiny13 because it behaved very strangely and I was not able to figure out why. The two
ATmegas that are stroked out have program counters with some bits stuck at one (see Section 8.9). For this
reason, GDB has problems debugging them and dw-link rejects these MCUs.
Additionally, there exist a few more exotic MCUs, which also have the debugWIRE interface:
AT90USB82, AT90USB162
AT90PWM81, AT90PWM161
AT90PWM216, AT90PWM316
The debugger contains code for supporting all listed MCUs except for the ones stroked out, which are
obsolete. I expect the debugger to work on the supported MCUs. However, there are always surprises. If
you can debug such an MCU using dw-link, please drop me a note.
3.3 Requirements concerning the RESET line of the target system
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 to start the bootloader. One can disconnect the capacitor by
cutting the 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.
Other Arduino boards, such as the Nano, are a bit harder to modify. A Pro Mini, on the other hand, 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.
The most reliable way to resurrect you MCU is high-voltage programming, where 12 volt have to be applied
to the RESET pin, and a lot of other things have to happen. 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.
Second, you need to download the dw-link firmware somewhere, where the IDE is able to find the Arduino
sketch. If you use PlatformIO, note that the repository is already prepared to be opened as a PlatformIO
project, i.e., it contains a platformio.ini file. Either download the dw-link repository or download a
release.
Third, you have to connect your future hardware debugger, i.e., the ATmega328 board, to your computer,
select the right board in the Arduino IDE, and upload the dw-link.ino sketch to the board. Similarly, in
PlatformIO, you have to choose the right board and choose the Upload menu entry.
Usually, it should not be necessary to change a compile-time constant in dw-link. I will nevertheless
document all these constants here. If you want to change one of them, you can do that when using
arduino-cli by using the --build-property option or by changing the value in the source code.
Name Default Meaning
current
Current version number of dw-link; should not be changed, except
VERSION version
when one generates a new version
number
In order to debug an ATtiny85, we will assume it is completely "naked" and plugged into a breadboard as
shown below.
First of all, notice the capacitor of 10 µF or more between RESET and GND on the UNO board. This will
disable the auto-reset of the UNO board. Second, note the yellow LED plugged into pin D7. This is the
system LED which is used to visualise the internal state of the debugger (see below). You can also build an
LED with a series resistor soldered on and then use pins D6 and D7, where D6 is used as GND.
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. 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 an 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).
Here is a table of all the connections so that you can check that you have made all the connections.
2 (D3)
both LED (-), decoupling cap 100 nF, RESET blocking cap of 10µF
4 (GND) GND
(-)
5 (D0,
D11
MOSI)
6 (D1,
D12
MISO)
If instead of an ATtiny85, you want to debug a UNO board, everything said above applies here as well. The
Fritzing sketch below shows you the connections. Here, the series resistor for the system LED is soldered to
the LED cathode so that we do not need a breadboard at all. The hardware debugger needs a USB
connection to your host, but the target should not be connected by a USB cable to the host! Otherwise, the
automatic power-cycling will not work (but see below).
Remember to cut the RESET EN solder bridge on the target board (see Section 3.3)! When you first establish
a connection with the UNO as a target, the target will be completely erased (including the boot loader),
because the lock bits have to be cleared.
When after a debugging session you want to restore the target so that it behaves again like an ordinary
UNO, you have to execute the following steps:
1. Exit the debugWIRE state as described in Section 2. This should have happened automatically when last
quitting GDB. However, to make sure the UNO is not in debugWIRE state, you should use the method
described in Section 5.5 or Section 6.4.
3. Burn the bootloader into the UNO again. This means that you need to select the Arduino UNO again as
the target board in the Tools menu, select AVR ISP as the Programmer , and choose Burn
Bootloader from the Tools menu. As mentioned in Section 1, since version 2.2.0, the hardware
debugger can also act as a programmer!
If you want to debug an Arduino UNO that needs a serial connection to the host, special considerations are
necessary. You may just plug in a USB cable into the target and open up a terminal connection to the target,
using e.g., cu, screen, PuTTY, HTerm, or any other program that can establish a serial connection. The
disadvantage is that now automatic power-cycling does not work any longer. So, when the system LED
signals that you should power-cycle (0.1 sec flash every second), you need to disconnect and reconnect the
USB-cable to the target.
A more elegant solution is to use a USB-to-serial converter and plug the TX, RX, and GND connections into
the appropriate sockets on the target. Or you can use a USB power blocker, something you can also find on
Amazon under the name PortaPow USB Power Blocker. Such a blocker cuts off the power line so that
automatic power-cycling can work its magic.
waiting for power-cycling the target (LED flashes every second for 0.1 sec),
error, i.e., it is not possible to connect to the target or there is an internal error (LED blinks furiously
every 0.1 sec).
If the hardware debugger is in the error state, one should try to find out the reason by typing the command
monitor lasterror , studying the error message table at the end of the document, finishing the GDB
session, resetting the debugger, and restarting everything. If the problem persists, please check the section
on troubleshooting.
Linux: Use your favorite packet manager to install the package gdb-avr, i.e., under Ubuntu/Debian:
Windows: You can download the AVR-toolchain from the Microchip website or Zak's Electronic Blog~*](
https://blog.zakkemble.net/avr-gcc-builds/). This includes avr-gdb. You have to copy avr-gdb.exe
(which you find in the bin folder) to some place (e.g., to C:\ProgramFiles\bin) and set the PATH
variable to point to this folder. Afterward, you can execute the debugger by simply typing avr-
gdb.exe into a terminal window (e.g. Windows Powershell).
https://felias-fogg.github.io/MiniCore/package_MCUdude_MiniCore_index.json
After that, you can download and install the board using the Boards Manager , which you find in the
Arduino IDE menu under Tools --> Board . Currently, choose the versions that have a +debug suffix in their
version number! I hope the capability of generating debug-friendly binaries will be incorporated in future
versions of these board manager files, in which case you can rely on the regular board manager files by
MCUdude and SpenceKonde.
Then set additional parameters for the board. Most importantly, you need to select Debug for the Debug
Compiler Flags option. The other possibilities for this option can be chosen when the debugger should
create code that more closely mirrors the source code, as described in Section 8.11. Now compile the
example varblink.ino with debugging enabled by requesting to Export compiled Binary in the Sketch
menu. This will generate the file varblink.ino.elf in the sketch or build directory, depending on which
version of the IDE or CLI you are using.
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 hardware debugger and <bps>
is the baud rate, i.e., 115200, provided you have not changed the compile-time constant HOSTBPS .
Quit anyway? (y or n) y
>
> avr-gdb
GNU gdb (GDB) 10.1
...
step [number] single step statement, descending into functions (step in), number times
next [number] single step statement without descending into functions (step over)
finish finish current function and return from call (step out)
continue from current position and stop after number breakpoints have
continue [number]
been hit.
break function |
set breakpoint at beginning of function or at line number in file
[file:]number
In order to display information about the program and variables in it, the following commands are helpful.
Further, you may want to change the value of variables.
command action
list [function | show source code around current point, of function, or around line
[filename:]number] number in filename
display[/f] expression display expression using format f each time the program halts
delete display [number ...] delete auto-display commands(s) or all auto-display commands
In addition to the commands above, you have to know a few more commands that control the execution of
avr-gdb.
command action
set serial set baud rate of serial port to the hardware debugger (same as using the -b option
baud when starting avr-gdb); only effective when called before establishing a connection with
number the target command
target establish a connection to the hardware debugger via serialport, which in turn will set up
[extended- a connection to the target via debugWIRE; if extended is used, then establish a
]remote connection in the extended remote mode, i.e., one can restart the program using the run
serialport command
file
load the symbol table from the specified ELF file
name.elf
load the ELF file into flash memory (should be done every time after the target
load remote command; it will only change the parts of the flash memory that needs to be
changed)
Finally, there are commands that control the settings of the debugger and the MCU, which are particular to
dw-link. They all start with the keyword monitor . You can abbreviate all keywords to 2 characters if this is
unambiguous.
command action
monitor
print version number of firmware
version
monitor + activate debugWIRE; - disables debugWIRE; without any argument, it will report
dwire [+|-] MCU type and whether debugWIRE is enabled (*)
monitor ckdiv 1 unprograms the CKDIV8 fuse, 8 programs it; without an argument, the state of the
[1|8] fuse is reported (*)
monitor
set clock source to rc osc., alternate rc osc., xtal, external osc., or slow osc. (128 kHz);
oscillator
without argument, it reports the fuse setting (*)
[r|a|x|e|s]
monitor
set number of allowed breakpoints to 1, when hardware breakpoint only, or 25,
breakpoint
when also software breakpoints are permitted; without argument it reports setting
[h|s]
monitor set communication speed limit to low (=150kbps) or to high (=300kbps); without an
speed [l|h] argument, the current communication speed and limit is printed
monitor
Sets single stepping to safe (no interrupts) or unsafe (interrupts can happen);
singlestep
without an argument, it reports the state
[s|u]
monitor
print error number of last fatal error
lasterror
monitor
reports on how many flash-page write operation have taken place since start
flashcount
monitor
report number of timeouts (should be 0!)
timeouts
The dw-server directory of the dw-link directory contains a Python script called dw-server.py , which you
should copy to /usr/local/bin .
Open now a terminal window, cd into the folder that contains the ELF file, and type
dw-server.py -g
The script will try to discover a dw-link adapter connected to a serial line. After that, it starts gede, and then
it forwards the serial connection over TCP/IP to gede, which will present you with the following window.
Project dir and Program are specific to your debugging session. The rest should be copied as it is shown.
And with clicking on OK, you start a debugging session. Well, the startup takes a while because the
debugger always loads the object file into memory.
The GUI looks as shown in the next figure. Johan Henriksson, the author of the GUI, has written up two
short tutorials about using it.
6 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 ordinary C++ projects. Projects are highly configurable, that is a lot of parameters can be set for
different purposes. However, that makes things in the beginning a bit more challenging.
1. You do not select the MCU and its parameters using a dropdown menu, but you have to write/modify
the INI-style file platform.ini .
2. Libraries are not global by default, but they are usually local to each project. That means that a new
library version will not break your project, but you have to update library versions for each project
separately.
3. No preprocessor generates function declarations automagically as the Arduino IDE/CLI does for you.
You have to add the include statement for the Arduino header file and all function declarations by
yourself. In addition, you need to import Arduino.h explicitly.
5. Most importantly, the IDE contains ways to configure the debugging interface, which makes it possible
to integrate dw-link easily. Note that this is still not possible for the Arduino IDE 2.X!
So, moving from the Arduino IDE to PlatformIO is a significant step!
On a Mac, unfortunately, it does not work out of the box, because the gcc-toolchain PlatformIO uses is quite
dated, and the included version of avr-gdb is no longer compatible with recent macOS versions. Simply
install avr-gdb with homebrew and copy the file ( /usr/local/bin/avr-gdb ) into the toolchain directory
( ~/.platformio/packages/toolchain-atmelavr/bin/ ).
After that, you have to navigate through your file structure and find the project that you want to open.
Finally, click on Open pio-varblink.
6.2 Adapting the platformio.ini file
PlatformIO will then open the project and come up with platform.ini in the editor window. There it is
obvious that we need to replace the string SERIAL-PORT with the actual port, our hardware debugger is
connected to.
To find out, which port it is, we can ask for the connected serial ports as shown below. Then we can just
copy the name of the port by clicking right on paper icon.
After that, the string can be inserted into the platformio.ini file.
After a brief moment. the debugger will then stop at the breakpoint.
6.4 Disabling debugWIRE mode
There are two ways of switching off the debugWIRE mode. It happens automatically when you terminate the
debugger using the exit button. Alternatively, you should be able to bring back your MCU to the normal
state by typing monitor dwire - in the debugging terminal window after having started a debugging
session in PlatformIO IDE.
You may have noticed that there is an alternate INI file platformio.ini-with-dw-server in the project
folder, where a debug_sever is mentioned:
debug_server = dw-server.py
-p 3333
Instead of communicating directly over the serial line, which implies that one always has to specify the serial
device, which sometimes changes, here a debug server is used, which communicates over a TCP/IP
connection. This server discovers the serial line the hardware debugger is connected to and then provides a
serial-to-TCP/IP bridge. You can use this alternate INI file (by renaming it to platform.ini ), provided the
Python module PySerial is installed and the dw-server.py script (which you find in the dw-server folder) is
stored in an executable path, i.e., in /usr/local/bin on a *nix machine.
When creating new projects, you can take this project folder as a blueprint and modify and extend
platformio.ini according to your needs. You can find an extensive description of how to do that in the
PlatformIO documentation. A very readable introduction to debugging using PlatformIO has been written by
Valerii Koval. It explains the general ideas and all the many ways how to interact with the PlatformIO GUI.
Part II of this introduction covers embedded debugging.
As argued in my blog post on being cheap, with such an ISP cable, we have sort of constructed a hardware
debugger for less than 10 €, which can be considered semi-durable. Just add the optional system LED with
an attached resistor and a capacitor between RESET and GND.
The relevant pins are therefore as defined in the following table.
D13 3 SCK
D12 1 MISO
D11 4 MOSI
D9 2 VTG
D8 5 RESET
GND 6 GND
D7 System LED+
switchable target power supply (supporting power-cycling by the hardware debugger) offering 5-volt
and 3.3-volt supply up to 300 mA,
high-impedance status for the two output signals MOSI and SCK when ISP is inactive.
Such a board does not need to be very complex. The electronic design is minimalistic. It uses just three
MOS-FETs, one LED, one voltage regulator, and some passive components. We need to (conditionally) level-
shift the RESET line in a bidirectional manner and the SPI lines unidirectionally. One needs to shift the MISO
line from 3.3-5 V up to 5 V, and the MOSI and SCK lines from 5 V down to 3.3-5 V. For the former case, no
level shifting is done at all, relying on the fact that the input pins of the hardware debugger recognize a
logical one already at 3.0 V. For the RESET line, which is open drain, we rely on the same fact. This means
that this hardware debugger cannot deal with systems that use a supply voltage of less than 3 V, though.
For downshifting, we use the output pins of the hardware debugger in an open drain configuration and
have pull-up resistors connected to the target supply voltage. These have to be particularly strong because
some possible target boards, e.g., the Arduino UNO, use the SCK line for driving an LED with a series
resistor of 1kΩ. For this reason, we use 680Ω pull-up resistors that guarantee that the signal level is above
3V on the SCK line, when we supply the board with 5V. These pull-ups will be disabled when no ISP
programming is active, giving the target system full control of the two lines. The schematic looks as follows
(thanks to gwideman for the reworked schematic).
The pin mapping is a bit different from the basic design described above. The change from the basic
mapping is controlled by pin D5, which is tied to GND in order to signal that the more complex pin mapping
is used. The additional pins are all in italics.
Arduino pin ISP pin Function
D13 3 SCK
D12 1 MISO
D9 2 VTG
D8 5 RESET
GND 6 GND
D7 System LED+
D5 Sense pin: Connected to GND when a board with level shifter is used
And here is the early breadboard prototype, which worked beautifully. It contains a bug, though. Can you
spot it?
I have turned the modified prototype into a PCB, which you can buy at Tindie.
Before you start, you have to configure three jumpers. Then you are all set and can start debugging.
There is no pull-
A 10 kΩ pull-up resistor is
up resistor
Pullup connected to the RESET line of the
connected to
target
RESET
Atomatic
Automatic transitions to and from
transitions to and
Auto_DW debugWIRE mode is on. This is the
from debugWIRE
default and recommended mode!
mode is off
One perfect way to document a debugger error is to switch on logging and command tracing in the
debugger:
set trace-commands on
set remote debug 1
set logging on
...
set logging off
I have prepared an issue form for you, where I ask for all the information necessary to replicate the error.
Apart from bugs, there are, of course, shortcomings that one cannot avoid. I will present some of them in
the next subsections.
GDB does not pass breakpoint set and breakpoint delete commands from the user to the hardware debugger,
but instead, it sends a list of breakpoint set commands before execution starts. After execution stops, it
sends breakpoint delete commands for all breakpoints. In particular, when thinking about conditional
breakpoints, it becomes clear that GDB may send a large number of breakpoint set and breakpoint delete
commands for one breakpoint during one debug session. Although it is guaranteed that flash memory can
be reprogrammed at least 10,000 times according to the data sheets, this number can easily be reached
even in a few debug sessions, provided there are loops that are often executed and where a conditional
breakpoint has been inserted. Fortunately, the situation is not as bad as it looks since there are many ways
of getting around the need of reprogramming flash memory.
First of all, dw-link leaves the breakpoint in memory, even when GDB requests to remove them. Only when
GDB requests to continue execution, the breakpoints in flash memory are updated. Well, the same happens
before loading program code, detaching, exiting, etc. Assuming that the user does not change breakpoints
too often, this will reduce flash reprogramming significantly.
Second, if there are many breakpoints on the same flash page, then the page is reprogrammed only once
instead of reprogramming it for each breakpoint individually.
Third, when one restarts from a location where a breakpoint has been set, GDB removes this breakpoint
temporarily, single steps to the next instruction, reinserts the breakpoint, and only then continues
execution. This would lead to two reprogramming operations. However, dw-link does not update flash
memory before single-stepping. Instead, if the instruction is a single-word instruction, it loads the original
instruction into the instruction register of the MCU and executes it there.
For two-word instructions (i.e., LDS, STS, JUMP, and CALL), things are a bit more complicated. The Microchip
documents state that one should refrain from inserting breakpoints at double-word instructions, implying
that this would create problems. Indeed, RikusW noted in his reverse engineering notes about debugWIRE:
Seems that its not possible to execute a 32 bit instruction this way.
The Dragon reflash the page to remove the SW BP, SS and then reflash again with the SW BP!!!
I noticed that this is still the case, i.e., MPLAB-X in connection with ATMEL-ICE still reprograms the page twice
for hitting a breakpoint at a two-word instruction. The more sensible solution is to simulate the execution of
these instructions, which is at least as fast and saves two reprogramming operations. And this is what dw-
link does.
Fourth, each MCU contains one hardware breakpoint register, which stops the MCU when the value in the
register equals the program counter. Dw-link uses this for the breakpoint introduced most recently. With
this heuristic, temporary breakpoints (as the ones GDB generates for single-stepping) will always get priority
and more permanent breakpoints set by the user will end up in flash.
Fifth, when reprogramming of a flash page is requested, dw-link first checks whether the identical contents
should be loaded, in which case it does nothing. Further, it checks whether it is possible to achieve the
result by just turning some 1's into 0's. Only if these two things are not possible, the flash page is erased
and reprogrammed. This helps in particular when reloading a file with the GDB load command after only a
few things in the program have been changed.
With all of that in mind, you do not have to worry too much about flash memory wear when debugging. As a
general rule, you should not make massive changes to the breakpoints each time the MCU stops executing.
Finally, Microchip recommends that chips that have been used for debugging using debugWIRE should not
be shipped to customers. Well, I never ship chips to customers anyway.
For the really paranoid, there is the option that permits only one breakpoint, i.e., the hardware breakpoint:
monitor breakpoint h . In this case, one either can set one breakpoint or one can single-step, but not
both. So, if you want to continue after a break by single-stepping, you first have to delete the breakpoint. By
the way, with monitor breakpoint s , one switches back to normal mode, in which 25 (including one
temporary) breakpoints are allowed.
In addition, there is the debugger command monitor flashcount , which returns the number of how many
flash page reprogramming commands have been executed since the debugger had been started. This
includes also the flash reprogramming commands needed when loading code.
Setting the CLKDIV8 fuse can cause connection problems when using debugWIRE. For best results,
leave this fuse un-programmed during debugging.
"Leaving the fuse un-programmed" means that you probably have to change the fuse to be un-
programmed using a fuse-programmer, because the fuse is programmed by default. In order to simplify
life, I added the two commands monitor ckdiv 8 and monitor ckdiv 1 to the hardware debugger that
allows you to change this fuse. monitor ckdiv 8 programs the fuse, i.e., the clock is divided by 8, monitor
ckdiv 1 un-programs this fuse. Using the monitor ckdiv command without an argument reports the
setting of this fuse. In addition to changing the CKDIV8 fuse, you can also change the clock source with
monitor commands, whereby always the slowest startup time is chosen. Be careful about setting it to XTAL
or external clock! Your MCU will get unresponsive if there is no crystal oscillator or external clock,
respectively. Note that after executing these commands, the MCU is reset (and the register values shown by
the GDB register info command are not valid anymore).
With an optimal setting, i.e., 250 kbps for the debugWIRE line and 230400 bps for the host communication
line, loading is done with 500-800 bytes/second. It should be 3-5 KiB/second when the identical file is loaded
again (in which case only a comparison with the already loaded file is performed). For the default setting
(115200bps to host, 125000bps for debugWIRE), it is probably half the speed.
Do not insert breakpoints immediately after an LPM instruction and do not single-step LPM code
Setting the PRSPI bit can disable the clock for the debugWIRE line and should be avoided for this reason.
The latter three situations may lead to problems stopping at the breakpoint or executing the instructions,
respectively.
The list of known issues mentions also the following four potential problems:
Be aware that the On-chip Debug system is disabled when any lock bits are set
The OSCCAL and CLKPR registers should not be changed during a debug session
The CKDIV8 fuse should not be in the programmed state when running off a 128 kHz clock source
The first issue is mitigated by dw-link erasing the chip when lock bits are set. This is not an industrial-
strength solution, but it makes life easier because all UNO boards have their lock bits set initially. So, instead
of explaining that the bits have to be cleared, it is just done automatically. Concerning resets, I had no
problems reconnecting to the target when the target had been stopped asynchronously or by a breakpoint.
The only problem was that the target would not stop at the hardware breakpoint after a reset, since this
hardware breakpoint will be cleared by the reset. So, if you want to be sure to stop after a reset, place two
different breakpoints in the startup routine. Changing the clock frequency is also not a problem since at
each stop the debugger re-synchronizes with the target. Further, changing the supply voltage can be done, if
you have level-shifting hardware in place. Finally, debugging at very low clock frequencies (32 kHz/8 = 4 kHz)
is not impossible, but communication is extremely slow. I have implemented that mainly because of
curiosity and to help you recover and switch to a higher frequency.
The only reasonable way to deal with this problem is to use a different MCU, one with an A, PA, or PB suffix.
If you really need to debug this particular MCU and are aware of the problems and limitations, you can
recompile the sketch with the compile-time constant STUCKAT1PC set to 1.
I have encountered situations when it was impossible to get the right information about C++ objects. This
can be avoided by disabling link-time optimization (LTO). Choose Debug (no LTO) in this case. Finally, if
there are still discrepancies between what you expect and what the debugger delivers, you can try Debug
(no LTO, no comp. optim.) , which effectively switches off any optimization (corresponding to -O0 -fno-
lto).
In PlatformIO, you can set the options for generating the debug binary in the platform.ini file.
9 Troubleshooting
Problem: It is impossible to upload the dw-link firmware to the UNO board
Maybe, the dw-link probe shield or the auto-reset disabling capacitor is still plugged into the UNO board?
Remove, and try gain.
Problem: After debugging, the chip is unresponsive, i.e., does not respond
anymore to ISP programming or bootloader upload
The DWEN fuse is still programmed, i.e., the MCU is still in debugWIRE mode. In this case, it may help to
enter and leave the debugger again, provided that there are not any problems with the RESET line. It
may also be helpful to issue the command monitor dwire - .
Another fuse has been programmed by accident. In particular, there are the monitor commands that
change the clock source. If an external clock or an XTAL has been chosen, then you can recover the
chip only by providing such an external clock or XTAL and then use either ISP programming or connect
again to dw-link.
As mentioned in Section 3.4, it apparently happens that the MCU is stuck halfway when transitioning to
debugWIRE state. Then only HV programming can resurrect the chip.
Problem: After changing optimization options, the binary is still too large/very
small
You switched the optimization option from -Og -fno-lto back to normal and you recompiled, but your
program still looks very big. The reason for that can be that the Arduino IDE/CLI does not always recompile
the core, but reuses the compiled and linked archive. In the Arduino IDE 1, you can force a recompile of the
core by exiting the IDE. In IDE 2, this is no longer an option. You need to look at where the files are compiled
are stored and delete them manually.
Problem: When starting the debug session in PlatformIO, you get the message
pioinit:XX: Error in sourced command file
Something in the platformio.ini file is not quite right. Sometimes an additional line of information is
given that identifies the problem. If you see also see the message "monitor" command not supported by
this target then the dw-link adapter could not be found.
One other common problem is that the debug environment is not the first environment or the default
environment. In this case, the wrong environment is used to configure the debug session and probably
some environment variables are not set at all or set to the wrong values. So, you need to edit the
platformio.ini file accordingly.
Problem: When connecting to the target using the target remote command, it
takes a long time and then you get the message Remote replied unexpectedly to
'vMustReplyEmpty': timeout
The serial connection to the hardware debugger could not be established. The most likely reason for that is
that there is a mismatch of the bit rates. The Arduino uses by default 115200 baud, but you can recompile
dw-link with a changed value of HOSTBPS , e.g., using 230400. If GDB is told something differently, either as
the argument to the -b option when starting avr-gdb or as an argument to the GDB command set serial
baud ... , you should change that. If you did not specify the bitrate at all, GDB uses its default speed of
9600, which will not work!
My experience is that 230400 bps works only with UNO boards. The Arduino Nano cannot communicate at
that speed.
A further (unlikely) reason for a failure in connecting to the host might be that a different communication
format was chosen (parity, two stop bits, ...).
Problem: When connecting to the target using the target remote command, you
do not get an error message, but the system LED is still off and there is
apparently no connection
This happens when you select the AutoDW-off jumper option on the dw-link board. In this case, you have
to initiaite the connection with the monitor dwire + command. And in the end before you disconnect you
should switch back into normal mode with monitor dwire - . Usually, you wantg to have the jumper in the
on position.
Problem: In response to the monitor dwire ++ command, you get the error
monitor dwire
message Cannot connect: ...
Cannot connect: Check wiring: The debugger can neither establish an ISP nor a debugWIRE connection.
Check wiring. It could also be a problem with the RESET line (see Section 3.3). If this is not the reason,
disconnect everything and put it together again.
Cannot connect: Unsupported MCU: This MCU is not supported by dw-link. It most probably has no
debugWIRE connectivity.
Cannot connect: PC with stuck-at-one bits: dw-link tried to connect to an MCU with stuck-at-one bits in
the program counter (see Section 8.9). These MCUs cannot be debugged with GDB.
Cannot connect for unknown reasons: This error message should not be shown at all. If it does, please
tell me!
This is a generic GDB error message that indicates that the last monitor command you typed could not be
successfully executed. Usually, also a more specific error message is displayed, e.g., debugWIRE could NOT be
disabled. These messages are suppressed in some GUIs, though.
Problem: You get the message Connection to target lost, the program receives a
SIGHUP signal when you try to start execution, and/or the system LED is off
SIGHUP
The target is not responsive any longer. Possible reasons for such a loss of connectivity could be that the
RESET line of the target system does not satisfy the necessary electrical requirements (see Section 3.3).
Other reasons might be that the program disturbed the communication by changing, e.g., the MCU clock
frequency (see Section 8.7). Try to identify the reason, eliminate it, and then restart the debug session.
Most probably, there are still BREAK instructions in flash memory, so the load command should be used to
reload the program.
This happens with avr-gdb versions older than version 10.1. You can instead use monitor reset and
continue .
Problem: When stopping the program with Ctrl-C (or with the stop button), you
get the message Cannot remove breakpoints because program is no longer writable.
The reason is most probably that the communication connection to the target system has been lost (see
above).
One reason for that could be that the target is run with a clock less than 1 MHz, e.g. at 128 kHz. Since the
debugWIRE communication speed is MCU usually clock/8, the debugWIRE communication speed could be
16kbps. If the CKDIV8 fuse is programmed, it could even be only 2kbps. Unprogram CKDIV8 and if possible
choose a higher clock frequency (see Section 8.2).
Problem: The debugger does not start execution when you request single-
stepping or execution and you get the warning Cannot insert breakpoint ...
Command aborted
You use more than the allowed number of breakpoints, i.e., usually 25 (including one for a temporary
breakpoint for single-stepping). If you have executed the monitor breakpoint h command, this number is
reduced to 1. In this case, you can either set a breakpoint or you can single-step, but not both! In any case,
you need to reduce the number of breakpoints before you can continue.
The problem is similar to the one above: You used too many breakpoints and there is no temporary
breakpoint left for GDB. The program is probably stopped somewhere you have not anticipated. You may
be able to recover by deleting one or more breakpoints, setting a breakpoint close to where you wanted to
step, and then using the continue command. If this is not possible, restart and use fewer breakpoints.
Problem: While single-stepping, time seems to be frozen, i.e., the timers do not
advance and no timer interrupt is raised
This is a feature, not a bug. It allows you to single-step through the code without being distracted by
interrupts that transfer the control to the interrupt service routine. Time passes and interrupts are raised
only when you use the continue command (or when the next command skips over a function call). You
can change this behavior by using the command monitor singlestep u , which enables the timers and
interrupts while single-stepping. In this case, however, it may happen that during single-stepping control is
transferred into an interrupt routine.
This should only happen when you have used the command monitor singlestep u before, which enables
interrupts while single-stepping. In this case, an interrupt might have been raised which has transferred
control to the interrupt vector table at the beginning of flash memory. If you want to continue debugging,
set a breakpoint at the line you planned to stop with the single-step command and use the continue
command. If you want to avoid this behavior in the future, issue the debugger command monitor
singlestep s .
Problem: The debugger does not start execution when you request single-
stepping or execution, you get the message illegal instruction, and the program
receives a SIGILL
SIGILL signal
The debugger checks whether the first instruction it has to execute is a legal instruction according to the
Microchip specification. Additionally, a BREAK instruction (which has not been inserted by the debugger) is
considered as illegal since it would halt the MCU. Such a BREAK instruction might have been inserted as part
of the program code or may be a leftover from a previous debugging session that has not been terminated
in a clean way.
Check the instruction by using the command x/i $pc . If the BREAK instruction is a leftover from a previous
debug session, you can remove it using the load command. Note that the program counter is set to
0x0000 and you should use the monitor reset command to reset your MCU before restarting.
If you simply want to continue, you can set the PC to another value, e.g., one that is higher by two or four.
Do that by using the command set $pc=... .
This is a GDB problem. It can happen when a function call is inlined at the beginning of the function one
intends to stop at. While the place where execution will stop looks crazy (e.g., HardwareSerial.h at line 121),
the execution stops indeed at the beginning of the specified function (in this case at the beginning of setup).
Problem: The debugger does not stop at the line a breakpoint was set
Not all source lines generate machine code so that it is sometimes impossible to stop at a given line. The
debugger will then try to stop at the next possible line. This effect can get worse with different compiler
optimization levels. For debugging, -Og is the recommended optimization option, which applies
optimizations in a debug-friendly way. This is also the default for PlatformIO. In the Arduino IDE, you have to
select the Debug option. You can also disable all possible optimizations (choose Debug (no comp. optim.)
in the Arduino IDE).
The debugger starts execution, but it never stops at a breakpoint it should stop, single-stepping does not
lead to the expected results, etc. I have seen three possible reasons for that (apart from a programming
error that you are hunting).
Often, I had forgotten to load the binary code into flash. Remember to use the load command every time
after you have started a debugging session. Otherwise it may be the case that the MCU flash memory
contains old code! Note that after the load command the program counter is set to zero. However, the
MCU and its registers have not been reset. You should probably force a hardware reset by using the
command monitor reset . Alternatively, when you initiated your session with target extended-remote
... , you can use the run command that resets the MCU and starts at address zero.
Second, you may have specified a board/MCU different from your actual target. This happens quite easily
with PlatformIO when you work with different targets. In this case, some things appear to work, but others
do not work at all.
Another possible reason for strange behavior is the chosen compiler optimization level. If you have not
chosen -Og (or -O0), then single-stepping may not work as expected and/or you may not be able to assign
values to local variables. If objects are not printed the right way, then you may consider disabling LTO (by
using the compiler option -fno-lto). Have a look into the Section about compiler optimization flags.
So, before blaming the debugger, check for the three possible causes.
One would expect that the target would start execution at 0x000 after a reset. However, if the fuse BOOTRST
is set (i.e., when the target has a boot loader), then the execution is started at the beginning of the boot
loader area.
Problem: You have set the value of a local variable using the set
set var
var <var>=
<var>=
<value> command, but the value is still unchanged when you inspect the
<value>
variable using the print
print command
This appears to happen even when the optimization level is set to -Og, but not when you use -O0. So, if it is
important for you to change the value of local variables, you should use the latter optimization level (see the
preceding problem).
Problem: The system LED blinks furiously and/or the program receives an
ABORT signal when trying to start execution
ABORT
In this case some serious internal error had happened. You have to stop the current debug session and
restart.
The reason for such an error could be that the connection to the target could not be established or that
there was an internal debugger error. It may be that the corresponding error message has already been
displayed. You can find out what kind of error happened by typing the following command:
monitor lasterror
If the error number is less than 100, then it is a connection error. Errors above 100 are serious internal
debugger errors (see below).
If you have encountered an internal debugger error, then please try to reproduce the problem and tell me
how it happened. Please try to distill a minimal example leading to the problem and fill out the issue form.
By the way: monitor dwire - can still be executed, provided there is still a functioning connection to the
target. So you should still be able to disable debugWIRE on the target MCU even if a fatal error has
happened.
Error # Meaning
106 Memory address in flash read operation does not point to page start
109 Memory address in flash write operation does not point to page start
114 BREAK inserted by debugger at a point where a step or execute operation is required
Acknowledgements
The cover picture was designed based on vector graphics by captainvector at 123RF.
Revision history
V 1.1
Initial version
V 1.2
Changed pin mapping. The default is now to use ISP pins on the debugger so that a simple ISP cable
with broken out RESET line is sufficient. System LED is pin D7, GND for the system LED is provided at
pin D6. In order to use the pin mapping for shields/adapters, one has to tie SNSGND to ground,
whereby the pin number of SNSGND depends on the Arduino board dw-link is compiled for (see
mapping described in Section 7.3.3).
Added wording to recommend optimization level -O0 instead of -Og, because otherwise assignments
to local variables will not work. Single-stepping works now with -Og after dw-link hides all inserted
BREAK instructions.
V 1.3
Removed Arduino Mega boards from the set of boards that can be used as hardware debuggers
V 1.4
V 1.5
V 1.6
V 1.7
Changes in 8.7
Section 5.1-5.3 have been reworked, in particular concerning ATTinyCore 2.0.0 and the new Python
script for extending the boards.txt files.
V 1.8
V 1.9
V 1.10
Pointed out in Section 4.2 that when debugging an Uno the first time you try to debug it, you need to
erase the chip in order to clear the lock bits.
V 1.11
Added explanation that lock bits are automatically removed by erasing the entire chip
Restructured Introduction
Removed instructions how to modify board and platform files. Now the board definition files are
downloaded from my fork.
More explanation how to start a debugging session using the Arduino IDE
Corrected wrong placement in the table about the connections between UNO and ATtiny85
added dw-server.py
V 3.0
Number of breakpoints reduced from 33 to 25 because of stability problems (when debugging was on)
Added problem that stopping at a function might display the location of the inlined function