Skip to content
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

Open
Harvie opened this issue May 5, 2019 · 19 comments
Open

Enable use of attachInterrupt() in classes #85

Harvie opened this issue May 5, 2019 · 19 comments

Comments

@Harvie
Copy link

Harvie commented May 5, 2019

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?

@Harvie
Copy link
Author

Harvie commented May 5, 2019

Example:

I want to create communication library (class) which uses attachInterrupt() to listen on input pin.
I want to be able to pass input pin to class constructor, so it can be processed using digitalPinToInterrupt().
I want to be able to create multiple instances (objects) of this class, each using different pin/interrupt.

This is not possible.

@s-light
Copy link

s-light commented Jun 27, 2019

have a look at #58
i think that does just what you want..

@aentinger
Copy link
Contributor

The reasons why you can't add a class member to attachInterrupt is because every class member function has a hidden this parameter pointing to the instance of the class added to it's signature. E.g.

class MyClass  {
  public:
    void int_callback_func();
};

One would think that void int_callback_func(void) has exactly the signature required by attachInterrupt but that's wrong since there is the hidden this parameter which is why the signature looks like that in reality void int_callback_func(MyClass * this). One solution to handle this is to use functionality provided by boost::bind (and you are very welcome porting boost to AVR) or to extend the signature of the interrupt callback to pass the class instance as proposed in #58.

@Harvie
Copy link
Author

Harvie commented Sep 18, 2019

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 std:: version? or it's also not ported to AVR?

@aentinger
Copy link
Contributor

Unfortunately not because std::bind is part of the C++ standard library libstdc++ for which there is currently no support for avr-gcc.

@neu-rah
Copy link

neu-rah commented Sep 18, 2019

https://github.com/neu-rah/PCINT, does it help?

@facchinm
Copy link
Member

This is the solution we came to after a lot of iterations.
This way, you can write code like

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.
All cores that will be ported to arduino-api will receive this, so right now it's not widespread and thus undocumented

@Harvie
Copy link
Author

Harvie commented Sep 18, 2019

@facchinm maybe the void set2(test* t) can be replaced with some generalized code directly in core? eg.:

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.

@neu-rah
Copy link

neu-rah commented Sep 18, 2019

not a table please, this are embedded system and we don't have much memory most of the time.
as for the code, we can increase the library size, but before that please consider this.

struct Data {
  void myFunc() {/*...*/}
} data;

void setup() {
  attachInterrupt(
    digitalPinToInterrupt(1),
    [](){data.myFunc();},
    CHANGE
  );
}

note:
for reference, its a "c++ lambda function", they are part of c++11 and made for cases like this, otherwise we would have to go a long way making intermediate external function

@neu-rah
Copy link

neu-rah commented Sep 18, 2019

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 this pointer

@Harvie
Copy link
Author

Harvie commented Sep 19, 2019

however none of those will have access to this pointer

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.

@matthijskooijman
Copy link
Collaborator

@Harvie, the template argument approach suggested by @neu-rah could work for a the SoftwareSerial case you pose, but would require a lookup table from the int template argument to a SoftwareSerial*, or require passing the SoftwareSerial* as a template argument (which only works when it refers to a global object). In either case, this only works from outside an object (where you know how many objects there are and/or what their global address is), not from inside an object/method.

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 attachInterrupt that accepts a method pointer and a this pointer).

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

@ghost
Copy link

ghost commented Jun 5, 2020

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 EIC->INTFLAG.reg and some global variables magic).

However on AVR there's no way to do the same, as the EIFR register is cleared before the ISR is executed.

What is the plan? What does the Arduino team intend to do to solve this problem?

@PhilippMolitor
Copy link

@facchinm Can you give any info on the progress of rolling out the templated argument? Is there any indicator for that?

@Harvie
Copy link
Author

Harvie commented Nov 9, 2020

Any progress on this?

@ghrasko
Copy link

ghrasko commented Dec 23, 2020

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;
}

@ndinsmore
Copy link

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:
Header file:

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 .

@hitech95
Copy link

So looks like that AVR is the only core that is missing the attachInterruptParam?

@Mqxx
Copy link

Mqxx commented Aug 25, 2022

Looks like so

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests