Another tool for debugging AVR microcontrollers

After the positive simavr reception, I decided to keep exploring methods to enhance even more the coding experience with Arduino and AVR in general. As software can’t simulate hardware at 100% and sometimes there is no choice but to develop and test using the real thing, then the lack of proper debugging tools returns.

That’s why I’m going to explain another tool or better said library I found interesting. This library already had support for Arduino so I went ahead and registered it on PlatformIO, if it gets accepted then it will be available under the name avr-debugger. The library doesn’t have any dependencies, however, it needs the ISRs enabled, which Arduino already does by default, but a bare-metal configuration would need the sei() command.

The library has two relevant functions, debug_init() and breakpoint(), and as their name suggests the former will configure the related hardware and the later is only needed to add fixed breakpoints. So a main.cpp and platformio.ini would look like this:

The library has a couple of options that configure the mechanism used for debugging and also for enabling some extra capabilities, those are found here

#define AVR8_BREAKPOINT_MODE	(1) // Selects RAM of Flash breakpoints
#define AVR8_SWINT_SOURCE	(0) // Select ISR source, 0 -> INT0
#define AVR8_LOAD_SUPPORT	(0) // Enables load command for GDB, requieres modified bootloader
#define AVR8_USER_BAUDRATE	(115200) // Changes the baudrate of the GDB server

This library has two mechanisms to determine if a breakpoint was reached.
The first and default one, called RAM breakpoint mode, enables an ISR like INT0, configs it to trigger at a low level, and then puts the related pin at a low level. That will trigger the ISR constantly, however, the AVR core will always execute one instruction in the main code before jumping into ISR even if the interrupt is pending, allowing to step instructions. Its main drawback is that execution will be slower.

The second mechanism, called Flash breakpoint mode, is very similar to the technique that uses the debugWIRE protocol to set breakpoints, which involves the re-writing of the instruction where the breakpoint is set with the break instruction, and then the debugger regularly verifies if the CPU was stopped. The main difference with debugWire is that as there is no external hardware that can detect said condition then it uses the clever idea of enabling the watchdog and uses a jump instruction that jumps to itself where the breakpoint was placed, creating an infinite loop that hangs the program.

The second mechanism would have no performance penalty and give the same results as if a real external debugger was connected, however, it would also have its same drawbacks, like the faster wearing of the Flash memory.

I haven’t tested the Flash breakpoint mode yet, but for what I’ve read it would involve replacing the bootloader and having to change some fuses that enable the re-writing of flash memory.

3 Likes

Update
I got the Flash mode working. Now is possible to place breakpoints inside an ISR, so the experience should be almost as complete as if a real external debugger was used.

To make this possible the modified bootloader was a key piece, otherwise, the MCU just hangs. The author already had published its source code, but as it was meant to be built using eclipse, things got a bit harder.

I didn’t like the idea of having to install and use another IDE just for this and ended up porting the eclipse project to PlatformIO, project here, with all the configurations flags, fuses and lock bits as the author documented. The platformio.ini was configured for a crystal-less atmega328p in a breadboard, nonetheless, it should be easy to change it for any other model.

Now, to upload the bootloader and test this library to its full potential is as easy as to connect any supported external programmer to an Arduino Uno or Nano board and do:

pio run # to build the project
pio run -t fuses # to set the fuses
pio run -t program # to upload bootloader

Once done you are running the modded bootloader and the define AVR8_BREAKPOINT_MODE inside avr8-stub.h can be changed to 0 to use the Flash breakpoint mode.

Also, I could verify that the modded bootloader still works like the old one and is able to load firmware using the regular tools, so once you are done debugging just remove/comment the library.

I couldn’t test the library for any other board besides Arduino Nano and crystal-less atmega328p, and as it has the same MCU it should work with the Uno too. It would be great if somebody could test for the Mega as I don’t have one at hand to test it myself. :frowning_face:

What I could also verify was that it works fine without the Arduino framework. However I had to make my own version of the lib with a modified library.json file and comment out the framework field, otherwise, the library manager refused to add it as a dependency.

Edit: Part of the procedure was updated, better follow the instructions in the next post.

1 Like

Update,
I wrote a better howto explaining the compiling and programming procedure for the bootloader and also uploaded a ready to test example.

I found that setting up lib_compat_mode to off does the trick and no modification is required. link

1 Like

Maybe it’s better to modify library.json to explicitly state that no framework is required?

1 Like

That’s ok if you own the file… setting the lib_compat_mode in your platformio.ini is much easier if you don’t and you know it’s a ‘false positive’ :wink:

1 Like

True. Though, I could prepare a pull request to the original library.json if someone pointed me towards it.

1 Like

You’d have to ask @msquirogac about that… since he registered it, probably with a custom library manifest since it doesn’t seem to have it’s own?

The underlying issue could be the library might work without the Arudino framework, the author only supports/intended it for Arduino, so this seems to be going beyond the scope intended…

Yeah, that’s the main problem. The library was registered as an Arduino one and doesn’t have a library.json but a library.properties file.
Nonetheless, as the author likes the idea of his library working on PlatformIO, then proper support outside Arduino isn’t ruled out yet.
I was also thinking of the possibility to add this tool as an official one just like simavr, however, that’s something @ivankravets has to decide/confirm first.

1 Like

Derp… obviously didn’t go far enough down the rabbit hole :wink:

Since it’s a library + bootloader, I suspect it will remain a third-party method, but a well documented one, since you’ve basically got it in a ready to go state. But as you said, that’s for Ivan to determine.

I’ll have to dig a Mega out later and give that a try… oh, dammit… you said Mega1280, not the Mega2560… well, that won’t work! :laughing: :man_facepalming: Although, on second reading of the repo, it does say Mega2560 support… will have to investigate further now. Plus, I see this mention in a code comment (my emphasis):

Example for debugging with avr8-stub.c in plain C language, without Arduino libraries.

Could you summarize what should be done from PlatformIO side? Thanks!

Hi @ivankravets

First, as we recently got a proper library.json file, could you tell the crawler to use that new manifest?

Second, would be great if we could invoke this tool with a simple

debug_tool = avr-debugger

Instead of this long thing

debug_tool = custom
debug_port = /dev/ttyUSB0
debug_load_cmds = preload ; Force to load the firmware before debugging
debug_init_break =; Can't breakpoint on main because the stub isn't initialized yet
debug_init_cmds =
  define pio_reset_halt_target
  ; Void until support for the monitor command is included
  end
  define pio_reset_run_target
  ; Void until support for the monitor command is included
  end
  file "$PROG_PATH"
  set remotetimeout 1
  set serial baud 115200
  set remote hardware-breakpoint-limit 4 ; To determine how many breakpoints support the stub, limit to 4 to be safe
  set remote hardware-watchpoint-limit 4 ; To determine how many watchpoints support the stub, limit to 4 to be safe
  target remote $DEBUG_PORT

Third, we need help with testing, especially with AVR microcontrollers that aren’t the atmega328p. The minimum necessary for this library to work is an available UART port and an external ISR. There is a number of models that comply with that, yet we don’t have the hardware to verify it.

Finally, I think the bootloader should be put aside for now. Certainly, it adds some interesting features (flash breakpoint mode), but for what I tested is still quite experimental and only supports the atmega328p. However, even without the bootloader the library still has the RAM breakpoint mode which is pretty usable.

2 Likes

Hi, @ivankravets
I know you are pretty busy these days, so I decided to give a hand and implement it myself. All my advances are in this fork and I think we will be ready to create a PR soon.

We’ve been working on avr_debugger to improve its compatibility and usability with platformio and pretty much the user experience got to the point is almost as good as using simavr, but this time we are talking about the real hardware and not a simulation. :slight_smile:

Please, give it a shot and tell me your opinion.

3 Likes

Yes, crazy summer :slight_smile: We want to release the next projects this month:

P.S: This topic is already added to our TODO list where we plan to back soon after releases mentioned above. Sorry for the delay :frowning: All releases above are blockers.

1 Like

Hello,

Not sure it’s the good topics but I tried to use this library for debugging ATMEGA2560 like but I have this linking errors when I build with this library (I have applied what the author of the library described in his doc) :

… Local\Temp\cc8WcTqd.ltrans3.ltrans.o: In function __vector_5': developpement\VSCode\PlatformIO\Projects\GestionPompe/.pio\libdeps\GestionPompe\avr-debugger\avr8-stub/avr8-stub.c:2066: undefined reference to regs’
developpement\VSCode\PlatformIO\Projects\GestionPompe/.pio\libdeps\GestionPompe\avr-debugger\avr8-stub/avr8-stub.c:2066: undefined reference to regs' developpement\VSCode\PlatformIO\Projects\GestionPompe/.pio\libdeps\GestionPompe\avr-debugger\avr8-stub/avr8-stub.c:2066: undefined reference to regs’

All in source code of avr_debugger.
Do you have an idea ?

Thank you

Are you using the exact same code as in the docs? Line 2066 is the start of assembly code which references the regs array

…and that array is declared in the exact same file…

… so it should be visible to the assembly code. Have you tried cleaning and rebuilding the project?

Thanks for your answer
Yes unfortunately and I already saw that so it’s why I request help here :slight_smile:
I will try today to use it with a simple project from scratch to see if there is something in my project that causes this even if I don’t undertand this error.

So I made some progress.
Configuration works with a simple blink project but not with full code of my project that use U8G2 lib and owned class.
So I build a new project and introuduce only one of my own classes then I remove methods until it works again (no error in linker).
What it is strange for me if I add code it can’t link any more. It looks like I reach a limit …
if I try with other function same results (so it’s not dependant on the function but it looks like the code size to produce.

Do you have some guidance on which I can look ?

thanks for your help.

Author of lib has find a way to work with my test program and it works as well with my full program but this is a strange issue more detail here .

1 Like

Let me continue the topic by announcing that I just completed the design of a debugWIRE hardware debugger. Actually, it is an Arduino sketch that turns your Uno or Nano into a hardware debugger. Integration into PlatformIO is smooth, there are some small hickups, though. Here is the Github rep: https://github.com/felias-fogg/debugWIRE-probe

The first problem is that I need some way of switching off the debugWIRE mode. I introduced a new target for that, which also shows up under the “Custom” target. Now I would have liked to use the value for DEBUG_PORT, but this is apparently not part of the environment. I have to use UPLOAD_PORT, which does not really make sense since I do not use the UPLOAD_PORT in the debug environment.

The second problem happens when gdb complains with an error and does not start to execute the program after one has used the PlatformIO execute command. In this case, PlatformIO thinks the target executes and one cannot stop the execution any more. This problem does not happen very often because the error only happens when one uses more then 32 breakpoints.

Here are the two files I use when debugging under PlatformIO:

platformio.ini
; PlatformIO Project Configuration File

[platformio]
default_envs = debug

[env:debug]
platform = atmelavr
board = attiny85
framework = arduino
board_build.f_cpu = 8000000L
build_type = debug
extra_scripts = extra_script.py
upload_port = /dev/cu.usbmodem1451101
debug_port = /dev/cu.usbmodem1451101
debug_tool = custom
debug_load_cmds = load
debug_build_flags = 
-Og
-g
debug_init_cmds = 
define pio_reset_halt_target
       monitor reset
end
define pio_reset_run_target
       monitor reset
       detach
end
file "$PROG_PATH"
set remotetimeout 5
set serial baud 115200
target remote $DEBUG_PORT
monitor init
load
$INIT_BREAK

[env:attiny85]
platform = atmelavr
board = attiny85
framework = arduino

extra_script:py

Import("env", "projenv")
import subprocess

def dw_stop(*args, **kwargs):
    print("Disable debugWire")
    tc = env.Dump().find('toolchain-atmelavr')
    sl = env.Dump().find('/',tc+1)
    if (sl > tc and sl >= 0 and tc >= 0):
        toolchain = env.Dump()[tc:sl]
    else:
        toolchain = "toolchain-atmelavr-original"
    gdb = [env['PROJECT_PACKAGES_DIR'] + '/' + toolchain + '/bin/avr-gdb' ]
    gdb += ["-batch-silent", "-b", "115200", "-ex", "target remote " + \
        env['UPLOAD_PORT'], "-ex", "monitor stop"]
    try:
        ret = subprocess.call(gdb, shell=False)
        print("DWEN fuse disabled")
    except subprocess.CalledProcessError as exc:
        print("Call failed with " + str(exc))



env.AddCustomTarget(
    name="dwstop",
    dependencies=None,
    actions=[
        dw_stop
    ],
    title="DebugWire Stop",
    description="Stops debugWire mode and re-enables the reset line"
)

Well, as I mentioned it is brand new and so probably a bit buggy.

Solved the second problem mentioned above.