-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable use of attachInterrupt() in classes #85
Comments
Example: I want to create communication library (class) which uses attachInterrupt() to listen on input pin. This is not possible. |
have a look at #58 |
The reasons why you can't add a class member to class MyClass {
public:
void int_callback_func();
}; One would think that |
in https://www.boost.org/doc/libs/1_64_0/libs/bind/doc/html/bind.html This is what they say: boost::bind is a generalization of the standard functions std::bind1st and std::bind2nd. can't we use the |
Unfortunately not because |
https://github.com/neu-rah/PCINT, does it help? |
This is the solution we came to after a lot of iterations. class test;
void set2(test* t);
class test {
public:
int get() {
return q;
}
void set(int s) {
q = s;
}
void begin() {
attachInterrupt(12, set2, FALLING, this);
}
private:
int q;
};
void set2(test* t) {
t->set(15);
} so you have a single handler that can accept a pointer to an object and dispatch it to the right one. |
@facchinm maybe the attachInterrupt(12, this->myfunc, FALLING, this); so when last parameter of attachInterrupt() is not 0, it will set interrupt callback to some internal core method, which will then call this->myfunc() and pass "this" to it. So there's no need to make wraper by yourself. Another solution might be to just create array with one element per pin and store needed pointers in there. Such universal callback contained in core will then look up the right object&method pair and call it. |
not a table please, this are embedded system and we don't have much memory most of the time. struct Data {
void myFunc() {/*...*/}
} data;
void setup() {
attachInterrupt(
digitalPinToInterrupt(1),
[](){data.myFunc();},
CHANGE
);
} note: |
we can put it inside a class if using a static function struct Data {
static void myFunc() {/*...*/}
void begin() {
attachInterrupt(
digitalPinToInterrupt(1),
Data::myFunc,
CHANGE
);
}
} data;
void setup() {
data.begin();
} now we need more interrupts and if we want to use multiple instances of that Data class they will all share the same handler template<int pin>
struct Data {
static void myFunc() {/*...*/}
void begin() {
attachInterrupt(
digitalPinToInterrupt(pin),
Data::myFunc,
CHANGE
);
}
};
//attaching to pins 1 and 2
Data<1> i1;
Data<2> i2;
void setup() {
i1.begin();
i2.begin();
} however none of those will have access to |
That will not work in my use case. Imagine you are implementing something like "software serial". You have object with buffer where you store new data during interrupts and from time to time the user reads that buffer. Also you need multiple instances, so you can't reuse the static method, because that would mean the buffer is also shared across instances, which is probably not good. |
@Harvie, the template argument approach suggested by @neu-rah could work for a the However, the approach implemented in the API repo (shown above in #85 (comment) which should be imported into AVR later) would work fine for this usecase, I think? Having a special case support for method pointers, removing the need for this extra wrapper function shown by @facchinm would be nice, but can probably be implemented alongside the existing implementation (just one more overload of Regarding the current implementation as linked by @facchinm, I have a few remarks, but I opened a new issue about that in the ArduinoCore-API repo: arduino/ArduinoCore-API#99 |
What is the current state of this issue? It's my understanding that users still can not use class methods, attach userdata or even detect which pin triggered the interrupt. Even though the linked Arduino Core API has commited specific functions for this (class methods) 2 years ago, the AVR core has not seen this functionality yet. With SAMD you can work around this limitation by checking which pin triggered the interrupt and pull the correct class instance out of the abyss by yourself (using However on AVR there's no way to do the same, as the What is the plan? What does the Arduino team intend to do to solve this problem? |
@facchinm Can you give any info on the progress of rolling out the templated argument? Is there any indicator for that? |
Any progress on this? |
I use the following code successfuly. My actual interrupt handler routine in the class is void MyClass::pollState(). That should be called with the proper instance reference. In this implementation I defined 5 callback functions p0(), p1(), ... p4() as a pool, but this can be easily extended. In my class constructor I call my callback registration routine registerCB() that looks for an unused record in the callbacks[] allocation table, fills in its this pointer and uses the function for callback with attachInterrupt(). Header file: class MyClass { // Sample class with callback method
public:
MyClass();
void pollState(); // Class callback to be finally triggered by the interrupt
private:
int _pin; // Arduino board pin number for that the interrupt is to be raised
}
typedef struct callback { // callback table record structure
void (*pcb)(); // pointer to pool callback routine
MyClass *pobj; // place for object instance "this" pointer
} callback_t;
void px( callback_t *p ); // error catching wrapper for callbacks
void p0(), p1(), p2(), p3(), p4(); // callback routine pool
void (*registerCB(MyClass *pobj))(); // callback routine allocation routine The implementation ino (or cpp) file: MyClass::MyClass(int pin) {
_pin = pin;
...
void (*pcb)() = registerCB(this);
if( pcb ) {
attachInterrupt(digitalPinToInterrupt(_pin), pcb, CHANGE);
}
else {
// No free callback available in the pool.
}
}
void MyClass::pollState() {
// Here is the interrupt handling logic for the class
}
callback_t callbacks[] = {
{ p0, NULL },
{ p1, NULL },
{ p2, NULL },
{ p3, NULL },
{ p4, NULL } // Add more records here...
};
void px( callback_t *p ) { if(p->pobj) p->pobj->pollState(); };
void p0() { px(callbacks + 0); };
void p1() { px(callbacks + 1); };
void p2() { px(callbacks + 2); };
void p3() { px(callbacks + 3); };
void p4() { px(callbacks + 4); }; // Add more functions here...
// Callback alocation function. This is called from within the class object constructor
// to find a free callback function for the interrupt registration.
void (*registerCB(MyClass *pobj))() {
for( int i; sizeof(callbacks)/sizeof(callbacks[0]) ; i++ ) {
if(!callbacks[i].pobj) {
callbacks[i].pobj = pobj;
return callbacks[i].pcb;
}
}
return NULL;
} |
The solution I can up with is to use a Create which creates a prototype encapsulation function based on the object variable name, then calls the class constructor passing that prototype function in, and finally creates the encapsulation function using the newly created object. Based on @ghrasko example the solution looks like: typedef void (*userfunc)();
class MyClass { // Sample class with callback method
public:
MyClass(int pin, userfunc encapsulatedMethod);
void pollState(); // Class callback to be finally triggered by the interrupt
private:
userfunc _encapsulatedMethod;
int _pin; // Arduino board pin number for that the interrupt is to be raised
}
//These two macros have been made generic so they don't have to be changed to different implimentations
//This macro is used to get the name of the function that will capture the method
#define GenericCapturedName(varname,methodname) varname##Captured_##methodname
//This macro prototypes the function -> calls the constructor -> creates the function
#define GenericCreateObject(typename, methodname, varname,args...) \
void GenericCapturedName(varname,methodname)(); \
typename varname = typename(args,GenericCapturedName(varname,methodname)); \
void GenericCapturedName(varname,methodname)() { varname.methodname();}
//This is the macro that gets customized for each class that needs it
#define CreateMyClass(varname,args...) GenericCreateObject(MyClass, pollState, varname, args) The source file: MyClass::MyClass(int pin,userfunc encapsulatedMethod) {
_pin = pin;
_encapsulatedMethod = encapsulatedMethod;
...
attachInterrupt(digitalPinToInterrupt(_pin), _encapsulatedMethod, CHANGE);
}
void MyClass::pollState() {
// Here is the interrupt handling logic for the class
} Then in the sketch: //Please note that the macros are formated so this is called like a function with a trailing ";"
CreateMyClass(myclass1,1); //Instead of: MyClass myclass1 = MyClass(1);
CreateMyClass(myclass2,2);
CreateMyClass(myclass3,10); I know that this does not use the normal object creation paradigm, but it seems easier to follow and it doesn't need any hard code limits. I think that this will work for the cases mentioned by @Harvie mentioned and seems more friendly than the template approach of @neu-rah . |
So looks like that AVR is the only core that is missing the |
Looks like so |
Hello, i've found that it's impossible to call attachInterrupt from class.
Eg. from class constructor. And i've found it very annoying. Lots of people are complaining about this across the internet...
Can you please add some hack to make this possible?
I think we can make something that automaticaly creates wrapper function each time it's called?
The text was updated successfully, but these errors were encountered: