This article describes source level debugger for Arduino. This debugger can be used to step through your code, place breakpoints, view variables etc. It does not need any modification of the Arduino board or external hardware. It is just a piece of code added to your Arduino program. It works for Arduinos based on ATmega328 microcontroller (tested with Arduino Uno) and also for Arduinos with ATmega2560 or ATmega1280 (Arduino Mega). This debugger has some limitations, as described at the end of the article, but I believe it will be usefull for many people.
When someone with programming experience from other computer platform starts with Arduino, he or she usually finds it surprising that there is no debugger. After you run your program, there is no way to see what is happening inside other than by printing messages to the serial monitor and/or blinking LEDs. True, it is possible to debug your programs this way, but there are ocasions when stepping through the code or looking at the variables at certain moment can save you a lot of time.
You may think "OK, this is microcontroller; I just have to live with this". But that is not true. Microcontrollers normally have similar debugging capabilities as the "big computers" these days. It is just that the Arduino platform was designed without debugger - there is no debugger interface in the IDE and there is no direct hardware support on the board (in the hardware) either. This is not to say that Arduino is bad. Omitting the debugger is a valid design decision and it is true that for the intended "non-programmer" audience debugger could be just too complicated. But Arduino became so popular that it is now used by almost everyone who needs to do something with microcontrollers, including programmers. And for those people debugger is a useful thing.
There is one misconception about Arduino debugging which seems to be quite common - that if you use a real IDE, for example Eclipse or Atmel Studio, to develop your Arduino programs, you will be also able to debug it as well. It is true that these IDEs contain debugger, but there is no way for your program to communicate with the debugger . In reality the ways to obtain debugger functionality with Arduino are:
- Buy an external debugger device (for example, AVR Dragon) and connect it to your Arduino board. You will need to do small modification of your Arduino board.
- Visual Micro debugger plugin for Atmel Studio. I tried it more than a year ago, so please check this for yourself if in doubt, but at that time I did not consider this to be a real debugger. It dit not support stepping through the code and it was based on hidden code inserted into your program before build to communicate with the debugger.
- This debugger
In January 2017 the support for Arduino Mega (ATmega2560 and ATmega1280) has been added. I also found out that it is sometimes possible to use direct serial connection without the TCP-to-Serial converter (proxy). This seems to work with all board on Windows 10, on Window 7 only with Arduino Mega. The tutorial below has been updated to describe the direct connection also.
This section provides some information about how the debugger works. If you just want to start debugging as soon as possible, feel free to skip to the next section.
Modern microcontrollers contain circuitry to support debugging the program inside them. In case of ATmega328 microcontroller used in Arduino Uno, this circuitry is called debugWire. As mentioned above, the problem is that the Arduino platform is not designed to use it and moreover you need an extra piece of hardware to talk with the microcontroller over debugWire, which costs extra money and complicates things.
If we do not want to (or can not) use debugWire, we can use the serial line for debugging. For this to work, the program in the MCU must "talk" with the debugger - our program must contain some code which handles this communication. In principle this is wat you do when you use Serial.print() to output some information to debug your program. But it is not comfortable to stuff your code with the Serial.print() commands. It is better to have a piece of code which talks with the debugger in the background, so you do not need to worry about it. Such a piece of code was invented long time ago, in the times when serial line was the only way to communicate with the MCU. It is usually called remote stub. This stub has to be able to control the program - to stop it at certain points, read and write memory etc.
What about the other side - the PC, the computer where your IDE is installed? You could probably imagine writing a program for PC which talks to the debugger stub. But there is no need to reinvent the wheel. There is GDB - the GNU debugger, which can handle this and it is supported by many IDEs, including Eclipse.
To summarize, the debugger described in this article is a GDB stub for the ATmega328 microcontroller. It is able to communicate over the virtual serial port provided by Arduino board with GDB debugger on the PC. This way you can debug the program in Arduino using GDB with (or without) some graphical front end (debugger GUI), such us the one included in eclipse IDE.
How to use the debugger
Please note that setting up all the things needed for debugging your programs in Arduino may seem quite complicated, especially if you only have experience with the Arduino IDE and start from scratch. If, on the other hand, you are experienced programmer, this should not be too hard.
Here is overview of what needs to be done:
- Set up Eclipse IDE to be able to build (and debug) your programs for Arduino. There are numerous tutorials dealing with this elsewhere, for example my own here on codeproject: Creating Arduino programs in Eclipse.
- Add debugger library to your Arduino program - this library (driver) is provided with this Article
- Set up the debugger (GDB) in Eclipse.
The documentation provided with the source code covers all the steps and it may contain more up-to-date information than this article. But anyway, here is a tutorial on setting it up.
Setting up Eclipse IDE
As mentioned above, this is covered in my earlier article here. Please use this article to set up your development environment.
There is one important step needed which is not covered in the article - adding the GDB Hardware debugging launch configuration to eclipse. To do this:
In Eclipse go to menu Help > Install New Software…
In the “Work with” box enter the following update site address and press the Enter key:
After a while the list in the window will display some items.
Expand the CDT Optional Features category and select the C/C++ GDB Hardware Debugging.
Follow the wizard to install this feature and restart Eclipse when prompted.
Note for Arduino Mega
When configuring the AVR eclipse plugin to upload your program (using avrdude), use:
- For ATmega2560 profile “Wiring” and baud rate 115200.
- For ATmega1280 profile “Arduino” and baud rate 57600.
Adding debugger support into your program
First, extract the attached zip file into some folder on your computer, for example, c:\avr_debug. Preferably without spaces and/or special characters in the path.
Next, add avr8-stub.c and avr8-stub.h files into your project in Eclipse.
These files are located in the avr8-stub folder in the avr_debug folder.
You can drag the files from your file manager and drop them on your project in the Project Explorer view in Eclipse.
Select Copy files option in the File Operation window which appears after dropping the files.
Open Properties of your project (Alt + Enter) and go to C/C++ Build > Settings.
Select AVR C++ Compiler > Debugging. In the “Debug Info Format” select dwarf-2.
Do the same for the AVR C Compiler.
Close the Preferences window with OK.
In your source file include the debugger header avr8-stub.h
And at the beginning of main() call
TIP: you can also insert call to
breakpoint() function into your code and the probram will stop at that line. But you can also insert breakpoints "dynamically", when debugging.
Here is example program to try:
Try to build the program.
There will be some build errors. The linker complains about “multiple definition of __vector_1 and vector 18. These are interrupt vectors for the INT0 external interrupt (on pin 2) and interrupt from UART module which signals that a character was received via the serial line. Both these interrupts are needed for the debug driver to work, but are also used by the Arduino software library.
To fix this:
Expand the Arduino folder in your project in Project Explorer in Eclipse and locate the HardwareSerial0.cpp file.
Right-click this file and from the context menu select Resource Configurations > Exclude from Build…
In the window which opens select both Debug and Release configurations and click OK.
Now the HardwareSerial0.cpp file will not be built with your program. This will solve the multiple definition for vector 18 (UART), but it also means the Arduino Serial functions will not work. Note that this applies only to this program (project), not to other programs you create either in Eclipse or in the Arduino IDE. You are not modifying anything in your Arduino installation.
Repeat the same procedure for the WInterrupts.c file. That is, exclude this file for built as well.
This solves the multiple definitions for vector 1, but by excluding WInterrupts.c from build, your program cannot use the
attachInterrupt Arduino function at all. If you need to use
attachInterrupt in your program, you can exclude just the definition of vector 1 in this file. The easiest way is to replace the original file with WInterrupts.c file provided with this article in avr_debug/arduino - see the readme.txt file for details. It will not affect your other Arduino programs. Alternatively, you can modify the file yourself as described in the avr_debug/doc/avr_debug.pdf.
Build the project. There should be no errors now.
Upload the program to your Arduino board.
Configuring the debugger in Eclipse
For this section, it is assumed that you already have a program with the debug driver (stub) loaded in your Arduino board. In other words, that you have completed the previous section of this tutorial.
Right-click your project in the Project Explorer in Eclipse. From the context menu select Debug As > Debug Configurations...
In the Debug Configurations window select GDB Hardware debugging item and click the New launch configuration button in upper left corner of the window.
This will create new launch configuration under the GDB Hardware Debugging item.
Note: If you do not see GDB Hardware Debugging in the list, you probably haven’t installed this type of configuration. Please see the Setting up Eclipse IDE section above.
Select the new configuration under GDB Hardware debugging to configure its properties. The name of the configuration is based on the name of your project. It is test1 Debug in the picture below.
On the righ select Startup tab.
Uncheck (clear) all the boxes (Reset and Delay, Halt, Load image and Load symbols).
Switch to the Debugger tab.
In the “GDB command” field enter (or browse to) the path to the GDB executable avr-gdb.exe, followed by the path to your "executable" (.elf) file.
The path to GDB is [arduino location]\hardware\tools\avr\bin\avr-gdb.exe.
The path to your file can use eclipse variables.
Here is my example for this field:
Tip: Use the Browse button to select the avr-gdb.exe. Then enter space and paste the following line:
Check the” Use remote target” box.
Now there are two options for connecting to the debugged program.
- Direct serial connection
- Connection via TCP-to-serial port converter (proxy server)
The direct serial connection is easier to use, so I recommend trying this first. I was able to use it on Windows 10 for both Arduino Uno and Mega; on Windows 7 for Mega only. If it does not work, use the connection via TCP-to-Serial proxy.
Note that on Linux you can always use the direct serial connection; just enter the name of the device instead of COMx, e.g. /dev/ttyACM0.
To debug via direct serial connection...
In “JTAG device” select “Generic Serial”.
In the "GDB Connection String" enter the COM port number where your Arduino board is connected, e.g. COM5.
You are now ready to debug. Click the Debug button in the bottom of the Debug configurations window and continue with the Debug session chapter (skip the TCP-to-Serial section below).
To debug via TCP-to-Serial proxy...
Please use this connection if the direct serial connection described above does not work.
In “JTAG device” select “Generic TCP/IP” and enter:
Host name or IP address: localhost
Port number: 11000. Note that the default port number is different, change it to 11000.
Click the Apply button to save the changes, but do not close the Debug configurations window yet.
Now use your file manager (e.g. Windows Explorer) to open the folder where the source code package provided with this article is located. For example, c:\avr_debug.
You should see a start_proxy.bat file in this folder.
Open the start_proxy.bat file in Notepad or other text editor (right click + Edit or Open with...).
Change the number of the COM port in this file. There is this line:
hub4com-184.108.40.206-386\com2tcp --baud 115200 \\.\COM15 11000
Just change the number after COM from 15 to the number of the COM port on your computer where you Arduino board is connected.
Save and close the start_proxy.bat file.
Run the start_proxy.bat file (double click it).
This will start a convertor between TCP/IP port 11000 used by the GDB debugger (which we configured above) and the serial port to which your Arduino is connected. You should see a console window with some information. This window will be opened all the time during debugging.
You may be prompted to unblock the port by Widnows firewall, allow this.
Now return to Eclipse. We still have the debug configurations window opened.
Click the Debug button at the bottom right of this window.
When you click the Debug button, Eclipse should ask you if you want to switch to debug view (Perspective). Answer Yes.
You should see the program stopped in debugger, as in the following picture.
You can now step through the code (Step over button in toolbar) to see the LED turn on, etc.
Note that after stepping from the end of the loop, you will find yourself in the Arduino library’s main.cpp file. If you continue stepping, you will get into your loop again. Also, it seems as if the setup function was called again, but this is just discrepancy between the code you see in the C language and the real code generated by the compiler; the setup is not really executed again.
You can use the Resume button to let the program run until it hits the breakpoint we have “hard-coded” at the beginning of loop.
Of course, you can also place breakpoints by right-clicking in the left margin and selecting Toggle Breakpoint from context menu.
If you want to let the program run at full speed, you need to edit the code to remove the call to breakpoint() function. To do so you need to terminate the debug session first. Then rebuild and re-upload the program into the board before connecting with the debugger again. The procedure for changing the program is as follows:
- Terminate debug session with the red square Terminate button.
- Close the command prompt window with tcp2com proxy (needed to free the COM port).
- Change your program, build it and upload to the Arduino board.
- Start the start_proxy.bat script again.
- Start debugging in Eclipse (expand the Debug button in toolbar and sclick your debug configuration name).
- If you receive error when launching, it may be because the project is not selected in Eclipse Project Explorer. Just click on the project in the left window and try again. Or right-click the project, select Debug As > Debug Configurations and start the debug session from there.
If you do not place the call to breakpoint() function into your program, it will run (LED blinking) right after upload. When you connect with the debugger, it will stop at random line; most likely somewhere in the delay() code. You may see something similar to this:
In the upper window (Debug) there is so called call stack - the “chain” of calls which led the program to its current location. The program is stopped inside the micros() function, which was called from the delay() function, which was itself called from loop() function and so on.
To quickly get into your own code, click loop() in the Debug window (select the loop function). This will display code of the loop function in the lower window. Now you can place a breakpoint, for example, on the digitalWrite(13, HIGH) line, and resume the program. It will stop at the breakpoint.
It may seem difficult to set up everything as described above. And it really is. One reason is that the above tutorial covers everything at once. It is possible (and perhaps better) to start with simple program written in plain C language and when it works, move up to programs which contain the Arduino software framework. This is how the tutorial in the documentation provided in the attached file is organized. So if the above procedure seems too complicated, please use the step-by-step tutorial in avr_debug/doc/avr_debug.pdf.
Limitations of the debugger
The debugger presented here still has lot of room for improvement. When you want to use the debugger, your program cannot use the Serial functions (the hardware serial) and it cannot use one pin with external interrupt such as INT0, INT1, etc. These are both used by the debug driver. In more details:
The debugger communicates with the program via serial line, so the serial line cannot be used by the program. This may look like a big problem if you write your programs "the Arduino way", that is, print debugging messages to serial line. When you debug with a debugger you usually do not need to print messages. If you do need it, there is function
debug_message which can be used to send mesages to debug console in eclipse. If you need to send data from your program (for normal operation, not for debugging), then you have to first debug the program without the serial output and then enable the serial output and disable the debugger.
The debugger also uses external interrupt to be able to stop the program at breakpoints (other than those which are hard-coded in the program with the breakpoint function). This means one of the external interrupt pins (pin 2 or 3 on Uno) cannot be used in your the program. By default the INT0 pin is used, but you can change this in the avr-stub.h file in the definition of AVR8_SWINT_SOURCE. On Arduino Mega I recommend using INT6 or INT7 because the corresponding pins are not connected at all on this board. So in fact you do not waste any usable pin. It would be possible to use pin change interrupt for the same purpose, but still one pin would need to be reserved for the debugger. The ultimate solution would be to place the breakpoints in flash memory. I believe it is possible but so far I haven't had the time to implement it. This would solve also the next problem.
The program executes at much lower speed when breakpoints are set in the program. This is because the breakpoints are implemented using a little strange feature of the Atmel AVR architecture - there is always one instruction executed after return from interrupt service routine (ISR) before the same or other ISR can be entered again. Thanks to this feature it is possible to single step the program and compare current program counter with desired breakpoint addresses. But having an interrupt triggered after each instruction does slow down the program a lot. Unfortunately, the only other way to implement breakpoints is by modifying the program in flash memory - which is not easy to do and not done so far.
I should mention that it would take much longer to develop this debugger if it were not for some older projects dealing with GDB stub for Atmel AVR. Please see the header of the avr8-stub.h file for more information and links.
The code for this article is also available on github.com at: https://github.com/jdolinay/avr_debug.
October 9, 2015 - First version.
January 30, 2017 - Updated version with Arduino Mega support.