ESP32 Arduino: Can someone explain me this behavior

Hello. This is not original source, I just reproduce situation.
Project tree

.
└── Define_Test
    ├── include
    │   ├── config
    │   │   └── hw.h
    │   ├── debug.h
    │   ├── init.h
    │   └── README
    ├── lib
    │   └── README
    ├── platformio.ini
    ├── src
    │   ├── debug.cpp
    │   └── main.cpp
    └── test
        └── README

File init.h

#define VALUE_TO_STRING(x) #x
#define VALUE(x) VALUE_TO_STRING(x)
#define VAR_NAME_VALUE(var) #var "=" VALUE(var)

void debugSetup();  //proto
#include "config/hw.h"
#include "debug.h"

File config/hw.h

#if defined(PIOINI_DFLAG)
   #define DEBUG_SERIAL    1
#endif

File debug.h

//#define DEBUG_SUPPORT           DEBUG_SERIAL || DEBUG_UDP || DEBUG_BLINK
#pragma once
#if( DEBUG_SERIAL || DEBUG_UDP || DEBUG_BLINK)
    #define DEBUG_SUPPORT   1
#else
    #define DEBUG_SUPPORT   0
#endif

#pragma message(VAR_NAME_VALUE(DEBUG_SERIAL))

File debug.cpp

#include "debug.h"
#pragma message(VAR_NAME_VALUE(DEBUG_SUPPORT))
#if DEBUG_SUPPORT
    void debugSetup()
    {}
#endif

File main.cpp

#include <Arduino.h>

#include <init.h>

void setup() 
{
  #if(DEBUG_SUPPORT)
    #define TEST 1
  #endif
}

void loop()
{
  int i=0; //dummy
  debugSetup();
}

and finally platformio.ini

[env:esp32doit-devkit-v1]
platform = espressif32
board = esp32doit-devkit-v1
framework = arduino
build_flags = -DPIOINI_DFLAG

When I try to build this simple project linker trows this nice error

Linking .pio/build/esp32doit-devkit-v1/firmware.elf
.pio/build/esp32doit-devkit-v1/src/main.cpp.o:(.literal._Z4loopv+0x0): undefined reference to `debugSetup()'
.pio/build/esp32doit-devkit-v1/src/main.cpp.o: In function `loop()':
main.cpp:(.text._Z4loopv+0x3): undefined reference to `debugSetup()'
collect2: error: ld returned 1 exit status
*** [.pio/build/esp32doit-devkit-v1/firmware.elf] Error 1

The main reason is “DEBUG_SUPPORT expands in debug.cpp as 0, and same time expands as 1 in main.cpp”. So can someone explain this situation? 10x

When the file debug.cpp is built, it includes debug.h. However, since debug.h does not include config/hw.h, it is unaffected by the definition of DEBUG_SERIAL. Thus it is not set, and thus it doesn’t enable the macro which enables the building of the functinon. Fix it by adding #include <config/hw.h> at the top of debug.h where you use all these defines.

1 Like

This is some kind of workaround. Actually i need to “fix” build order ( load config with macro, define all DEBUG flags like DEBUG_SUPPORT ) and then build all other things. It is possible, or not?

Would something like this help? It’s a standalone header file I use for some Arduino ESP8266 debugging, and the functions are either present or optimised out if the master DEBUG flag isn’t set. Might give you some ideas anyway! :wink:

i.e. at the top of my code I have

#define DEBUG true
#define DEBUG_OI Serial
#include "debug.h"

And in the code something like

  DebugBegin(115200); // default baud after boot
  DebugPrintln();

  DebugPrintln(F("App starting: TFT_thingspeak v0.8"));

debug.h

#ifdef DEBUG
#define DebugBegin(...) DEBUG_OI.begin(__VA_ARGS__)
#define DebugPrint(...) DEBUG_OI.print(__VA_ARGS__)
#define DebugPrintln(...) DEBUG_OI.println(__VA_ARGS__)
#define DebugPrintf(...) DEBUG_OI.printf(__VA_ARGS__)
#define DebugWrite(...) DEBUG_OI.write(__VA_ARGS__)
#define DebugFlush(...) DEBUG_OI.flush(__VA_ARGS__)

#define DebugInfo(...)                                                 \
    DebugPrint("Compiled: " __DATE__ ", " __TIME__ ", Arduino IDE v"); \
    DebugPrintln(ARDUINO, DEC);                                        \
    DebugPrint(F("Core version: "));                                   \
    DebugPrintln(ESP.getCoreVersion());                                \
    DebugPrint(F("SDK version: "));                                    \
    DebugPrintln(ESP.getSdkVersion());                                 \
    DebugPrint(F("Chip ID: "));                                        \
    DebugPrintln(ESP.getChipId());                                     \
    DebugPrint(F("Reset reason: "));                                   \
    DebugPrintln(ESP.getResetReason());                                \
    DebugPrint(F("Heap free: "));                                      \
    DebugPrintln(ESP.getFreeHeap());                                   \
    DebugPrintln("");

#else
// Define the counterparts that cause the compiler to generate no code
#define DebugBegin(...) (void(0))
#define DebugPrint(...) (void(0))
#define DebugPrintln(...) (void(0))
#define DebugPrintf(...) (void(0))
#define DebugWrite(...) (void(0))
#define DebugFlush(...) (void(0))
#define DebugInfo(...) (void(0))
#endif

No it’s not o_O. It’s a plain C/C++ error, not the error of a build system. Create this project in any other IDE for windows or linux and you will get the exact same behavior. You must include the file which defines your needed macros before you use them, since the #define .. done during one compilation unit is not globally visible. Or you can use advanced scripting to globally add new build flags based on the existing build_flags option. But that would be more complicated since it can all be solved in the C/C++ code directly.

1 Like

Strange. For example DEBUG macro used with no debug.h included in file. Actually debug.h for example has been invoked once from one of other files in this project. Is there any difference between ino and cpp files?

Yes, it works differently, as you can see from the compilation output

Detecting libraries used...
"C:\\Users\\Maxi\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\avr-gcc\\7.3.0-atmel3.6.1-arduino5/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10810 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR "-IC:\\Users\\Maxi\\Desktop\\Programming_stuff\\arduino-1.8.8\\hardware\\arduino\\avr\\cores\\arduino" "-IC:\\Users\\Maxi\\Desktop\\Programming_stuff\\arduino-1.8.8\\hardware\\arduino\\avr\\variants\\standard" "C:\\Users\\Maxi\\AppData\\Local\\Temp\\arduino_build_275963\\sketch\\sketch_feb13a.ino.cpp" -o nul
Error while detecting libraries included by C:\Users\Maxi\AppData\Local\Temp\arduino_build_275963\sketch\sketch_feb13a.ino.cpp
Generating function prototypes...
"C:\\Users\\Maxi\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\avr-gcc\\7.3.0-atmel3.6.1-arduino5/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10810 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR "-IC:\\Users\\Maxi\\Desktop\\Programming_stuff\\arduino-1.8.8\\hardware\\arduino\\avr\\cores\\arduino" "-IC:\\Users\\Maxi\\Desktop\\Programming_stuff\\arduino-1.8.8\\hardware\\arduino\\avr\\variants\\standard" "C:\\Users\\Maxi\\AppData\\Local\\Temp\\arduino_build_275963\\sketch\\sketch_feb13a.ino.cpp" -o "C:\\Users\\Maxi\\AppData\\Local\\Temp\\arduino_build_275963\\preproc\\ctags_target_for_gcc_minus_e.cpp"
Sketch is being compiled...
"C:\\Users\\Maxi\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\avr-gcc\\7.3.0-atmel3.6.1-arduino5/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -MMD -flto -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10810 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR "-IC:\\Users\\Maxi\\Desktop\\Programming_stuff\\arduino-1.8.8\\hardware\\arduino\\avr\\cores\\arduino" "-IC:\\Users\\Maxi\\Desktop\\Programming_stuff\\arduino-1.8.8\\hardware\\arduino\\avr\\variants\\standard" "C:\\Users\\Maxi\\AppData\\Local\\Temp\\arduino_build_275963\\sketch\\sketch_feb13a.ino.cpp" -o "C:\\Users\\Maxi\\AppData\\Local\\Temp\\arduino_build_275963\\sketch\\sketch_feb13a.ino.cpp.o"

it first generates function prototypes from the .ino fiels to share them accross all other files.

So e.g. if you have these 3 files

main.ino

/* main sketch includes config */
#include "general.h"

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

}

general.h

#pragma once

#define SOME_DEBUG_FLAG

part_2.ino

#ifdef SOME_DEBUG_FLAG
#error "SOME_DEBUG_FLAG defined!!!"
#else
#error "SOME_DEBUG_FLAG not defined!!"
#endif

void someFunction() { 
  return;
}

You can see that the part_2.ino does not include general.h where SOME_DEBUG_FLAG is defined. Yet, when compiled, the following “error” confirms that it was defined:

part_2:2:2: error: #error "SOME_DEBUG_FLAG defined!!!"

 #error "SOME_DEBUG_FLAG defined!!!"

  ^~~~~

exit status 1

This is due to when main.ino is pre-processed, the result is:

#include <Arduino.h>
#line 1 "C:\\Users\\Maxi\\Documents\\Arduino\\sketch_feb13a\\sketch_feb13a.ino"
/* main sketch includes config */
#include "general.h"

#line 4 "C:\\Users\\Maxi\\Documents\\Arduino\\sketch_feb13a\\sketch_feb13a.ino"
void setup();
#line 9 "C:\\Users\\Maxi\\Documents\\Arduino\\sketch_feb13a\\sketch_feb13a.ino"
void loop();
#line 7 "C:\\Users\\Maxi\\Documents\\Arduino\\sketch_feb13a\\part_2.ino"
void someFunction();
#line 4 "C:\\Users\\Maxi\\Documents\\Arduino\\sketch_feb13a\\sketch_feb13a.ino"
void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

}

#line 1 "C:\\Users\\Maxi\\Documents\\Arduino\\sketch_feb13a\\part_2.ino"
#ifdef SOME_DEBUG_FLAG
#error "SOME_DEBUG_FLAG defined!!!"
#else
#error "SOME_DEBUG_FLAG not defined!!"
#endif

void someFunction() { 
  return;
}

As you can see, all the .ino files hahve simply been concatinated after each other, starting at the main.ino, producing on giant .cpp file.

That’s why when you look at the debug.ino file, it doesn’t seem to be including anything (and still work), but under the hood the Arduino IDE has concatinated every ´ino, starting at the main espurna.ino`, which as a first act includes the configuration file, which makes the debug flags visible.

When you’re using PlatformIO it compiles the .cpp files as their own compilation unit (which is absolutely standard). So that side-effect of the Arduino IDE is not done there. PlatformIO can still do it though, you just have to use .ino files everywhere, too.

However, I would highly sugest to write proper C++ code in .cpp file, not Arduino’s hacky .ino file + preprocessing format.

2 Likes

I did not guess such a behavior. Thank you for the explanation.

1 Like