Preprocessor directives acting strange

Hello Everybody!

I've got the proprocessor directive code below on Arduino 1.8.19. I'm trying to figure out what I'm doing wrong. It actually seems to be crashing when uploaded to the microcontroller. When I define MCU as ADAFRUIT-ESP32-S3, it all works fine, when MCU is XIAO-ESP32-C3 it does not like it.

Now I can comment out the defines and force the definitions it will work.. so it seems like something is just not liking my #if statements relating to the MCU.

Anyone spot anything wrong?

Thank you!

Regards,
-Moses

#define MODEL A           // MODEL = A, B
#define MCU XIAO-ESP32-C3   // MCU = XIAO-ESP32-C3, ADAFRUIT-ESP32-S3

#if MODEL == A
  #define WRITE_DISPLAY(a,b,c,d,e,f) WRITE_DISPLAY_A(a,b,c,d,e,f)
  #if MCU == XIAO-ESP32-C3  // XIAO ESP32-C3 WORKING ON NEW WIRING P1-3
    #define OE_PIN 2        // A0
    #define DATA_PIN 3      // A1
    #define CLOCK_PIN 4     // A2
    #define RCK_PIN 5       // A3
  #endif
  #if MCU == ADAFRUIT-ESP32-S3  // ADAFRUIT-ESP32-S3 WORKING ON NEW WIRING P1-3
    #define OE_PIN 18       // A0
    #define DATA_PIN 17     // A1
    #define CLOCK_PIN 9     // A2
    #define RCK_PIN 8       // A3
  #endif
#elif MODEL == B
  #define WRITE_DISPLAY(a,b,c,d,e,f) WRITE_DISPLAY_B(a,b,c,d,e,f)
  #define OE_PIN 19
  #define CLOCK_PIN 18
  #define DATA_PIN 4
#endif

That is wonderfully un-helpful. What does "it does not like it" even mean???

It appears to crash the code, endless bootloader messages from the MCU. Yea, not very helpful.. but I'm hoping I'm just doing something silly in the code that is easy to spot.

If I comment out the second "#if MCU".. it works fine! See code below. I'm not sure if I'm doing something wrong, or I've hit a bug.

#if MCU == XIAO-ESP32-C3  // XIAO ESP32-C3 WORKING ON NEW WIRING P1-3
    #define OE_PIN 2        // A0
    #define DATA_PIN 3      // A1
    #define CLOCK_PIN 4     // A2
    #define RCK_PIN 5       // A3
  #endif
  //#if MCU == ADAFRUIT-ESP32-S3  // ADAFRUIT-ESP32-S3 WORKING ON NEW WIRING P1-3
    //#define OE_PIN 18       // A0
    //#define DATA_PIN 17     // A1
    //#define CLOCK_PIN 9     // A2
    //#define RCK_PIN 8       // A3
  //#endif

Well, I would be looking very closely at WHAT those #defines actually, physically, do. The #ifdef and #defines CANNOT, by themselves, cause a crash. The problem is USING the settings they define is causing something to go sideways. Perhaps you are using a pin that is already in use by some critical function, like external memory. Since you've posted only that small snippet of code, nobody here can possibly explain what you're seeing crashes. It is NOT caused by the #ifdefs themselves, bur rather what your code is DOING with them.

When you change the macro defined to the other one - other definitions are used, potentially here to define pins.
It is not the macro itself which causes the crash:

  • if you change the macro - other pin (definitions) are used by the code
  • this can generate a conflict, e.g. the same pin is used for something else (e.g. also in the bootloader)

You need a clear picture about your MCU, which pins are used, reserved, the right pins for you application. Than you define the macro. And check carefully if the pin definitions are really correct (reasonable) for your board.
The macro definition tells just your code "what to use" - if it is right for HW version and board - compiler has no clue.

When you toggle via this commenting of lines: it sound more like:
the other definitions (pins) are wrong and do not fit for your real board.
I guess, you do not know exactly which define to set for your board. Figure out what your board is and try to understand how the macro definition "toggles" between two versions of your board (or different boards).

1 Like

If you really are able to use a minus sign "-" in preprocessor variable names, then I have learned something new and will test it at the next opportunity.

Interesting point about minus signs. I'm sure it is covered in the C preprocessor reference, but I couldn't find it quickly.

Underscore is allowed, and it would be wise to use that instead.

I guess it is just an arithmetic operator so XIAO-ESP32-C3 would be a compound arithmetic expression which could explain the otherwise strange behaviour the OP reported. In any case, changing it to an underscore would be a good idea.

The individual variables would be undefined, and so, (I guess) the expression might just evaluate to zero.

Each should then throw an error, as their values would be unknown, unless XIAO, ESP32 and C3 are all defined, which seems unlikely to me.

When I saw 6v6gt's post on the "-" I thought that would be it.. but sadly no.
At this point I can use other means of getting this done, but I'm curious as to what is going on.

Good point tjaekel, I think the crashing was caused by using those values incorrectly. I distilled it down to the simplest form I could, and removed all dependencies on the defines so the code would run. Test code below.
So far, it looks like #if statements (or the #define) does not like character comparisons. As soon as I switch to using numbers everything is working correctly. Characters or strings and it is no good.

What was really throwing me off, and is still a question, is when I use characters in the #define, the SECOND (or last?) #if always executes! I'm guessing some unintended consequence of how it works?

I've not messed with this stuff for a while. So please excuse my ignorance.

Thank you everyone for your replies!

Regards,
-Moses

#define BLAVAR X 
#if BLAVAR == X
  #define BLA 1
#endif
#if BLAVAR == Y
  #define BLA 2
#endif

BLA=2	NOT WHAT I EXPECTED

=====================

#define BLAVAR Y 
#if BLAVAR == X
  #define BLA 1
#endif
#if BLAVAR == Y
  #define BLA 2
#endif

BLA=2	MAYBE IT'S WORKING?

=====================

#define BLAVAR Y 
#if BLAVAR == Y
  #define BLA 2
#endif
#if BLAVAR == X
  #define BLA 1
#endif

BLA=1	NOT WHAT I EXPECTED,  SECOND IF SEEMS TO ALWAYS WORK!! ?!?

=====================

#define BLAVAR 1 
#if BLAVAR == 1
  #define BLA 1
#endif
#if BLAVAR == 2
  #define BLA 2
#endif

BLA=1	WORKING!

=====================

#define BLAVAR 2 
#if BLAVAR == 1
  #define BLA 1
#endif
#if BLAVAR == 2
  #define BLA 2
#endif

BLA=2	WORKING!

Maybe look here to see how to compare strings in the C++ preprocessor: How to compare strings in C conditional preprocessor-directives - Stack Overflow

EDIT

Also use the #error directive, in testing, to get visible signs of which "if" statements are true. Your last "working" code sample appears to be the standard approach.

EDIT 2

I added some warning directives to see how XIAO-ESP32-C3 etc. evaluated. It is treated as a 0.

#define MODEL A           // MODEL = A, B
#define MCU XIAO-ESP32-C3   // MCU = XIAO-ESP32-C3, ADAFRUIT-ESP32-S3

#if MODEL == A
  #warning "A"
  #define WRITE_DISPLAY(a,b,c,d,e,f) WRITE_DISPLAY_A(a,b,c,d,e,f)
  #if MCU == XIAO-ESP32-C3  // XIAO ESP32-C3 WORKING ON NEW WIRING P1-3
     #warning "MCU == XIAO-ESP32-C"
  #endif
  #if MCU == ADAFRUIT-ESP32-S3  // ADAFRUIT-ESP32-S3 WORKING ON NEW WIRING P1-3
    #warning "ADAFRUIT-ESP32-S3"
  #endif
  #if MCU == 0
    #warning "0"
  #endif
  
#elif MODEL == B
  #warning "B"
#endif

int main()
{
   return 0;
}

results:

main.cpp:5:4: warning: #warning "A" [-Wcpp]
   #warning "A"
    ^~~~~~~
main.cpp:8:7: warning: #warning "MCU == XIAO-ESP32-C" [-Wcpp]
      #warning "MCU == XIAO-ESP32-C"
       ^~~~~~~
main.cpp:11:6: warning: #warning "ADAFRUIT-ESP32-S3" [-Wcpp]
     #warning "ADAFRUIT-ESP32-S3"
      ^~~~~~~
main.cpp:14:6: warning: #warning "0" [-Wcpp]
     #warning "0"
      ^~~~~~~

and, of course, attempting to use a minus sign as a character of a variable name does not work:

#define XIAO-ESP32-C3 3

main.cpp:3:13: warning: ISO C++11 requires whitespace after the macro name
 #define XIAO-ESP32-C3 3
             ^



2 Likes

Thanks for checking and demonstrating this!

There are evidently good reasons that people tend to use constructs like the following, rather than string comparisons.

#define __ATmega328__
...
#ifdef __ATmega328__
(processor specific code)
#endif

Macros are like text placeholders. If you assign a character or string to another macro - it has to be defined before because it is also a macro. Macros, their value, have to be text.
Macros are like aliases.

#define BLAVAR X
does not work, because X has to be defined before using it - otherwise X is 'empty'. But
#define BLAVAR "X"
#define BLAVAR 'X'
will work, because now it is a string or character and macro has a value now (text).

Macros are done by the Preprocessor. It is technically just a text substitution, not instructions, and not variables. It is just text or #if defined(...)

A macro can be empty, e.g.
#define BLAVAR
#ifdef BLAVAR

If you assign a macro X which is not defined - there is nothing to substitute for X.
So, the
#define BLABLA X
becomes just defined, but BLABLA does not have any text.
It acts like:
#define BLABLA

You can do
#ifdef BLABLA //which works fine
but you cannot do
#if BLABLA == X
It becomes comparing against nothing (X is not defined and empty, nothing substituted for X).

Thank you everyone for the advice!

I switched to all #ifdef statements, no funny text assignments and no minus signs and all is working as it should.

Regards,
-Moses

Cool! Well done.
A minus in macro names: you could assume a macro name is just "text" (even the minus as a text character becomes part of name), but I would assume: the minus means really minus (and it acts as minus operand!):

#define A 10
#define B 5
//and you do:
#define C A - B   //this is for sure a real minus!
//or even:
#define C A-B     //preprocessor sees minus even without spaces

it will do the minus as operation:
A plus, minus, multiply... in macro definition works as such an operation, for sure:

#define A 10
#define B (A + 10)
#define C (A * B)

Golden rule:
avoid minus, plus (math operators) characters in names, macro names. Instead: use underline ("_").

BTW: the same if you would use variable names with a minus, plus etc.:

int other-var;
//will be seen as:
int other - var;   //and var is another variable, but compiler error here

One more comment for those searching in the future..

My goal with writing these preprocessor directives was to have unified code I could use for a few different MCU types. I have since found that the board type is actually defined as a preprocessor directive itself and there is no need to define them manually. I use something like the following now..

#ifdef ARDUINO_XIAO_ESP32C3
and
#ifdef ARDUINO_ESP32_WROVER_KIT

Sometimes the value is not very obvious.. but If you turn on the verbose compile option, copy and paste the output somewhere and search for DARDUINO.. you should find your board type. I'm sure it's in boards.txt as well.

Cherio!

Regards,
-Moses

Yes, often you had to set a macro, e.g. telling which MCU board it is, also which board revision etc.
Yes, it helps to "toggle" between different boards: same code source for different boards, but the macro selects which real board is used.

I use often to distinguish between two different revisions: a newer revision of a board can have different GPIO pins to use.

But to write a source code, with macros, which should run on completely different MCU boards - very challenging (but possible).
If you want to toggle with FW source code between an STMF768I-DISCO and a Portenata H7, or a NUCLEO-STM32H745ZI-Q - your project needs also other Driver LIB, other HAL files, other BSP files etc.
This can cause to toggle also LIB files, much more project settings, e.g. the MCU type, instruction set, HW FPU used or not.

I use macros to differentiate between different revisions of a board, but not really to differentiate between different MCU types and platforms. Pretty tough to know and handle the differences.
And: the code becomes really messy when you mix two boards with different macros set.
Often, one macro is not properly maintained: you flip the macro and it does not work, even on the correct board: to support two versions of the same board with a different macro is often lazy and an update not done for the other flavor.

BTW:
You can "ask" the MCU to tell you which MCU type, revision it is (not the board revision but if it is a STM32H7 vs. STM32F7). You can use this register value on the fly to adapt in your code what to do when you this or the other MCU (not a macro, a run-time "if" depending on what MCU found).

The revision of a base board - you cannot figure out, you have to know. Even not possible to realize if you use a breakout board, or a VisionShield etc.
The Raspberry Pi is a bit smarter: it has an I2C flash on board from where you can read which HW version and config it is.

You can also use I2C flash for "system config" - I use:
My FW has a lot of configurable parameters, used during startup. It looks into an I2C flash memory and reads important values there.
These values can be: UART baud rate, GPIO pins usable, HW features, also max. MCU clock, SPI mode, SPI clock etc.
It configures the MCU FW on startup with these values.
When I change now to a different board - I change this "system config" in I2C flash and the FW "adapts itself" to the modified and correct setting.
My own mainboard has an I2C flash: and putting a different MCU module on my main board - it reads on start/reset the I2C flash which tells my FW what main board it is (and which features it has).

So, macros are "cool" to handle slightly different changes between different versions.
But a "runtime system config" to read (Linux, Raspberry Pi ... they use a "device tree") makes it even more flexible but needs a lot of coding to support.

The drawback with macros: changing a board, type etc. needs a new compile of your source code, flash code etc.
But a FW "reading" the board config can follow automatically what it will find (e.g. which main board used, which revision ...).

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.