Arduino | Debugging

Using Serial

This is the most common way to debug on Arduino... and also the most frustrating in the beginning. Here are some problems I've encountered, and the best solution that I have found.


Problem 1: Cross-platform development

When you develop libraries for multiple platforms (i.e. Raspberry Pi Linux, Windows IoT, etc.) you don't want to be switching macros everywhere with Serial.println(). That is extremely tideous and painful, and not really "cross- platform". On top of that, everywhere you want to error check, you now have to put a statement for each platform that respectively does printing.

#ifdef ARDUINO
Serial.println("Error!");
#elif LINUX
... other printers ...
#endif

Solution 1: Cross-platform statements defined in library

So then I decided it would be a great idea to make a debugger class a part of the library that would capture the different methods by which a platform could print. But this of course meant each platform had to have it's own implementation of the logger class. Logger::WriteLine()


class Logger
{
public:
    static void WriteLine(const char* format, ...);
}

Now each platform would just implement WriteLine and you could then call the method anywhere in code.

Problem 2: Smoothest?

Uh, no. I didn't want to ship a library with a logger in it. Sure you could use a .gitignore, but then the library won't compile where it is dependent on Logger::WriteLine() in a deliverable. So I needed a method that was well established and already cross-platform.

Solution 2: Isn't there printf?

Well, yes, but not for Arduino. At the time, the only way to use printf on Arduino was if you had the Azure-C-SDK. Since I wanted the only dependency to be the C99 library, I avoided using printf for some time. Being a naive coder at the time, I hadn't realized GCC libc exported the method. For those that don't know, inside of your local Arduino15/ directory, you have the GCC compiler, 4.8.3-2014q1 as of writing this. See more on Compilers and IoT. Packaged with the GCC compiler, is the C library, libc. libc carries all dependencies for C99, however not all methods are implemented.

Inside libc include/stdio.h ...

int _EXFUN(printf, (const char *__restrict, ...)
    _ATTRIBUTE ((__format__ (__printf__, 1, 2))));

So, just implement the method at the top of your sketch *.ino.

#include <stdio.h>
#include <assert.h>


int printf(const char* __restrict format, ...)
{
    char buf[300];
    va_list ap;
    va_start(ap, format);
    vsnprintf(buf, sizeof(buf), format, ap);
    Serial.write(buf);
    va_end(ap);
    Serial.println();
}


void __assert_func(const char* srcFileName, int srcFileLine, const char* fnName, const char* val)
{
    printf("!> Assertion Failed:\n>\tIn %s on line %d\n>\tIn %s\n>\tFailed on: %s", srcFileName, srcFileLine, fnName, val);
    abort();
}


void setup() {
    Serial.begin(115200);
}


void loop() {

}

In addition to printf, I use a lot of assertions to prevent dumb mistakes. In Arduino, assert doesn't do anything other than hang the device. So implementing assert in a way that provides us with an idea of where a failure occurs leveraging printf is very helpful.


These are just a couple of notes on Serial debugging that I hope will help, but they aren't perfect. For example, you want to avoid using printf() in a interrupt service routine. Nevertheless, this is much more helpful than being limited to Serial.println() in your sketch, or causing your library to become Arduino specific for a period. Remember to initialize Serial in setup in order for the print statements to work. Note that because of this, you cannot use printf() in constructors if the objects being constructed are static or at file scope. Construction would then take place prior to setup, consequently before Serial is ready.