Arduino "#define" Scope and creating a library

I have a problem that, I fear, doesn't have a solution that I'd like. But before doing that I though I'd post my question here to see if there might perhaps be a way to do this after all.

I am creating a "classic" Arduino library with the normal file structure (sub-directory "src" with the .c and .h files; another sub-directory "examples" with yet another directory "test" with a file "test.ino").

In "test.ino" I'd like to have a pre-compiler statement

#define PIN_NUMBER 3

and in the "myLibrary.h" I'd like to put in

#ifdef PIN_NUMBER
// define certain hardware-specific items here
#else
#error Error, "PIN_NUMBER" not defined
#endif

Unfortunately, the scope of the "PIN_NUMBER" definition does not extend to the library.
I need to use pre-compiler macros here since I want to define registers and Interrupt vectors depending upon the hardware and pin number and this needs to be done a compile-time rather than at runtime (saves a lot of code complexity and size by not having lots of if-then-else or switch type code, and ISR()s cannot be dynamically allocated at runtime).
At the same time I want to keep the library easy to install without requiring the user to change c++ compiler INCLUDE paths (needed if there's an include file in the program directory with the pin definition that can be included in the library header). Along the same lines, I don't want to make the library user edit a file in the library directory to set the value.
I think that what I'd like to do, just setting a #define in the user sketch, might not be possible - but I certainly hope that I'm wrong...

Put the #define above the #include, then it should work

guix - thanks for posting, but did you actually try this? Of course I tried this and of course it didn't work - otherwise I wouldn't have posted the question.

1 Like

Post an MRE of what you tried. I have a feeling it does "work" but your understanding of how #include relates to the compilation / build process is flawed.

SV_Zanshin:
I have a problem that...

I am creating a "classic" Arduino library...

Your library is in the form of one dotH file and one dotCPP file?

If yes

The IDE first compile the dotCPP and only after compile the dotINO file, with the included dotH

Then any alteration on #define is 'after' the compilation of the dotCPP code

I have only two 'not so bad' solutions

  1. write library in the form of one single dotH file, with all the code in a single file

  2. #include directly the dotCPP file

If you read Italian you can read this
https://forum.arduino.cc/index.php?topic=654169.msg4408655#msg4408655

There may be better ways to approach this. Whenever you start getting "tricky" and/or "clever" in your approach, it should be a red flag that you're barking up the wrong tree.

-jim lee

Standardoil - I think that the scope of the "#define" is as you stated regarding the compile order, which is why my main program "#defined" macro is not defined in the library at compile time. I did go to your approach, but if I include a file in the library, then that file needs to be in the include search path, and by default the Arduino IDE doesn't include the source path so one would need to either add that or hard-code an absolute path in the library; neither of which I want to do (the requirement here is for a bog-standard library install without additional changes to the environment).

jimLee - I don't know where your recommendation that I'm doing something incorrect comes from. In this case the library allows use of any INT or PCINT pin and it handles the interrupts. Without a compile-time limitation, the library would need to explicitly define ISRs for all those interrupts. Which means that any program using the library could not use any of those interrupts at all, due to the way the IDE works and where ISR vectors are placed in memory.

I think that this is just not going to work, otherwise people would already have done this for such things as the 32-byte buffer size in the I2C library - if the main program could use a "#define BUFFER_LENGTH 64" to override the default buffer rather than having to edit the library source code.

It works for me

ExampleLibrary/examples/Example/Example.ino :

#define PIN_NUMBER 3

#include <ExampleLibrary.h>

void setup() {}

void loop() {}

ExampleLibrary/ExampleLibrary.h :

#ifdef PIN_NUMBER
// define certain hardware-specific items here
#else
#error Error, "PIN_NUMBER" not defined
#endif

If I comment #define I get the error as expected.

And there are some libraries using this method, for example GitHub - RemoteXY/RemoteXY-Arduino-library: RemoteXY library for Arduino IDE

guix - This is the exact case that I have, yet I get a compile error both from the Arduino IDE as well as from Visual Studio (using Visual Micro).

test.ino

#define PIN_NUMBER 3
#include "testLibrary.h"
void setup() {}
void loop() {}

testLibrary.h

#ifndef _TESTLIBRARY_h
#define _TESTLIBRARY_h
#ifndef PIN_NUMBER
#error PIN is not defined
#endif
class test {
public:
  test();
  ~test();
};
#endif

testLibrary.cpp

#include "testLibrary.h"
test::test(){}
test::~test(){}

And if I cut-and-paste your example, I also have the compile stop on the "#error" line. I have a windows installation using the IDE 1.8.13 and have made no customization of the Arduino IDE environment. Which environment did you use for your test case? I've deleted my Arduino IDE installation and re-installed it, just to make sure that this problem isn't due to a funky installation on my part. Unfortunately, nothing has changed after re-installing.

I'm confident that the Arduino IDE version is not the problem, I used 1.8.1 to compile this example, I didn't update to the latest version yet because I rarely use the Arduino IDE, I only use it for testing things like in this case. I normally use VS Code with PlatformIO.

I remember a strange thing about libraries, but that was many years ago so I don't remember exactly and I may be wrong : sometimes when I made modifications to a library, an older precompiled version was used instead of the modified one. If I remember right, to solve it I had to delete the temporary build folder (AppData/temp/arduino_build_XXXXXX), so maybe try that

Also try remake the library in a new folder with a different name, who knows...

@guix - I just installed a fresh Windows 10 virtual machine, downloaded the IDE and tried both my files and yours. Both had compile errors due to the missing declaration in the library. So it would seem that your installation is non-standard but functions for this; which unfortunately does not solve the issue at hand.

I have had similar issues to what you mentioned regarding libraries; that is due to persisted "precompiled headers" in c++ libraries. But those are turned off for the Arduino compiles, so aren't a factor.

In the end all of the ways to make this type of "#define" in the main program usable in the library code involved making changes to the IDE (either by adding compiler switches or playing around with #include paths) and that wasn't what I wanted for a standard library. The main reason I wanted to define the pin at pre-compile time is to avoid large blocks of "switch(pin){}" code and to have code definitions of all possible ISRs for INTn and PCINTn which would reserve all those ISRs despite all-but-one of them being free.
I opted to use those large code blocks (although there are "#ifdef" for the different hardware platforms making those blocks smaller) and to use the catch-all ISR "BADISR_vect" to handle any interrupts not explicitly defined - not a pretty solution but a viable one.

The library is a generic and extensible 1-Wire slave library for Arduino ATmega platform devices. It runs in the background and supports standard speed 1-Wire commands. The slave can define its own commands and responses, these are handled in a callback routine which is triggered by 1-Wire activity - meaning the main sketch can run program code and doesn't need to poll the 1-Wire microLAN for activity.

The library is working and once I test all of the pins for all of the hardware platforms I have on hand I'll publish it on GitHub.

Just curious as to why you don't use a constructor that takes a pin number as the argument?

May we assume that you understand why you get the compile error?

sterretje - you may assume that I understand the compile error, it is due to the scope of c++ pre-processor commands and the order in which they are executed.

What makes you think that my solution doesn't use the constructor to set the pin? As I've mentioned before, the problem revolves around the ISR()s needed. Since the pin number, and hence the corresponding ISR (which could be a INTn vector or a PCINTn vector), is not known to the library at compile time once would normally have to define all possible values. And that means that the user program calling the library cannot use any of them. By using the catch-all ISR "BADISR_vect" I've avoided having to do that.

An interesting side-note is that when using that catch-all vector the compiled code for all ISRs contains "sei()" as the first command, ignoring the option "ISR_BLOCK"! That is a strange side-effect.

What makes you think that my solution doesn't use the constructor to set the pin?

Because the constructor in your code in reply #8 does not take an argument.

corresponding ISR

I missed that in the opening post.

sterretje - that code was attempting to use the #define in the main program to set the pin, which the library would then use with "#ifdef" to only compile in the ISR and code necessary for that pin. Since this thread has demonstrated that the scope of a "#define" function declaration does not extend to that of an included library, I have had to change the method to pass the pin at runtime in either the constructor or the "begin()" (I opted to use a begin() call, allowing the pin to be declared in the code). Now I've got "#ifdef" for each hardware platform, and within that block I have a switch() statement to iterate through all the allowed pins. I set 5 variables with pointers to the actual runtime registers (interrupt, DDR, PORT, PIN, pending interrupt) and another 5 variables with the corresponding masks.

I've now got the ATmega32U4, ATmega328P, ATmega1284P, ATtiny85 and ATmega2560 registers entered and tested. Luckily, the processors with the most pins also have the most memory so all is not lost.

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