2

I am using Arduino and motor encoders to track the rotations of a motor. To do this, I am using interrupts on the Arduino. I can create a function, an ISR, that will be executed by the processor whenever the signal changes on a pin. That Interrupt/ISR combinations works like this:

void setup() {
    attachInterrupt(1,ISR_function,FALLING);
}

void ISR_function() {
    // do something
}

Seeing as I have multiple motors with encoders, I decided I would make a class to handle this. However, the attachInterrupt method requires a function pointer, and I am aware that in C++ you cannot have a pointer to a method function of an instance of an object. So something like this will not work:

class Encoder {
    public:
        Encoder(void);
        void ISR_function(void);
    private:
        // Various private members
}

Encoder::Encoder() {
    attachInterrupt(1,ISR_function,FALLING);
}

Encoder::ISR_function() {
    // Do some interrupt things with private members
}

Because ISR_function is not static. The ISR_function however executes code that is dependent on the the private data members of each specific instance.

Is it possible to create a function dynamically? And then retrieve a pointer to that function? Almost like in javascript:

class Encoder {
    public:
        Encoder(void);
        void* ISR_function(void);
    private:
        // Various private members
}

Encoder::Encoder() {
    attachInterrupt(1,ISR_function(),FALLING);
}

Encoder::ISR_function() {
    return dynamicFunctionPointer;
}

Is this possible? If not, how can accomplish what I am trying to do without manually creating separate static ISR_functions.

Hurricane Development
  • 2,449
  • 1
  • 19
  • 40
  • 4
    *"in C++ you cannot have a pointer to a method function"*... what? yes you can. – Cory Kramer May 18 '17 at 15:11
  • You can? On an instance object? – Hurricane Development May 18 '17 at 15:11
  • Your terminology is weird: "method function"/"instance object". Neither of those things exist. But if we correct your terminology, yes, of course you can. Pointers-to-members are absolutely a thing. You can't treat one as a normal pointer-to-function though, which makes them useless for C callbacks. – Lightness Races in Orbit May 18 '17 at 15:14
  • You can [pass class methods as function pointers](https://stackoverflow.com/questions/12662891/passing-a-member-function-as-an-argument-in-c) and even [call those methods off specific instances of that class](https://stackoverflow.com/questions/151418/calling-a-c-function-pointer-on-a-specific-object-instance) – Cory Kramer May 18 '17 at 15:14
  • 1
    @CoryKramer: Read the answers to that question. No, you can't pass a "class method" as a function pointer. – Lightness Races in Orbit May 18 '17 at 15:15
  • @BoundaryImposition Sorry about the terminology. I am, obviously, a novice with c++ and c in general. Is there any other way to accomblish what I am looking for here? A way to get a pointer to a function that will run instance specific code. – Hurricane Development May 18 '17 at 15:18
  • No, you'll have to use a non-member wrapper. This has been well covered on Stack Overflow and in Arduino forums – Lightness Races in Orbit May 18 '17 at 15:20
  • Wishful thinking I suppose, I had seen some comments with casting expressions but nothing clear cut. – Hurricane Development May 18 '17 at 15:22
  • 1
    @HurricaneDevelopment if you put all these thing static,then you can access them as regular function. (although you can only have one set of variables) – apple apple May 18 '17 at 15:25
  • @appleapple yea the one set of variables is the kicker. I will have to end up making tens of functions. I guess I can throw it in a another file and call it a day though. Thank you. – Hurricane Development May 18 '17 at 15:33

3 Answers3

4
// type of an interrupt service routine pointer
using ISR = void(*)();

// a fake version of the environment we are working with
// for testing purposes
namespace fake_environment {
    enum bob{FALLING};

    ISR isrs[100] = {0};

    void attachInterrupt(int i, void(*f)(), bob) {
        isrs[i] = f;
    }

    void runInterrupt(int i) {
        isrs[i]();
    }
}

// type storing a pointer to member function
// as a compile-time constant
template<class T, void(T::*m)()>
struct pmf {};

// stores a pointer to a class instance
// and a member function.  Invokes it
// when called with operator().  Type erases
// stuff down to void pointers.
struct funcoid {
  using pfunc = void(*)(void*);
  pfunc pf = 0;
  void* pv = 0;
  void operator()()const { pf(pv); }
  template<class T, void(T::*m)()>
  funcoid(T* t, pmf<T,m>):
    pv(t)
  {
    // create a lambda, then decay it into a function pointer
    // this stateless lambda takes a void* which it casts to a T*
    // then invokes the member function m on it.
    pf = +[](void* pt) {
      (static_cast<T*>(pt)->*m)();
    };
  }
  funcoid()=default;
};

// a global array of interrupts, which have a this pointer
// and a member function pointer type erased:
namespace client {
  enum {interrupt_count = 20};
  std::array<funcoid, interrupt_count> interrupt_table = {{}};
  // with a bit of work, could replace this with a std::vector        
}

// some metaprogramming utility code
// this lets me iterate over a set of size_t at compile time
// without writing extra helper functions at point of use.
namespace utility {
  template<std::size_t...Is>
  auto index_over( std::index_sequence<Is...> ) {
    return [](auto&& f)->decltype(auto) {
      return f(std::integral_constant<std::size_t, Is>{}...);
    };
  }
  template<std::size_t N>
  auto index_upto( std::integral_constant<std::size_t, N> ={} ) {
    return index_over( std::make_index_sequence<N>{} );
  }
}

// builds an array of interrupt service routines
// that invoke the same-index interrupt_table above.
namespace client {
  // in g++, you'd write a helper function taking an `index_sequence`
  // and take the code out of that lambda and build the array there:
  std::array<ISR, interrupt_count> make_isrs() {
    // creates an array of ISRs that invoke the corresponding element in interrupt_table.
    // have to do it at compile time, because we are generating 20 different functions
    // each one "knows" its index, then storing pointers to them.
    // Could be done with a lot of copy-pasta or a macro
    return ::utility::index_upto< interrupt_count >()(
      [](auto...Is)->std::array<ISR, interrupt_count>{
        return {{ []{ interrupt_table[decltype(Is)::value](); }... }};
      }
    );
  }
  // isr is a table of `void(*)()`, suitable for use
  // by your interrupt API.  Each function pointer "knows" its
  // index, which it uses to invoke the appropraite `interrupt_table`
  // above.
  auto isr = make_isrs();
  // with a bit of work, could replace this with a std::vector        
}

// interrupt is the interrupt number
// index is the index in our private table (0 to 19 inclusive)
// t is the object we want to use
// mf is the member function we call
// kind is FALLING or RISING or the like
// index must be unique, that is your job.
template<class T, void(T::*m)()>
void add_interrupt( int interrupt, int index, T* t, pmf<T, m> mf, fake_environment::bob kind ) {
  client::interrupt_table[index] = {t, mf};
  fake_environment::attachInterrupt(interrupt,client::isr[index],kind);
}


class Encoder {
  public:
    Encoder():Encoder(1, 7) {};
    Encoder(int interrupt, int index);
    void ISR_function(void);
    // my choice for some state:
    std::string my_name;
};

Encoder::Encoder(int interrupt, int index) {
  add_interrupt( interrupt, index, this, pmf<Encoder, &Encoder::ISR_function>{}, fake_environment::FALLING );
}

void Encoder::ISR_function() {
  // display state:
  std::cout << my_name << "\n";
}

int main() {
  Encoder e0;
  e0.my_name = "Hello World";
  fake_environment::runInterrupt(1);
  Encoder e1(0, 10);
  e1.my_name = "Goodbye World";
  fake_environment::runInterrupt(0);
}

Does not compile in g++ and uses C++14.

Does solve your problem. g++ problem is in make_isrs, which can be replaced by verbose copy-paste initialization. C++14 is from index_upto and index_over, which can similarly be reworked for C++11.

Live example.

However, ISRs are supposed to be minmal; I suspect you should just record the message and handle it elsewhere instead of interacting with object state.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    I sure love reading your answers. Making sense of it is a smidgeon hard though :) – Passer By May 18 '17 at 17:36
  • @PasserBy Yes. Added comments. May help marginally. – Yakk - Adam Nevraumont May 18 '17 at 17:52
  • Really great reading through this, difficult, but great. When creating the compile-time constant for pointer to a member function `template struct pmf {};` I don't understand the second argument in the template. Could you explain it? – Hurricane Development May 18 '17 at 18:16
  • 1
    @HurricaneDevelopment The first is the type of the class involved, the second is a compile-time member function pointer. We need our functions to be *stateless*, which means any state must be known at compile time. By passing a compile-time pointer to member function, the function pointer can be invoked *without passing it to the lambda* or capturing it. I could have stored the PMF in "type erased storage" like the `void*` and the `void(*)(void*)` does, but that is tricky to do portably; I found this easier. – Yakk - Adam Nevraumont May 18 '17 at 18:27
  • 1
    @HurricaneDevelopment Yet more comments added, including vague way to get g++ to compile it. – Yakk - Adam Nevraumont May 18 '17 at 18:31
  • Ok I have a couple more questions: 1. Why does funcoid need a `pmf`? Isn't a pointer to the class instance and a pointer to the function enough? 2. What is the elipses and `Is` in this template line: `template` – Hurricane Development May 18 '17 at 20:14
  • @HurricaneDevelopment Where do I store the pointer to the function? Storing generic pointers to member function is a real pain; they don't fit in a `void*`, for example, and often you know which one you are calling. So by making it compile-time, I remove need for storage. Second, that is a pack of `std::size_t`s called `Is`. In particular, it will store `0,1,2,3,4` all the way up to `19` when called from `index_upto<20>`, which is called in `make_isrs`. That pack is then expanded into arguments for the lambda in `make_isrs`, passing it `std::integral_constant`... – Yakk - Adam Nevraumont May 18 '17 at 20:26
  • through to `std::integral_constant`. These types can then be used to generate the functions we initialize our `std::array< ISR, 20 >` from, each one of which knows to invoke the same index in `interrupt_table` (so `isrs[0]` is a function pointer that knows to do `interrupt_table[0]()`, `isrs[19]` is a function pointer that knows to do a `interrupt_table[19]()`.) As we only have a `void(*)()` and no state, we are doing this work to get the indexes into a set of generated functions, to which we store pointers, without writing out all 20 (or however many) of them manually. – Yakk - Adam Nevraumont May 18 '17 at 20:28
  • @Yakk-AdamNevraumont, in fact this is no better than simply having a static array of 20 lambdas that retrieve a context from a table with 20 elements? – mmomtchev Feb 20 '22 at 16:02
0

To call a member function you need an instance to invoke it on, so it doesn't seem like a good choice to use for interrupts.

From pointers-to-members:

A member function is meaningless without an object to invoke it on.

Non-static member functions have a hidden parameter that corresponds to the this pointer. The this pointer points to the instance data for the object. The interrupt hardware/firmware in the system is not capable of providing the this pointer argument. You must use “normal” functions (non class members) or static member functions as interrupt service routines.

One possible solution is to use a static member as the interrupt service routine and have that function look somewhere to find the instance/member pair that should be called on interrupt. Thus the effect is that a member function is invoked on an interrupt, but for technical reasons you need to call an intermediate function first.

Nogoseke
  • 969
  • 1
  • 12
  • 24
0

First of all, you can extract pointer to a class method and call it:

auto my_method_ptr = &MyClass::my_method;
....
(myClassInstance->*my_method_ptr)(); // calling via class ptr
(myclassInstance.*my_method_ptr)(); // calling via class ref

This basically passes myClassInstance pointer to MyClass::my_method as an implicit argument, accessible via this.

Unfortunately, AVR interrupt controller can't call class method, as the hardware operate on simple pointers only and can't call that method with implicit argument. You'll need a wrapper function for this.

MotorEncoderClass g_motor; // g_ for global

void my_isr() {
    g_motor.do_something();
}

int main() {
    // init g_motor with relevant data
    // install my_isr handler
    // enable interrupts
    // ... do rest of stuff
    return 0;
}
  1. Create your class instance as a global variable.
  2. Create ordinary function that calls that method
  3. Initialize your motor class with relevant data
  4. Install my_isr as IRQ handler.
  5. Press start to begin :)
ezaquarii
  • 1,914
  • 13
  • 15
  • Thank you for explaining thoroughly. Seeing as I will be making multiple instances of `MotorEncoderClass`, do you have any recommendations for handling the multiple wrapper classes `my_isr()`? – Hurricane Development May 18 '17 at 16:31
  • If you have many hardware interrupts then just create multiple wrapper isr functions and call different MotorEncoderClass instances. If you have one interrupt, use interrup source in `if-else` or `switch`. – ezaquarii May 19 '17 at 16:57