11
\$\begingroup\$

I started coding for Arduino very recently. And these bugs in the code are killing me. As there is no hardware debugger in the Arduino, serial.print() is my only resort. What are the methods/practices you implement to debug Arduino code?

\$\endgroup\$
9
\$\begingroup\$

Modularity is your friend. Write your main loop to do its thing by calling functions, which call functions, ..., until the level at which your functions would be simple. Start with the main loop and the next level down, make stub functions; either empty:

function foo(){
    ;
}

or fake:

function read_temperature(){
    return(95);
}

, that do nothing but return whatever the calling level needs for it to be able to continue. When that level works, move down a level and start filling in simple code that likewise calls stub functions. Gradually un-stub a function at a time until you have a working application.

To debug a function that returns a bad value, or to create one without any influence from the rest of your application, you can build scaffolding - a simple sketch that just feeds the function some example values, and within the function, print out parameter values and some intermediate values, until you gain some insight into what part of the function is failing. I've even made faking-functions that prompt me on the terminal for a value to return. (Obviously this technique can only work if the system can tolerate the relatively glacial speed of us humans! Another use for scaffolding.)

Stubbing works especially well to stand in for functions that interface to hardware, allowing you start bringing up the application before you have to dive into data-sheets, timing issues, and other minutiae (like, not having the parts!) that might otherwise stall your progress.

Speaking of timing issues, toggling an output pin at a particular point in your program, such as entry to and exit from an ISR, gives you a square wave at the Arduino pin whose frequency or duty cycle can give you some insight into the internal timing of your program. The direct port-I/O form, e.g.,

PORTC ^= 0x01;

, will distort the timing less than calling digitalWrite(). Useful if you have a 'scope handy, or one of the DMMs with the ability to measure frequency and/or duty-cycle.

Similarly, you can use an analog output pin to output a numerical value to your meter from inside the program without disturbing the timing too much or bloating the code with the serial I/O functions. Use the direct I/O forms here, too.

\$\endgroup\$
0
\$\begingroup\$

Blink LEDs, print things on the serial port, and write and debug small sections of code at a time, sometimes only a few lines.

There are times where you can modularize. If in C, for example, you can develop and test a computation function for example that doesn't touch hardware on a host computer, other processor, wrap the function with a test bench to feed it inputs and check the outputs, etc.

Another similar way might be to use an instruction set simulator if you have access to one (if not, it is a very educational and rewarding project, after you have done a few of them you can bang one out in a weekend or two). Even better if someone has a Verilog or VHDL clone of the processor (OpenCores for example) you can try GHDL, Verilator, or Icarus Verilog. It might be close enough to include the peripherals you are interested in, and you can get signal level visibility into what is going on inside.

Granted it is probably not a perfect clone, but it might be good enough. Verilator makes it really really easy to create peripherals in C/C++ so you can simulate whatever you have your AVR device hooked up to.

UART output and blinking LEDs and/or blinking GPIO lines and using an oscilloscope or voltmeter. The key to not going crazy is writing and debugging small sections of code. I is better to write 10 lines at a time and perform 100 tests than 1000 lines and try to debug them all in one shot. Especially when you find out most datasheets and programmers reference manuals for hardware are not always correct. Some hacking is always required.

\$\endgroup\$
0
\$\begingroup\$

Better than directly serial.print, use macros. Example:

#ifdef TRACE1
#define trace1(s) serial.print(s)
#define traceln1(s) serial.println(s)
#else
#define trace1(s)
#define traceln1(s)
#endif

Use it like this:

function doIt(param1, param2) {
    trace1("->doIt("); trace1("param1: "); trace1(param1); trace1(" param2: "); trace1(param2); traceln1(")");

    ...

    traceln1("<-doIt");
}

You might have different trace levels (#ifdef TRACE2 ...) with more details.

And you might use the "F" macro, (trace1(F("param1"));). The "F" macro prevents that the string will use the extremely limited amount of SRAM.

\$\endgroup\$
0
\$\begingroup\$

To make the use of serial.print more controlled you can define a global boolean variable to toggle debug on and off. Any lines of serial.print will be wrapped within an if statement that will be executed only if the debug flag is ON. This way you can leave the debug lines in the code even when you are done, but make sure to set the debug flag to OFF later.

\$\endgroup\$
3
\$\begingroup\$

The Visual Micro plugin for Visual Studio provides Arduino Debug. Includes source code trace and break also allows expressions and variables to be "watched" and/or modified.

enter image description here

\$\endgroup\$
  • 1
    \$\begingroup\$ Note that this is done via automatically generated serial prints inserted into your code. \$\endgroup\$ – per1234 Jul 20 '17 at 9:22
  • \$\begingroup\$ That's right except the serial or wifi prints and reads are inserted into a temp copy of the code not the real code! It's all explained clearly in the visualmicro.com docs and for those few that have hardware debuggers (not usually for Uno etc) they are also supported. Each method has it's own advantages and dis-advantages. \$\endgroup\$ – Visual Micro Jul 20 '17 at 22:00
  • \$\begingroup\$ From Debugger for Arduino: "It did not support stepping through the code and it was based on hidden code inserted into your program before build to communicate with the debugger." \$\endgroup\$ – Peter Mortensen Sep 16 '17 at 10:20
  • \$\begingroup\$ That's right. It avoids the need for users to add serial prints, allows named vars to be updated while the cpu runs, shows graphs and digital pins etc. It's different from a hardware debugger but fast and easy and has other benefits. This is all well documented on visualmicro.com so please have a read. If you board supports gdb or you use using atmel studio there are other debug options of course. It all depends on your expertise and what hardware you have. If you have genuine Arduino, in Atmel studio you can combine arduino with atmel debug or sim. \$\endgroup\$ – Visual Micro Sep 16 '17 at 15:15
5
\$\begingroup\$

3 Other Techniques:

  • Build up the functionality of a program by slowly testing at each stage, that way you confront only a small set of bugs at a time.

  • Build the program around a command interpreter so you can work with sections at a time as in here.

  • Pulse out at significant times and use a scope.

\$\endgroup\$
2
\$\begingroup\$

Going from luxury tools like ARM or other platforms (AVR, PIC with decent tools) I agree that the Arduino debugging facilities are too limited. But it is a starter tool with low entry.

Serial.print() is your friend. To my particular project (college), I didn't had any LEDs attached, so Serial.print() it is. If I want to test out whether code runs properly through the statements, I usually place Serial.print("A"); , then going to B, C, etc. or something through the section I am debugging. I compare the debug letters with what I expect it should do.

Other than that, there is no breakpoints or code stepping. Arduino is nothing more than a board with a AVR atmega chip, a bootloader + development environment and a ton of software libraries. Unfortunately, working with a bootloader limits the debugging capabilities.

\$\endgroup\$
6
\$\begingroup\$

I use Serial.print() and I make LEDs flash.

That's pretty much all you can do.

Also, I make sure the code is readable and easy to understand. Break things down into simple steps and create functions for each step, so you can see the exact sequence of events.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.