There was a recently a thread that involved interrupt handlers in a C++ class. There’s a fairly straightforward technique of using a static member function to do this. It requires storing the class instance in a global variable (as shown here) or in a static member variable.
#include "Particle.h"
// To run this code, connect D0 and D1 together with a jumper.
// We setup up D0 as a tone() output and connecting to D1, which is interrupt triggered.
class TestClass {
public:
TestClass();
virtual ~TestClass();
void report();
void interruptFunc();
static void interruptFuncStatic();
private:
volatile unsigned long counter;
};
TestClass testClass;
TestClass::TestClass() {
counter = 0;
}
TestClass::~TestClass() {
}
void TestClass::report() {
Serial.printlnf("%lu", counter);
}
void TestClass::interruptFunc() {
// This is an interrupt function. Don't perform very length calculations, use delay(),
// allocate memory, call most Particle functions, etc.
counter++;
}
// [static]
void TestClass::interruptFuncStatic() {
// Uses global variable "testClass" because the static function can't access counter directly
// because it does not have a "this" pointer.
testClass.interruptFunc();
}
void setup() {
Serial.begin(9600);
attachInterrupt(D1, TestClass::interruptFuncStatic, RISING);
pinMode(D0, OUTPUT);
tone(D0, 10000);
}
void loop() {
testClass.report();
delay(1000);
}
The attachInterrupt() call now has a variation that allows you to directly specify a class member function (non-static) along with a class instance pointer, which makes life much easier. No global variables required.
#include "Particle.h"
// To run this code, connect D0 and D1 together with a jumper.
// We setup up D0 as a tone() output and connecting to D1, which is interrupt triggered.
class TestClass {
public:
TestClass();
virtual ~TestClass();
void report();
void interruptFunc();
private:
volatile unsigned long counter;
};
TestClass testClass;
TestClass::TestClass() {
counter = 0;
}
TestClass::~TestClass() {
}
void TestClass::report() {
Serial.printlnf("%lu", counter);
}
void TestClass::interruptFunc() {
// This is an interrupt function. Don't perform very length calculations, use delay(),
// allocate memory, call most Particle functions, etc.
counter++;
}
void setup() {
Serial.begin(9600);
// This version of attachInterrupt takes a non-static member function and a pointer to "this" to
// use to call it
attachInterrupt(D1, &TestClass::interruptFunc, &testClass, RISING);
pinMode(D0, OUTPUT);
tone(D0, 10000);
}
void loop() {
testClass.report();
delay(1000);
}
There’s another technique available in C++11 that work on the Particle, the lambda. The syntax is positively bizarre, but it works. This is unnecessary for attachInterrupt() because the technique above is easier to understand. But this technique can be used when you need to pass a function pointer to something that takes a normal C function pointer but you want to encapsulate a C++ class member and class instance without using a global variable or static class member.
#include "Particle.h"
// To run this code, connect D0 and D1 together with a jumper. Also D0 to D2.
// We setup up D0 as a tone() output and connecting to D1 and D2, which are both interrupt triggered.
class TestClass {
public:
TestClass(int pin);
virtual ~TestClass();
void report();
void attachInterrupt(InterruptMode mode, int8_t priority = -1, uint8_t subpriority = 0);
void interruptFunc();
private:
int pin;
volatile unsigned long counter;
};
TestClass testClassD1(D1);
TestClass testClassD2(D2);
TestClass::TestClass(int pin) : pin(pin), counter(0) {
}
TestClass::~TestClass() {
}
void TestClass::report() {
Serial.printlnf("%d: %lu", pin, counter);
}
void TestClass::interruptFunc() {
// This is an interrupt function. Don't perform very length calculations, use delay(),
// allocate memory, call most Particle functions, etc.
counter++;
}
void TestClass::attachInterrupt(InterruptMode mode, int8_t priority, uint8_t subpriority) {
// This is the magical lambda in the 2nd parameter. It created a standard C function pointer that
// can encapsulates the this pointer ([this]) and uses it to call interruptFunc.
// http://en.cppreference.com/w/cpp/language/lambda
// https://msdn.microsoft.com/en-us/library/dd293608.aspx
::attachInterrupt(pin, [this]() { interruptFunc(); }, mode, priority, subpriority);
}
void setup() {
Serial.begin(9600);
testClassD1.attachInterrupt(RISING);
testClassD2.attachInterrupt(CHANGE);
pinMode(D0, OUTPUT);
tone(D0, 10000);
}
void loop() {
testClassD1.report();
testClassD2.report();
delay(1000);
}
And you get to write crazy lines of code like this and look like a C++ master!
void TestClass::attachInterrupt(InterruptMode mode, int8_t priority, uint8_t subpriority) {
::attachInterrupt(pin, [this]() { interruptFunc(); }, mode, priority, subpriority);
}