How to avoid extremely redundant `#ifdef DEBUG` instructions for every Serial.print?

I find myself using a lot of instructions like this:

#ifdef DEBUG
Serial.println("foo");
#endif

Or:

#ifdef DEBUG
Debug.println("foo");
#endif

This makes it unnecessarily complicated to read my code.

Is there a way to not use #ifdef DEBUG but still compile without the Serial/Debug print commands when DEBUG is not set?

Would it maybe be possible to have something like this:

#ifdef DEBUG
    Stream& Debug = Serial;
#elif
    // Create fake Debug class extending from stream
    class Debug : public Stream {}
#endif

The idea of course would the that the compiler is smart enough to see that class Debug : public Stream {} creates a useless class that doesn't do anything and thus it and all lines using it can be optimized away.

Any ideas?

Or just: (untested uncompiled)

#if DEBUG_ACTIVE
#define DEBUG(x) Serial.print(x)
#else
#define DEBUG(x)
#endif

Then all your debug lines just disappear if you undef DEBUG_ACTIVE and your code just needs to say:

DEBUG(someValue);

That wouldn't really work for me because I use print, println and printf a lot and I would like to stay as close to the original interface as possible.

Set up different defines. I've seen one member here who has awhole list of stuff like this they can use. Even stuff that print on timed intervals.

The trick is having the define controlled by another define so i is either creating a print statement of some type or nothing.

I really prefer working with the actual object. In my opinion it makes the code easier to read, easier to understand, especially for new people working on it. It's also more intuitive imo.
I dislike the idea of creating a ton of macros just to handle all possible cases.
The automatic optimization idea (if possible) seems like a much less invasive drop-in replacement.

Use a function to handle printing and include the #ifdef directive within that function. This approach makes the code much easier to read. The function will still be called, but if the directive excludes printing, nothing will be output.

I always do it similar to the approach of @Delta_G , but with printf:

#ifdef debug
    #warning "Debug-printing is active"
     #define DB_PRINT( x, ... ) {char dbgBuf[80]; snprintf_P( dbgBuf, 80, PSTR( x ), ##__VA_ARGS__ ) ; Serial.print( dbgBuf ); }
#else
    #define DB_PRINT( x, ... ) ;
#endif

This is for AVR. Of course you don't need the snprintf if your core supports printf directly.

Within code you simply write something like:

    DB_PRINT("Servoattach: pwmNbr=%d, servoIx=%d, Pin=%d\n\r", servoData.pwmNbr, _servoData.servoIx, pinArg );

Hi @felic. Here is a simple system for switching on and off serial debug output that doesn't require adding any additional lines to your print/println, etc. calls:

Add this code near the top of your sketch:

#define DEBUG true  // Set to true for debug output, false for no debug output.
#define DEBUG_SERIAL \
  if (DEBUG) Serial

Then use code like this to initialize the serial communication:

DEBUG_SERIAL.begin(9600);

If you want your program to wait for Serial Monitor to be opened before running when using native USB boards (e.g., Leonardo), add this line:

#if DEBUG == true
  while (!Serial) {
    ;  // wait for serial port to connect. Needed for native USB port only
  }
#endif  // DEBUG == true

(unfortunately yes, it does still require the addition of a couple extra lines here, but that only occurs once in the sketch)

and code like this for debug output:

DEBUG_SERIAL.println("Some debug output");

When the DEBUG macro is set to false, the compiler will optimize the calls using DEBUG_SERIAL out of the code because it knows they will never run. This means the debug output code won't use up any memory and won't slow down the execution of the program.

In the rare case where your debug system needs to read serial input, you would use an approach similar to that of the while(!Serial) code:

#if DEBUG == true
  if (Serial.available()) {
    x = Serial.read();
  }
#endif  // DEBUG == true

This system can easily be extended to allow multiple levels of debug output, still with no overhead when it's disabled:

#define DEBUG_ERROR true
#define DEBUG_ERROR_SERIAL \
  if (DEBUG_ERROR) Serial

#define DEBUG_WARNING true
#define DEBUG_WARNING_SERIAL \
  if (DEBUG_WARNING) Serial

#define DEBUG_INFORMATION true
#define DEBUG_INFORMATION_SERIAL \
  if (DEBUG_INFORMATION) Serial

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ;  // wait for serial port to connect. Needed for native USB port only
  }
  DEBUG_ERROR_SERIAL.println("This is an error message");
  DEBUG_WARNING_SERIAL.println("This is a warning message");
  DEBUG_INFORMATION_SERIAL.print("The state of pin 5 is ");
  DEBUG_INFORMATION_SERIAL.println(digitalRead(5) ? "HIGH" : "LOW");
  Serial.println("This is standard program output");
}

void loop() {}

You can use the Boolean debug macros to switch on and off other parts of your code too.

2 Likes

If it is an ESP32, you also have a built in debug message handler. You select the debug message level in the tools menu under "core debug level".

#include <esp_log.h>
. . .
. . .
log_d("Exiting function. \n"); // debug example
log_i("runNumber= (%d)" , config.getInt("runNumber") ) ;  // information example
log_w("this is a warning"); // warning example
log_e("this is an error"); // error example

//***************************************************************
//   Example of use: 
//   #define DEBUG  //                              <---<<< this line must appear before the include line
//
//   #include <DebugMacros.h>
// 
//If you comment the line:    #define DEBUG
//the Macro lines are defined as blank, thus would be ignored by the compiler
//#define DEBUG  // if this line is NOT commented, these macros will be included in the sketch
//examples:
//  This  converts to >>>>----------------------->  This OR a Blank Line.  
// DPRINTLN("Testing123");                          Serial.println("Testing123");  
// DPRINTLN(0xC0FFEEul,DEC);                        Serial.println(0xC0FFEEul,DEC); 
// DPRINTLN(12648430ul,HEX);                        Serial.println(12648430ul,HEX); 
// DPRINTLNF("This message came from your flash");  Serial.println(F("This message came from your flash"));
// DPRINT(myVariable);                              Serial.print(myVariable);
// DELAY(100);                                      delay(100);
// TOGGLEd13;                                       PINB = 0x20;  // D13 Toggle,for UNO ONLY
//
// Also, this works  #define INFO(...)  { Console->printf("INFO: "); Console->printf(__VA_ARGS__); }   >>>--->   where {} allows multiple lines of code.
// See: http://forum.arduino.cc/index.php?topic=511393.msg3485833#new

#ifdef DEBUG
//OR the next two lines
//#define DEBUG 1
//#if DEBUG || (another thing etc.)

//examples:
//#define DPRINT(args...)  Serial.print(args)  OR use the following syntax:
#define SERIALBEGIN(...)   Serial.begin(__VA_ARGS__)
#define DPRINT(...)        Serial.print(__VA_ARGS__)
#define DPRINTLN(...)      Serial.println(__VA_ARGS__)
#define DRINTF(...)        Serial.print(F(__VA_ARGS__))
#define DPRINTLNF(...)     Serial.println(F(__VA_ARGS__)) //printing text using the F macro
#define DELAY(...)         delay(__VA_ARGS__)
#define TOGGLEd13          PINB = 0x20                    //UNO's pin D13
#define PULSEd13           PINB = 0x20; PINB = 0x20       //a 62.5ns pulse is output on pin 13 "UNO"

#define DEBUG_PRINT(...)   Serial.print(F(#__VA_ARGS__" = ")); Serial.print(__VA_ARGS__); Serial.print(F(" ")) 
#define DEBUG_PRINTLN(...) DEBUG_PRINT(__VA_ARGS__); Serial.println()

//***************************************************************
#else

#define SERIALBEGIN(...)  
#define DPRINT(...)       
#define DPRINTLN(...)     
#define DPRINTF(...)      
#define DPRINTLNF(...)    
#define DELAY(...)        
#define PINMODE(...)      
#define TOGGLEd13      
#define PULSEd13

#define DEBUG_PRINT(...)    
#define DEBUG_PRINTLN(...)  

#endif
//***************************************************************

What "ton"? There should be one for Serial.print, one for Serial.println, and one for printf - what else?

While having unused debug statements "compile to nothing" is useful, there are some advantages with being able to change the debug level of the sketch without having to re-upload. The sketch would require some mechanism to listen for such a change, either through software or hardware; or it could detect some internal condition.

And if you never actually change the effective level, you have debug statements at various levels baked into your code that are easy to write and read.

You may already have the Arduino_DebugUtils library installed, perhaps as a dependency of something else. Its advanced example shows how to use something besides the default output to Serial, in addition to setting the debug level.

#include "Arduino_DebugUtils.h"
#include <SoftwareSerial.h>

SoftwareSerial mySerial(10, 11); // RX, TX

void setup() {
  mySerial.begin(9600);
  Debug.setDebugOutputStream(&mySerial);
  Debug.setDebugLevel(DBG_VERBOSE);
  Debug.timestampOn();
}

int i = 0;

void loop() {
  DEBUG_VERBOSE("i = %d", i);
  i++;
  delay(1000);
}

I don't want to over simplify things, but without a Serial.begin() or after a Serial.end()any calls to the functions do not produce any result. They are still compiled of course and do add to the compiled code, and until the tx buffer is full there is probably even some data moved thru memory, but nothing ends up in the UART FIFO, and for boards that enable or disable the pin connections. these will be disabled.

what about using a run-time changable variable

    if (dbg & D_Load)
          Serial.println (" something");

For me what approach I might take, may change depending on usage.

I will often start off with approach that many have suggested here...
I often start off simple like:

#ifndef DEBUG_BT
#undef DEBUG_BT_VERBOSE
void inline DBGPrintf(...) {};
void inline DBGFlush() {};
#else
#define DBGPrintf Serial.printf
#define DBGFlush  Serial.flush
#endif

Sometimes I want to control, which stream the debug data is printed on,
so might have method to set the Print object: like my class that I am trying to
debug might have a method like: void setDebugPrint(Print *pr);

Why most of the time when I want the debug stuff, it goes to Serial, but sometimes I don't want to pollute Serial, it is used for something else, so on Teensy maybe I set up for dual serial, and output on SerialUSB1....

Sometimes I just setup to use the member varialble, like DBGSerial->print(...), or
more likely: if (DBGSerial) DBGSerial.print(....).
Sometimes I will wrap this if(...) into macros or inline functions.

Sometimes, I have found that Serial output to USB or Hardware Serial ports, during some operations, screw the timing enough that it acts differently, so instead I pass in some real simple implementation of Print class, that stores the data into memory, which I print out later.

Something like:

class MemoryPrint : public Print {
  public:
    size_t write (uint8_t b) { buffer[cb++] = b; return 1 }

    uint8_t buffer[4096];
    uint16_t cb = 0;
};
...
void setup() {
   MemoryPrint mpr;
   myobject.setDebugPrint(&mpr);
   // Do initialization stuff...
   Serial.begin(115200);
   myObject.setDebugPrint(&Serial);
   Serial.write(mpr.buffer, mpr.cb);

Obviously, this could be made more robust and the like. I have typically done something like this, when I am trying to debug a USB protocol such as MTP, that part of the initialization is done before the USB Serial object is initialized and as such can not be directly printed out to Serial at the time. Or that the printing screwed up the timing enough that behaviors changed.

The reason I mention all of this, is another version of this could be the bitbucket...

class BitBucketPrint : public Print {
  public:
    size_t write (uint8_t b) { return 1 }
};

...

Try this one:

easy to use; I like it!
One line to print a variable, one on/off "button" for all, and TRACE() helps you in figureing out in what branch your programm is actually running.

You can download it using library manager.

A different slice, no better, no worse. I have a large servo/pixel/button program in development. As I work on each section, I use printing to debug. Later, I may work on a different section, then return to the first; remove one //, add another. Yes, the OP's pain is felt, but here's how I lessen it.

//Debugging tools
//#define DEBUGGING       //enables general purpose debugging messages & begin
//#define DEBUGEEPROM     //comment out to suppress tracing in EEPROM Write/Reads
//#define DEBUGLIFO       //enables tracing messages in LIFO code
//#define DEBUGEEPROMPUT //uncomment to get a message every time a TO is updated in EEPROM
//#define WRITEEEPROM     //comment out to not write EEPROM during setup, only read it
//#define DEBUGLED    //enables text string depiction of the LED states for testing
//#define DEBUGMOVE       //enables position readout during moves
//#define DEBUGROUTE       //enables debugging statements for programming routes
//#define NOBUTTON        //comment out to enable button reading (needs AI pulled up to 5V)
//#define NOHARDWARE //all input via Serial monitor, all output via serial monitor

These things just pile up as the code grows. If I feel the need, I prune as necessary, but usually, I leave it in. Otherwise, it's only when the code limits are challenged that I prune.
YMMV, works for me, No warranties implied, etc. etc. Use at your own risk!

1 Like