Arduino debugging 2: Using #define to manage debugging output

Last time, in the post here we discovered how the __FILE__ and __LINE__ symbols make it possible for a program to print out a tracing information. This can be very useful if, as is the case with Arduino development, there is no easy way to step through your code. The problem that we now have is that we need to add calls to the trace method, and we will have to remove them later.

show_location(__FILE__, __LINE__);

However, there is another feature of the C compiler that we can use which makes it very easy to add and remove debugging statements. This feature is provided by the C compiler pre-processor. The pre-processor, as the name implies, is the part of the compiler that takes in the source code file and can do some processing on the program text. Commands to the pre-processor are pre-ceded by the # character.

One pre-processor directive (something we use to tell the pre-processor what to do) is #define, which defines a symbol. If we get bored with typing lots of digits of PI (3.141592654) we can use #define to tell the pre-processor to do the hard work for us.

#define PI 3.141592654

Now, whenever the pre-processor sees the symbol PI it will replace it with the digit sequence. So, consider if my program contained the following statement (assuming I have variables called circ and rad declared somewhere):

circ = rad * 2 * PI;

The symbol PI is replaced by the number sequence that it was defined with, so that the circumference of the circle is calculated using the value of PI as defined.

The #define pre-processor directive is really powerful, and should probably come with some warnings. Here are a few:

  • There is nothing to stop a silly programmer re-defining the value of the PI symbol at any point in your program. They could define it as 4 which would cause your program to produce the wrong results, or they could define it as “chicken” which would cause your program to produce some very interesting compilation errors as your program was now trying to use the word “chicken” in arithmetic statements.

  • The defined symbol must be an identifier that “makes sense” in C. There is a convention that #defined symbols are given names that are in all capitals, with words separated by underscore so that they stand out in your code

  • The pre-processor does not replace defined symbols inside strings. In other words the statement:

    Serial.println(“Have some PI”);

    - just prints the message “Have some PI”. However, you can use #define to create symbols that are complete strings:

    #define WELCOME_MESSAGE “Hello”

    I can now write:

    Serial.println(WELCOME_MESSAGE);

  • I regard the #define statement as strong magic. I do all my #defines in one place in each source file and I only use them sparingly.

Anyhoo, back to how to use #define to make our lives easier. We can save typing by replacing the call of the show_location with #define symbol:

#define TRACE show_location(__FILE__, __LINE__)

Now, when I want to produce trace output to show where the program has reached I can just use the TRACE statement:

TRACE ;

The pre-processor will extend this and enter the text of a call of the show_location method.

It gets better. If I want to turn off all the trace statements I can just remove the TRACE symbol from my program by defining it as an empty string:

#define TRACE

This means that the pre-processor will now replace the symbol TRACE with nothing so that the trace statements are no longer present in the source of the code. A program can re-define a symbol as it is compiled, so you can enable and disable the trace behaviour in different parts of the code if you like.