06 - Macros #define, #ifdef etc

Submitted by Webbot on September 24, 2008 - 3:13pm.

I will keep this section short - because it is covered by most C/C++ tutorials.

 

#define can be used to prevent you having to write the same code over and over.

For example:-

#define max(a,b) (a>b)?a:b

in your code you can then say:-

int val = max( read_sensor_left(), read_sensor_right() );

the 'max' macro is expanded (where 'a'='read_sensor_left()' and b='read_sensor_right()' ) and is therefore just as if you had written:-

int val = (read_sensor_left() > read_sensor_right()) ? read_sensor_left() : read-sensor_right();

Note the side effect here:- one sensor is read once and the other is read twice.

 

#define can be used in conjunction with #ifdef to conditionally include or exclude code. Lets assume you are building a maze solving bot for a competition. Whilst developing the bot you may have a whole bunch of debugging info that may, say, be written out to a serial port for logging to a PC. However, for the competition, then all of this code needs to be removed. This can easily be done via the makefile where you can create '#define's that are passed to the compiler (just like F_CPU is for the processor speed). So, using my makefile from earlier, then you could change the line for CDEFS and append '-D logging=1'. In your code you can then add code all over the place to say:

#ifdef logging

  add code for logging

#endif

When you are ready for the competition then remove the '-D logging=1' from the makefile and recompile. All of the logging code will have disappeared.

 

You can also create code that changes depending on the value of the #define. This is used a lot in the avr-lib files. For example we are already passing the processor speed, from the makefile, to the compiler. So when setting baud rates, PWM speeds etc then the processor speed is critical in knowing what our code should do. For example:-


#if F_CPU == 20000000
    ICR1 = 50000/2 ;
    // Configure timer 1 for Phase and Frequency Correct PWM mode, with 8x prescaling
    TCCR1B = (1 << WGM13) | (1 << CS11);
#elif F_CPU == 16000000
    ICR1 = 40000/2 ;
    // Configure timer 1 for Phase and Frequency Correct PWM mode, with 8x prescaling
    TCCR1B = (1 << WGM13) | (1 << CS11);
#elif F_CPU == 8000000
    ICR1 = 20000/2 ;
    // Configure timer 1 for Phase and Frequency Correct PWM mode, with 8x prescaling
    TCCR1B = (1 << WGM13) | (1 << CS11);
#elif F_CPU == 1000000
    ICR1 = 20000/2 ;
    // Configure timer 1 for Phase and Frequency Correct PWM mode, with no prescaling
    TCCR1B = (1 << WGM13) | (1 << CS10);
#else
#error    No F_CPU has been set or it is an unrecognised value
#endif


Note the '#error' to output an error if F_CPU is not an expected value.

So this is like if..then...else...endif in C except that it is telling the compiler what code should be compiled.

 

The #define program is also often used to prevent errors if you include the same header (.h) file more than once. So a header file called 'test.h' would normally have all of its code surrounded by:-

#ifndef _TEST_H_

#define _TEST_H_ 1

... the contents ....

#endif

So the first time it is included then '_TEST_H_' has not been defined so: we define the variable and then all of the file contents. If it is later included again then '_TEST_H' has already been defined so the contents of the file are ignored.

 

 

 

 

A better explanation

This forum post does a good job explaining macros versus functions in terms of efficiency and code size:

 

http://www.societyofrobots.com/robotforum/index.php?topic=10980.0