I'm trying to keep my code well-organized as I learn C++ for Arduino (specifically Nano in my case). There's a function attachInterrupt() that allows me to bind a hardware interrupt to a specific pin when it undergoes a state change.
Signature:
void attachInterrupt(uint8_t interruptNum, void (*userFunc)(void), int mode)
So far, so good. However, I've created a class to manage a specific aspect of my program. This way, I can reuse the code instead of redoing the setup() multiple times.
So, imagine I have a certain type of digital sensor and I'm going to use attachInterrupt() for it. Normally, I would write the following code:
#include <Arduino.h>
unsigned int count = 0;
void countISR() {
count++;
}
void setup() {
pinMode(PIN2, INPUT);
attachInterrupt(digitalPinToInterrupt(PIN2), countISR, CHANGE);
}
void loop() {
Serial.println("Count = " + String(count));
delay(1000);
}
This tends to work very well. However, as mentioned, I'd like to better organize my code and allow for code reuse for various interrupt-supporting pins (or even using libraries like EnableInterrupt, but that's beside the point).
Therefore, I created a class called Counter:
class Counter {
public:
uint8_t pin;
unsigned int count = 0;
Counter(uint8_t pin): pin(pin) {}
void setup() {
pinMode(pin, INPUT);
// attachInterrupt() should go here.
}
void update() {
count++;
}
};
This way, I can instantiate multiple independent Counters, something like:
Counter counterA(PIN2);
Counter counterB(PIN3);
void setup() {
counterA.setup(); // Set pinMode() and interrupt to PIN2.
counterB.setup(); // Set pinMode() and interrupt to PIN3.
}
However, when trying to perform an attachInterrupt() inside Counter::setup(), I need to create a C++ Lambda, and I would typically do something like:
void Counter::setup() {
// pinMode(...)
attachInterrupt(pin, []() { update(); }, CHANGE);
}
In theory, this should be enough. But C++ indicates that I can't do it this way, issuing the following error:
Error: the enclosing-function 'this' cannot be referenced in a lambda body unless it is in the capture list. C/C++ (1738)
Alright, so let's try putting this in the capture list:
void Counter::setup() {
// pinMode(...)
attachInterrupt(pin, [this]() { update(); }, CHANGE);
}
And now there's another error:
Error: no suitable conversion function from "lambda ->void" to "void (*)()" exists. C/C++(413)
I understand the reason for the error, as it conflicts with the signature of attachInterrupt(). However, if I do this within the main void setup(), it will work:
void setup() {
// pinMode(...)
attachInterrupt(PIN2, []() { counterA.update(); }, CHANGE);
}
In other words, one way or another, I'm passing an instance into a lambda without even needing to capture it explicitly.
So I thought: maybe the problem is the use of this, so I tried assigning it to a variable first, like:
void Counter::setup() {
// pinMode(...)
const auto self = this;
attachInterrupt(pin, []() { self.update(); }, CHANGE);
}
The error changes, but it's practically the same as the first one:
Error: an enclosing-function local variable cannot be referenced in a lambda body unless it is in the capture list. C/C++(1735)
This left me quite confused: why is it allowed to do this outside of a class, but inside it, I need to go through this capture process?
To solve the problem for now, I had to make an adjustment that I found ugly:
void setup() {
counterA.setup([]() { counterA.update(); })
}
Thus, in Counter::setup(), I receive a lambda in the format allowed by attachInterrupt(), already capturing counterA or counterB without problems.
So, what am I doing so wrong?