Coiled Serpent, 15th–early 16th century, Aztec

How to write an Arduino Library that contains Sketch-conditional code

Recently while working on an open source Arduino library (more about that later) I ran into a challenge: how to make the library’s interrupt-related code compile into the Sketch only if the Sketch writer needed it?

TL;DR: You can conditionally compile the functions you need by 1) having the Sketch – and only the Sketch – always #define a unique Macro, defining it as true or false as needed, AND 2) putting the definitions (vs. declarations) of the optional functions in an .h file in the library, conditional on the Sketch’s #defined Macro being true. The rest of this post describes the reasoning behind the design, and details to make it work.

Before I go much further I should point out that much of this work would be unnecessary if Arduino provided an attachInterrupt() for Timer interrupts. So Arduino core folks: how about it?

The Issue

The library supports two different styles of using it:

  1. Using a Timer interrupt to sample data from the sensor at 2ms intervals.
  2. Reading data from the sensor without interrupts, by frequently calling an “isDataReady()” function in the Sketch loop().

It supports both styles because interrupts can be used in a Sketch that has a multi-millisecond loop() that would run too slowly to read data itself, yet using Timer interrupts can interfere with other Arduino functions, such as PWM (Pulse Width Modulation) on certain output pins. In other words, some users will want to use interrupts, while other users won’t.

For pin-related interrupts, Arduino provides a nice way to dynamically associate an ISR (Interrupt Service Routine) with a change on an input pin: attachInterrupt(). If an external device had a “data ready” pin, the attachInterrupt() code could be called if the user wanted to use interrupts, and not called if they didn’t.

Unfortunately, as far as I’ve found, Arduino doesn’t support an attachInterrupt() for Timer interrupts. The only way I know of attaching an ISR to a Timer is to do it at compile time, via the ISR() macro.

My First Try, and Why it Didn’t Work

My first inclination was to simply use conditional compilation in the library’s interrupts.cpp file.  Something like the following:

#if USE_INTERRUPTS
ISR(TIMER1_COMPA_vect) {
...
}
#endif // USE_INTERRUPTS

To explain why this approach didn’t work, we need to take a short trip into how the Arduino IDE (really, the gcc C++ toolchain) compiles code: the toolchain conceptually consists of 1) a Preprocessor, 2) a Compiler, and 3) a Linker.

The Preprocessor handles all the lines that begin with #, such as #include, #define, #if, #else, etc. Its input is your Sketch and all the files your Sketch includes. Its output is conceptually a single file, consisting of all the include files and the Sketch file, including or excluding code based on the #if, #ifdef, #else, #elif, and #endif.

The Compiler takes the code that’s output by the Preprocessor and converts it to machine code (often a .o file).

The Linker takes all the compiled .o files and knits them together into a single executable image, that then is downloaded into your Arduino and run.

#define statements create Macros (string substitutions) that the Preprocessor expands. For example, a line

#define USE_INTERRUPTS false

causes the Preprocessor to replace every following occurrence of USE_INTERRUPTS in the original code with false before it proceeds. If USE_INTERRUPTS is defined as false, as in this case, the Preprocessor will ignore (not output) any code that is inside a #if USE_INTERRUPTS construct. This arrangement works great for writing one set of code for, say, an 8MHz Arduino and another set of code for a 16MHz Arduino, and having the right code compile for the right processor.

Now suppose you have a library that contains sensor.h and sensor.cpp, you have a Sketch that does a #include <sensor.h>, and sensor.cpp has code based on an #if defined() like above.

When Sensor.cpp is Preprocessed and Compiled, the only file that can affect its compilation is sensor.h (and of course all the built in Arduino .h files). Importantly, the Sketch cannot change the code in the sensor.cpp library file. So my first inclination is a bust.

.h, .cpp files, declaration and definition

Normally in C++ code (Arduino library code), you declare a class in a .h file, and define the code and variables of that class in a corresponding .cpp file. For example, let’s say that I want to create a Sensor class, declared in sensor.h and defined in sensor.cpp.  Normally, sensor.h contains declarations such as:

class Sensor {
  public:
    static int usingInterrupts;
    void setupInterrupts();
};

Further, your sensor.cpp file normally contains definitions such as:

int Sensor::usingInterrupts = true;
void Sensor::setupInterrupts() {
  ...code to set up the Timer registers for your sensor.
}

Your sensor.cpp file can also define global functions, that are not part of a class. For example, an Interrupt Service Routine:

ISR(TIMER1_COMPA_vect) {
  ...code to handle a timer interrupt
}

Note: declaration is necessary for any .cpp file that uses a given class; there can (generally) be only one definition of a function or a static class variable in a set of files that are ultimately linked together by the Linker. So the separation of declaration into .h and definition into .cpp works great: everybody who needs to know about sensors does a #include <sensor.h>, and the one definition of sensor functions and static variables happens in sensor.cpp.

The key: the Compiler doesn’t know a .h from a .cpp

Remember that the Preprocessor mashes .h files and a .cpp file together before handing the result to the Compiler? The consequence of that architecture is that the Compiler doesn’t know what code came from a .h file and what code came from a .cpp file; it just worries about declarations and definitions.

So a glimmer of a solution showed itself: I held my nose and put the ISR() into a .h file, interrupts.h, even though it would normally go into a .cpp file. Then I made the ISR() conditional on the Macro USE_INTERRUPTS:

#if USE_INTERRUPTS
ISR(TIMER1_COMPA_vect) {
...
}
#endif // USE_INTERRUPTS

Now I can have the ISR() compile in or not based on the following code in my Sketch:

#define USE_INTERRUPTS true
#include <interrupts.h>

This takes care of one part: Now if the Sketch wants an ISR() it does a #define USE_INTERRUPTS true. If it doesn’t want interrupts, it does a #define USE_INTERRUPTS false. Beauty.

Conditional function definitions

Next I need to make setupInterrupts() conditionally contain setup code (or not) based on the Sketch’s USE_INTERRUPTS Macro definition. No worries. My sensor.h file contains the declaration of setupInterrupts() as it normally does:

class Sensor {
  public:
    void setupInterrupts();
};

and my interrupts.h file defines setupInterrupts(), in a way similar to the way it defined the ISR():

#if defined(USE_INTERRUPTS)
void Sensor::setupInterrupts() {
#if USE_INTERRUPTS
..code to setup the Timer interrupt.
#endif // USE_INTERRUPTS
}
#endif // defined(USE_INTERRUPTS)

“Why the #if defined()?” I hear you ask. It happens that the interrupts.h file is included by several files in the library, so the #if defined is there to avoid defining setupInterrupts() for all those other .cpp files in the library (which would cause a link error), and to avoid preprocessor problems around a #if on a macro that isn’t defined (which would cause a Preprocessor error). The simple effect is this: when each library file is compiled, setupInterrupts() is only declared (via the sensor.h file), not defined. The Sketch, by defining USE_INTERRUPTS true, creates the single definition of setupInterrupts that the Linker requires.

A static variable for USE_INTERRUPTS

Lastly, I happened to need a variable that other functions in the library could use – at runtime – to know whether interrupts were being used, and take appropriate action. Recall that those library .cpp files can’t use USE_INTERRUPTS directly, because they can’t see the Sketch’s code. So I added a static variable in the Sensor class:

class Sensor {
  public:
    static int usingInterrupts;
    void setupInterrupts();
};

Then I added the definition of usingInterrupts to interrupts.h, again using a similar pattern to the function definition:

#if defined(USE_INTERRUPTS)
int Sensor::usingInterrupts = USE_INTERRUPTS;
#endif // defined(USE_INTERRUPTS)

Like the setupInterrupts() function definition, usingInterrupts is defined (vs. declared) only when the Sketch file is compiled.

By the way, this uses another feature/hack in C++: the name of the file has nothing to do with the class functions and variables it defines. So having a Sensor class static variable defined in interrupts.h is not an error.

In Summary

So the library now has three capabilities:

  1. An ISR() that is compiled only if the Sketch needs it.
  2. An interrupt setup function that contains code only if the Sketch needs it.
  3. A static class variable that is true only if the Sketch wants to use interrupts.

The code is definitely smelly to C++ purists (and me), but it illustrates how you can use C++ to address some messy situations that can crop up when your Arduino library uses interrupts.

Featured Image: “Coiled Serpent” Aztec, 15th-16th century. Courtesy of the New York Metropolitan Museum of Art.