Member Function Callback - (ISR)

So, this post made me wonder how I may handle processing interrupts within a class. I've considered it and came up with a solution but I had to define a callback function outside the class, and there it starts to get messy:

class MyClass{
  
  public:
    MyClass() {};
    MyClass(int pin, void(*callBackFunction)());
    ~MyClass() {};
    
  private:
    void (*pCallback)();
    
    unsigned long 
      _lastMillis = 0,
      _interval = 1;
};

MyClass::MyClass(int pin, void(*callBackFunction)())
{
  pCallback = callBackFunction;
  attachInterrupt(digitalPinToInterrupt(pin), pCallback, RISING);
}

MyClass instance1(1, &myCallbackFunction1);
MyClass instance2(2, &myCallbackFunction2);

void myCallbackFunction1()
{
  // process here
}

void myCallbackFunction2()
{
  // process here
}

void setup() 
{
}

void loop() 
{
}

So I thought "create a callback member function", right? Well for me it is not so easy. I tried this, but as I now know, static member functions have no associated "this" pointer...

So how could one accomplish something like this:

class MyClass{
  
  public:
    MyClass() {};
    MyClass(int pin);
    ~MyClass() {};
    
    static void 
      memberCallback();
    
    int 
      getSpeed();
    
  private:
    unsigned long 
      _lastMillis = 0,
      _interval = 1;
};

MyClass::MyClass(int pin)
{
  attachInterrupt(digitalPinToInterrupt(pin), memberCallback, RISING);
}

void MyClass::memberCallback()
{
  unsigned long _currentMillis = millis();
  _interval = _currentMillis - _lastMillis; //<<< error: invalid use of member 'MyClass::_lastMillis' in static member function
  _lastMillis = _currentMillis;
}

int MyClass::getSpeed()
{
  return 60000/_interval;
}

MyClass instance1(1);
MyClass instance2(2);

void setup() 
{
  //MyClass::memberCallback();
}

void loop() 
{
  static unsigned long lastUpdateMillis = 0;
  if(millis() - lastUpdateMillis > 1000UL)
  {
    Serial.print("Fan one speed="); Serial.println(instance1.getSpeed());
    Serial.print("Fan two speed="); Serial.println(instance2.getSpeed());
    lastUpdateMillis = millis();
  }
}

but I had to define a callback function outside the class

You can define the function in the class, but the function has to be static. That way, the function belongs to the class, not an instance of the class.

The real question that needs to be answered is which instance of the class should deal with the interrupt. How to do that depends on the answer.

If there is only to be one instance of the class, then perhaps a class is not the answer. If there are to be multiple instance of the class, and all instances need to know about the interrupt, then the class needs to keep track of the instances (easy enough, since it is responsible for creating the instances), and call each of them in turn.

@PaulS thanks!

PaulS:
You can define the function in the class, but the function has to be static. That way, the function belongs to the class, not an instance of the class.

you can see from the 2nd example, I did just that...

The real question that needs to be answered is which instance of the class should deal with the interrupt. How to do that depends on the answer.

I envisioned passing the interrupt pin in the constructor as in the examples.

If there is only to be one instance of the class, then perhaps a class is not the answer. If there are to be multiple instance of the class, and all instances need to know about the interrupt, then the class needs to keep track of the instances (easy enough, since it is responsible for creating the instances), and call each of them in turn.

in my example, I create a separate instance for each interrupt pin. So, how to determine which interrupt fired the callback? Or, which associated pin went RISING?

So would you use another member function (e.g. MyClass::process()) located in loop(), which could constantly update all of the instance variables?

I envisioned passing the interrupt pin in the constructor as in the examples.

I don't see how this answers the question. If you have one instance tied to each pin that an interrupt is to occur on, why do you have a no argument constructor?

So, how to determine which interrupt fired the callback? Or, which associated pin went RISING?

Given that the interrupts are triggered by external events, and you have the same function responding to all events, you can't tell which event triggered the interrupt handler. You could poll each pin, to see if it was now HIGH but wasn't before. But, if that was reasonable, you wouldn't need interrupts.

@PaulS, thanks again.

PaulS:
why do you have a no argument constructor?

fair enough, I put it in there but didn't really have a plan on using it! Yes, that is, well... dumb.

Given that the interrupts are triggered by external events, and you have the same function responding to all events, you can't tell which event triggered the interrupt handler. You could poll each pin, to see if it was now HIGH but wasn't before. But, if that was reasonable, you wouldn't need interrupts.

yeah, so apart from getting there with the first method, and all of the issues that come about from that (in essence, rather not do it in a class) am I missing some knowledge of how to do this "within the class."

BulldogLowell:
@PaulS, thanks again.

fair enough, I put it in there but didn't really have a plan on using it! Yes, that is, well... dumb.

yeah, so apart from getting there with the first method, and all of the issues that come about from that (in essence, rather not do it in a class) am I missing some knowledge of how to do this "within the class."

There are no direct ways of doing it. What I usually do is declare the ISR outside the class, and have a method in the instance to handle the interrupt. The external ISR consists of a single line of code to call that instance handler. Not pretty, but it works. I know of no more direct way of doing it, as you CANNOT get a pointer to a non-static class method.

You can make it very slightly cleaner by having a class method such as "void setHandler(void (*ISR)(void))" which gets called after the instance is created. This allows the class instance to do the attachInterrupt() call, setting ISR as the handler. ISR, of course, consists of the single line "thingInstance->InterruptHandler()", which gives the class intance access to the interrupt with only one minor side-trip through the ISR function.

Regards,
Ray L.

RayLivingston:
There are no direct ways of doing it. What I usually do is declare the ISR outside the class, and have a method in the instance to handle the interrupt. The external ISR consists of a single line of code to call that instance handler. Not pretty, but it works. I know of no more direct way of doing it, as you CANNOT get a pointer to a non-static class method.

You can make it very slightly cleaner by having a class method such as "void setHandler(void (*ISR)(void))" which gets called after the instance is created. This allows the class instance to do the attachInterrupt() call, setting ISR as the handler. ISR, of course, consists of the single line "thingInstance->InterruptHandler()", which gives the class intance access to the interrupt with only one minor side-trip through the ISR function.

Regards,
Ray L.

Thanks Ray,

I'll make a go at trying your 2nd suggestion.

Here's an example, to get you started:

SomeClass.h:

#ifndef SOMECLASS_H
#define SOMECLASS_H

class SomeClass
{
public:
    void setupInterruptHandler(uint8_t irq_pin, void (*ISR)(void));
    void handleInterrupt(void);
};

#endif

SomeClass.cpp:

#include "SomeClass.h"

void SomeClass::setupInterruptHandler(uint8_t pin, void (*ISR)(void))
{
    attachInterrupt(irq_pin, topHandler);
}

void SomeClass::handleInterrupt(void)
{
    // This is your ISR handler, which is called by ISR when interrupt occurs
}

Calling Program:

#include "SomeClass.h"

SomeClass*foo;       // My SomeClass instance
uint8_t fooPin = 2;   // Pin to assign foo's interrupt to

void fooInterruptHandler(void)
{
    foo->handleInterrupt();
}

void setup()
{
    ...
    foo= new SomeClass();
    foo->setInterruptHandler( fooPin, fooInterruptHandler);
    ...
}


void loop()
{
}

Regards,
Ray L.

RayLivingston:
Here's an example, to get you started:

thanks Ray! I guess I was closer than I thought, your example is appreciated!

cheers!

@RayLivingston,

This compiles, and I'll try to get to testing it in the next couple of days. I find it easier to work out a class in one file and then break it into a library...

class FanSpeed {

  public:
    void
      setupInterruptHandler(uint8_t irq_pin, void (*ISR)(void)),
      handleInterrupt(void);
    uint16_t
      getSpeed();

  private:
    uint32_t
      _lastMillis = 0,
      _interval = 1;
};

void FanSpeed::setupInterruptHandler(uint8_t irq_pin, void (*ISR)(void))
{
  attachInterrupt(digitalPinToInterrupt(irq_pin), ISR, RISING);
}

void FanSpeed::handleInterrupt(void)
{
  uint32_t nowMillis = millis();
  _interval = nowMillis - _lastMillis;
  _lastMillis = nowMillis;
}

uint16_t FanSpeed::getSpeed()
{
  if (millis() - _lastMillis < 60000) // has rotated in the last 60 seconds
  {
    return 60000 / _interval;
  }
  else
  {
    return 0;
  }   
}

FanSpeed* fan1;
uint8_t fan1pin = 2;
FanSpeed* fan2;
uint8_t fan2pin = 3;

void fan1InterruptHandler(void)
{
  fan1->handleInterrupt();
}

void fan2InterruptHandler(void)
{
  fan2->handleInterrupt();
}

void setup()
{
  fan1 = new FanSpeed();
  fan1-> setupInterruptHandler(digitalPinToInterrupt(fan1pin), fan1InterruptHandler);
  fan2 = new FanSpeed();
  fan2-> setupInterruptHandler(digitalPinToInterrupt(fan2pin), fan2InterruptHandler);
}

void loop()
{
  static uint32_t lastMillis = 0;
  if (millis() - lastMillis > 1000UL)
  {
    Serial.print(F("Fan one speed=")); Serial.println(fan1->getSpeed());
    Serial.print(F("Fan two speed=")); Serial.println(fan2->getSpeed());
    lastMillis = millis();
  }
}

Thanks for your assistance in helping me learn.

@RayLivingston,

Works well with a few tweaks.. and converted to micros().

//
// CLASS DEFINITION
//

class FanSpeed {

  public:
    void
      setupInterruptHandler(uint8_t irq_pin, void (*ISR)(void), int value),
      handleInterrupt(void);
    double
      getSpeed();

  private:
    uint32_t
      _lastMicros = 0UL,
      _interval = 60000000UL;
};

void FanSpeed::setupInterruptHandler(uint8_t irq_pin, void (*ISR)(void), int value)
{
  attachInterrupt(digitalPinToInterrupt(irq_pin), ISR, value);
}

inline void FanSpeed::handleInterrupt(void)
{
  uint32_t nowMicros = micros();
  _interval = nowMicros - _lastMicros;
  _lastMicros = nowMicros;
}

double FanSpeed::getSpeed()
{
  if (micros() - _lastMicros < 60000000UL) // has rotated in the last 60 seconds
  {
    return 60000000.0 / _interval;
  }
  else
  {
    return 0;
  }   
}

//
// PROGRAM START
//

FanSpeed* fan1;
uint8_t fan1pin = 2;
FanSpeed* fan2;
uint8_t fan2pin = 3;

void fan1InterruptHandler(void)
{
  fan1->handleInterrupt();
}

void fan2InterruptHandler(void)
{
  fan2->handleInterrupt();
}

void setup()
{
  Serial.begin(115200);
  pinMode(fan1pin, INPUT_PULLUP);
  pinMode(fan2pin, INPUT_PULLUP);
  fan1 = new FanSpeed();
  fan1-> setupInterruptHandler(fan1pin, fan1InterruptHandler, FALLING);
  fan2 = new FanSpeed();
  fan2-> setupInterruptHandler(fan2pin, fan2InterruptHandler, FALLING);
}

void loop()
{
  static uint32_t lastMillis = 0;
  if (millis() - lastMillis > 1000UL)
  {
    Serial.print(F("Fan one speed = ")); Serial.print(floor(fan1->getSpeed() + 0.5), 0);Serial.println(F(" RPM"));
    Serial.print(F("Fan two speed = ")); Serial.print(floor(fan2->getSpeed() + 0.5), 0);Serial.println(F(" RPM"));
    Serial.println('\r');
    lastMillis = millis();
  }
}

thanks again.