AVR/Arduino Hardware Debugger on the Cheap

2/17/2018 Update: I've removed the code links at the bottom of this page except for the link to the DebugWireTest, as new code is now available.  For the latest code, see my most recent article AVR/Arduino Hardware Debugger on the Cheap (part 3) 


This article describes how you can use an Arduino sketch running on one Arduino to control and debug another Arduino, or an AVR-Series micro-controller such as an ATTiny85.  With some limitations, you can use the controlling Arduino to disassemble code, single step code, inspect and modify the current value of registers. I/O ports and SRAM and run code with a hardware breakpoint set.  And, all you need to do this is the RESET pin on the target device, a >10K resistor pulling RESET to Vcc, and a standard, ATMega328-based Arduino to act as the controlling debugger.  Because using another Arduino as the target device requires some changes to the target Arduino's circuitry, this article is going to show how to use an ATTiny85 as the target device.  A future article will go into the changes needed to use an Arduino as the target device.  And, while most of these currently remain untested, the code I'm presenting is designed to ultimately support debugging the following AVR devices:
  • Tiny13
  • Tiny2313
  • Tiny24/44/84
  • Tiny25/45/85
  • Tiny441/841
  • Mega48A/PA
  • Mega88A/PA
  • Mega8u2/16U2/32U2
  • Mega168A/PA
  • Mega328
  • Mega328P
FYI - this project is a work in progress, as there are some details I'm still working to unravel.  So, if you experience problems trying to use this code, please check back here for possible future updates.  In addition, I want to mention that much of what I've been able to accomplish was made possible by the hard work of others, such as David C W Brown and RikusW.  Their work in deciphering the undocumented debugWire protocol was invaluable. 

How it Works

The secret sauce that makes this work are special features already built into most AVR-series devices.  In fact, you may have noticed debugWire listed as a feature in many of the AVR-series data sheets:


While won't find many more details about debugWire in the data sheet, it consists of hardware that supports two protocols that support uploading code and another, largely undocumented protocol called debugWire™,  With debugWire you can take control of a running AVR device and perform typical debugging operations like stopping code to inspect registers and SRAM values, single step through code, etc.  In this article I intend to  focus mostly on the debugWire protocol and present code for making use of it but, before you can use debugWire, you must also have a way to set a special fuse bit on the target device called DWEN that enables debugWire in the device.   And, you can only change this fuse bit by using one of the two programming protocols that support uploading code and modifying fuse bits, which are:
  • The AVR In-System Programming Protocol
  • The AVR High Voltage Programming Protocol
If you've ever used a Hardware Programmer, such as an AVRISP mkII, or an Arduino running the ArduinoISP sketch to program the bootloader into an Arduino, you were using the AVR In-System Programming Protocol to do this.  This protocol uses the MISO, MOSI, SCK and RESET pins and uses the SPI protocol to send 4 byte commands perform the programming functions.  This protocol can upload a code and change the settings of fuse bits, but some of the changes you can make to the fuse bits cannot be undone later using this same protocol.  So, unless you are careful, it's fairly easy to "brick" a device by setting the wrong fuse bits.  This is where the  AVR High Voltage Programming Protocol come it.  It uses the SCI, SDO, SII and SDI pins, a completely different protocol and applies 12 volts to the RESET pin, which is why it's called the High Voltage Programming Protocol.  The 12 volt signal serves as a type of override signal that activates the High Voltage Programming Protocol.

A later article will show how to build a simple circuit that lets an Arduino implement the AVR High Voltage Programming Protocol and I eventually plan to integrate this feature into the Arduino sketch named DebugWireDebugger that I'm presenting here.  But, for the time being, the sketch only implements the basic, AVR In-System Programming Protocol and, to try and keep you from bricking your device, it only implement enough code to set and clear the DWEN fuse bit while leaving the other fuses untouched.  To get started, you'll need to connect an ATTiny85 to the ATMega386-based Arduino running the DebugWireDebugger sketch using the following test circuit:

Click for Larger View

Note: This test circuit can work with the ArduinoISP sketch to program an ATTiny85 (move jumper to "Program" position, or debug an ATTiny85.  I describe how in later sections of this article.

I built this test circuit onto a cheap, eBay version of the Proto Shield mounted onto a Sparkfun RedBoard, which is an Arduino-compatible version of the now discontinued Arduino Duemilanove board.  Other Arduino boards, such as the Arduino UNO, should work fine, too, but I have not tested them.  The completed test circuit (shown below) does not contain a jumper block.  I simply move the jumper wire connected to the ATTiny85 pin 8 (Vcc) to the Arduino's D9 to debug the ATTiny85 or to +5 to program it.

https://sites.google.com/site/wayneholder/debugwire/IMG_8380.JPG?attredirects=0
Click for Larger View

Note 1: the code I'm presenting can also program and control an ATTiny25, or ATTiny45, but the ATTiny85 has twice as much RAM, Flash and EEPROM as the ATTiny45 (4 times as much as the ATTiny25) and is not much more expensive ($.76, $1.06 and $1.16, respectively), so I just use ATTiny85s in my projects.

Note 2: the few, sparse notes on debugWire that Atmel/Microchip chose to publish suggest that debugWire requires a pull up resistor of "greater than 10K" to work.  I actually used a 10K resistor and it seem to work fine.  But, your mileage may vary.

AVR In-System Programming Protocol

The In-System Programming Protocol works by sending a series of 4 byte commands to the target via the target's SPI interface while the RESET pin is held LOW.  Each byte is sent by setting the MOSI pin and while toggling the SCK line 8 times.  This could be done via the host Arduino's SPI interface but, to make  DebugWireDebugger as portable as possible, I decided to emulate an SPI Master in software.  Here's the bit of code that does this:

byte transfer (byte val) {
  for (byte ii = 0; ii < 8; ++ii) {
    digitalWrite(MOSI, (val & 0x80) ? HIGH : LOW);
    digitalWrite(SCK, HIGH);
    delayMicroseconds(2);
    val = (val << 1) | digitalRead(MISO);
    digitalWrite(SCK, LOW); // slow pulse
    delayMicroseconds(2);
  }
  return val;
}

You'll note that, as each byte is sent via the MISO pin, another byte is being read back at the same time using the MOSI pin.  However, when sending a 4 byte command, we only care about the last byte received which, in most cases, will contain a response code or data value read back from the target.  To send a complete, 4 byte command and get back the one byte response code we can use a function like this:

byte sendCommand (byte* cmd) {
  byte rsp;
  for (byte ii = 0; ii < 4; ii++) {
   rsp = transfer(cmd[ii]);
  }
  return rsp;
}

After RESET is pulled LOW, the The first 4 byte command sent must be the "Program Enable" command 0xAC, 0x53, 0xXX, 0xXX where 0xXX values do not matter.  This command puts the chip into programming mode and echos back 0xXX 0xAC 0x53 0xXX.  You can verify that the chip is in programming mode by sending the command to read the chip's Vendor Code by sending 0x30 0x00 0x00 0x00.  This should echo back something like 0xXX 0x30 0xXX 0x1E, where the last byte received, 0x1E, is the device code indicating an ATMEL chip.  I won't cover any more details about the In-System Programming Protocol here, but there are extensive notes and code for working with the In-System Programming Protocol in the Arduino sketch named DebugWireDebugger which you can download via the links at the bottom of this page.

The OnePinSerial Library

The debugWire protocol requires only the RESET pin for communication via the asynchronous serial protocol, but it expects to use this pin for both the TX and RX signals.  This is where the >10K resistor shown in the schematic above comes in.  To engage debugWire mode the controlling host pulls RESET line LOW for approximately 10 bits times to send what is called a BREAK signal.  It then releases control of the RESET line (which is pulled back HIGH by the resistor) and then monitors the RESET pin for the response back from the target.  In response to a BREAK signal, the target sends back a single, 0x55 byte to indicate that debugWire mode is engaged.  The following image is an oscilloscope capture that shows the approximate timing of the BREAK signal on the RESET line and the 0x55 byte echoed back by an ATTiny85 target device.

Click for Larger View

It's possible to use a diode on the TX line from the controlling host to combine two, independent TX and RX lines into a single TX/RX line but I chose, instead, to adapt the Arduino's SoftwareSerial library into a new library I call the OnePinSerial library.  You'll need to download and install this library, as well, in order to compile and run the DebugWireDebugger sketch.  If you're unfamiliar with how to install an Arduino library, click this link for more details.

The baud rate used by debugWire is the clock rate of the target device divided by 128.  The ATTiny85's clock rate is set by programming fuses in the deice and is set by default to approximately 1 MHz for a factory fresh device.  So, 1000000 / 128 is 7812.5 baud.  However, since the response to a BREAK signal is always a 0x55 byte with alternating 1s and 0s, the code is able measure the timing between it transitions and then compute the needed baud rate automatically.  However, using software for serial communications does pose some limitations on the maximum clock rate you can set for your target device.  For example, some AVR devices, such as a the ATMega328 used on may Arduino boards use a 16 MHz, clock rate.  This means that an target running at 16 MHz will require the debugWire interface to run at 125,000 baud (16,000,000 / 128), which may be too fast for reliable communication using my OnePinSerial library.

Programming an ATTiny85 using the Arduino IDE

Before I can demo how my debugWire debugger works, you'll need an already-programmed ATTiny85 to use as a test target.  Fortunately, the prolific David A. Mellis has created a way to do this using the standard Arduino IDE.  You can learn how to install the needed code by following this link and how to use the ArduinoISP sketch to program it by following this link.  I recommend that you take the time to get familiar with writing code for the ATTiny85 before going to the next step but, if you're curious to jump ahead, you can use Mellis' code to program the following test sketch into an ATTiny85.  But, be careful to select 1 MHz as the clock rate in the tools setup menu, like this:


As mentioned earlier, you can use the test circuit shown above to program an ATTiny85, as most of the the connections used by the test circuit are the same as those used by the ArduinoISP sketch.  However, you must make two changes:

  1. You must connect the ATTiny's pin 8 (Vcc) to +5 instead of to the Arduino's D9 pin.  The connection of pin 8 to the Arduino's D9 pin is used to switch the ATTiny85's Vcc power ON and OFF and is used to engage debugging mode.
  2. After you've uploaded the ArduinoISP sketch to the Arduino, you must connect a 10uf capacitor from the Arduino's RESET connection to ground.  This prevents the Arduino's boot loader from starting and allows the ArduinoISP sketch to act like a boot loader.   Note: If you're using a polarized capacitor, connection the (+) side to RESET.  
Note: you'll need to undo these connections to before uploading the DebugWireDebugger sketch and using the debugger.

//
//  Toggle ATTiny85 (Toggle PORTB.PB4, pin 3)
//
//                +-\/-+
//  RESET / PB5 1 |    | 8 Vcc
//   CLKI / PB3 2 |    | 7 PB2 / SCK
//   CLK) / PB4 3 |    | 6 PB1 / MISO
//          Gnd 4 |    | 5 PB0 / MOSI
//                +----+

#define LEDBIT    (1 << 4)            // PB4 (connected to LED to ground via 330 ohm resistor
#define INBIT     (1 << 3)            // PB3 (connected to pushbutton switch to gnd)

void setup() {
  DDRB = LEDBIT;                      // Set PB4 as output, all others as inputs
  PORTB = INBIT;                      // Set pullups on input pin PB3
}

void loop() {
  while (true) {
    PORTB |= LEDBIT;                  // Turn the LED On
    PORTB &= ~LEDBIT;                 // Turn the LED Off
    if ((PINB & INBIT) == 0) {        // Check if switch pressed
      __asm__ ("break\n\t");          // If so, return control to debugger
    }
  }
}

How It All Works

If you install the OnePinSerial Library, load, compile and run the DebugWireDebugger sketch and connect a factor fresh ATTiny85 according to the working diagram above, you'll be greeting by a prompt that says simply "Ready" when you open up the Serial Monitor window (set to window to 115200 baud).  At startup, the DebugWireDebugger responds only to mostly single letter commands for the In-System Programming Protocol.  These are:

e           Erase Flash and EEPROM Memory
cxxxxxxxx   Send arbitrary 4 byte (xxxxxxxx) command sequence
i           Identify part type
f           Print current fuse settings for chip
+           Enable debugWire Fuse
-           Disable debugWire Fuse
p           Turn on power to chip (used to run code)
o           Switch off power to chip
b           Cycle Vcc and Send BREAK to engage debugWire Mode (debugWire Fuse must be enabled) 

Most of these commands are left over from my development work, but feel free to try the 'I' command by typing just the letter 'I' (or 'i', as case doesn't matter) and press "Send."  If all is working well, you should see the following response:

SIG:    93 0B = Tiny85

Next, in order to use the debugWire protocol, you must enable the DWEN bit.  However, remember that, once the DWEN bit is enabled, you'll no longer be able to communicate with the ATTiny85 using the In-System Programming Protocol until the DWEN is disabled.  I'll show you how to do this in a later step.  If everything looks ok and you got back the expected response to the 'I' command, you can go ahead and enable debugWire mode by typing the plus sign character ('+') and then pressing Send.  This should echo back:

DWEN Enabled

At this point, you're ready to but you need to engage debugWire mode.  You can do this by typing the letter 'B' and pressing Send   This will cause the DebugWireDebugger sketch to perform a series of steps, such as cycling power to the chip and missing the break command needed to engage debugWire.  If everything goes ok, you should see the following response:

Cycling Vcc
Speed:  7949 bps
Sending BREAK: Ok
debugWire Enabled
SIG:    93 0B = Tiny85
Flash:  8192 bytes
SRAM:   512 bytes
SRBase: 0x0060
EEPROM: 512 bytes
DWDR:   0x22

The response displays the measured baud rate, the signature bytes for the target device (0x93 and 0x0B,which it identifies as an "ATTiny85."), the amount of Flash and SRAM available in the device and the base address of SRAM Memory.  The last line indicates the location of a special register called the DWDR register that's used by debugWire to send data back and forth to the target device.  This location of this register varies from one AVR device to another but, on an ATTiny85, it's located at I/O address 0x22.  This detail isn't important for now, but will be when I talk about debugging other devices, such as the AVR ATMega328 used in most Arduino boards.

Using debugWire

Once in debugWire mode, all the In-System Programming Protocol commands are disabled (as they will not work while debugWire is enabled) and a new set of commands is now available to you.  I'll start by showing a list of the most useful available commands, but resist the temptation to randomly try a few until I can explain a bit more about the proper way to use these commands:

PC            Read and print Program Counter
PC=xxxx       Set Program Counter to xxxx
BREAK         Send Async BREAK to Target (stops execution)
STEP          Single Step One Instruction at Current PC
RESET         Reset Target
SIG           Read and Print Device Signature
REGS          Print Registers labelled as 0-31
Rdd           Print Reg dd, where dd is a decimal value from 0 - 31
Rdd=xx        Set Reg dd to new value xx, where dd is a decimal value from 0 - 31
IOxx          Print I/O space location xx, where xx is address for "in", or "out"
IOxx=xx       Set I/O space location xx to new value
IOxx.0=b      Change bit 'o' (0-7) in I/O location xx to 'b' (1 or 0)
SRAMxxxx      Read and Print 32 bytes from SRAM address xxxx
SBxxxx        Print byte value of SRAM location xxxx
SBxxxx=yy     Set SRAM location xxxx to new byte value yy
SWxxxx        Print word value of SRAM location xxxx
SWxxxx=yyyy  Set SRAM location xxxx to new word value yyyy
EBxxxx        Print byte value of EEPROM location xxxx
EBxxxx=yy     Set EEPROM location xxxx to new byte value yy
EWxxxx        Print word value of EEPROM location xxxx
EWxxxx=yyyy   Set EEPROM location xxxx to new word value yyyy
EXIT          Exit from debugWire mode back to In-System Programming
CMD=xxxx      Send sequence of bytes xxxx... and show response
FWxxxx        Print 64 words (128 bytes) from Flash addr xxxx
FB0000        Print 128 bytes from Flash addr xxxx
Lxxxx         Disassemble 64 words (128 bytes) from Flash addr xxxx
RUN           Start execution on Target at current value of PC (use BREAK to stop)
RUNxxxx yyyy  Start execution on Target at xxxx with A breakpoint at yyyy
RUN xxxx      Start execution on Target at current value of PC with breakpoint at xxxx

The first thing you should note is that, should things step working as you expect, you can usually get control again by issuing a "BREAK" or "breakcommand (case does not matter.)  This avoids power cycling the target like the 'B' command, but does send the BREAK signal to it again.  If everything is ok, the response to the BREAK command should be something like:

BREAK: Ok
L0110:  0000  nop

Where the 2nd line shows the address where the target was just about to execute the instruction shown when it was interrupted and stopped by the BREAK command.  If the target code is running normally, a BREAK command should be able to give you control again.  But, if everything goes south and even a BREAK command fails to make the target respond, you can close and reopen the Serial Monitor window to get back to the In-System Programming Protocol menu.  After the "Ready" prompt prints, you can then type the 'B' command which will force a full power cycle and then issue the BREAK signal.  Note: since the DWEN fuse is still enabled, none of the other In-System Programming commands will work.  You should then be back in debugWire mode.

Unsetting the DWEN Fuse to disable debugWire

If you need to update the code on the ATTiny85, you'll need to first disable the DWEN fuse.  You can do this by following these steps:
  1. Close and Reopen the Serial Monitor Window (or open, if not already open)
  2. Type 'B' and press Send
  3. Type "EXIT" and press Send (this should echo back "debugWire Disabled")
  4. Type the minus (or dash) symbol ('-') and press Send.  (This should echo back "DWEN Disabled")
  5. You can now remove the ATTiny85 from the test circuit and connect it to a programmer to program in new code.
Disassembilng and Single Stepping Code

Now that you understand all the recovery steps, let's try out some real debugging.  Once of the features I added to my DebugWireDebugger sketch is a simple disassembler so you can see a human readable version of the code you're debugging.  My disassembler is rather crude and I have not had time to check that it can properly disassemble all of the AVR opcodes, but it seems to work fine on the code I have tested it with. 

To see my disassembler in action, type "L0000" ('L" followed by 4 zeros) and press Send   If you're using the example ATTiny85 code I showed above, you should see the following AVR assembly language code listed out:

L0000:: C00E  rjmp  001E
L0002:: C01D  rjmp  003E
L0004:: C01C  rjmp  003E
L0006:: C01B  rjmp  003E
L0008:: C01A  rjmp  003E
L000A:: C01A  rjmp  0040
L000C:: C018  rjmp  003E
L000E:: C017  rjmp  003E
L0010:: C016  rjmp  003E
L0012:: C015  rjmp  003E
L0014:: C014  rjmp  003E
L0016:: C013  rjmp  003E
L0018:: C012  rjmp  003E
L001A:: C011  rjmp  003E
L001C:: C010  rjmp  003E
L001E:: 2411  eor   r1,r1
L0020:: BE1F  out   0x3F,r1
L0022:: E5CF  ldi   r28,0x5F
L0024:: E0D2  ldi   r29,0x02
L0026:: BFDE  out   0x3E,r29
L0028:: BFCD  out   0x3D,r28
L002A:: E020  ldi   r18,0x00
L002C:: E6A0  ldi   r26,0x60
L002E:: E0B0  ldi   r27,0x00
L0030:: C001  rjmp  0034
L0032:: 921D  st    X+,r1
L0034:: 36A9  cpi   r26,0x69
L0036:: 07B2  cpc   r27,r18
L0038:: F7E1  brne  0032
L003A:: D04C  rcall 00D4
L003C:: C06E  rjmp  011A
L003E:: CFE0  rjmp  0000

This displays the set of rjmp commands that correspond to the the various interrupt vectors supported by the ATTiny85.  The first rjmp is what executes when the ATTiny85 starts running at power up after a RESET.  You'll note that it jumps down to address 0x001E, which is an "eor r1,r1" instruction that, effectively, clears register r1 to zero.)  But, let's see this in action by single stepping though the code.  To do this, first type "RESET" and press Send.  You should see the following response:

0000:   C00E  rjmp   001E

This shows the instruction that will be executed next if you were to use the STEP command.  To do this, type "STEP" and press Send and you should see the following response:

Note: when single stepping, a special run mode is used that disables all the target's timers.  This is an area where the existing, 3rd party documentation about debugWire is a bit vague, but I believe disabling the timers will prevent timer interrupts, which would interfere with single stepping if they were allowed to run.

STEP:   Ok
001E:   2411  eor    r1,r1           ; 01, 01

Each time you use the STEP command it will complete by displaying the instruction it will execute on the next STEP command just like the RESET command showed you the instruction the the first STEP command would execute.  To step through more instructions, you can press Send (without typing anything) multiple times and the STEP command will repeat again and again and advance down through the code.  If you were to press Send 5 more times, here's what you should see displayed in the Serial monitor Window (starting from where you typed the RESET command):

RESET:  Ok
0000:   C00E  rjmp   001E
STEP:   Ok
001E:   2411  eor    r1,r1           ; 01, 01
STEP:   Ok
0020:   BE1F  out    0x3F,r1         ; 00
STEP:   Ok
0022:   E5CF  ldi    r28,0x5F        ; 1C
STEP:   Ok
0024:   E0D2  ldi    r29,0x02        ; 1D
STEP:   Ok
0026:   BFDE  out    0x3E,r29        ; 02
STEP:   Ok
0028:   BFCD  out    0x3D,r28        ; 5F

The hexadecimal numbers listed after the ';' (semicolon) characters on some lines show the state of the registers that will be potentially altered when the instruction executes. Other lines, such as those displaying one of the various branch instructions, may show a representation of the state of the status register, SREG before the instruction is executed.  This information is provided to help you see what path a branch instruction might take when it executes.  For example, if you continue to step until you reach address 0x0038, you see an example of this:

STEP:   Ok
L0038:  F7E1  brne  0032            ; --HS-N-C

The instruction above, "brne", will branch to the indicated address, 0x0032, only when the Z bit (bit 1) in the status register is 0.  In this case, since a '-' is displayed in place of a 'Z' in the bit 1 position, the branch will be taken when you do another STEP command.  Note: this feature is currently only implemented for a subset of the full instruction set, but I plan to expand it as time permits.

You'll need to take the time to learn a bit about AVR assembly language to really follow what's going on in the above steps but, to help you understand, the steps you've executed so far have done the following things:
  1. The "eor r1,r1" instruction set the value of register r1 to zero.
  2. The "out 0x3F,r1" instruction wrote the contents of r1 (0x00) into I/O register 0x3F, which is the status register, SREG.
  3. The "ldi 28,0x5F" instruction set the value of register r28 to 0x5F
  4. The "ldi 29,0x02" instruction set the value of register r29 to 0x02
  5. The "out 0x3E,r29" instruction wrote the contents of r29 (0x02) into I/O register 0x3E (SPH, stack pointer high byte)
  6. The "out 0x3D,r28" instruction will write the contents of r28 (0x5F) to I/O register 0x3D (SPL. stack pointer low byte) on the next STEP command
To see the changes to the registers, you can type "REGS" and press Send to list out the current state of registers r0-r31:

 r0:00,  r1:00,  r2:02,  r3:03,  r4:04,  r5:05,  r6:06,  r7:07
 r8:08,  r9:09, r10:0A, r11:0B, r12:0C, r13:0D, r14:0E, r15:0F
r16:10, r17:11, r18:12, r19:13, r20:14, r21:15, r22:16, r23:17
r24:18, r25:19, r26:1A, r27:1B, r28:5F, r29:02, r30:1E, r31:1F

You'll see that, because of the code you stepped through above, the value of r1 is now 0x00, the value of r28 is now 0x5F and the value of r29 is now 0x02.  Note: you'll notice the other registers are initialized to the values 0x00 - 0x1F.  This was done by the RESET command you typed as a result of a test feature I added during development.  You can disable this behavior in the DebugWireDebugger sketch by removing, or commenting out the following lines of code:

// Preset registers 0-31
for (byte ii = 0; ii < 32; ii++) {
  writeRegister(ii, ii);
}

Reading and Writing Registers

If you only want to examine the state of an individual register, type "Rd" where d is the one or two digit decimal number of the register you want to inspect.  So, for example, you can type R1 and press Send to list out the current value of register r1, like this:

R1:     00

Likewise, you can change the value of a register by typing "Rd=xx" where d is a one or two digit decimal number (0-31) indicating the register you want to change and xx is the new value to set (a two digit, hexadecimal number).  For example, typing "R1=55" will echo back:

R1:=    55

And, you can then type "R1" to see the new value:

R1:     55

Reading and Writing I/O Registers

in a similar fashion, you can inspect the value of some of the I/O registers (not all are readable) using the command "IOxx" where xx is the I/O address of the register (for the ATTiny85, this is a value from 0x00 - 0x3F.)  For example to example the SHL register, you type "IO3D" to get:

IO3D:   5F

Or, you can change the value of I/O register 0x3D to 0x4F by typing "IO3D=4F", which will echo:

IO3D:=  4F

On the ATTiny85, you can use these commands to do actual I/O operations.  For example, you can set the DDRB direction register (0x17) to directly flip the I/O pins between input and output mode and then write to the PORTB register (0x18) to set output pins HIGH or LOW, or engage the pull up resistors for input pins.  For example:

IO17=10    Set Pin 3 (PB4) as output, all others input
IO18=10    Set Pin 3 (PB4) HIGH
IO18=00    Set Pin 3 (PB4) LOW

You have to be careful with this command, as it modifies all bits in the register at once, but it can be a useful way to toggle a pin while debugging a circuit.  So, as a convenience, the debugger supports a command that generates and then invokes either an "sbi" or a "cbi" (set bit, or clear bit) instruction which only modifies one bit of an I/O address by typing "IOxx.o=b" where xx is an I/O address < 0x20 (a limitation of the sbi and chi instructions), 'o' indicates a bit number (0-7) and 'b' is either '1' or '0'.  For example, to set bit 4 of address 0x18, type "IO18.4=1" and see this response:

IO18.4:=1

Reading and Writing SRAM

You can use the "SRAMxxxx" command to dump a block of 32 bytes of SRAM starting at address xxxx as long as the starting address if greater than, or equal to the SRAM base address for the target device being debugged.  Note: the debugger prints out the SRAM base address as the "SRBase:" value each time you start the debugger.  For example, to dump SRAM starting at the SRAM base address for the ATTiny85 (0x0060), type "SRAM0060".  The debugger will respond by printing something like this:

S0060:  00 3E 00 00 22 B6 03 00 00 E5 E5 CE BB 25 63 FF
S0070:  7A D4 BB 1E 3A 5C 5F E9 EB BD 53 C7 7B 9F 42 D1

Also, as a convenience, you can also press "Send" again (without typing anything), to display the next 32 bytes of SRAM and you can do this multiple times until you reach the end of the available SRAM.

You can display the value of any particular byte of SRAM (subject to the same address restriction mentioned above) by using the "SBxxxx" command.  For example, to display the contents of SRAM location 0x0064, type "SB0064" and press Send.  This will echo back something like this:

S0064:  22

And, just like with the Register and I/O commands, you can set a new value for any valid SRAM address by using the "SBxxxx=xx" command.  For example, to change the value of SRAM location 0x0064, from 0x22 to 0x55, type "SB0064=55", which will echo back:

S0064:= 55

You can also read and write 16 bit SRAM word values using the commands "SWxxxx" to display the word value at location xxxx and the command "SWxxxx=yyyy" to set a new value yyyy at location xxxx.  For example:

S0060:  0100     Display content of address 0x0060
S0060=: 1234     Set value of address 0x0060 to 0x1234
S0060:  1234     Display changed content of address 0x0060

Reading and Writing EEPROM

You can display the value of any particular byte of EEPROM (up to the limit supported by the device) by using the "EExxxx" command.  For example, to display the contents of EEPROM location 0x0012, type "EB0012" and press Send.  This will echo back something like this:

E0012:  FF

You can set a new value for any valid SRAM address by using the "EBxxxx=xx" command.  To change the value of SRAM location 0x0012, from 0xFF to 0x55, type "EB0012=55", which will then echo back:

E0012:= 55

And, you can also use the commands "EWxxxx" and "EWxxxx=yyyy" to read and write EEPROM word values.

Displaying Flash Memory Data

While you will probably display the contents of Flash memory using the "Lxxxx" command to display a disassembly of the program instructions, you can also dump the content of Flash memory as 16 bit words, or bytes.  The "FWxxxx" command will dump 64 words of Flash memory starting at address xxxx.  For example, if you type "FW0000" you'll see something like the following echo back:

F0000:  C00E C01D C01C C01B C01A C01A C018 C017
F0010:  C016 C015 C014 C013 C012 C011 C010 2411
F0020:  BE1F E5CF E0D2 BFDE BFCD E020 E6A0 E0B0
F0030:  C001 921D 36A9 07B2 F7E1 D04C C06C CFE0
F0040:  921F 920F B60F 920F 2411 932F 933F 938F
F0050:  939F 93AF 93BF 9180 0065 9190 0066 91A0
F0060:  0067 91B0 0068 9130 0064 E320 0F23 372D
F0070:  F420 9640 1DA1 1DB1 C005 EB23 0F23 9641

Note the "FWxxxx" command reorders the bytes so the leftmost 2 digits of the 16 bit hexadecimal word shows the contents of the byte at address xxxx + 1 and the rightmost 2 digits display the contents of the byte at the address.  This is to match how the instruction words are shown in the AVR assembly language manual.

Alternately, you can use the "FBxxxx" command to dump 128 bytes of Flash memory starting at the same address by typing "FB0000".  This will produce a display like this:

F0000:  0E C0 1D C0 1C C0 1B C0 1A C0 1A C0 18 C0 17 C0    ................
F0010:  16 C0 15 C0 14 C0 13 C0 12 C0 11 C0 10 C0 11 24    ...............$
F0020:  1F BE CF E5 D2 E0 DE BF CD BF 20 E0 A0 E6 B0 E0    .......... .....
F0030:  01 C0 1D 92 A9 36 B2 07 E1 F7 4C D0 6C C0 E0 CF    .....6....L.l...
F0040:  1F 92 0F 92 0F B6 0F 92 11 24 2F 93 3F 93 8F 93    .........$/.?...
F0050:  9F 93 AF 93 BF 93 80 91 65 00 90 91 66 00 A0 91    ........e...f...
F0060:  67 00 B0 91 68 00 30 91 64 00 20 E3 23 0F 2D 37    g...h.0.d. .#.-7
F0070:  20 F4 40 96 A1 1D B1 1D 05 C0 23 EB 23 0F 41 96     .@.......#.#.A.

The "FBxxxx" commands display the data both in Hex and then again as ASCII characters (for data >= 0x20 and <= 0x7F, where non ASCII characters display a '.' as a placeholder.)  The ASCII display is useful if you use the PROGMEM directive or the F("xx") macro to put string data into Flash memory.

And, just like with the "SRAMxxxx", command, you can press "Send" again and again to advance to the next 64 words, or 128 bytes of Flash. 

The RUN Command

To run the code at full speed type "RUN" and then press Send.  Unlike the STEP command, the RUN command enables the target's timers, so timer interrupts should be active while your code runs.  You should execute the RESET command before using RUN if you are using RUN for the first time.  Here's what this will look like:

RESET: Ok
L0000:: C00E rjmp 001E
RUN: Running

Alternately, try can use the "RUNxxxx" command to start execution at address xxxx.  For example, instead of typing RESET to se the PC (program counter) to 0x0000 before you typed run, you could instead type "RUN0000" and press Send to get this response:

RUN:=   PC:0000

In either case  if you implemented the test circuit correctly,  you should now see the LED glowing at about 1/2 intensity, as it quickly toggles back and forth between ON and OFF.  To stop the target and regain control with the debugger, type "BREAK" and press Send.  You should get a response like this:

BREAK:  Ok
L0112:: 99B3  sbic  0x16,3

Where the 2nd line shows the next instruction that will execute if you do a STEP command, or use the RUN command again to resume execution.  If you continue to issue STEP commands, you can watch as the program toggles the LED on and off.  For example, the following sequence shows this in action:

RESET:  Ok                            Typed RESET
L0000:  C00E  rjmp  001E
RUN:    Running                       Typed RUN
BREAK:  Ok                            Typed BREAK
L010A:  9AC4  sbi   0x18,4            Turn LED ON
STEP:   Ok
L010C:  0000  nop
STEP:   Ok
L010E:  98C4  cbi   0x18,4            Turn LED OFF
STEP:   Ok
L0110:  0000  nop
STEP:   Ok
L0112:  99B3  sbic  0x16,3            Check if Switch is pressed
STEP:   Ok
L0114:  CFFA  rjmp  010A              Not Pressed
STEP:   Ok
L010A:  9AC4  sbi   0x18,4            Turn LED ON
STEP:   Ok
L010C:  0000  nop
...

After issuing a BREAK command, you can issue a RUN command to resume execution at the point where you stopped.

Using "break" Instructions in Your Code

Another way to stop execution is to insert one, or more AVR "break" instructions (which compile to the AVR opcode 0x9598) into your code.  In the test code I asked you to program into your target ATTiny85, you may have noticed some odd-looking syntax, like this:

__asm__ ("break\n\t");

Note: This is called "in-line" assembly code and is normally used to hand craft some optimized assembly code for use in places where code needs to be as efficient as possible.  But, it's also a useful way to insert breakpoints.

With this instruction in place while running the test sketch and using the test circuit show earlier, you can force the execution of a "break" instruction by pressing the pushbutton switch connected to pin 2 of the ATTiny85.  To try this, first issue a RESET command and then a RUN command to start the program from the beginning.  Then, when the LED is toggling on and off at 1/2 intensity, press the switch to force the break instruction to execute.  You should see something like this:

RESET:  Ok
L0000:  C00E  rjmp  001E
RUN:    Running
BREAKPOINT
L0116:  9598  break

As you can see, the code will always stop showing the break instruction as the next instruction to execute.  If you issue a STEP command at this point, you see this displayed:

STEP:   Skipping break
L0118:  CFF8  rjmp  010A

This shows how the STEP command will automatically skip over the break instruction and displays the next instruction to execute which, in this case, is a relative jump (rjmp) to location 0x010A.  You can continue by single stepping through code with the STEP command., or issue a RUN command to resume execution.

Using a Hardware Breakpoint

The hardware that supports debugWire also has a hardware breakpoint register you can use to stop execution at some designated point without having to recompile to insert break instructions into your code.  The RUNxxxx yyyy command will start execution at location xxxx and set a hardware breakpoint at location yyyy.  For example, to start execution from location 0x0000 and break at location 0x10A, you would type "RUN0000 010A" and press Send.  Here's what the console will then show:

RUN:=   PC:0000 BP:010A
BREAKPOINT
L010A:  9AC4  sbi   0x18,4

After stopping at a hardware breakpoint, you can resume execution from the code stopped while also setting a new hardware breakpoint by using the command "RUN xxxx" (note the space) where xxxx is the new breakpoint location.


Update - For the latest code, see my most recent article AVR/Arduino Hardware Debugger on the Cheap (part 3) 
ċ
DebugWireTest.zip
(1k)
Wayne Holder,
Jan 27, 2018, 1:51 PM