Using #define in .ino sketch to control action in an include'd file

I think I've hit a snag.

I'm using the TFT_eSPI library and part of the process of using it is to define which of many drivers to include. By design, I'm supposed to go into the user_setup_select.h file and uncomment one and only one include for the display I'm using. And this works.

But over time I'm using various different displays, and if I forget to modify the selection file, then things don't work (obviously).

What I'd like to do is have a #define in my main sketch, such as

#define Use_Board_A

or

#define Use_Board_B

so that the sketch itself says which selection to make, and modify user_setup_select.h to do the following:

#if defined (Use_Board_A)  
     #include <Board_A_Driver.h>
#elif defined (Use_Board_B)
    #include <Board_B_Driver.h>
      .
      .
      .
#endif

But this doesn't work. I'm thinking that something is happening in some pre-compile phase that makes this not work.

Is there a way to do this?

If I'm stuck, I may be able to just do the different include's from the sketch, but that's not quite as clean.

The #define in your sketch is only visible to your .ino file and the library's .h file at the time the latter is included in your sketch.

The .cpp files in the library itself are all compiled separately, and no definition you make in your sketch file will be visible to them. Any time they include that .h file, none of your sketch's #defines will be seen.

1 Like

I use "#ifdef " and that works OK. I make sure the defines are loaded first and only in one place. Example if you use #define x = 5 then later use #define x = 9, x becomes 9. I have to be careful to use it only once. Some libraries use the #define and that can mess you up as well.

It could work only if the library put all the code only in the .h files. Once it uses a .cpp source, you have no way to influence the compilation of this .cpp from the sketch.

1 Like

The best you probably can get is an abort if you selected the wrong "driver" in user_setup_select.h.

Assuming that your xxxDriver.h files are like below

#ifndef __DRIVERA_
#define __DRIVERA_
// whatever
#endif

and

#ifndef __DRIVERB_
#define __DRIVERB_
// whatever
#endif

You can use the following construct in user_setup_select.h

#include "Board_A_Driver.h"

#if defined (Use_Board_A)
  #if !defined (__DRIVERA_)
    #error "Wrong driver for board A"
  #endif
#elif defined (Use_Board_B)
  #if !defined (__DRIVERB_)
    #error "Wrong driver for board B"
  #endif
#else
  #error "No driver"
#endif

And your ino file

//#define Use_Board_A
#define Use_Board_B

#include "user_setup_select.h"

void setup()
{
}

void loop()
{
}

With this example, you did select board B in the ino file and board A in the user_setup_select.h resulting in

     #error "Wrong driver for board B"
      ^~~~~
exit status 1

Compilation error: #error "Wrong driver for board B"

As mentioned, each library is compiled separately, without regard to your .ino. But they are cached per-sketch, which is important for what you're trying to do.

When any C/C++ source is compiled, a pre-computed set of {includes}

the list of include paths in the format "-I/include/path -I/another/path...."

is added to the command, comprising sometimes dozens of directories (often named 'include') under the platform's SDK. If you were to add your own sketch directory, you could have a per-sketch required header file when using any given library.

Instead of modifying {includes}, it's easier to use an existing setting for such shenanigans. First, you'll need to locate your board's platform.txt file, starting at the OS-specific Arduino15 directory. For example, on Linux

$ cd ~/.arduino15
$ find . -name platform.txt
./packages/arduino/hardware/avr/1.8.6/platform.txt
./packages/arduino/hardware/renesas_uno/1.2.0/platform.txt
./packages/m5stack/hardware/esp32/2.1.1/platform.txt
$ cd ./packages/arduino/hardware/renesas_uno/1.2.0

Go to that directory and verify the expected flag

$ grep -B 5 ^compiler.cpp.extra_flags platform.txt

# These can be overridden in platform.local.txt
compiler.c.extra_flags=
compiler.c.elf.extra_flags=
compiler.S.extra_flags=
compiler.cpp.extra_flags=

We'll be using the cpp one -- unless your library uses something else? As mentioned in the comment, create platform.local.txt as a sibling file in the same directory, with one line

compiler.cpp.extra_flags=-I{build.source.path}

The {build.source.path} is your sketch directory. (Exercise for the reader if it contains a space.) Whenever you modify the platform files, you have to restart the IDE to the changes to take effect.

The IDE will open library headers with Go To Definition, but they are read-only. You can hover on the file's tab to get the path (or sometimes the #include statement) to get the path. In this case you have a specific file you're supposed to edit, so use that. (Sometimes a library has a header file that is intended for users, but doesn't use it itself. It usually includes other files; you need to edit the appropriate one.)

Let's say you'll call this configuration-header "per_sketch.h" So add (after any include guard)

#if __has_include("per_sketch.h")
  #include "per_sketch.h"
#else
  #error "missing per_sketch.h: explain how this works"
#endif

The __has_include allows for a more descriptive #error message. Otherwise, you can just try to do the #include, and it will fail if missing.

If you try to build now, you may get the error twice: once when compiling your sketch, if it includes the library and eventually this header; and once when trying to build the library. So use the meatball menu on the right of the file tabs in the IDE to create a New Tab, "per_sketch.h" One advantage of having the file at the root level of the sketch is that it will auto-open in the IDE. If you build again, it should build without error, or as well as it did before.

Now you can do further modifications and put that #if defined thing in the library's header, and your #define in the "per_sketch.h"

Very interesting.

...but I would like to point out...
This should be a folder of a specific sketch. And every time the user creates a new sketch, he will have to edit the IDE system files.

It's a Global predefined property

  • {build.source.path}: Path to the sketch being compiled. If the sketch is in an unsaved state, it will the path of its temporary folder.

so it goes in the platform.local.txt file verbatim. There are other reasons why this might not work :slight_smile: but the resulting value should change with each sketch.

(Oh: If the library previously compiled successfully and has been cached, you may need to delete those cached files to force a new compile with the modified headers. The build system may not detect that modification like it would with a new library version. You'll see the necessary details in the Verbose compiler output.)

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