Advertisements

Archive for October 6th, 2009

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…

Advertisements

,

6 Comments