Managing Serial.print() as a debug tool

Like most users, I have become adept at using Serial.print() as a tool to assist debugging. The trouble is that in the process the program becomes littered with such statements, and it is a significant chore to remove these at the end to get production code. Leaving them to "dangle" is bad practice, and they should be either deleted or commented out. Note I am not whinging about a program with a dozen lines either, many of my programs are thousands of lines with pin change, timer and conventional interrupts and tracking down every single one of those lingering Serial.print() in every tab takes considerable time.

I was watching YouTube not long ago and came across a suggestion to use the pre-processor to help alleviate the problem. My apologies to the YouTuber in question, I have lost the link, or I would quote it for reference.

The suggestion is not rocket science. Basically the idea is to create a substitute for Serial.print() which can be made null easily when you are done debugging. Like this:

#define debug_print                                                // manages most of the print and 
println debug, not all but most

#if defined debug_print
   #define debug_begin(x)        Serial.begin(x)
   #define debug(x)                   Serial.print(x)
   #define debugln(x)                 Serial.println(x)
#else
   #define debug_begin(x)
   #define debug(x)
   #define debugln(x)
#endif

This is pretty obvious but, for the sake of completeness (as the pre-processor is anything but obvious to anyone new to C), I will give a short explanation. If debug_print is defined then a new series of "functions" are created which are used for debugging just like Serial.print() but when you are done you just undefine debug_print (by commenting it out) and all the associated statements just vanish as the pre-processor does its substitution. So for example, you use debug(variable) to print the variable while debugging. When the bugs are all dead (or at least dormant) just undefine debug_print, recompile, download, and you are done.

Now, this works pretty well, and I can't see any real downside. (But please comment if you can think of anything). It is pretty clever of the original YouTuber, 100% obvious once you see it, and I cannot comment if it is original. It is certainly helpful.

But there is a problem. Serial.print() usually takes one argument, but it can take at least 2. Like when I want to see the output in hex, like this: Serial.print(variable,HEX). When tried this with debug it looks like this:
debug(temp,HEX);

But as you would expect the compiler will have none of it, returning an error:
macro "debug" passed 2 arguments, but takes just 1

I can see exactly why it complains, and that is not the question.

OK, I thought, just pop the arguments in parentheses to make it appear like one parameter:
Like this:

Serial.print((temp,HEX));    //original
debug((temp,HEX));            // debug version

Surely the parser will handle this and strip off the redundant brackets. And indeed it compiles. Example Code:

#define debug_print                                                // manages most of the print and println debug, not all but most

#if defined debug_print
   #define debug_begin(x)        Serial.begin(x)
   #define debug(x)                   Serial.print(x)
   #define debugln(x)                 Serial.println(x)
#else
   #define debug_begin(x)
   #define debug(x)
   #define debugln(x)
#endif

byte temp;

void setup() 
{
 debug_begin(115200);

 temp = 42;                                     // pick a number, any number

 Serial.print(temp,HEX);                // original for reference
 Serial.println();
  
  Serial.print((temp,HEX));             // extra brackets
  Serial.println();
  
  debug((temp,HEX));                    // extra brackets
  debugln();
}

void loop() 
{
  // no loop needed
}

and the result is:
2A
16
16

2A is correct, the rest are not. And you get 0x16 no matter what the variable is, there has to be a clue there.

So I am wondering why the extra brackets make so much difference. I was expecting the C parser to manage this. Obviously not. There is something I do not understand.

Also, has anyone with more experience with C than me got a fix for this other than defining another debug substitute with 2 parameters, and use the one you need and perhaps what I am missing about the parser?

Regards,

I use this

#define DEBUG 1    // SET TO 0 OUT TO REMOVE TRACES

#if DEBUG
#define D_SerialBegin(...) Serial.begin(__VA_ARGS__);
#define D_print(...)    Serial.print(__VA_ARGS__)
#define D_write(...)    Serial.write(__VA_ARGS__)
#define D_println(...)  Serial.println(__VA_ARGS__)
#else
#define D_SerialBegin(...)
#define D_print(...)
#define D_write(...)
#define D_println(...)
#endif
 

That takes care of the multiple versions of print

1 Like

Those pesky copypastas

Oops… typing on my iPhone… indeed meant write() there

Thx — corrected above

I just use the search function in the IDE to locate all the instances of Serial.Print and comment them out or remove them as needed. This means you don't miss any.

However since serial print is used to debug, then I normally remove them or comment them out once that specific problem has been fixed.

If the code is going for publication, and hence an example to readers of the book, I normally leave them commented out.

1 Like

btw, on this:
The parenthesis have precedence on the function call so what is within parenthesis gets evaluated. The comma operator says that you first evaluate the left part, which basically does nothing there is no side effect, then evaluate the right part and return whatever that expression gives you. So it’s like you had written

Serial.print(HEX);

Which will print the value associated with the defined keyword HEX (16 in decimal, not 0x16)

Thanks Mike, I know that process all too well, having done it countless times. When I do that, the most annoying thing is that the search brings up serial. prints which are already commented out. As a result, if happen to have had multiple bugs separated temporally in the same block of code I end up looking at previous lines as well as the current ones. Just tedious and time consuming. I was hoping to either automate it or preferably eliminate it. Regards,

Thanks JML, that looks the answer to making it work. I will have to research VA_ARGS and this ellipsis. New to me, thanks again.

Thanks again, JML, that makes sense. I now have both parts of the question answered and I have a better grip of the parser.

Great - have fun !

you can actively control debugs using defines and a debug variable.

unsigned int debug = DBG_BRAKE;

a header file

koala.h:#define DBG_ENGINE   2
koala.h:#define DBG_BRAKE    4
koala.h:#define DBG_CYLPRESS 8
koala.h:#define DBG_MENU     32
koala.h:#define DBG_BUT      64
koala.h:#define DBG_KEYPAD  128

usage (on esp32 which allows prints)

    if (DBG_CYLPRESS & debug)
        printf (" fill %.1f, consume %.1f", st.fill, st.consume);

the serial interface can of course be used as a command line interface including a command to set "debug"

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.