The best tools to make your project dreams come true

Login or Signup

3/21/2019 | By Maker.io Staff

How To Debug Your Arduino

 

The Arduino range of microcontroller boards are incredibly useful in prototyping situations and can be used in many DIY and professional projects alike. However, one feature not available on the Arduino that other devices such as dedicated PIC and AVR circuits benefit from is debugging, which allows users to find problems with their code and then fix them. Here, we will learn how we can use simple code techniques to debug an Arduino!

The Hard Way

While Arduino boards are based on standard microcontrollers, such as the ATMega328, they use a boot loader, which is a special program located at the start of program memory. This boot loader, upon starting, will usually look at the USB or UART port for incoming data from a PC or other computing device. If specific signals are sent by the PC (often with the use of the RTS and DTR lines) the boot loader begins to load in bytes over the serial port (which represent bytes in program memory).

These bytes are then saved into program memory, and when all bytes have been transferred, the Arduino will then execute the code in program memory. This is how a PC can upload new programs to an Arduino without the need for a special programmer that connects to the ICSP lines. But since the boot loader itself is a program executed by the Arduino, and there is no dedicated programmer using the ICSP lines, the executing program cannot be stepped through or debugged.

Therefore, the only method for getting true debugging would be to use an external debugger that could halt the execution of the Arduino and then step through the assembly program line by line. But this causes another issue; you would not be able to debug the user-friendly C code but instead would be debugging raw assembler.

Unless an engineer can determine a way to link a debugger to the Arduino IDE, know where the boot loader sits, and understand how the program has been uploaded, the only option a debugger has left is to read the contents of all the memory (which is all in machine language).

The Easy Way

So, it is established that unless a dedicated programmer and debugging IDE are used, real debugging cannot be done. However, the Arduino does have some useful features that can be used to debug a program to try and find out where problems exist. One of the most useful tools for tracking down problems is the Hardware Serial Port that can be tied to a computer terminal -- just about anything can be printed to the screen.

For example, a variable could be printed to see the value of a loop counter, a string could be printed to see if a piece of code was executed, and the Serial input could even be used to inject values into a program to test functions. One of the simplest methods involves placing unique print string instructions at key locations in the code; if the code gets stuck in a specific place, the user will see that only some of the strings were printed. This can be used to isolate the exact code snippet that is causing problems, which can then be looked at more closely.

EXAMPLE 1 – DETERMINING IF A FUNCTION IS HALTING OR FREEZING THE ARDUINO

Copy Code
Serial.println("code got to part 1"); // Print this if the execution gets to here
myFunction(); // Try to execute myFunction()
Serial.println("code got to part 2"); // If myFunction does not freeze then print this
anotherFunction(); // Try to execute anotherFunction()
Serial.println("code got to the end"); // All the functions executed without stalling 

EXAMPLE 2 – PRINTING THE VALUE OF A VARIABLE TO SEE IF ILLEGAL CHARACTERS ARE BEING PRINTED

Copy Code
for(int i = 0; str[i] != 0x00; i ++)
{
Serial.print(str[i]); // Print the character at i
Serial.println(); // Print a new line
Serial.println(i); // Print the value of i for checking
} 

EXAMPLE 3 – VALUE INJECTING FOR TESTING PWM OUTPUT

Copy Code
// Determine if the user has sent a value to write
if(Serial.available() > 0)
{
	// Get the integer value from the Serial port
	pwmTestValue = Serial.parseInt();
}

analogWrite(4, pwmTestValue);		// Write the PWM value to the digital output 4

Another useful feature is to try and compartmentalize code, so that each piece of code can be passed through a testing function and checked against an expected result. The program could pass a series of different inputs and the outputs checked; any errors would result in the Arduino printing an output error message or halting entirely.

These testing functions could then be placed into an external header file that keeps the debugging code separate from the main code, which can then be removed from the project in the final build. Such a method for debugging, however, is only helpful in scenarios where code can be compartmentalized, and the use of such as method may see the main code consist of functions (as opposed to readable code).

Copy Code
int myFunction(int i, int j); // A custom function that multiplies i and j together

void setup()
{
// Testing myFunction for the correct answer
if(myFunction(2, 2) != 4)
{
Serial.println("First test failed"); 
}

// Testing myFunction for the correct answer
if(myFunction(2, 8) != 16)
{
Serial.println("Second test failed"); 
}
}

If the Serial Port is in use or testing functions are not particularly helpful, then the I/O can be used as inputs and outputs for debugging. LEDs connected to output ports, for instance, can be used to signal if the main loop is looping correctly or if a piece of code is being executed properly. Switches can be connected to interrupts, which can force the Arduino to execute a specific piece of code or even reset the whole system to get the Arduino out of an infinite loop.

Copy Code
Serial.println("code got to part 1"); // Print this if the execution gets to here
myFunction(); // Try to execute myFunction()
digitalWrite(4, HIGH); // Turn the LED on for one second here to indicate that it worked so far
delay(1000);
digitalWrite(4, LOW); 
anotherFunction(); // Try to execute anotherFunction()
digitalWrite(4, HIGH); // Turn the LED on for two seconds here to indicate that it worked
delay(2000);
digitalWrite(4, LOW);