Search code examples
c++arduinoc-preprocessorundefined-referenceesp32

undefined reference to function inside ifdef block


I have the following problem:

I'm writing a collection library for a bunch of sensors to be used with a microcontroller. Meaning I take a lot of libraries for sensors and abstract and simplify them a bit in a unified library for a student project.

I use #define structures to figure out which sensors the students want to use and connected.

Example:

#define IR_SENSOR_USED

In the .h and .cpp files of the library I then use #ifdef-#endif pairs to define and declare the functions for the given sensor as well as include the given libraries.

Example from my Sensors.h file:

#ifdef IR_SENSOR_USED
  #include "SparkFun_GridEYE_AMG88/src/SparkFun_GridEYE_Arduino_Library.h"
  extern GridEYE grideye;
  void setup_ir_sensor();
  void read_ir_sensor();
  void enable_ir_interrupt(float lower, float upper, float hysteresis);
  void disable_ir_interrupt();
#endif

and from my Sensors.cpp file:

#ifdef IR_SENSOR_USED
void setup_ir_sensor() {
  Wire.begin(16, 17, 0x69);
  grideye.begin(0x69, Wire);
}

void read_ir_sensor() {
  for (int i = 0; i <= 64; i++) {
    sensor_values.ir_pixel_temp[i] = grideye.getPixelTemperature(i);
  }
  sensor_values.ir_device_temp = grideye.getDeviceTemperature();
}

void enable_ir_interrupt(float lower, float upper, float hysteresis) {...}

void disable_ir_interrupt() {...}
#endif

However, so long as I have the #ifdef in the .cpp file, I get the following error if I try to call the function in the setup():

sketch/Sensors.ino.cpp.o:(.literal._Z5setupv+0xc): undefined reference to `read_ir_sensor()'
sketch/Sensors.ino.cpp.o: In function `setup()':
.../Sensors/Sensors.ino:112: undefined reference to `read_ir_sensor()'
collect2: error: ld returned 1 exit status
exit status 1

If I comment them out, the code executes fine. Another function (setup_sensors()), which is also in the Sensors.h and .cpp files and is not surrounded by an #ifdef works also fine.

This is my Sensors.ino sketch:

#define IR_SENSOR_USED
//#define COLOR_SENSOR_USED
//#define ENV_SENSOR_USED
//#define TEMP_SENSOR_USED

#include "Sensors.h"

void setup() {
  sensor_setup();

  read_ir_sensor();
}

void loop() {
}

What's the reason for this? (Why) does the preprocessor not execute the directives in the .cpp file properly?


Solution

  • As the comments and other answer pointed out, any #define directives are only visible in the files that contain them. So having

    #define IR_SENSOR_USED
    

    in Sensors.cpp won't affect any code that's not compiled in Sensors.cpp (importantly, it will affect code that's contained in .h files that are included in Sensors.cpp after the #define. But it wouldn't affect anything contained in a different .cpp file.

    More sophisticated build environments than Arduino have better and more complicated ways to deal with this problem, but the Arduino world doesn't give you a lot of tools to handle this.

    What I do in the Arduino world is simply have a file called config.h which contains all the #define statements that I need project-wide. I #include "config.h" in each file that needs these values.

    So in your case, you'd put your all the defines that show which devices are used in config.h and then #include "config.h" at the start of every file that depends on it.

    You could even include it at the start of your Sensors.h file. I'd consider keeping the two files separate so that there would be a clear boundary between the stuff that needs to be configured and the stuff that's useful code.

    I also keep any sensitive values (wifi credentials, passwords, API keys) in this file, and exclude it from code I publish on Github. In its place I include a "config-example.h" file with all the directives in it but with dummy values, so that others using the code can edit and rename it.