Arduino: Be Careful With the Preprocessor

This trips me up every damn time…

Although the Arduino language looks like C and parses like C and runs like C, it’s not really C. It’s a specialized language gnawed on by a vast conversion monster and eventually shat out as executable ATmega-style instructions.

Here’s a (rather contrived) trivial working program…

#define PIN_ARDUINOLED    13     // standard LED

typedef struct {
 int initial;
 int on;
 int off;
 int reps;
} timeout_ ;

timeout_ Trial = {2000,100,1000,5};

void setup() {
 pinMode(PIN_ARDUINOLED,OUTPUT);
}

void loop() {

int counter;

 digitalWrite(PIN_ARDUINOLED,LOW);
 delay(Trial.initial);

 for (counter = 0; counter < Trial.reps; ++counter) {
 digitalWrite(PIN_ARDUINOLED,HIGH);
 delay(Trial.on);
 digitalWrite(PIN_ARDUINOLED,LOW);
 delay(Trial.off);
 }

}

That compiles and runs just like you’d expect: a long delay, followed by five blinks, repeating endlessly.

Now, use some preprocessor conditionals to gut-and-replace the code, with a bit of garish colorization to make things more obvious. This is Bad Programming Practice, but work with me…

#define PIN_ARDUINOLED    13        // standard LED

#if 0
typedef struct {
 int initial;
 int on;
 int off;
 int reps;
} timeout_ ;
#endif

#if 0
timeout_ Trial = {2000,100,1000,5};
#else
int LoopCount = 50;
#endif

void setup() {
 pinMode(PIN_ARDUINOLED,OUTPUT);
}

void loop() {

int counter;

#if 0
 digitalWrite(PIN_ARDUINOLED,LOW);
 delay(Trial.initial);

 for (counter = 0; counter < Trial.reps; ++counter) {
 digitalWrite(PIN_ARDUINOLED,HIGH);
 delay(Trial.on);
 digitalWrite(PIN_ARDUINOLED,LOW);
 delay(Trial.off);
 }
#else
 digitalWrite(PIN_ARDUINOLED,LOW);
 for (counter = 0; counter < LoopCount; ++counter) {
 digitalWrite(PIN_ARDUINOLED,HIGH);
 delay(250);
 digitalWrite(PIN_ARDUINOLED,LOW);
 delay(500);
 }

#endif

}

The error message lump resulting from compiling that looks like:

In function ‘void setup()’:
error: ‘OUTPUT’ was not declared in this scope In function ‘void loop()’:
 In function ‘int main()’:

Really?

Delete the lines already removed by the preprocessor, the lines that shouldn’t be there when the code reaches the compiler, and you have:

#define PIN_ARDUINOLED        13              // standard LED

int LoopCount = 50;

void setup() {
 pinMode(PIN_ARDUINOLED,OUTPUT);
}

void loop() {

int counter;

 digitalWrite(PIN_ARDUINOLED,LOW);
 for (counter = 0; counter < LoopCount; ++counter) {
 digitalWrite(PIN_ARDUINOLED,HIGH);
 delay(250);
 digitalWrite(PIN_ARDUINOLED,LOW);
 delay(500);
 }

}

Which compiles and runs like a champ, of course. It blinks merrily away, more off than on, forever. The LoopCount variable doesn’t do much for us, but it’s the thought that counts.

Put the original lines back, comment out the new stuff, and you get:

#define PIN_ARDUINOLED        13              // standard LED

typedef struct {
 int initial;
 int on;
 int off;
 int reps;
} timeout_ ;

timeout_ Trial = {2000,100,1000,5};

#if 0
int LoopCount = 50;
#endif

void setup() {
 pinMode(PIN_ARDUINOLED,OUTPUT);
}

void loop() {

int counter;

 digitalWrite(PIN_ARDUINOLED,LOW);
 delay(Trial.initial);

 for (counter = 0; counter < Trial.reps; ++counter) {
 digitalWrite(PIN_ARDUINOLED,HIGH);
 delay(Trial.on);
 digitalWrite(PIN_ARDUINOLED,LOW);
 delay(Trial.off);
 }

#if 0
 digitalWrite(PIN_ARDUINOLED,LOW);
 for (counter = 0; counter < LoopCount; ++counter) {
 digitalWrite(PIN_ARDUINOLED,HIGH);
 delay(250);
 digitalWrite(PIN_ARDUINOLED,LOW);
 delay(500);
 }

#endif

}

And that compiles and runs perfectly, just like you’d expect. Uh-huh. Right.

Basically, there’s a complex interaction between ordinary C preprocessor directives, ordinary C language elements, and the inscrutable innards of the Arduino IDE & compiler chain.

As nearly as I can tell, you can wrap #if whatever around simple declarations and most executable code with impunity, but putting anything more elaborate than that, like a simple typedef struct, inside the conditionals causes bizarre problems.

In fact, just typedef can cause problems, particularly if you attempt to use the ensuing tag in a function declaration. Don’t even think about anything along these lines:

typedef struct {whatever} taggit_;
... snippage ...
void SomeFunction(taggit_ *pThing) {
... snippage ...
}

However, this seems to work fine:

struct taggit_ {whatever};
... snippage ...
void SomeFunction(struct taggit_ *pThing) {
... snippage ...
}

Trying to make sense of this will drive you mad (well, it drives me mad), but when you get bizarre error messages that can’t be explained by the usual operator screwups, well, most likely you’re being too clever with the preprocessor.

Or you’ve (also) made a trivial typo that cannot be discovered by inspection.

It seems the right (only?) way to handle typedef definitions is to put ’em in a separate header file, as described there, then add the header file to your sketch in a separate tab. That seems to insert the definitions in the proper order within the *.cpp file that actually goes into the compiler.

However, some limited fiddling reveals that I don’t understand the nuances or I’m screwing it up. Probably both.

Memo to Self: define the structs & variables without typedefs, then prepare for some protracted tweakage…

6 thoughts on “Arduino: Be Careful With the Preprocessor

  1. Thanks heaps for this info.

    I have been trying to define a struct and return it in a few functions and couldnt for the life of me figure out what was going wrong. Placing the struct def in the header file resolved all the issues!

    1. resolved all the issues

      That’s the optimistic view, anyway…

      Good luck!

  2. If you’re going to be doing advanced stuff like typedefs and preprocessor macros, you should consider ditching the arduino sketchpad IDE, and just coding straight C. You can use a gcc cross compiler for building, and avrdude for the programmer.

    1. That’s what creeping featuritis looks like in the rear-view mirror! [grin]

      The fact that Arduinos are really 8-bit microcontrollers (admittedly, sporting a nice instruction set) tells you that they’re best suited for relatively small problems with not too much data. The stock IDE and the restricted C-oid language work perfectly well for that class of problem; the trick is not pretending they’re good for much more.

      But I forget all that far too often, so the next time I try something non-trivial, I’ll set up the avr-gcc stack.

      Thanks for plinking me…

  3. Arduino automatically adds an #include line and function prototypes to the beginning of pde/ino files it compiles:
    It then creates a cpp file that is compiled through avr-gcc. The lines it adds are just before the first non-preprocessor line, which in your example is typedef, giving the following output:

    #if 0
    #include “Arduino.h”
    void setup();
    void loop();
    typedef struct {
    int initial;
    int on;
    int off;
    int reps;
    } timeout_ ;
    #endif

    1. giving the following output

      Which obviously falls flat on its face!

      Part of the problem with the Arduino IDE is that the error messages generally don’t get you anywhere close to the actual problem. When it works, it’s great. When you stray from the beaten path, things get ugly in a hurry!

      Thanks for the insight… it makes perfect sense now.

Comments are closed.